SPE-742: Builtin pad feature in zero elevation mode.
This commit is contained in:
parent
c7ba8c4daa
commit
ddd0a9abb6
@ -46,7 +46,7 @@ BreakConstructorInitializersBeforeComma: false
|
|||||||
BreakConstructorInitializers: BeforeComma
|
BreakConstructorInitializers: BeforeComma
|
||||||
BreakAfterJavaFieldAnnotations: false
|
BreakAfterJavaFieldAnnotations: false
|
||||||
BreakStringLiterals: true
|
BreakStringLiterals: true
|
||||||
ColumnLimit: 75
|
ColumnLimit: 78
|
||||||
CommentPragmas: '^ IWYU pragma:'
|
CommentPragmas: '^ IWYU pragma:'
|
||||||
CompactNamespaces: false
|
CompactNamespaces: false
|
||||||
ConstructorInitializerAllOnOneLineOrOnePerLine: true
|
ConstructorInitializerAllOnOneLineOrOnePerLine: true
|
||||||
|
@ -15,7 +15,8 @@ const std::string USAGE_STR = {
|
|||||||
|
|
||||||
namespace Slic3r { namespace sla {
|
namespace Slic3r { namespace sla {
|
||||||
|
|
||||||
Contour3D create_base_pool(const ExPolygons &ground_layer,
|
Contour3D create_base_pool(const Polygons &ground_layer,
|
||||||
|
const Polygons &holes = {},
|
||||||
const PoolConfig& cfg = PoolConfig());
|
const PoolConfig& cfg = PoolConfig());
|
||||||
|
|
||||||
Contour3D walls(const Polygon& floor_plate, const Polygon& ceiling,
|
Contour3D walls(const Polygon& floor_plate, const Polygon& ceiling,
|
||||||
@ -42,37 +43,28 @@ int main(const int argc, const char *argv[]) {
|
|||||||
model.ReadSTLFile(argv[1]);
|
model.ReadSTLFile(argv[1]);
|
||||||
model.align_to_origin();
|
model.align_to_origin();
|
||||||
|
|
||||||
ExPolygons ground_slice;
|
Polygons ground_slice;
|
||||||
sla::Contour3D mesh;
|
|
||||||
// TriangleMesh basepool;
|
|
||||||
|
|
||||||
sla::base_plate(model, ground_slice, 0.1f);
|
sla::base_plate(model, ground_slice, 0.1f);
|
||||||
|
|
||||||
if(ground_slice.empty()) return EXIT_FAILURE;
|
if(ground_slice.empty()) return EXIT_FAILURE;
|
||||||
|
|
||||||
// ExPolygon bottom_plate = ground_slice.front();
|
Polygon gndfirst; gndfirst = ground_slice.front();
|
||||||
// ExPolygon top_plate = bottom_plate;
|
sla::offset_with_breakstick_holes(gndfirst, 0.5, 10, 0.3);
|
||||||
// sla::offset(top_plate, coord_t(3.0/SCALING_FACTOR));
|
|
||||||
// sla::offset(bottom_plate, coord_t(1.0/SCALING_FACTOR));
|
sla::Contour3D mesh;
|
||||||
|
|
||||||
|
|
||||||
bench.start();
|
bench.start();
|
||||||
|
|
||||||
// TriangleMesh pool;
|
|
||||||
sla::PoolConfig cfg;
|
sla::PoolConfig cfg;
|
||||||
cfg.min_wall_height_mm = 0;
|
cfg.min_wall_height_mm = 0;
|
||||||
cfg.edge_radius_mm = 0.2;
|
cfg.edge_radius_mm = 0;
|
||||||
mesh = sla::create_base_pool(ground_slice, cfg);
|
mesh = sla::create_base_pool(ground_slice, {}, cfg);
|
||||||
|
|
||||||
// mesh.merge(triangulate_expolygon_3d(top_plate, 3.0, false));
|
|
||||||
// mesh.merge(triangulate_expolygon_3d(bottom_plate, 0.0, true));
|
|
||||||
// mesh = sla::walls(bottom_plate.contour, top_plate.contour, 0, 3, 2.0, [](){});
|
|
||||||
|
|
||||||
bench.stop();
|
bench.stop();
|
||||||
|
|
||||||
cout << "Base pool creation time: " << std::setprecision(10)
|
cout << "Base pool creation time: " << std::setprecision(10)
|
||||||
<< bench.getElapsedSec() << " seconds." << endl;
|
<< bench.getElapsedSec() << " seconds." << endl;
|
||||||
|
|
||||||
// auto point = []()
|
|
||||||
for(auto& trind : mesh.indices) {
|
for(auto& trind : mesh.indices) {
|
||||||
Vec3d p0 = mesh.points[size_t(trind[0])];
|
Vec3d p0 = mesh.points[size_t(trind[0])];
|
||||||
Vec3d p1 = mesh.points[size_t(trind[1])];
|
Vec3d p1 = mesh.points[size_t(trind[1])];
|
||||||
|
@ -7,9 +7,9 @@
|
|||||||
#include "Tesselate.hpp"
|
#include "Tesselate.hpp"
|
||||||
|
|
||||||
// For debugging:
|
// For debugging:
|
||||||
//#include <fstream>
|
// #include <fstream>
|
||||||
//#include <libnest2d/tools/benchmark.h>
|
// #include <libnest2d/tools/benchmark.h>
|
||||||
//#include "SVG.hpp"
|
#include "SVG.hpp"
|
||||||
|
|
||||||
namespace Slic3r { namespace sla {
|
namespace Slic3r { namespace sla {
|
||||||
|
|
||||||
@ -180,9 +180,10 @@ Contour3D walls(const Polygon& lower, const Polygon& upper,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Offsetting with clipper and smoothing the edges into a curvature.
|
/// Offsetting with clipper and smoothing the edges into a curvature.
|
||||||
void offset(ExPolygon& sh, coord_t distance) {
|
void offset(ExPolygon& sh, coord_t distance, bool edgerounding = true) {
|
||||||
using ClipperLib::ClipperOffset;
|
using ClipperLib::ClipperOffset;
|
||||||
using ClipperLib::jtRound;
|
using ClipperLib::jtRound;
|
||||||
|
using ClipperLib::jtMiter;
|
||||||
using ClipperLib::etClosedPolygon;
|
using ClipperLib::etClosedPolygon;
|
||||||
using ClipperLib::Paths;
|
using ClipperLib::Paths;
|
||||||
using ClipperLib::Path;
|
using ClipperLib::Path;
|
||||||
@ -199,11 +200,13 @@ void offset(ExPolygon& sh, coord_t distance) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto jointype = edgerounding? jtRound : jtMiter;
|
||||||
|
|
||||||
ClipperOffset offs;
|
ClipperOffset offs;
|
||||||
offs.ArcTolerance = 0.01*mm(1);
|
offs.ArcTolerance = 0.01*mm(1);
|
||||||
Paths result;
|
Paths result;
|
||||||
offs.AddPath(ctour, jtRound, etClosedPolygon);
|
offs.AddPath(ctour, jointype, etClosedPolygon);
|
||||||
offs.AddPaths(holes, jtRound, etClosedPolygon);
|
offs.AddPaths(holes, jointype, etClosedPolygon);
|
||||||
offs.Execute(result, static_cast<double>(distance));
|
offs.Execute(result, static_cast<double>(distance));
|
||||||
|
|
||||||
// Offsetting reverts the orientation and also removes the last vertex
|
// Offsetting reverts the orientation and also removes the last vertex
|
||||||
@ -233,6 +236,49 @@ void offset(ExPolygon& sh, coord_t distance) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void offset(Polygon& sh, coord_t distance, bool edgerounding = true) {
|
||||||
|
using ClipperLib::ClipperOffset;
|
||||||
|
using ClipperLib::jtRound;
|
||||||
|
using ClipperLib::jtMiter;
|
||||||
|
using ClipperLib::etClosedPolygon;
|
||||||
|
using ClipperLib::Paths;
|
||||||
|
using ClipperLib::Path;
|
||||||
|
|
||||||
|
auto&& ctour = Slic3rMultiPoint_to_ClipperPath(sh);
|
||||||
|
|
||||||
|
// If the input is not at least a triangle, we can not do this algorithm
|
||||||
|
if(ctour.size() < 3) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << "Invalid geometry for offsetting!";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ClipperOffset offs;
|
||||||
|
offs.ArcTolerance = 0.01*mm(1);
|
||||||
|
Paths result;
|
||||||
|
offs.AddPath(ctour, edgerounding ? jtRound : jtMiter, etClosedPolygon);
|
||||||
|
offs.Execute(result, static_cast<double>(distance));
|
||||||
|
|
||||||
|
// Offsetting reverts the orientation and also removes the last vertex
|
||||||
|
// so boost will not have a closed polygon.
|
||||||
|
|
||||||
|
bool found_the_contour = false;
|
||||||
|
for(auto& r : result) {
|
||||||
|
if(ClipperLib::Orientation(r)) {
|
||||||
|
// We don't like if the offsetting generates more than one contour
|
||||||
|
// but throwing would be an overkill. Instead, we should warn the
|
||||||
|
// caller about the inability to create correct geometries
|
||||||
|
if(!found_the_contour) {
|
||||||
|
auto rr = ClipperPath_to_Slic3rPolygon(r);
|
||||||
|
sh.points.swap(rr.points);
|
||||||
|
found_the_contour = true;
|
||||||
|
} else {
|
||||||
|
BOOST_LOG_TRIVIAL(warning)
|
||||||
|
<< "Warning: offsetting result is invalid!";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Unification of polygons (with clipper) preserving holes as well.
|
/// Unification of polygons (with clipper) preserving holes as well.
|
||||||
ExPolygons unify(const ExPolygons& shapes) {
|
ExPolygons unify(const ExPolygons& shapes) {
|
||||||
using ClipperLib::ptSubject;
|
using ClipperLib::ptSubject;
|
||||||
@ -303,6 +349,118 @@ ExPolygons unify(const ExPolygons& shapes) {
|
|||||||
return retv;
|
return retv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Polygons unify(const Polygons& shapes) {
|
||||||
|
using ClipperLib::ptSubject;
|
||||||
|
|
||||||
|
bool closed = true;
|
||||||
|
bool valid = true;
|
||||||
|
|
||||||
|
ClipperLib::Clipper clipper;
|
||||||
|
|
||||||
|
for(auto& path : shapes) {
|
||||||
|
auto clipperpath = Slic3rMultiPoint_to_ClipperPath(path);
|
||||||
|
|
||||||
|
if(!clipperpath.empty())
|
||||||
|
valid &= clipper.AddPath(clipperpath, ptSubject, closed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!valid) BOOST_LOG_TRIVIAL(warning) << "Unification of invalid shapes!";
|
||||||
|
|
||||||
|
ClipperLib::Paths result;
|
||||||
|
clipper.Execute(ClipperLib::ctUnion, result, ClipperLib::pftNonZero);
|
||||||
|
|
||||||
|
Polygons ret;
|
||||||
|
for (ClipperLib::Path &p : result) {
|
||||||
|
Polygon pp = ClipperPath_to_Slic3rPolygon(p);
|
||||||
|
if (!pp.is_clockwise()) ret.emplace_back(std::move(pp));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to cut tiny connector cavities for a given polygon. The input poly
|
||||||
|
// will be offsetted by "padding" and small rectangle shaped cavities will be
|
||||||
|
// inserted along the perimeter in every "stride" distance. The stick rectangles
|
||||||
|
// will have a with about "stick_width". The input dimensions are in world
|
||||||
|
// measure, not the scaled clipper units.
|
||||||
|
void offset_with_breakstick_holes(ExPolygon& poly,
|
||||||
|
double padding,
|
||||||
|
double stride,
|
||||||
|
double stick_width,
|
||||||
|
double penetration)
|
||||||
|
{
|
||||||
|
// We do the basic offsetting first
|
||||||
|
const bool dont_round_edges = false;
|
||||||
|
offset(poly, coord_t(padding / SCALING_FACTOR), dont_round_edges);
|
||||||
|
|
||||||
|
SVG svg("bridgestick_plate.svg");
|
||||||
|
svg.draw(poly);
|
||||||
|
|
||||||
|
auto transf = [stick_width, penetration, padding, stride](Points &pts) {
|
||||||
|
// The connector stick will be a small rectangle with dimensions
|
||||||
|
// stick_width x (penetration + padding) to have some penetration
|
||||||
|
// into the input polygon.
|
||||||
|
|
||||||
|
Points out;
|
||||||
|
out.reserve(2 * pts.size()); // output polygon points
|
||||||
|
|
||||||
|
// stick bottom and right edge dimensions
|
||||||
|
double sbottom = stick_width / SCALING_FACTOR;
|
||||||
|
double sright = (penetration + padding) / SCALING_FACTOR;
|
||||||
|
|
||||||
|
// scaled stride distance
|
||||||
|
double sstride = stride / SCALING_FACTOR;
|
||||||
|
double t = 0;
|
||||||
|
|
||||||
|
// process pairs of vertices as an edge, start with the last and
|
||||||
|
// first point
|
||||||
|
for (size_t i = pts.size() - 1, j = 0; j < pts.size(); i = j, ++j) {
|
||||||
|
// Get vertices and the direction vectors
|
||||||
|
const Point &a = pts[i], &b = pts[j];
|
||||||
|
Vec2d dir = b.cast<double>() - a.cast<double>();
|
||||||
|
double nrm = dir.norm();
|
||||||
|
dir /= nrm;
|
||||||
|
Vec2d dirp(-dir(Y), dir(X));
|
||||||
|
|
||||||
|
// Insert start point
|
||||||
|
out.emplace_back(a);
|
||||||
|
|
||||||
|
// dodge the start point, do not make sticks on the joins
|
||||||
|
while (t < sright) t += sright;
|
||||||
|
double tend = nrm - sright;
|
||||||
|
|
||||||
|
while (t < tend) { // insert the stick on the polygon perimeter
|
||||||
|
|
||||||
|
// calculate the stick rectangle vertices and insert them
|
||||||
|
// into the output.
|
||||||
|
Point p1 = a + (t * dir).cast<coord_t>();
|
||||||
|
Point p2 = p1 + (sright * dirp).cast<coord_t>();
|
||||||
|
Point p3 = p2 + (sbottom * dir).cast<coord_t>();
|
||||||
|
Point p4 = p3 + (sright * -dirp).cast<coord_t>();
|
||||||
|
out.insert(out.end(), {p1, p2, p3, p4});
|
||||||
|
|
||||||
|
// continue along the perimeter
|
||||||
|
t += sstride;
|
||||||
|
}
|
||||||
|
|
||||||
|
t = t - nrm;
|
||||||
|
|
||||||
|
// Insert edge endpoint
|
||||||
|
out.emplace_back(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
// move the new points
|
||||||
|
out.shrink_to_fit();
|
||||||
|
pts.swap(out);
|
||||||
|
};
|
||||||
|
|
||||||
|
transf(poly.contour.points);
|
||||||
|
for (auto &h : poly.holes) transf(h.points);
|
||||||
|
|
||||||
|
svg.draw(poly);
|
||||||
|
svg.Close();
|
||||||
|
}
|
||||||
|
|
||||||
/// Only a debug function to generate top and bottom plates from a 2D shape.
|
/// Only a debug function to generate top and bottom plates from a 2D shape.
|
||||||
/// It is not used in the algorithm directly.
|
/// It is not used in the algorithm directly.
|
||||||
inline Contour3D roofs(const ExPolygon& poly, coord_t z_distance) {
|
inline Contour3D roofs(const ExPolygon& poly, coord_t z_distance) {
|
||||||
@ -467,40 +625,37 @@ inline Point centroid(Points& pp) {
|
|||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline Point centroid(const ExPolygon& poly) {
|
inline Point centroid(const Polygon& poly) {
|
||||||
return poly.contour.centroid();
|
return poly.centroid();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A fake concave hull that is constructed by connecting separate shapes
|
/// A fake concave hull that is constructed by connecting separate shapes
|
||||||
/// with explicit bridges. Bridges are generated from each shape's centroid
|
/// with explicit bridges. Bridges are generated from each shape's centroid
|
||||||
/// to the center of the "scene" which is the centroid calculated from the shape
|
/// to the center of the "scene" which is the centroid calculated from the shape
|
||||||
/// centroids (a star is created...)
|
/// centroids (a star is created...)
|
||||||
ExPolygons concave_hull(const ExPolygons& polys, double max_dist_mm = 50,
|
Polygons concave_hull(const Polygons& polys, double max_dist_mm = 50,
|
||||||
ThrowOnCancel throw_on_cancel = [](){})
|
ThrowOnCancel throw_on_cancel = [](){})
|
||||||
{
|
{
|
||||||
namespace bgi = boost::geometry::index;
|
namespace bgi = boost::geometry::index;
|
||||||
using SpatElement = std::pair<BoundingBox, unsigned>;
|
using SpatElement = std::pair<Point, unsigned>;
|
||||||
using SpatIndex = bgi::rtree< SpatElement, bgi::rstar<16, 4> >;
|
using SpatIndex = bgi::rtree< SpatElement, bgi::rstar<16, 4> >;
|
||||||
|
|
||||||
if(polys.empty()) return ExPolygons();
|
if(polys.empty()) return Polygons();
|
||||||
|
|
||||||
ExPolygons punion = unify(polys); // could be redundant
|
const double max_dist = mm(max_dist_mm);
|
||||||
|
|
||||||
|
Polygons punion = unify(polys); // could be redundant
|
||||||
|
|
||||||
if(punion.size() == 1) return punion;
|
if(punion.size() == 1) return punion;
|
||||||
|
|
||||||
// We get the centroids of all the islands in the 2D slice
|
// We get the centroids of all the islands in the 2D slice
|
||||||
Points centroids; centroids.reserve(punion.size());
|
Points centroids; centroids.reserve(punion.size());
|
||||||
std::transform(punion.begin(), punion.end(), std::back_inserter(centroids),
|
std::transform(punion.begin(), punion.end(), std::back_inserter(centroids),
|
||||||
[](const ExPolygon& poly) { return centroid(poly); });
|
[](const Polygon& poly) { return centroid(poly); });
|
||||||
|
|
||||||
|
|
||||||
SpatIndex boxindex; unsigned idx = 0;
|
|
||||||
std::for_each(punion.begin(), punion.end(),
|
|
||||||
[&boxindex, &idx](const ExPolygon& expo) {
|
|
||||||
BoundingBox bb(expo);
|
|
||||||
boxindex.insert(std::make_pair(bb, idx++));
|
|
||||||
});
|
|
||||||
|
|
||||||
|
SpatIndex ctrindex;
|
||||||
|
unsigned idx = 0;
|
||||||
|
for(const Point &ct : centroids) ctrindex.insert(std::make_pair(ct, idx++));
|
||||||
|
|
||||||
// Centroid of the centroids of islands. This is where the additional
|
// Centroid of the centroids of islands. This is where the additional
|
||||||
// connector sticks are routed.
|
// connector sticks are routed.
|
||||||
@ -511,25 +666,32 @@ ExPolygons concave_hull(const ExPolygons& polys, double max_dist_mm = 50,
|
|||||||
idx = 0;
|
idx = 0;
|
||||||
std::transform(centroids.begin(), centroids.end(),
|
std::transform(centroids.begin(), centroids.end(),
|
||||||
std::back_inserter(punion),
|
std::back_inserter(punion),
|
||||||
[&punion, &boxindex, cc, max_dist_mm, &idx, throw_on_cancel]
|
[¢roids, &ctrindex, cc, max_dist, &idx, throw_on_cancel]
|
||||||
(const Point& c)
|
(const Point& c)
|
||||||
{
|
{
|
||||||
throw_on_cancel();
|
throw_on_cancel();
|
||||||
double dx = x(c) - x(cc), dy = y(c) - y(cc);
|
double dx = x(c) - x(cc), dy = y(c) - y(cc);
|
||||||
double l = std::sqrt(dx * dx + dy * dy);
|
double l = std::sqrt(dx * dx + dy * dy);
|
||||||
double nx = dx / l, ny = dy / l;
|
double nx = dx / l, ny = dy / l;
|
||||||
double max_dist = mm(max_dist_mm);
|
|
||||||
|
|
||||||
ExPolygon& expo = punion[idx++];
|
Point& ct = centroids[idx];
|
||||||
BoundingBox querybb(expo);
|
|
||||||
|
|
||||||
querybb.offset(max_dist);
|
|
||||||
std::vector<SpatElement> result;
|
std::vector<SpatElement> result;
|
||||||
boxindex.query(bgi::intersects(querybb), std::back_inserter(result));
|
ctrindex.query(bgi::nearest(ct, 2), std::back_inserter(result));
|
||||||
if(result.size() <= 1) return ExPolygon();
|
|
||||||
|
|
||||||
ExPolygon r;
|
double dist = max_dist;
|
||||||
auto& ctour = r.contour.points;
|
for (const SpatElement &el : result)
|
||||||
|
if (el.second != idx) {
|
||||||
|
dist = Line(el.first, ct).length();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
idx++;
|
||||||
|
|
||||||
|
if (dist >= max_dist) return Polygon();
|
||||||
|
|
||||||
|
Polygon r;
|
||||||
|
auto& ctour = r.points;
|
||||||
|
|
||||||
ctour.reserve(3);
|
ctour.reserve(3);
|
||||||
ctour.emplace_back(cc);
|
ctour.emplace_back(cc);
|
||||||
@ -576,13 +738,14 @@ void base_plate(const TriangleMesh &mesh, ExPolygons &output, float h,
|
|||||||
|
|
||||||
ExPolygons utmp = unify(tmp);
|
ExPolygons utmp = unify(tmp);
|
||||||
|
|
||||||
for(auto& o : utmp) {
|
for(ExPolygon& o : utmp) {
|
||||||
auto&& smp = o.simplify(0.1/SCALING_FACTOR);
|
auto&& smp = o.simplify(0.1/SCALING_FACTOR); // TODO: is this important?
|
||||||
output.insert(output.end(), smp.begin(), smp.end());
|
output.insert(output.end(), smp.begin(), smp.end());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Contour3D create_base_pool(const ExPolygons &ground_layer,
|
Contour3D create_base_pool(const Polygons &ground_layer,
|
||||||
|
const ExPolygons &obj_self_pad = {},
|
||||||
const PoolConfig& cfg = PoolConfig())
|
const PoolConfig& cfg = PoolConfig())
|
||||||
{
|
{
|
||||||
// for debugging:
|
// for debugging:
|
||||||
@ -597,7 +760,7 @@ Contour3D create_base_pool(const ExPolygons &ground_layer,
|
|||||||
// serve as the bottom plate of the pad. We will offset this concave hull
|
// serve as the bottom plate of the pad. We will offset this concave hull
|
||||||
// and then offset back the result with clipper with rounding edges ON. This
|
// and then offset back the result with clipper with rounding edges ON. This
|
||||||
// trick will create a nice rounded pad shape.
|
// trick will create a nice rounded pad shape.
|
||||||
ExPolygons concavehs = concave_hull(ground_layer, mergedist, cfg.throw_on_cancel);
|
Polygons concavehs = concave_hull(ground_layer, mergedist, cfg.throw_on_cancel);
|
||||||
|
|
||||||
const double thickness = cfg.min_wall_thickness_mm;
|
const double thickness = cfg.min_wall_thickness_mm;
|
||||||
const double wingheight = cfg.min_wall_height_mm;
|
const double wingheight = cfg.min_wall_height_mm;
|
||||||
@ -617,42 +780,37 @@ Contour3D create_base_pool(const ExPolygons &ground_layer,
|
|||||||
|
|
||||||
Contour3D pool;
|
Contour3D pool;
|
||||||
|
|
||||||
for(ExPolygon& concaveh : concavehs) {
|
for(Polygon& concaveh : concavehs) {
|
||||||
if(concaveh.contour.points.empty()) return pool;
|
if(concaveh.points.empty()) return pool;
|
||||||
|
|
||||||
// Get rid of any holes in the concave hull output.
|
|
||||||
concaveh.holes.clear();
|
|
||||||
|
|
||||||
// Here lies the trick that does the smoothing only with clipper offset
|
// Here lies the trick that does the smoothing only with clipper offset
|
||||||
// calls. The offset is configured to round edges. Inner edges will
|
// calls. The offset is configured to round edges. Inner edges will
|
||||||
// be rounded because we offset twice: ones to get the outer (top) plate
|
// be rounded because we offset twice: ones to get the outer (top) plate
|
||||||
// and again to get the inner (bottom) plate
|
// and again to get the inner (bottom) plate
|
||||||
auto outer_base = concaveh;
|
auto outer_base = concaveh;
|
||||||
outer_base.holes.clear();
|
|
||||||
offset(outer_base, s_safety_dist + s_wingdist + s_thickness);
|
offset(outer_base, s_safety_dist + s_wingdist + s_thickness);
|
||||||
|
|
||||||
ExPolygon bottom_poly = outer_base;
|
ExPolygon bottom_poly; bottom_poly.contour = outer_base;
|
||||||
bottom_poly.holes.clear();
|
|
||||||
offset(bottom_poly, -s_bottom_offs);
|
offset(bottom_poly, -s_bottom_offs);
|
||||||
|
|
||||||
// Punching a hole in the top plate for the cavity
|
// Punching a hole in the top plate for the cavity
|
||||||
ExPolygon top_poly;
|
ExPolygon top_poly;
|
||||||
ExPolygon middle_base;
|
ExPolygon middle_base;
|
||||||
ExPolygon inner_base;
|
ExPolygon inner_base;
|
||||||
top_poly.contour = outer_base.contour;
|
top_poly.contour = outer_base;
|
||||||
|
|
||||||
if(wingheight > 0) {
|
if(wingheight > 0) {
|
||||||
inner_base = outer_base;
|
inner_base.contour = outer_base;
|
||||||
offset(inner_base, -(s_thickness + s_wingdist + s_eradius));
|
offset(inner_base, -(s_thickness + s_wingdist + s_eradius));
|
||||||
|
|
||||||
middle_base = outer_base;
|
middle_base.contour = outer_base;
|
||||||
offset(middle_base, -s_thickness);
|
offset(middle_base, -s_thickness);
|
||||||
top_poly.holes.emplace_back(middle_base.contour);
|
top_poly.holes.emplace_back(middle_base.contour);
|
||||||
auto& tph = top_poly.holes.back().points;
|
auto& tph = top_poly.holes.back().points;
|
||||||
std::reverse(tph.begin(), tph.end());
|
std::reverse(tph.begin(), tph.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
ExPolygon ob = outer_base; double wh = 0;
|
ExPolygon ob; ob.contour = outer_base; double wh = 0;
|
||||||
|
|
||||||
// now we will calculate the angle or portion of the circle from
|
// now we will calculate the angle or portion of the circle from
|
||||||
// pi/2 that will connect perfectly with the bottom plate.
|
// pi/2 that will connect perfectly with the bottom plate.
|
||||||
@ -713,11 +871,56 @@ Contour3D create_base_pool(const ExPolygons &ground_layer,
|
|||||||
wh, -wingdist, thrcl));
|
wh, -wingdist, thrcl));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now we need to triangulate the top and bottom plates as well as the
|
if (cfg.embed_object) {
|
||||||
// cavity bottom plate which is the same as the bottom plate but it is
|
ExPolygons pp = diff_ex(to_polygons(bottom_poly),
|
||||||
// elevated by the thickness.
|
to_polygons(obj_self_pad));
|
||||||
|
|
||||||
|
// Generate outer walls
|
||||||
|
auto fp = [](const Point &p, Point::coord_type z) {
|
||||||
|
return unscale(x(p), y(p), z);
|
||||||
|
};
|
||||||
|
|
||||||
|
auto straight_walls = [&pool, s_thickness, fp](const Polygon &cntr)
|
||||||
|
{
|
||||||
|
auto lines = cntr.lines();
|
||||||
|
bool cclk = cntr.is_counter_clockwise();
|
||||||
|
|
||||||
|
for (auto &l : lines) {
|
||||||
|
auto s = coord_t(pool.points.size());
|
||||||
|
pool.points.emplace_back(fp(l.a, -s_thickness));
|
||||||
|
pool.points.emplace_back(fp(l.b, -s_thickness));
|
||||||
|
pool.points.emplace_back(fp(l.a, 0));
|
||||||
|
pool.points.emplace_back(fp(l.b, 0));
|
||||||
|
|
||||||
|
if(cclk) {
|
||||||
|
pool.indices.emplace_back(s + 3, s + 1, s);
|
||||||
|
pool.indices.emplace_back(s + 2, s + 3, s);
|
||||||
|
} else {
|
||||||
|
pool.indices.emplace_back(s, s + 1, s + 3);
|
||||||
|
pool.indices.emplace_back(s, s + 3, s + 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (ExPolygon &ep : pp) {
|
||||||
|
pool.merge(triangulate_expolygon_3d(ep));
|
||||||
|
pool.merge(triangulate_expolygon_3d(ep, -fullheight, true));
|
||||||
|
|
||||||
|
for (auto &h : ep.holes) straight_walls(h);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip the outer contour. TODO: make sure the first in the list
|
||||||
|
// IS the outer contour.
|
||||||
|
for (auto it = std::next(pp.begin()); it != pp.end(); ++it)
|
||||||
|
straight_walls(it->contour);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Now we need to triangulate the top and bottom plates as well as
|
||||||
|
// the cavity bottom plate which is the same as the bottom plate
|
||||||
|
// but it is elevated by the thickness.
|
||||||
pool.merge(triangulate_expolygon_3d(top_poly));
|
pool.merge(triangulate_expolygon_3d(top_poly));
|
||||||
pool.merge(triangulate_expolygon_3d(bottom_poly, -fullheight, true));
|
pool.merge(triangulate_expolygon_3d(bottom_poly, -fullheight, true));
|
||||||
|
}
|
||||||
|
|
||||||
if(wingheight > 0)
|
if(wingheight > 0)
|
||||||
pool.merge(triangulate_expolygon_3d(inner_base, -wingheight));
|
pool.merge(triangulate_expolygon_3d(inner_base, -wingheight));
|
||||||
@ -727,8 +930,8 @@ Contour3D create_base_pool(const ExPolygons &ground_layer,
|
|||||||
return pool;
|
return pool;
|
||||||
}
|
}
|
||||||
|
|
||||||
void create_base_pool(const ExPolygons &ground_layer, TriangleMesh& out,
|
void create_base_pool(const Polygons &ground_layer, TriangleMesh& out,
|
||||||
const PoolConfig& cfg)
|
const ExPolygons &holes, const PoolConfig& cfg)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
@ -738,7 +941,7 @@ void create_base_pool(const ExPolygons &ground_layer, TriangleMesh& out,
|
|||||||
// std::fstream fout("pad_debug.obj", std::fstream::out);
|
// std::fstream fout("pad_debug.obj", std::fstream::out);
|
||||||
// if(fout.good()) pool.to_obj(fout);
|
// if(fout.good()) pool.to_obj(fout);
|
||||||
|
|
||||||
out.merge(mesh(create_base_pool(ground_layer, cfg)));
|
out.merge(mesh(create_base_pool(ground_layer, holes, cfg)));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,9 @@
|
|||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
||||||
class ExPolygon;
|
class ExPolygon;
|
||||||
|
class Polygon;
|
||||||
using ExPolygons = std::vector<ExPolygon>;
|
using ExPolygons = std::vector<ExPolygon>;
|
||||||
|
using Polygons = std::vector<Polygon>;
|
||||||
|
|
||||||
class TriangleMesh;
|
class TriangleMesh;
|
||||||
|
|
||||||
@ -23,12 +25,24 @@ void base_plate(const TriangleMesh& mesh, // input mesh
|
|||||||
float layerheight = 0.05f, // The sampling height
|
float layerheight = 0.05f, // The sampling height
|
||||||
ThrowOnCancel thrfn = [](){}); // Will be called frequently
|
ThrowOnCancel thrfn = [](){}); // Will be called frequently
|
||||||
|
|
||||||
|
// Function to cut tiny connector cavities for a given polygon. The input poly
|
||||||
|
// will be offsetted by "padding" and small rectangle shaped cavities will be
|
||||||
|
// inserted along the perimeter in every "stride" distance. The stick rectangles
|
||||||
|
// will have a with about "stick_width". The input dimensions are in world
|
||||||
|
// measure, not the scaled clipper units.
|
||||||
|
void offset_with_breakstick_holes(ExPolygon& poly,
|
||||||
|
double padding,
|
||||||
|
double stride,
|
||||||
|
double stick_width,
|
||||||
|
double penetration = 0.0);
|
||||||
|
|
||||||
struct PoolConfig {
|
struct PoolConfig {
|
||||||
double min_wall_thickness_mm = 2;
|
double min_wall_thickness_mm = 2;
|
||||||
double min_wall_height_mm = 5;
|
double min_wall_height_mm = 5;
|
||||||
double max_merge_distance_mm = 50;
|
double max_merge_distance_mm = 50;
|
||||||
double edge_radius_mm = 1;
|
double edge_radius_mm = 1;
|
||||||
double wall_slope = std::atan(1.0); // Universal constant for Pi/4
|
double wall_slope = std::atan(1.0); // Universal constant for Pi/4
|
||||||
|
bool embed_object = false;
|
||||||
|
|
||||||
ThrowOnCancel throw_on_cancel = [](){};
|
ThrowOnCancel throw_on_cancel = [](){};
|
||||||
|
|
||||||
@ -42,8 +56,9 @@ struct PoolConfig {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// Calculate the pool for the mesh for SLA printing
|
/// Calculate the pool for the mesh for SLA printing
|
||||||
void create_base_pool(const ExPolygons& base_plate,
|
void create_base_pool(const Polygons& base_plate,
|
||||||
TriangleMesh& output_mesh,
|
TriangleMesh& output_mesh,
|
||||||
|
const ExPolygons& holes,
|
||||||
const PoolConfig& = PoolConfig());
|
const PoolConfig& = PoolConfig());
|
||||||
|
|
||||||
/// TODO: Currently the base plate of the pool will have half the height of the
|
/// TODO: Currently the base plate of the pool will have half the height of the
|
||||||
|
@ -72,6 +72,7 @@ public:
|
|||||||
~EigenMesh3D();
|
~EigenMesh3D();
|
||||||
|
|
||||||
inline double ground_level() const { return m_ground_level; }
|
inline double ground_level() const { return m_ground_level; }
|
||||||
|
inline double& ground_level() { return m_ground_level; }
|
||||||
|
|
||||||
inline const Eigen::MatrixXd& V() const { return m_V; }
|
inline const Eigen::MatrixXd& V() const { return m_V; }
|
||||||
inline const Eigen::MatrixXi& F() const { return m_F; }
|
inline const Eigen::MatrixXi& F() const { return m_F; }
|
||||||
@ -149,6 +150,12 @@ public:
|
|||||||
#endif /* SLIC3R_SLA_NEEDS_WINDTREE */
|
#endif /* SLIC3R_SLA_NEEDS_WINDTREE */
|
||||||
|
|
||||||
double squared_distance(const Vec3d& p, int& i, Vec3d& c) const;
|
double squared_distance(const Vec3d& p, int& i, Vec3d& c) const;
|
||||||
|
inline double squared_distance(const Vec3d &p) const
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
Vec3d c;
|
||||||
|
return squared_distance(p, i, c);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
#include <libslic3r/Model.hpp>
|
#include <libslic3r/Model.hpp>
|
||||||
|
|
||||||
#include <libnest2d/optimizers/nlopt/genetic.hpp>
|
#include <libnest2d/optimizers/nlopt/genetic.hpp>
|
||||||
|
#include <libnest2d/optimizers/nlopt/subplex.hpp>
|
||||||
#include <boost/log/trivial.hpp>
|
#include <boost/log/trivial.hpp>
|
||||||
#include <tbb/parallel_for.h>
|
#include <tbb/parallel_for.h>
|
||||||
#include <libslic3r/I18N.hpp>
|
#include <libslic3r/I18N.hpp>
|
||||||
@ -71,6 +72,8 @@ const double SupportConfig::normal_cutoff_angle = 150.0 * M_PI / 180.0;
|
|||||||
// The shortest distance of any support structure from the model surface
|
// The shortest distance of any support structure from the model surface
|
||||||
const double SupportConfig::safety_distance_mm = 0.5;
|
const double SupportConfig::safety_distance_mm = 0.5;
|
||||||
|
|
||||||
|
const double SupportConfig::pillar_base_safety_distance_mm = 0.5;
|
||||||
|
|
||||||
const double SupportConfig::max_solo_pillar_height_mm = 15.0;
|
const double SupportConfig::max_solo_pillar_height_mm = 15.0;
|
||||||
const double SupportConfig::max_dual_pillar_height_mm = 35.0;
|
const double SupportConfig::max_dual_pillar_height_mm = 35.0;
|
||||||
const double SupportConfig::optimizer_rel_score_diff = 1e-6;
|
const double SupportConfig::optimizer_rel_score_diff = 1e-6;
|
||||||
@ -413,7 +416,7 @@ struct Pillar {
|
|||||||
assert(steps > 0);
|
assert(steps > 0);
|
||||||
|
|
||||||
height = jp(Z) - endp(Z);
|
height = jp(Z) - endp(Z);
|
||||||
if(height > 0) { // 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.
|
||||||
@ -556,28 +559,47 @@ struct Pad {
|
|||||||
PoolConfig cfg;
|
PoolConfig cfg;
|
||||||
double zlevel = 0;
|
double zlevel = 0;
|
||||||
|
|
||||||
Pad() {}
|
Pad() = default;
|
||||||
|
|
||||||
Pad(const TriangleMesh& object_support_mesh,
|
Pad(const TriangleMesh& object_support_mesh,
|
||||||
const ExPolygons& baseplate,
|
const ExPolygons& modelbase,
|
||||||
double ground_level,
|
double ground_level,
|
||||||
const PoolConfig& pcfg) :
|
const PoolConfig& pcfg) :
|
||||||
cfg(pcfg),
|
cfg(pcfg),
|
||||||
zlevel(ground_level +
|
zlevel(ground_level +
|
||||||
(sla::get_pad_fullheight(pcfg) - sla::get_pad_elevation(pcfg)) )
|
sla::get_pad_fullheight(pcfg) -
|
||||||
|
sla::get_pad_elevation(pcfg))
|
||||||
{
|
{
|
||||||
ExPolygons basep;
|
Polygons basep;
|
||||||
cfg.throw_on_cancel();
|
cfg.throw_on_cancel();
|
||||||
|
|
||||||
// The 0.1f is the layer height with which the mesh is sampled and then
|
// The 0.1f is the layer height with which the mesh is sampled and then
|
||||||
// the layers are unified into one vector of polygons.
|
// the layers are unified into one vector of polygons.
|
||||||
base_plate(object_support_mesh, basep,
|
ExPolygons platetmp;
|
||||||
|
base_plate(object_support_mesh, platetmp,
|
||||||
float(cfg.min_wall_height_mm + cfg.min_wall_thickness_mm),
|
float(cfg.min_wall_height_mm + cfg.min_wall_thickness_mm),
|
||||||
0.1f, pcfg.throw_on_cancel);
|
0.1f, pcfg.throw_on_cancel);
|
||||||
|
|
||||||
for(auto& bp : baseplate) basep.emplace_back(bp);
|
// We don't need the holes for the base plate from the supports
|
||||||
|
for (const ExPolygon &bp : platetmp) basep.emplace_back(bp.contour);
|
||||||
|
for (const ExPolygon &bp : modelbase) basep.emplace_back(bp.contour);
|
||||||
|
|
||||||
|
if(pcfg.embed_object) {
|
||||||
|
|
||||||
|
auto modelbase_sticks = modelbase;
|
||||||
|
for(auto& poly : modelbase_sticks)
|
||||||
|
sla::offset_with_breakstick_holes(
|
||||||
|
poly,
|
||||||
|
SupportConfig::pillar_base_safety_distance_mm, // padding
|
||||||
|
10, // stride (mm)
|
||||||
|
0.3, // stick_width (mm)
|
||||||
|
0.1); // penetration (mm)
|
||||||
|
|
||||||
|
create_base_pool(basep, tmesh, modelbase_sticks, cfg);
|
||||||
|
} else {
|
||||||
|
create_base_pool(basep, tmesh, {}, cfg);
|
||||||
|
}
|
||||||
|
|
||||||
create_base_pool(basep, tmesh, cfg);
|
|
||||||
tmesh.translate(0, 0, float(zlevel));
|
tmesh.translate(0, 0, float(zlevel));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -763,9 +785,9 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Pad& create_pad(const TriangleMesh& object_supports,
|
const Pad& create_pad(const TriangleMesh& object_supports,
|
||||||
const ExPolygons& baseplate,
|
const ExPolygons& modelbase,
|
||||||
const PoolConfig& cfg) {
|
const PoolConfig& cfg) {
|
||||||
m_pad = Pad(object_supports, baseplate, ground_level, cfg);
|
m_pad = Pad(object_supports, modelbase, ground_level, cfg);
|
||||||
return m_pad;
|
return m_pad;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1149,7 +1171,7 @@ class SLASupportTree::Algorithm {
|
|||||||
auto hr = m.query_ray_hit(p + sd*dir, dir);
|
auto hr = m.query_ray_hit(p + sd*dir, dir);
|
||||||
|
|
||||||
if(ins_check && hr.is_inside()) {
|
if(ins_check && hr.is_inside()) {
|
||||||
if(hr.distance() > r + sd) hits[i] = HitResult(0.0);
|
if(hr.distance() > 2 * r + sd) hits[i] = HitResult(0.0);
|
||||||
else {
|
else {
|
||||||
// re-cast the ray from the outside of the object
|
// re-cast the ray from the outside of the object
|
||||||
auto hr2 =
|
auto hr2 =
|
||||||
@ -1265,8 +1287,11 @@ class SLASupportTree::Algorithm {
|
|||||||
// 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) {
|
||||||
|
|
||||||
auto nearpillar = [this, nearpillar_id]() { return m_result.pillar(nearpillar_id); };
|
auto nearpillar = [this, nearpillar_id]() {
|
||||||
if(nearpillar().bridges > m_cfg.max_bridges_on_pillar) return false;
|
return m_result.pillar(nearpillar_id);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (nearpillar().bridges > m_cfg.max_bridges_on_pillar) return false;
|
||||||
|
|
||||||
Vec3d headjp = head.junction_point();
|
Vec3d headjp = head.junction_point();
|
||||||
Vec3d nearjp_u = nearpillar().startpoint();
|
Vec3d nearjp_u = nearpillar().startpoint();
|
||||||
@ -1370,6 +1395,108 @@ class SLASupportTree::Algorithm {
|
|||||||
return nearest_id >= 0;
|
return nearest_id >= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is a proxy function for pillar creation which will mind the gap
|
||||||
|
// between the pad and the model bottom in zero elevation mode.
|
||||||
|
void create_ground_pillar(const Vec3d &jp,
|
||||||
|
const Vec3d &sourcedir,
|
||||||
|
double radius,
|
||||||
|
int head_id = -1)
|
||||||
|
{
|
||||||
|
// People were killed for this number (seriously)
|
||||||
|
static const double SQR2 = std::sqrt(2.0);
|
||||||
|
|
||||||
|
double gndlvl = m_result.ground_level;
|
||||||
|
Vec3d endp = {jp(X), jp(Y), gndlvl};
|
||||||
|
double sd = SupportConfig::pillar_base_safety_distance_mm;
|
||||||
|
int pillar_id = -1;
|
||||||
|
double min_dist = sd + m_cfg.base_radius_mm + EPSILON;
|
||||||
|
double dist = 0;
|
||||||
|
bool can_add_base = true;
|
||||||
|
bool normal_mode = true;
|
||||||
|
|
||||||
|
if (m_cfg.object_elevation_mm < EPSILON
|
||||||
|
&& (dist = std::sqrt(m_mesh.squared_distance(endp))) < min_dist) {
|
||||||
|
// Get the distance from the mesh. This can be later optimized
|
||||||
|
// to get the distance in 2D plane because we are dealing with
|
||||||
|
// the ground level only.
|
||||||
|
|
||||||
|
normal_mode = false;
|
||||||
|
double mv = 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;
|
||||||
|
scr.stop_score = min_dist;
|
||||||
|
SubplexOptimizer solver(scr);
|
||||||
|
|
||||||
|
auto result = solver.optimize_max(
|
||||||
|
[this, dir, jp, gndlvl](double mv) {
|
||||||
|
Vec3d endp = jp + SQR2 * mv * dir;
|
||||||
|
endp(Z) = gndlvl;
|
||||||
|
return std::sqrt(m_mesh.squared_distance(endp));
|
||||||
|
},
|
||||||
|
initvals(mv), bound(0.0, 2 * min_dist));
|
||||||
|
|
||||||
|
mv = std::get<0>(result.optimum);
|
||||||
|
endp = jp + std::sqrt(2) * mv * dir;
|
||||||
|
Vec3d pgnd = {endp(X), endp(Y), gndlvl};
|
||||||
|
can_add_base = result.score > min_dist;
|
||||||
|
|
||||||
|
// We have to check if the bridge is feasible.
|
||||||
|
if (bridge_mesh_intersect(jp, dir, radius) < (endp - jp).norm()) {
|
||||||
|
normal_mode = true;
|
||||||
|
endp = {jp(X), jp(Y), gndlvl};
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// If the new endpoint is below ground, do not make a pillar
|
||||||
|
if (endp(Z) < gndlvl)
|
||||||
|
endp = endp - SQR2 * (gndlvl - endp(Z)) * dir; // back off
|
||||||
|
else {
|
||||||
|
Pillar &plr = m_result.add_pillar(endp, pgnd, radius);
|
||||||
|
|
||||||
|
if (can_add_base)
|
||||||
|
plr.add_base(m_cfg.base_height_mm,
|
||||||
|
m_cfg.base_radius_mm);
|
||||||
|
|
||||||
|
pillar_id = plr.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_result.add_bridge(jp, endp, radius);
|
||||||
|
m_result.add_junction(endp, radius);
|
||||||
|
|
||||||
|
// Add a degenerated pillar and the bridge.
|
||||||
|
// The degenerate pillar will have zero length and it will
|
||||||
|
// prevent from queries of head_pillar() to have non-existing
|
||||||
|
// pillar when the head should have one.
|
||||||
|
if (head_id >= 0)
|
||||||
|
m_result.add_pillar(unsigned(head_id), jp, radius);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (normal_mode) {
|
||||||
|
Pillar &plr = head_id >= 0
|
||||||
|
? m_result.add_pillar(unsigned(head_id),
|
||||||
|
endp,
|
||||||
|
radius)
|
||||||
|
: m_result.add_pillar(jp, endp, radius);
|
||||||
|
|
||||||
|
if (can_add_base)
|
||||||
|
plr.add_base(m_cfg.base_height_mm, m_cfg.base_radius_mm);
|
||||||
|
|
||||||
|
pillar_id = plr.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(pillar_id >= 0) // Save the pillar endpoint in the spatial index
|
||||||
|
m_pillar_index.insert(endp, pillar_id);
|
||||||
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
Algorithm(const SupportConfig& config,
|
Algorithm(const SupportConfig& config,
|
||||||
@ -1473,14 +1600,14 @@ public:
|
|||||||
std::cos(polar)).normalized();
|
std::cos(polar)).normalized();
|
||||||
|
|
||||||
// check available distance
|
// check available distance
|
||||||
double t = pinhead_mesh_intersect(
|
EigenMesh3D::hit_result t
|
||||||
hp, // touching point
|
= pinhead_mesh_intersect(hp, // touching point
|
||||||
nn, // normal
|
nn, // normal
|
||||||
pin_r,
|
pin_r,
|
||||||
m_cfg.head_back_radius_mm,
|
m_cfg.head_back_radius_mm,
|
||||||
w);
|
w);
|
||||||
|
|
||||||
if(t <= w) {
|
if(t.distance() <= w) {
|
||||||
|
|
||||||
// Let's try to optimize this angle, there might be a
|
// Let's try to optimize this angle, there might be a
|
||||||
// viable normal that doesn't collide with the model
|
// viable normal that doesn't collide with the model
|
||||||
@ -1523,12 +1650,17 @@ public:
|
|||||||
// save the verified and corrected normal
|
// save the verified and corrected normal
|
||||||
m_support_nmls.row(fidx) = nn;
|
m_support_nmls.row(fidx) = nn;
|
||||||
|
|
||||||
if(t > w) {
|
if (t.distance() > w) {
|
||||||
|
// Check distance from ground, we might have zero elevation.
|
||||||
|
if (hp(Z) + w * nn(Z) < m_result.ground_level) {
|
||||||
|
m_iheadless.emplace_back(fidx);
|
||||||
|
} else {
|
||||||
// mark the point for needing a head.
|
// mark the point for needing a head.
|
||||||
m_iheads.emplace_back(fidx);
|
m_iheads.emplace_back(fidx);
|
||||||
} else if( polar >= 3*PI/4 ) {
|
}
|
||||||
// Headless supports do not tilt like the headed ones so
|
} else if (polar >= 3 * PI / 4) {
|
||||||
// the normal should point almost to the ground.
|
// Headless supports do not tilt like the headed ones
|
||||||
|
// so the normal should point almost to the ground.
|
||||||
m_iheadless.emplace_back(fidx);
|
m_iheadless.emplace_back(fidx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1594,16 +1726,22 @@ public:
|
|||||||
// from each other in the XY plane to not cross their pillar bases
|
// from each other in the XY plane to not cross their pillar bases
|
||||||
// These clusters of support points will join in one pillar,
|
// These clusters of support points will join in one pillar,
|
||||||
// possibly in their centroid support point.
|
// possibly in their centroid support point.
|
||||||
|
|
||||||
auto pointfn = [this](unsigned i) {
|
auto pointfn = [this](unsigned i) {
|
||||||
return m_result.head(i).junction_point();
|
return m_result.head(i).junction_point();
|
||||||
};
|
};
|
||||||
auto predicate = [this](const SpatElement& e1, const SpatElement& e2) {
|
|
||||||
|
auto predicate = [this](const SpatElement &e1,
|
||||||
|
const SpatElement &e2) {
|
||||||
double d2d = distance(to_2d(e1.first), to_2d(e2.first));
|
double d2d = distance(to_2d(e1.first), to_2d(e2.first));
|
||||||
double d3d = distance(e1.first, e2.first);
|
double d3d = distance(e1.first, e2.first);
|
||||||
return d2d < 2 * m_cfg.base_radius_mm &&
|
return d2d < 2 * m_cfg.base_radius_mm
|
||||||
d3d < m_cfg.max_bridge_length_mm;
|
&& d3d < m_cfg.max_bridge_length_mm;
|
||||||
};
|
};
|
||||||
m_pillar_clusters = cluster(ground_head_indices, pointfn, predicate,
|
|
||||||
|
m_pillar_clusters = cluster(ground_head_indices,
|
||||||
|
pointfn,
|
||||||
|
predicate,
|
||||||
m_cfg.max_bridges_on_pillar);
|
m_cfg.max_bridges_on_pillar);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1615,7 +1753,7 @@ public:
|
|||||||
void routing_to_ground()
|
void routing_to_ground()
|
||||||
{
|
{
|
||||||
const double pradius = m_cfg.head_back_radius_mm;
|
const double pradius = m_cfg.head_back_radius_mm;
|
||||||
const double gndlvl = m_result.ground_level;
|
// const double gndlvl = m_result.ground_level;
|
||||||
|
|
||||||
ClusterEl cl_centroids;
|
ClusterEl cl_centroids;
|
||||||
cl_centroids.reserve(m_pillar_clusters.size());
|
cl_centroids.reserve(m_pillar_clusters.size());
|
||||||
@ -1648,13 +1786,8 @@ public:
|
|||||||
|
|
||||||
Head& h = m_result.head(hid);
|
Head& h = m_result.head(hid);
|
||||||
h.transform();
|
h.transform();
|
||||||
Vec3d p = h.junction_point(); p(Z) = gndlvl;
|
|
||||||
auto& plr = m_result.add_pillar(hid, p, h.r_back_mm)
|
|
||||||
.add_base(m_cfg.base_height_mm,
|
|
||||||
m_cfg.base_radius_mm);
|
|
||||||
|
|
||||||
// Save the pillar endpoint and the pillar id in the spatial index
|
create_ground_pillar(h.junction_point(), h.dir, h.r_back_mm, h.id);
|
||||||
m_pillar_index.insert(plr.endpoint(), unsigned(plr.id));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// now we will go through the clusters ones again and connect the
|
// now we will go through the clusters ones again and connect the
|
||||||
@ -1681,15 +1814,12 @@ public:
|
|||||||
!search_pillar_and_connect(sidehead))
|
!search_pillar_and_connect(sidehead))
|
||||||
{
|
{
|
||||||
Vec3d pstart = sidehead.junction_point();
|
Vec3d pstart = sidehead.junction_point();
|
||||||
Vec3d pend = Vec3d{pstart(X), pstart(Y), gndlvl};
|
//Vec3d pend = Vec3d{pstart(X), pstart(Y), gndlvl};
|
||||||
// Could not find a pillar, create one
|
// Could not find a pillar, create one
|
||||||
auto& pillar = m_result.add_pillar(unsigned(sidehead.id),
|
create_ground_pillar(pstart,
|
||||||
pend, pradius)
|
sidehead.dir,
|
||||||
.add_base(m_cfg.base_height_mm,
|
pradius,
|
||||||
m_cfg.base_radius_mm);
|
sidehead.id);
|
||||||
|
|
||||||
// connects to ground, eligible for bridging
|
|
||||||
m_pillar_index.insert(pend, unsigned(pillar.id));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1718,12 +1848,7 @@ public:
|
|||||||
m_result.add_bridge(hjp, endp, head.r_back_mm);
|
m_result.add_bridge(hjp, endp, head.r_back_mm);
|
||||||
m_result.add_junction(endp, head.r_back_mm);
|
m_result.add_junction(endp, head.r_back_mm);
|
||||||
|
|
||||||
auto groundp = endp;
|
this->create_ground_pillar(endp, dir, head.r_back_mm);
|
||||||
groundp(Z) = m_result.ground_level;
|
|
||||||
auto& newpillar = m_result.add_pillar(endp, groundp, head.r_back_mm)
|
|
||||||
.add_base(m_cfg.base_height_mm,
|
|
||||||
m_cfg.base_radius_mm);
|
|
||||||
m_pillar_index.insert(groundp, unsigned(newpillar.id));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
std::vector<unsigned> modelpillars;
|
std::vector<unsigned> modelpillars;
|
||||||
@ -1884,6 +2009,28 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper function for interconnect_pillars where pairs of already connected
|
||||||
|
// pillars should be checked for not to be processed again. This can be done
|
||||||
|
// in O(log) or even constant time with a set or an unordered set of hash
|
||||||
|
// values uniquely representing a pair of integers. The order of numbers
|
||||||
|
// within the pair should not matter, it has the same unique hash.
|
||||||
|
template<class I> static I pairhash(I a, I b)
|
||||||
|
{
|
||||||
|
using std::ceil; using std::log2; using std::max; using std::min;
|
||||||
|
|
||||||
|
static_assert(std::is_integral<I>::value,
|
||||||
|
"This function works only for integral types.");
|
||||||
|
|
||||||
|
I g = min(a, b), l = max(a, b);
|
||||||
|
|
||||||
|
auto bits_g = g ? int(ceil(log2(g))) : 0;
|
||||||
|
|
||||||
|
// Assume the hash will fit into the output variable
|
||||||
|
assert((l ? (ceil(log2(l))) : 0) + bits_g < int(sizeof(I) * CHAR_BIT));
|
||||||
|
|
||||||
|
return (l << bits_g) + g;
|
||||||
|
}
|
||||||
|
|
||||||
void interconnect_pillars() {
|
void interconnect_pillars() {
|
||||||
// Now comes the algorithm that connects pillars with each other.
|
// Now comes the algorithm that connects pillars with each other.
|
||||||
// Ideally every pillar should be connected with at least one of its
|
// Ideally every pillar should be connected with at least one of its
|
||||||
@ -1901,16 +2048,22 @@ public:
|
|||||||
|
|
||||||
std::set<unsigned long> pairs;
|
std::set<unsigned long> pairs;
|
||||||
|
|
||||||
|
// A function to connect one pillar with its neighbors. THe number of
|
||||||
|
// neighbors is given in the configuration. This function if called
|
||||||
|
// for every pillar in the pillar index. A pair of pillar will not
|
||||||
|
// be connected multiple times this is ensured by the 'pairs' set which
|
||||||
|
// remembers the processed pillar pairs
|
||||||
auto cascadefn =
|
auto cascadefn =
|
||||||
[this, d, &pairs, min_height_ratio, H1] (const SpatElement& el)
|
[this, d, &pairs, min_height_ratio, H1] (const SpatElement& el)
|
||||||
{
|
{
|
||||||
Vec3d qp = el.first;
|
Vec3d qp = el.first; // endpoint of the pillar
|
||||||
|
|
||||||
const Pillar& pillar = m_result.pillar(el.second);
|
const Pillar& pillar = m_result.pillar(el.second); // actual pillar
|
||||||
|
|
||||||
|
// Get the max number of neighbors a pillar should connect to
|
||||||
unsigned neighbors = m_cfg.pillar_cascade_neighbors;
|
unsigned neighbors = m_cfg.pillar_cascade_neighbors;
|
||||||
|
|
||||||
// connections are enough for one pillar
|
// connections are already enough for the pillar
|
||||||
if(pillar.links >= neighbors) return;
|
if(pillar.links >= neighbors) return;
|
||||||
|
|
||||||
// Query all remaining points within reach
|
// Query all remaining points within reach
|
||||||
@ -1924,21 +2077,21 @@ public:
|
|||||||
return distance(e1.first, qp) < distance(e2.first, qp);
|
return distance(e1.first, qp) < distance(e2.first, qp);
|
||||||
});
|
});
|
||||||
|
|
||||||
for(auto& re : qres) {
|
for(auto& re : qres) { // process the queried neighbors
|
||||||
|
|
||||||
if(re.second == el.second) continue;
|
if(re.second == el.second) continue; // Skip self
|
||||||
|
|
||||||
auto a = el.second, b = re.second;
|
auto a = el.second, b = re.second;
|
||||||
|
|
||||||
// I hope that the area of a square is never equal to its
|
// Get unique hash for the given pair (order doesn't matter)
|
||||||
// circumference
|
auto hashval = pairhash(a, b);
|
||||||
auto hashval = 2 * (a + b) + a * b;
|
|
||||||
|
|
||||||
|
// Search for the pair amongst the remembered pairs
|
||||||
if(pairs.find(hashval) != pairs.end()) continue;
|
if(pairs.find(hashval) != pairs.end()) continue;
|
||||||
|
|
||||||
const Pillar& neighborpillar = m_result.pillars()[re.second];
|
const Pillar& neighborpillar = m_result.pillars()[re.second];
|
||||||
|
|
||||||
// this neighbor is occupied
|
// this neighbor is occupied, skip
|
||||||
if(neighborpillar.links >= neighbors) continue;
|
if(neighborpillar.links >= neighbors) continue;
|
||||||
|
|
||||||
if(interconnect(pillar, neighborpillar)) {
|
if(interconnect(pillar, neighborpillar)) {
|
||||||
@ -1961,46 +2114,74 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Run the cascade for the pillars in the index
|
||||||
m_pillar_index.foreach(cascadefn);
|
m_pillar_index.foreach(cascadefn);
|
||||||
|
|
||||||
|
// We would be done here if we could allow some pillars to not be
|
||||||
|
// connected with any neighbors. But this might leave the support tree
|
||||||
|
// unprintable.
|
||||||
|
//
|
||||||
|
// The current solution is to insert additional pillars next to these
|
||||||
|
// lonely pillars. One or even two additional pillar might get inserted
|
||||||
|
// depending on the length of the lonely pillar.
|
||||||
|
|
||||||
size_t pillarcount = m_result.pillars().size();
|
size_t pillarcount = m_result.pillars().size();
|
||||||
|
|
||||||
|
// Again, go through all pillars, this time in the whole support tree
|
||||||
|
// not just the index.
|
||||||
for(size_t pid = 0; pid < pillarcount; pid++) {
|
for(size_t pid = 0; pid < pillarcount; pid++) {
|
||||||
auto pillar = [this, pid]() { return m_result.pillar(pid); };
|
auto pillar = [this, pid]() { return m_result.pillar(pid); };
|
||||||
|
|
||||||
|
// Decide how many additional pillars will be needed:
|
||||||
|
|
||||||
unsigned needpillars = 0;
|
unsigned needpillars = 0;
|
||||||
if(pillar().bridges > m_cfg.max_bridges_on_pillar) needpillars = 3;
|
if (pillar().bridges > m_cfg.max_bridges_on_pillar)
|
||||||
else if(pillar().links < 2 && pillar().height > H2) {
|
needpillars = 3;
|
||||||
|
else if (pillar().links < 2 && pillar().height > H2) {
|
||||||
// Not enough neighbors to support this pillar
|
// Not enough neighbors to support this pillar
|
||||||
needpillars = 2 - pillar().links;
|
needpillars = 2 - pillar().links;
|
||||||
}
|
} else if (pillar().links < 1 && pillar().height > H1) {
|
||||||
else if(pillar().links < 1 && pillar().height > H1) {
|
|
||||||
// No neighbors could be found and the pillar is too long.
|
// No neighbors could be found and the pillar is too long.
|
||||||
needpillars = 1;
|
needpillars = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search for new pillar locations
|
// Search for new pillar locations:
|
||||||
|
|
||||||
bool found = false;
|
bool found = false;
|
||||||
double alpha = 0; // goes to 2Pi
|
double alpha = 0; // goes to 2Pi
|
||||||
double r = 2 * m_cfg.base_radius_mm;
|
double r = 2 * m_cfg.base_radius_mm;
|
||||||
Vec3d pillarsp = pillar().startpoint();
|
Vec3d pillarsp = pillar().startpoint();
|
||||||
|
|
||||||
|
// temp value for starting point detection
|
||||||
Vec3d sp(pillarsp(X), pillarsp(Y), pillarsp(Z) - r);
|
Vec3d sp(pillarsp(X), pillarsp(Y), pillarsp(Z) - r);
|
||||||
std::vector<bool> tv(needpillars, false);
|
|
||||||
std::vector<Vec3d> spts(needpillars);
|
// A vector of bool for placement feasbility
|
||||||
|
std::vector<bool> canplace(needpillars, false);
|
||||||
|
std::vector<Vec3d> spts(needpillars); // vector of starting points
|
||||||
|
|
||||||
|
double gnd = m_result.ground_level;
|
||||||
|
double min_dist = SupportConfig::pillar_base_safety_distance_mm +
|
||||||
|
m_cfg.base_radius_mm + EPSILON;
|
||||||
|
|
||||||
while(!found && alpha < 2*PI) {
|
while(!found && alpha < 2*PI) {
|
||||||
|
for (unsigned n = 0; n < needpillars; n++) {
|
||||||
for(unsigned n = 0; n < needpillars; n++) {
|
double a = alpha + n * PI / 3;
|
||||||
double a = alpha + n * PI/3;
|
|
||||||
Vec3d s = sp;
|
Vec3d s = sp;
|
||||||
s(X) += std::cos(a) * r;
|
s(X) += std::cos(a) * r;
|
||||||
s(Y) += std::sin(a) * r;
|
s(Y) += std::sin(a) * r;
|
||||||
spts[n] = s;
|
spts[n] = s;
|
||||||
|
|
||||||
|
// Check the path vertically down
|
||||||
auto hr = bridge_mesh_intersect(s, {0, 0, -1}, pillar().r);
|
auto hr = bridge_mesh_intersect(s, {0, 0, -1}, pillar().r);
|
||||||
tv[n] = std::isinf(hr.distance());
|
|
||||||
|
// If the path is clear, check for pillar base collisions
|
||||||
|
canplace[n] = std::isinf(hr.distance())
|
||||||
|
&& m_mesh.squared_distance({s(X), s(Y), gnd})
|
||||||
|
> min_dist;
|
||||||
}
|
}
|
||||||
|
|
||||||
found = std::all_of(tv.begin(), tv.end(), [](bool v){return v;});
|
found = std::all_of(canplace.begin(), canplace.end(),
|
||||||
|
[](bool v) { return v; });
|
||||||
|
|
||||||
// 20 angles will be tried...
|
// 20 angles will be tried...
|
||||||
alpha += 0.1 * PI;
|
alpha += 0.1 * PI;
|
||||||
@ -2010,7 +2191,7 @@ public:
|
|||||||
newpills.reserve(needpillars);
|
newpills.reserve(needpillars);
|
||||||
|
|
||||||
if(found) for(unsigned n = 0; n < needpillars; n++) {
|
if(found) for(unsigned n = 0; n < needpillars; n++) {
|
||||||
Vec3d s = spts[n]; double gnd = m_result.ground_level;
|
Vec3d s = spts[n];
|
||||||
Pillar p(s, Vec3d(s(X), s(Y), gnd), pillar().r);
|
Pillar p(s, Vec3d(s(X), s(Y), gnd), pillar().r);
|
||||||
p.add_base(m_cfg.base_height_mm, m_cfg.base_radius_mm);
|
p.add_base(m_cfg.base_height_mm, m_cfg.base_radius_mm);
|
||||||
|
|
||||||
@ -2075,9 +2256,12 @@ public:
|
|||||||
// 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, dir, R, true);
|
||||||
double dist = ray_mesh_intersect(sj, dir);
|
double dist = ray_mesh_intersect(sj, dir);
|
||||||
|
if (std::isinf(dist))
|
||||||
|
dist = sph(Z) - m_result.ground_level - HWIDTH_MM;
|
||||||
|
|
||||||
if(std::isinf(idist) || std::isnan(idist) || idist < 2*R ||
|
if(std::isnan(idist) || idist < 2*R ||
|
||||||
std::isinf(dist) || std::isnan(dist) || dist < 2*R) {
|
std::isnan(dist) || dist < 2*R)
|
||||||
|
{
|
||||||
BOOST_LOG_TRIVIAL(warning) << "Can not find route for headless"
|
BOOST_LOG_TRIVIAL(warning) << "Can not find route for headless"
|
||||||
<< " support stick at: "
|
<< " support stick at: "
|
||||||
<< sj.transpose();
|
<< sj.transpose();
|
||||||
@ -2214,7 +2398,9 @@ bool SLASupportTree::generate(const std::vector<SupportPoint> &support_points,
|
|||||||
return pc == ABORT;
|
return pc == ABORT;
|
||||||
}
|
}
|
||||||
|
|
||||||
SLASupportTree::SLASupportTree(): m_impl(new Impl()) {}
|
SLASupportTree::SLASupportTree(double gnd_lvl): m_impl(new Impl()) {
|
||||||
|
m_impl->ground_level = gnd_lvl;
|
||||||
|
}
|
||||||
|
|
||||||
const TriangleMesh &SLASupportTree::merged_mesh() const
|
const TriangleMesh &SLASupportTree::merged_mesh() const
|
||||||
{
|
{
|
||||||
@ -2226,7 +2412,7 @@ void SLASupportTree::merged_mesh_with_pad(TriangleMesh &outmesh) const {
|
|||||||
outmesh.merge(get_pad());
|
outmesh.merge(get_pad());
|
||||||
}
|
}
|
||||||
|
|
||||||
SlicedSupports SLASupportTree::slice(float layerh, float init_layerh) const
|
std::vector<ExPolygons> SLASupportTree::slice(float layerh, float init_layerh) const
|
||||||
{
|
{
|
||||||
if(init_layerh < 0) init_layerh = layerh;
|
if(init_layerh < 0) init_layerh = layerh;
|
||||||
auto& stree = get();
|
auto& stree = get();
|
||||||
@ -2247,34 +2433,29 @@ SlicedSupports SLASupportTree::slice(float layerh, float init_layerh) const
|
|||||||
fullmesh.merge(get_pad());
|
fullmesh.merge(get_pad());
|
||||||
fullmesh.require_shared_vertices(); // TriangleMeshSlicer needs this
|
fullmesh.require_shared_vertices(); // TriangleMeshSlicer needs this
|
||||||
TriangleMeshSlicer slicer(&fullmesh);
|
TriangleMeshSlicer slicer(&fullmesh);
|
||||||
SlicedSupports ret;
|
std::vector<ExPolygons> ret;
|
||||||
slicer.slice(heights, 0.f, &ret, get().ctl().cancelfn);
|
slicer.slice(heights, 0.f, &ret, get().ctl().cancelfn);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
SlicedSupports SLASupportTree::slice(const std::vector<float> &heights,
|
std::vector<ExPolygons> SLASupportTree::slice(const std::vector<float> &heights,
|
||||||
float cr) const
|
float cr) const
|
||||||
{
|
{
|
||||||
TriangleMesh fullmesh = m_impl->merged_mesh();
|
TriangleMesh fullmesh = m_impl->merged_mesh();
|
||||||
fullmesh.merge(get_pad());
|
fullmesh.merge(get_pad());
|
||||||
fullmesh.require_shared_vertices(); // TriangleMeshSlicer needs this
|
fullmesh.require_shared_vertices(); // TriangleMeshSlicer needs this
|
||||||
TriangleMeshSlicer slicer(&fullmesh);
|
TriangleMeshSlicer slicer(&fullmesh);
|
||||||
SlicedSupports ret;
|
std::vector<ExPolygons> ret;
|
||||||
slicer.slice(heights, cr, &ret, get().ctl().cancelfn);
|
slicer.slice(heights, cr, &ret, get().ctl().cancelfn);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TriangleMesh &SLASupportTree::add_pad(const SliceLayer& baseplate,
|
const TriangleMesh &SLASupportTree::add_pad(const ExPolygons& modelbase,
|
||||||
const PoolConfig& pcfg) const
|
const PoolConfig& pcfg) const
|
||||||
{
|
{
|
||||||
// PoolConfig pcfg;
|
return m_impl->create_pad(merged_mesh(), modelbase, pcfg).tmesh;
|
||||||
// pcfg.min_wall_thickness_mm = min_wall_thickness_mm;
|
|
||||||
// pcfg.min_wall_height_mm = min_wall_height_mm;
|
|
||||||
// pcfg.max_merge_distance_mm = max_merge_distance_mm;
|
|
||||||
// pcfg.edge_radius_mm = edge_radius_mm;
|
|
||||||
return m_impl->create_pad(merged_mesh(), baseplate, pcfg).tmesh;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const TriangleMesh &SLASupportTree::get_pad() const
|
const TriangleMesh &SLASupportTree::get_pad() const
|
||||||
|
@ -24,10 +24,11 @@ class TriangleMesh;
|
|||||||
class Model;
|
class Model;
|
||||||
class ModelInstance;
|
class ModelInstance;
|
||||||
class ModelObject;
|
class ModelObject;
|
||||||
|
class Polygon;
|
||||||
class ExPolygon;
|
class ExPolygon;
|
||||||
|
|
||||||
using SliceLayer = std::vector<ExPolygon>;
|
using Polygons = std::vector<Polygon>;
|
||||||
using SlicedSupports = std::vector<SliceLayer>;
|
using ExPolygons = std::vector<ExPolygon>;
|
||||||
|
|
||||||
namespace sla {
|
namespace sla {
|
||||||
|
|
||||||
@ -91,6 +92,10 @@ struct SupportConfig {
|
|||||||
// The shortest distance of any support structure from the model surface
|
// The shortest distance of any support structure from the model surface
|
||||||
static const double safety_distance_mm;
|
static const double safety_distance_mm;
|
||||||
|
|
||||||
|
// The shortest distance between a pillar base perimeter from the model
|
||||||
|
// body. This is only useful when elevation is set to zero.
|
||||||
|
static const double pillar_base_safety_distance_mm;
|
||||||
|
|
||||||
static const double max_solo_pillar_height_mm;
|
static const double max_solo_pillar_height_mm;
|
||||||
static const double max_dual_pillar_height_mm;
|
static const double max_dual_pillar_height_mm;
|
||||||
static const double optimizer_rel_score_diff;
|
static const double optimizer_rel_score_diff;
|
||||||
@ -160,7 +165,7 @@ class SLASupportTree {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
SLASupportTree();
|
SLASupportTree(double ground_level = 0.0);
|
||||||
|
|
||||||
SLASupportTree(const std::vector<SupportPoint>& pts,
|
SLASupportTree(const std::vector<SupportPoint>& pts,
|
||||||
const EigenMesh3D& em,
|
const EigenMesh3D& em,
|
||||||
@ -179,12 +184,16 @@ public:
|
|||||||
void merged_mesh_with_pad(TriangleMesh&) const;
|
void merged_mesh_with_pad(TriangleMesh&) const;
|
||||||
|
|
||||||
/// Get the sliced 2d layers of the support geometry.
|
/// Get the sliced 2d layers of the support geometry.
|
||||||
SlicedSupports slice(float layerh, float init_layerh = -1.0) const;
|
std::vector<ExPolygons> slice(float layerh, float init_layerh = -1.0) const;
|
||||||
|
|
||||||
SlicedSupports slice(const std::vector<float>&, float closing_radius) const;
|
std::vector<ExPolygons> slice(const std::vector<float>&, float closing_radius) const;
|
||||||
|
|
||||||
/// Adding the "pad" (base pool) under the supports
|
/// Adding the "pad" (base pool) under the supports
|
||||||
const TriangleMesh& add_pad(const SliceLayer& baseplate,
|
/// modelbase will be used according to the embed_object flag in PoolConfig.
|
||||||
|
/// If set, the plate will interpreted as the model's intrinsic pad.
|
||||||
|
/// Otherwise, the modelbase will be unified with the base plate calculated
|
||||||
|
/// from the supports.
|
||||||
|
const TriangleMesh& add_pad(const ExPolygons& modelbase,
|
||||||
const PoolConfig& pcfg) const;
|
const PoolConfig& pcfg) const;
|
||||||
|
|
||||||
/// Get the pad geometry
|
/// Get the pad geometry
|
||||||
|
@ -33,7 +33,7 @@ public:
|
|||||||
sla::EigenMesh3D emesh; // index-triangle representation
|
sla::EigenMesh3D emesh; // index-triangle representation
|
||||||
std::vector<sla::SupportPoint> support_points; // all the support points (manual/auto)
|
std::vector<sla::SupportPoint> support_points; // all the support points (manual/auto)
|
||||||
SupportTreePtr support_tree_ptr; // the supports
|
SupportTreePtr support_tree_ptr; // the supports
|
||||||
SlicedSupports support_slices; // sliced supports
|
std::vector<ExPolygons> support_slices; // sliced supports
|
||||||
|
|
||||||
inline SupportData(const TriangleMesh& trmesh): emesh(trmesh) {}
|
inline SupportData(const TriangleMesh& trmesh): emesh(trmesh) {}
|
||||||
};
|
};
|
||||||
@ -471,7 +471,7 @@ void SLAPrint::set_task(const TaskParams ¶ms)
|
|||||||
|
|
||||||
int n_object_steps = int(params.to_object_step) + 1;
|
int n_object_steps = int(params.to_object_step) + 1;
|
||||||
if (n_object_steps == 0)
|
if (n_object_steps == 0)
|
||||||
n_object_steps = (int)slaposCount;
|
n_object_steps = int(slaposCount);
|
||||||
|
|
||||||
if (params.single_model_object.valid()) {
|
if (params.single_model_object.valid()) {
|
||||||
// Find the print object to be processed with priority.
|
// Find the print object to be processed with priority.
|
||||||
@ -486,7 +486,7 @@ void SLAPrint::set_task(const TaskParams ¶ms)
|
|||||||
// Find out whether the priority print object is being currently processed.
|
// Find out whether the priority print object is being currently processed.
|
||||||
bool running = false;
|
bool running = false;
|
||||||
for (int istep = 0; istep < n_object_steps; ++ istep) {
|
for (int istep = 0; istep < n_object_steps; ++ istep) {
|
||||||
if (! print_object->m_stepmask[istep])
|
if (! print_object->m_stepmask[size_t(istep)])
|
||||||
// Step was skipped, cancel.
|
// Step was skipped, cancel.
|
||||||
break;
|
break;
|
||||||
if (print_object->is_step_started_unguarded(SLAPrintObjectStep(istep))) {
|
if (print_object->is_step_started_unguarded(SLAPrintObjectStep(istep))) {
|
||||||
@ -502,7 +502,7 @@ void SLAPrint::set_task(const TaskParams ¶ms)
|
|||||||
if (params.single_model_instance_only) {
|
if (params.single_model_instance_only) {
|
||||||
// Suppress all the steps of other instances.
|
// Suppress all the steps of other instances.
|
||||||
for (SLAPrintObject *po : m_objects)
|
for (SLAPrintObject *po : m_objects)
|
||||||
for (int istep = 0; istep < (int)slaposCount; ++ istep)
|
for (size_t istep = 0; istep < slaposCount; ++ istep)
|
||||||
po->m_stepmask[istep] = false;
|
po->m_stepmask[istep] = false;
|
||||||
} else if (! running) {
|
} else if (! running) {
|
||||||
// Swap the print objects, so that the selected print_object is first in the row.
|
// Swap the print objects, so that the selected print_object is first in the row.
|
||||||
@ -512,15 +512,15 @@ void SLAPrint::set_task(const TaskParams ¶ms)
|
|||||||
}
|
}
|
||||||
// and set the steps for the current object.
|
// and set the steps for the current object.
|
||||||
for (int istep = 0; istep < n_object_steps; ++ istep)
|
for (int istep = 0; istep < n_object_steps; ++ istep)
|
||||||
print_object->m_stepmask[istep] = true;
|
print_object->m_stepmask[size_t(istep)] = true;
|
||||||
for (int istep = n_object_steps; istep < (int)slaposCount; ++ istep)
|
for (int istep = n_object_steps; istep < int(slaposCount); ++ istep)
|
||||||
print_object->m_stepmask[istep] = false;
|
print_object->m_stepmask[size_t(istep)] = false;
|
||||||
} else {
|
} else {
|
||||||
// Slicing all objects.
|
// Slicing all objects.
|
||||||
bool running = false;
|
bool running = false;
|
||||||
for (SLAPrintObject *print_object : m_objects)
|
for (SLAPrintObject *print_object : m_objects)
|
||||||
for (int istep = 0; istep < n_object_steps; ++ istep) {
|
for (int istep = 0; istep < n_object_steps; ++ istep) {
|
||||||
if (! print_object->m_stepmask[istep]) {
|
if (! print_object->m_stepmask[size_t(istep)]) {
|
||||||
// Step may have been skipped. Restart.
|
// Step may have been skipped. Restart.
|
||||||
goto loop_end;
|
goto loop_end;
|
||||||
}
|
}
|
||||||
@ -536,8 +536,8 @@ void SLAPrint::set_task(const TaskParams ¶ms)
|
|||||||
this->call_cancel_callback();
|
this->call_cancel_callback();
|
||||||
for (SLAPrintObject *po : m_objects) {
|
for (SLAPrintObject *po : m_objects) {
|
||||||
for (int istep = 0; istep < n_object_steps; ++ istep)
|
for (int istep = 0; istep < n_object_steps; ++ istep)
|
||||||
po->m_stepmask[istep] = true;
|
po->m_stepmask[size_t(istep)] = true;
|
||||||
for (int istep = n_object_steps; istep < (int)slaposCount; ++ istep)
|
for (auto istep = size_t(n_object_steps); istep < slaposCount; ++ istep)
|
||||||
po->m_stepmask[istep] = false;
|
po->m_stepmask[istep] = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -555,9 +555,9 @@ void SLAPrint::set_task(const TaskParams ¶ms)
|
|||||||
void SLAPrint::finalize()
|
void SLAPrint::finalize()
|
||||||
{
|
{
|
||||||
for (SLAPrintObject *po : m_objects)
|
for (SLAPrintObject *po : m_objects)
|
||||||
for (int istep = 0; istep < (int)slaposCount; ++ istep)
|
for (size_t istep = 0; istep < slaposCount; ++ istep)
|
||||||
po->m_stepmask[istep] = true;
|
po->m_stepmask[istep] = true;
|
||||||
for (int istep = 0; istep < (int)slapsCount; ++ istep)
|
for (size_t istep = 0; istep < slapsCount; ++ istep)
|
||||||
m_stepmask[istep] = true;
|
m_stepmask[istep] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -599,17 +599,29 @@ sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c) {
|
|||||||
return scfg;
|
return scfg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool use_builtin_pad(const SLAPrintObjectConfig& c) {
|
||||||
|
return c.support_object_elevation.getFloat() <= EPSILON &&
|
||||||
|
c.pad_enable.getBool();
|
||||||
|
}
|
||||||
|
|
||||||
sla::PoolConfig make_pool_config(const SLAPrintObjectConfig& c) {
|
sla::PoolConfig make_pool_config(const SLAPrintObjectConfig& c) {
|
||||||
sla::PoolConfig pcfg;
|
sla::PoolConfig pcfg;
|
||||||
|
|
||||||
pcfg.min_wall_thickness_mm = c.pad_wall_thickness.getFloat();
|
pcfg.min_wall_thickness_mm = c.pad_wall_thickness.getFloat();
|
||||||
pcfg.wall_slope = c.pad_wall_slope.getFloat();
|
pcfg.wall_slope = c.pad_wall_slope.getFloat() * PI / 180.0;
|
||||||
pcfg.edge_radius_mm = c.pad_edge_radius.getFloat();
|
|
||||||
|
// We do not support radius for now
|
||||||
|
pcfg.edge_radius_mm = 0.0; //c.pad_edge_radius.getFloat();
|
||||||
|
|
||||||
pcfg.max_merge_distance_mm = c.pad_max_merge_distance.getFloat();
|
pcfg.max_merge_distance_mm = c.pad_max_merge_distance.getFloat();
|
||||||
pcfg.min_wall_height_mm = c.pad_wall_height.getFloat();
|
pcfg.min_wall_height_mm = c.pad_wall_height.getFloat();
|
||||||
|
|
||||||
|
// set builtin pad implicitly ON
|
||||||
|
pcfg.embed_object = use_builtin_pad(c);
|
||||||
|
|
||||||
return pcfg;
|
return pcfg;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string SLAPrint::validate() const
|
std::string SLAPrint::validate() const
|
||||||
@ -633,7 +645,9 @@ std::string SLAPrint::validate() const
|
|||||||
2 * cfg.head_back_radius_mm -
|
2 * cfg.head_back_radius_mm -
|
||||||
cfg.head_penetration_mm;
|
cfg.head_penetration_mm;
|
||||||
|
|
||||||
if(supports_en && pinhead_width > cfg.object_elevation_mm)
|
double elv = cfg.object_elevation_mm;
|
||||||
|
|
||||||
|
if(supports_en && elv > EPSILON && elv < pinhead_width )
|
||||||
return L("Elevation is too low for object.");
|
return L("Elevation is too low for object.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -818,13 +832,37 @@ void SLAPrint::process()
|
|||||||
BOOST_LOG_TRIVIAL(debug) << "Automatic support points: "
|
BOOST_LOG_TRIVIAL(debug) << "Automatic support points: "
|
||||||
<< po.m_supportdata->support_points.size();
|
<< po.m_supportdata->support_points.size();
|
||||||
|
|
||||||
// Using RELOAD_SLA_SUPPORT_POINTS to tell the Plater to pass the update status to GLGizmoSlaSupports
|
// Using RELOAD_SLA_SUPPORT_POINTS to tell the Plater to pass
|
||||||
m_report_status(*this, -1, L("Generating support points"), SlicingStatus::RELOAD_SLA_SUPPORT_POINTS);
|
// the update status to GLGizmoSlaSupports
|
||||||
|
m_report_status(*this,
|
||||||
|
-1,
|
||||||
|
L("Generating support points"),
|
||||||
|
SlicingStatus::RELOAD_SLA_SUPPORT_POINTS);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// There are either some points on the front-end, or the user removed them on purpose. No calculation will be done.
|
// There are either some points on the front-end, or the user
|
||||||
|
// removed them on purpose. No calculation will be done.
|
||||||
po.m_supportdata->support_points = po.transformed_support_points();
|
po.m_supportdata->support_points = po.transformed_support_points();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the builtin pad mode is engaged, we have to filter out all the
|
||||||
|
// points that are on the bottom of the object
|
||||||
|
if(use_builtin_pad(po.m_config)) {
|
||||||
|
double gnd = po.m_supportdata->emesh.ground_level();
|
||||||
|
auto & pts = po.m_supportdata->support_points;
|
||||||
|
|
||||||
|
// get iterator to the reorganized vector end
|
||||||
|
auto endit = std::remove_if(
|
||||||
|
pts.begin(),
|
||||||
|
pts.end(),
|
||||||
|
[&po, gnd](const sla::SupportPoint &sp) {
|
||||||
|
double diff = std::abs(gnd - double(sp.pos(Z)));
|
||||||
|
return diff <= po.m_config.pad_wall_thickness.getFloat();
|
||||||
|
});
|
||||||
|
|
||||||
|
// erase all elements after the new end
|
||||||
|
pts.erase(endit, pts.end());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// In this step we create the supports
|
// In this step we create the supports
|
||||||
@ -832,9 +870,17 @@ void SLAPrint::process()
|
|||||||
{
|
{
|
||||||
if(!po.m_supportdata) return;
|
if(!po.m_supportdata) return;
|
||||||
|
|
||||||
|
sla::PoolConfig pcfg = make_pool_config(po.m_config);
|
||||||
|
|
||||||
|
if(pcfg.embed_object)
|
||||||
|
po.m_supportdata->emesh.ground_level() += pcfg.min_wall_thickness_mm;
|
||||||
|
|
||||||
if(!po.m_config.supports_enable.getBool()) {
|
if(!po.m_config.supports_enable.getBool()) {
|
||||||
|
|
||||||
// Generate empty support tree. It can still host a pad
|
// Generate empty support tree. It can still host a pad
|
||||||
po.m_supportdata->support_tree_ptr.reset(new SLASupportTree());
|
po.m_supportdata->support_tree_ptr.reset(
|
||||||
|
new SLASupportTree(po.m_supportdata->emesh.ground_level()));
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -894,27 +940,26 @@ void SLAPrint::process()
|
|||||||
|
|
||||||
if(po.m_config.pad_enable.getBool())
|
if(po.m_config.pad_enable.getBool())
|
||||||
{
|
{
|
||||||
double wt = po.m_config.pad_wall_thickness.getFloat();
|
// Get the distilled pad configuration from the config
|
||||||
double h = po.m_config.pad_wall_height.getFloat();
|
sla::PoolConfig pcfg = make_pool_config(po.m_config);
|
||||||
double md = po.m_config.pad_max_merge_distance.getFloat();
|
|
||||||
// Radius is disabled for now...
|
|
||||||
double er = 0; // po.m_config.pad_edge_radius.getFloat();
|
|
||||||
double tilt = po.m_config.pad_wall_slope.getFloat() * PI / 180.0;
|
|
||||||
double lh = po.m_config.layer_height.getFloat();
|
|
||||||
double elevation = po.m_config.support_object_elevation.getFloat();
|
|
||||||
if(!po.m_config.supports_enable.getBool()) elevation = 0;
|
|
||||||
sla::PoolConfig pcfg(wt, h, md, er, tilt);
|
|
||||||
|
|
||||||
ExPolygons bp;
|
ExPolygons bp; // This will store the base plate of the pad.
|
||||||
double pad_h = sla::get_pad_fullheight(pcfg);
|
double pad_h = sla::get_pad_fullheight(pcfg);
|
||||||
auto&& trmesh = po.transformed_mesh();
|
const TriangleMesh &trmesh = po.transformed_mesh();
|
||||||
|
|
||||||
// This call can get pretty time consuming
|
// This call can get pretty time consuming
|
||||||
auto thrfn = [this](){ throw_if_canceled(); };
|
auto thrfn = [this](){ throw_if_canceled(); };
|
||||||
|
|
||||||
if(elevation < pad_h) {
|
if (!po.m_config.supports_enable.getBool() || pcfg.embed_object) {
|
||||||
// we have to count with the model geometry for the base plate
|
// No support (thus no elevation) or zero elevation mode
|
||||||
sla::base_plate(trmesh, bp, float(pad_h), float(lh), thrfn);
|
// we sometimes call it "builtin pad" is enabled so we will
|
||||||
|
// get a sample from the bottom of the mesh and use it for pad
|
||||||
|
// creation.
|
||||||
|
sla::base_plate(trmesh,
|
||||||
|
bp,
|
||||||
|
float(pad_h),
|
||||||
|
float(po.m_config.layer_height.getFloat()),
|
||||||
|
thrfn);
|
||||||
}
|
}
|
||||||
|
|
||||||
pcfg.throw_on_cancel = thrfn;
|
pcfg.throw_on_cancel = thrfn;
|
||||||
@ -1647,7 +1692,7 @@ double SLAPrintObject::get_elevation() const {
|
|||||||
// will be in the future, we provide the config to the get_pad_elevation
|
// will be in the future, we provide the config to the get_pad_elevation
|
||||||
// method and we will have the correct value
|
// method and we will have the correct value
|
||||||
sla::PoolConfig pcfg = make_pool_config(m_config);
|
sla::PoolConfig pcfg = make_pool_config(m_config);
|
||||||
ret += sla::get_pad_elevation(pcfg);
|
if(!pcfg.embed_object) ret += sla::get_pad_elevation(pcfg);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
@ -1661,8 +1706,9 @@ double SLAPrintObject::get_current_elevation() const
|
|||||||
|
|
||||||
if(!has_supports && !has_pad)
|
if(!has_supports && !has_pad)
|
||||||
return 0;
|
return 0;
|
||||||
else if(has_supports && !has_pad)
|
else if(has_supports && !has_pad) {
|
||||||
return se ? m_config.support_object_elevation.getFloat() : 0;
|
return se ? m_config.support_object_elevation.getFloat() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
return get_elevation();
|
return get_elevation();
|
||||||
}
|
}
|
||||||
@ -1786,7 +1832,7 @@ std::vector<sla::SupportPoint> SLAPrintObject::transformed_support_points() cons
|
|||||||
ret.reserve(spts.size());
|
ret.reserve(spts.size());
|
||||||
|
|
||||||
for(sla::SupportPoint& sp : spts) {
|
for(sla::SupportPoint& sp : spts) {
|
||||||
Vec3d transformed_pos = trafo() * Vec3d(sp.pos(0), sp.pos(1), sp.pos(2));
|
Vec3d transformed_pos = trafo() * Vec3d(double(sp.pos(0)), double(sp.pos(1)), double(sp.pos(2)));
|
||||||
ret.emplace_back(transformed_pos(0), transformed_pos(1), transformed_pos(2), sp.head_front_radius, sp.is_new_island);
|
ret.emplace_back(transformed_pos(0), transformed_pos(1), transformed_pos(2), sp.head_front_radius, sp.is_new_island);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user