Fix tests on all platforms
Try to link tests on Mac. Fix inaccurate pad brim size fix build on mac (attempt 2) Fixes for support tree faults and race conditions in release mode. Fix crashing test executable on gcc 4.9 fix warning on msvc
This commit is contained in:
parent
be7428d66e
commit
8ca7e56d0f
@ -271,8 +271,6 @@ ConfigOptionDef* ConfigDef::add_nullable(const t_config_option_key &opt_key, Con
|
|||||||
return def;
|
return def;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string ConfigOptionDef::nocli = "~~~noCLI";
|
|
||||||
|
|
||||||
std::ostream& ConfigDef::print_cli_help(std::ostream& out, bool show_defaults, std::function<bool(const ConfigOptionDef &)> filter) const
|
std::ostream& ConfigDef::print_cli_help(std::ostream& out, bool show_defaults, std::function<bool(const ConfigOptionDef &)> filter) const
|
||||||
{
|
{
|
||||||
// prepare a function for wrapping text
|
// prepare a function for wrapping text
|
||||||
|
@ -1444,7 +1444,7 @@ public:
|
|||||||
std::vector<std::string> cli_args(const std::string &key) const;
|
std::vector<std::string> cli_args(const std::string &key) const;
|
||||||
|
|
||||||
// Assign this key to cli to disable CLI for this option.
|
// Assign this key to cli to disable CLI for this option.
|
||||||
static std::string nocli;
|
static const constexpr char *nocli = "~~~noCLI";
|
||||||
};
|
};
|
||||||
|
|
||||||
// Map from a config option name to its definition.
|
// Map from a config option name to its definition.
|
||||||
|
@ -216,22 +216,22 @@ ClipperLib::Paths fast_offset(const ClipperLib::Paths &paths,
|
|||||||
using ClipperLib::etClosedPolygon;
|
using ClipperLib::etClosedPolygon;
|
||||||
using ClipperLib::Paths;
|
using ClipperLib::Paths;
|
||||||
using ClipperLib::Path;
|
using ClipperLib::Path;
|
||||||
|
|
||||||
ClipperOffset offs;
|
ClipperOffset offs;
|
||||||
offs.ArcTolerance = scaled<double>(0.01);
|
offs.ArcTolerance = scaled<double>(0.01);
|
||||||
|
|
||||||
for (auto &p : paths)
|
for (auto &p : paths)
|
||||||
// If the input is not at least a triangle, we can not do this algorithm
|
// If the input is not at least a triangle, we can not do this algorithm
|
||||||
if(p.size() < 3) {
|
if(p.size() < 3) {
|
||||||
BOOST_LOG_TRIVIAL(error) << "Invalid geometry for offsetting!";
|
BOOST_LOG_TRIVIAL(error) << "Invalid geometry for offsetting!";
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
offs.AddPaths(paths, jointype, etClosedPolygon);
|
offs.AddPaths(paths, jointype, etClosedPolygon);
|
||||||
|
|
||||||
Paths result;
|
Paths result;
|
||||||
offs.Execute(result, static_cast<double>(delta));
|
offs.Execute(result, static_cast<double>(delta));
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -249,25 +249,25 @@ void breakstick_holes(Points& pts,
|
|||||||
{
|
{
|
||||||
if(stride <= EPSILON || stick_width <= EPSILON || padding <= EPSILON)
|
if(stride <= EPSILON || stick_width <= EPSILON || padding <= EPSILON)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// SVG svg("bridgestick_plate.svg");
|
// SVG svg("bridgestick_plate.svg");
|
||||||
// svg.draw(poly);
|
// svg.draw(poly);
|
||||||
|
|
||||||
// The connector stick will be a small rectangle with dimensions
|
// The connector stick will be a small rectangle with dimensions
|
||||||
// stick_width x (penetration + padding) to have some penetration
|
// stick_width x (penetration + padding) to have some penetration
|
||||||
// into the input polygon.
|
// into the input polygon.
|
||||||
|
|
||||||
Points out;
|
Points out;
|
||||||
out.reserve(2 * pts.size()); // output polygon points
|
out.reserve(2 * pts.size()); // output polygon points
|
||||||
|
|
||||||
// stick bottom and right edge dimensions
|
// stick bottom and right edge dimensions
|
||||||
double sbottom = scaled(stick_width);
|
double sbottom = scaled(stick_width);
|
||||||
double sright = scaled(penetration + padding);
|
double sright = scaled(penetration + padding);
|
||||||
|
|
||||||
// scaled stride distance
|
// scaled stride distance
|
||||||
double sstride = scaled(stride);
|
double sstride = scaled(stride);
|
||||||
double t = 0;
|
double t = 0;
|
||||||
|
|
||||||
// process pairs of vertices as an edge, start with the last and
|
// process pairs of vertices as an edge, start with the last and
|
||||||
// first point
|
// first point
|
||||||
for (size_t i = pts.size() - 1, j = 0; j < pts.size(); i = j, ++j) {
|
for (size_t i = pts.size() - 1, j = 0; j < pts.size(); i = j, ++j) {
|
||||||
@ -277,16 +277,16 @@ void breakstick_holes(Points& pts,
|
|||||||
double nrm = dir.norm();
|
double nrm = dir.norm();
|
||||||
dir /= nrm;
|
dir /= nrm;
|
||||||
Vec2d dirp(-dir(Y), dir(X));
|
Vec2d dirp(-dir(Y), dir(X));
|
||||||
|
|
||||||
// Insert start point
|
// Insert start point
|
||||||
out.emplace_back(a);
|
out.emplace_back(a);
|
||||||
|
|
||||||
// dodge the start point, do not make sticks on the joins
|
// dodge the start point, do not make sticks on the joins
|
||||||
while (t < sbottom) t += sbottom;
|
while (t < sbottom) t += sbottom;
|
||||||
double tend = nrm - sbottom;
|
double tend = nrm - sbottom;
|
||||||
|
|
||||||
while (t < tend) { // insert the stick on the polygon perimeter
|
while (t < tend) { // insert the stick on the polygon perimeter
|
||||||
|
|
||||||
// calculate the stick rectangle vertices and insert them
|
// calculate the stick rectangle vertices and insert them
|
||||||
// into the output.
|
// into the output.
|
||||||
Point p1 = a + (t * dir).cast<coord_t>();
|
Point p1 = a + (t * dir).cast<coord_t>();
|
||||||
@ -294,38 +294,31 @@ void breakstick_holes(Points& pts,
|
|||||||
Point p3 = p2 + (sbottom * dir).cast<coord_t>();
|
Point p3 = p2 + (sbottom * dir).cast<coord_t>();
|
||||||
Point p4 = p3 + (sright * -dirp).cast<coord_t>();
|
Point p4 = p3 + (sright * -dirp).cast<coord_t>();
|
||||||
out.insert(out.end(), {p1, p2, p3, p4});
|
out.insert(out.end(), {p1, p2, p3, p4});
|
||||||
|
|
||||||
// continue along the perimeter
|
// continue along the perimeter
|
||||||
t += sstride;
|
t += sstride;
|
||||||
}
|
}
|
||||||
|
|
||||||
t = t - nrm;
|
t = t - nrm;
|
||||||
|
|
||||||
// Insert edge endpoint
|
// Insert edge endpoint
|
||||||
out.emplace_back(b);
|
out.emplace_back(b);
|
||||||
}
|
}
|
||||||
|
|
||||||
// move the new points
|
// move the new points
|
||||||
out.shrink_to_fit();
|
out.shrink_to_fit();
|
||||||
pts.swap(out);
|
pts.swap(out);
|
||||||
}
|
}
|
||||||
|
|
||||||
void breakstick_holes(Points &pts, const PadConfig::EmbedObject &c)
|
template<class...Args>
|
||||||
|
ExPolygons breakstick_holes(const ExPolygons &input, Args...args)
|
||||||
{
|
{
|
||||||
breakstick_holes(pts, c.object_gap_mm, c.stick_stride_mm,
|
ExPolygons ret = input;
|
||||||
c.stick_width_mm, c.stick_penetration_mm);
|
|
||||||
}
|
|
||||||
|
|
||||||
ExPolygons breakstick_holes(const ExPolygons &input,
|
|
||||||
const PadConfig::EmbedObject &cfg)
|
|
||||||
{
|
|
||||||
ExPolygons ret = offset_ex(input, scaled(cfg.object_gap_mm), ClipperLib::jtMiter, 1);
|
|
||||||
|
|
||||||
for (ExPolygon &p : ret) {
|
for (ExPolygon &p : ret) {
|
||||||
breakstick_holes(p.contour.points, cfg);
|
breakstick_holes(p.contour.points, args...);
|
||||||
for (auto &h : p.holes) breakstick_holes(h.points, cfg);
|
for (auto &h : p.holes) breakstick_holes(h.points, args...);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -335,7 +328,7 @@ ExPolygons breakstick_holes(const ExPolygons &input,
|
|||||||
/// centroids (a star is created...)
|
/// centroids (a star is created...)
|
||||||
class ConcaveHull {
|
class ConcaveHull {
|
||||||
Polygons m_polys;
|
Polygons m_polys;
|
||||||
|
|
||||||
Point centroid(const Points& pp) const
|
Point centroid(const Points& pp) const
|
||||||
{
|
{
|
||||||
Point c;
|
Point c;
|
||||||
@ -347,7 +340,7 @@ class ConcaveHull {
|
|||||||
auto MAX = std::numeric_limits<Point::coord_type>::max();
|
auto MAX = std::numeric_limits<Point::coord_type>::max();
|
||||||
auto MIN = std::numeric_limits<Point::coord_type>::min();
|
auto MIN = std::numeric_limits<Point::coord_type>::min();
|
||||||
Point min = {MAX, MAX}, max = {MIN, MIN};
|
Point min = {MAX, MAX}, max = {MIN, MIN};
|
||||||
|
|
||||||
for(auto& p : pp) {
|
for(auto& p : pp) {
|
||||||
if(p(0) < min(0)) min(0) = p(0);
|
if(p(0) < min(0)) min(0) = p(0);
|
||||||
if(p(1) < min(1)) min(1) = p(1);
|
if(p(1) < min(1)) min(1) = p(1);
|
||||||
@ -359,7 +352,7 @@ class ConcaveHull {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -372,10 +365,10 @@ class ConcaveHull {
|
|||||||
std::transform(m_polys.begin(), m_polys.end(),
|
std::transform(m_polys.begin(), m_polys.end(),
|
||||||
std::back_inserter(centroids),
|
std::back_inserter(centroids),
|
||||||
[this](const Polygon &poly) { return centroid(poly); });
|
[this](const Polygon &poly) { return centroid(poly); });
|
||||||
|
|
||||||
return centroids;
|
return centroids;
|
||||||
}
|
}
|
||||||
|
|
||||||
void merge_polygons() { m_polys = union_(m_polys); }
|
void merge_polygons() { m_polys = union_(m_polys); }
|
||||||
|
|
||||||
void add_connector_rectangles(const Points ¢roids,
|
void add_connector_rectangles(const Points ¢roids,
|
||||||
@ -385,78 +378,78 @@ class ConcaveHull {
|
|||||||
namespace bgi = boost::geometry::index;
|
namespace bgi = boost::geometry::index;
|
||||||
using PointIndexElement = std::pair<Point, unsigned>;
|
using PointIndexElement = std::pair<Point, unsigned>;
|
||||||
using PointIndex = bgi::rtree<PointIndexElement, bgi::rstar<16, 4>>;
|
using PointIndex = bgi::rtree<PointIndexElement, bgi::rstar<16, 4>>;
|
||||||
|
|
||||||
// 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.
|
||||||
Point cc = centroid(centroids);
|
Point cc = centroid(centroids);
|
||||||
|
|
||||||
PointIndex ctrindex;
|
PointIndex ctrindex;
|
||||||
unsigned idx = 0;
|
unsigned idx = 0;
|
||||||
for(const Point &ct : centroids)
|
for(const Point &ct : centroids)
|
||||||
ctrindex.insert(std::make_pair(ct, idx++));
|
ctrindex.insert(std::make_pair(ct, idx++));
|
||||||
|
|
||||||
m_polys.reserve(m_polys.size() + centroids.size());
|
m_polys.reserve(m_polys.size() + centroids.size());
|
||||||
|
|
||||||
idx = 0;
|
idx = 0;
|
||||||
for (const Point &c : centroids) {
|
for (const Point &c : centroids) {
|
||||||
thr();
|
thr();
|
||||||
|
|
||||||
double dx = c.x() - cc.x(), dy = c.y() - cc.y();
|
double dx = c.x() - cc.x(), dy = c.y() - cc.y();
|
||||||
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;
|
||||||
|
|
||||||
const Point &ct = centroids[idx];
|
const Point &ct = centroids[idx];
|
||||||
|
|
||||||
std::vector<PointIndexElement> result;
|
std::vector<PointIndexElement> result;
|
||||||
ctrindex.query(bgi::nearest(ct, 2), std::back_inserter(result));
|
ctrindex.query(bgi::nearest(ct, 2), std::back_inserter(result));
|
||||||
|
|
||||||
double dist = max_dist;
|
double dist = max_dist;
|
||||||
for (const PointIndexElement &el : result)
|
for (const PointIndexElement &el : result)
|
||||||
if (el.second != idx) {
|
if (el.second != idx) {
|
||||||
dist = Line(el.first, ct).length();
|
dist = Line(el.first, ct).length();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
idx++;
|
idx++;
|
||||||
|
|
||||||
if (dist >= max_dist) return;
|
if (dist >= max_dist) return;
|
||||||
|
|
||||||
Polygon r;
|
Polygon r;
|
||||||
r.points.reserve(3);
|
r.points.reserve(3);
|
||||||
r.points.emplace_back(cc);
|
r.points.emplace_back(cc);
|
||||||
|
|
||||||
Point d(scaled(nx), scaled(ny));
|
Point d(scaled(nx), scaled(ny));
|
||||||
r.points.emplace_back(c + Point(-d.y(), d.x()));
|
r.points.emplace_back(c + Point(-d.y(), d.x()));
|
||||||
r.points.emplace_back(c + Point(d.y(), -d.x()));
|
r.points.emplace_back(c + Point(d.y(), -d.x()));
|
||||||
offset(r, scaled(1.));
|
offset(r, scaled<float>(1.));
|
||||||
|
|
||||||
m_polys.emplace_back(r);
|
m_polys.emplace_back(r);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
ConcaveHull(const ExPolygons& polys, double merge_dist, ThrowOnCancel thr)
|
ConcaveHull(const ExPolygons& polys, double merge_dist, ThrowOnCancel thr)
|
||||||
: ConcaveHull{to_polygons(polys), merge_dist, thr} {}
|
: ConcaveHull{to_polygons(polys), merge_dist, thr} {}
|
||||||
|
|
||||||
ConcaveHull(const Polygons& polys, double mergedist, ThrowOnCancel thr)
|
ConcaveHull(const Polygons& polys, double mergedist, ThrowOnCancel thr)
|
||||||
{
|
{
|
||||||
if(polys.empty()) return;
|
if(polys.empty()) return;
|
||||||
|
|
||||||
m_polys = polys;
|
m_polys = polys;
|
||||||
merge_polygons();
|
merge_polygons();
|
||||||
|
|
||||||
if(m_polys.size() == 1) return;
|
if(m_polys.size() == 1) return;
|
||||||
|
|
||||||
Points centroids = calculate_centroids();
|
Points centroids = calculate_centroids();
|
||||||
|
|
||||||
add_connector_rectangles(centroids, scaled(mergedist), thr);
|
add_connector_rectangles(centroids, scaled(mergedist), thr);
|
||||||
|
|
||||||
merge_polygons();
|
merge_polygons();
|
||||||
}
|
}
|
||||||
|
|
||||||
// const Polygons & polygons() const { return m_polys; }
|
// const Polygons & polygons() const { return m_polys; }
|
||||||
|
|
||||||
ExPolygons to_expolygons() const
|
ExPolygons to_expolygons() const
|
||||||
{
|
{
|
||||||
auto ret = reserve_vector<ExPolygon>(m_polys.size());
|
auto ret = reserve_vector<ExPolygon>(m_polys.size());
|
||||||
@ -470,12 +463,12 @@ public:
|
|||||||
paths = fast_offset(paths, -delta, ClipperLib::jtRound);
|
paths = fast_offset(paths, -delta, ClipperLib::jtRound);
|
||||||
m_polys = ClipperPaths_to_Slic3rPolygons(paths);
|
m_polys = ClipperPaths_to_Slic3rPolygons(paths);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline coord_t get_waffle_offset(const PadConfig &c)
|
static inline coord_t get_waffle_offset(const PadConfig &c)
|
||||||
{
|
{
|
||||||
return scaled(c.brim_size_mm + c.wing_distance() + c.wall_thickness_mm);
|
return scaled(c.brim_size_mm + c.wing_distance());
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline double get_merge_distance(const PadConfig &c)
|
static inline double get_merge_distance(const PadConfig &c)
|
||||||
{
|
{
|
||||||
return 2. * (1.8 * c.wall_thickness_mm) + c.max_merge_dist_mm;
|
return 2. * (1.8 * c.wall_thickness_mm) + c.max_merge_dist_mm;
|
||||||
@ -508,26 +501,26 @@ struct PadSkeleton { ExPolygons inner, outer; };
|
|||||||
PadSkeleton divide_blueprint(const ExPolygons &bp)
|
PadSkeleton divide_blueprint(const ExPolygons &bp)
|
||||||
{
|
{
|
||||||
ClipperLib::PolyTree ptree = union_pt(bp);
|
ClipperLib::PolyTree ptree = union_pt(bp);
|
||||||
|
|
||||||
PadSkeleton ret;
|
PadSkeleton ret;
|
||||||
ret.inner.reserve(size_t(ptree.Total()));
|
ret.inner.reserve(size_t(ptree.Total()));
|
||||||
ret.outer.reserve(size_t(ptree.Total()));
|
ret.outer.reserve(size_t(ptree.Total()));
|
||||||
|
|
||||||
for (ClipperLib::PolyTree::PolyNode *node : ptree.Childs) {
|
for (ClipperLib::PolyTree::PolyNode *node : ptree.Childs) {
|
||||||
ExPolygon poly(ClipperPath_to_Slic3rPolygon(node->Contour));
|
ExPolygon poly(ClipperPath_to_Slic3rPolygon(node->Contour));
|
||||||
for (ClipperLib::PolyTree::PolyNode *child : node->Childs) {
|
for (ClipperLib::PolyTree::PolyNode *child : node->Childs) {
|
||||||
if (child->IsHole()) {
|
if (child->IsHole()) {
|
||||||
poly.holes.emplace_back(
|
poly.holes.emplace_back(
|
||||||
ClipperPath_to_Slic3rPolygon(child->Contour));
|
ClipperPath_to_Slic3rPolygon(child->Contour));
|
||||||
|
|
||||||
traverse_pt_unordered(child->Childs, &ret.inner);
|
traverse_pt_unordered(child->Childs, &ret.inner);
|
||||||
}
|
}
|
||||||
else traverse_pt_unordered(child, &ret.inner);
|
else traverse_pt_unordered(child, &ret.inner);
|
||||||
}
|
}
|
||||||
|
|
||||||
ret.outer.emplace_back(poly);
|
ret.outer.emplace_back(poly);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -536,32 +529,32 @@ PadSkeleton divide_blueprint(const ExPolygons &bp)
|
|||||||
class Intersector {
|
class Intersector {
|
||||||
BoxIndex m_index;
|
BoxIndex m_index;
|
||||||
ExPolygons m_polys;
|
ExPolygons m_polys;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
// Add a new polygon to the index
|
// Add a new polygon to the index
|
||||||
void add(const ExPolygon &ep)
|
void add(const ExPolygon &ep)
|
||||||
{
|
{
|
||||||
m_polys.emplace_back(ep);
|
m_polys.emplace_back(ep);
|
||||||
m_index.insert(BoundingBox{ep}, unsigned(m_index.size()));
|
m_index.insert(BoundingBox{ep}, unsigned(m_index.size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check an arbitrary polygon for intersection with the indexed polygons
|
// Check an arbitrary polygon for intersection with the indexed polygons
|
||||||
bool intersects(const ExPolygon &poly)
|
bool intersects(const ExPolygon &poly)
|
||||||
{
|
{
|
||||||
// Create a suitable query bounding box.
|
// Create a suitable query bounding box.
|
||||||
auto bb = poly.contour.bounding_box();
|
auto bb = poly.contour.bounding_box();
|
||||||
|
|
||||||
std::vector<BoxIndexEl> qres = m_index.query(bb, BoxIndex::qtIntersects);
|
std::vector<BoxIndexEl> qres = m_index.query(bb, BoxIndex::qtIntersects);
|
||||||
|
|
||||||
// Now check intersections on the actual polygons (not just the boxes)
|
// Now check intersections on the actual polygons (not just the boxes)
|
||||||
bool is_overlap = false;
|
bool is_overlap = false;
|
||||||
auto qit = qres.begin();
|
auto qit = qres.begin();
|
||||||
while (!is_overlap && qit != qres.end())
|
while (!is_overlap && qit != qres.end())
|
||||||
is_overlap = is_overlap || poly.overlaps(m_polys[(qit++)->second]);
|
is_overlap = is_overlap || poly.overlaps(m_polys[(qit++)->second]);
|
||||||
|
|
||||||
return is_overlap;
|
return is_overlap;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// This dummy intersector to implement the "force pad everywhere" feature
|
// This dummy intersector to implement the "force pad everywhere" feature
|
||||||
@ -577,7 +570,7 @@ class _AroundPadSkeleton : public PadSkeleton
|
|||||||
// A spatial index used to be able to efficiently find intersections of
|
// A spatial index used to be able to efficiently find intersections of
|
||||||
// support polygons with the model polygons.
|
// support polygons with the model polygons.
|
||||||
_Intersector m_intersector;
|
_Intersector m_intersector;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
_AroundPadSkeleton(const ExPolygons &support_blueprint,
|
_AroundPadSkeleton(const ExPolygons &support_blueprint,
|
||||||
const ExPolygons &model_blueprint,
|
const ExPolygons &model_blueprint,
|
||||||
@ -589,33 +582,41 @@ public:
|
|||||||
// support contours. The pad has to have a hole in which the model can
|
// support contours. The pad has to have a hole in which the model can
|
||||||
// fit perfectly (thus the substraction -- diff_ex). Also, the pad has
|
// fit perfectly (thus the substraction -- diff_ex). Also, the pad has
|
||||||
// to be eliminated from areas where there is no need for a pad, due
|
// to be eliminated from areas where there is no need for a pad, due
|
||||||
// to missing supports.
|
// to missing supports.
|
||||||
|
|
||||||
add_supports_to_index(support_blueprint);
|
add_supports_to_index(support_blueprint);
|
||||||
|
|
||||||
|
auto model_bp_offs =
|
||||||
|
offset_ex(model_blueprint,
|
||||||
|
scaled<float>(cfg.embed_object.object_gap_mm),
|
||||||
|
ClipperLib::jtMiter, 1);
|
||||||
|
|
||||||
ConcaveHull fullcvh =
|
ConcaveHull fullcvh =
|
||||||
wafflized_concave_hull(support_blueprint, model_blueprint, cfg, thr);
|
wafflized_concave_hull(support_blueprint, model_bp_offs, cfg, thr);
|
||||||
|
|
||||||
auto model_bp_sticks =
|
auto model_bp_sticks =
|
||||||
breakstick_holes(model_blueprint, cfg.embed_object);
|
breakstick_holes(model_bp_offs, cfg.embed_object.object_gap_mm,
|
||||||
|
cfg.embed_object.stick_stride_mm,
|
||||||
|
cfg.embed_object.stick_width_mm,
|
||||||
|
cfg.embed_object.stick_penetration_mm);
|
||||||
|
|
||||||
ExPolygons fullpad = diff_ex(fullcvh.to_expolygons(), model_bp_sticks);
|
ExPolygons fullpad = diff_ex(fullcvh.to_expolygons(), model_bp_sticks);
|
||||||
|
|
||||||
remove_redundant_parts(fullpad);
|
remove_redundant_parts(fullpad);
|
||||||
|
|
||||||
PadSkeleton divided = divide_blueprint(fullpad);
|
PadSkeleton divided = divide_blueprint(fullpad);
|
||||||
outer = std::move(divided.outer);
|
outer = std::move(divided.outer);
|
||||||
inner = std::move(divided.inner);
|
inner = std::move(divided.inner);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
// Add the support blueprint to the search index to be queried later
|
// Add the support blueprint to the search index to be queried later
|
||||||
void add_supports_to_index(const ExPolygons &supp_bp)
|
void add_supports_to_index(const ExPolygons &supp_bp)
|
||||||
{
|
{
|
||||||
for (auto &ep : supp_bp) m_intersector.add(ep);
|
for (auto &ep : supp_bp) m_intersector.add(ep);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the wafflized pad around all object in the scene. This pad doesnt
|
// Create the wafflized pad around all object in the scene. This pad doesnt
|
||||||
// have any holes yet.
|
// have any holes yet.
|
||||||
ConcaveHull wafflized_concave_hull(const ExPolygons &supp_bp,
|
ConcaveHull wafflized_concave_hull(const ExPolygons &supp_bp,
|
||||||
@ -624,16 +625,16 @@ private:
|
|||||||
ThrowOnCancel thr)
|
ThrowOnCancel thr)
|
||||||
{
|
{
|
||||||
auto allin = reserve_vector<ExPolygon>(supp_bp.size() + model_bp.size());
|
auto allin = reserve_vector<ExPolygon>(supp_bp.size() + model_bp.size());
|
||||||
|
|
||||||
for (auto &ep : supp_bp) allin.emplace_back(ep.contour);
|
for (auto &ep : supp_bp) allin.emplace_back(ep.contour);
|
||||||
for (auto &ep : model_bp) allin.emplace_back(ep.contour);
|
for (auto &ep : model_bp) allin.emplace_back(ep.contour);
|
||||||
|
|
||||||
ConcaveHull ret{allin, ConcaveHull::get_merge_distance(cfg), thr};
|
ConcaveHull ret{allin, ConcaveHull::get_merge_distance(cfg), thr};
|
||||||
ret.offset_waffle_style(ConcaveHull::get_waffle_offset(cfg));
|
ret.offset_waffle_style(ConcaveHull::get_waffle_offset(cfg));
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
// To remove parts of the pad skeleton which do not host any supports
|
// To remove parts of the pad skeleton which do not host any supports
|
||||||
void remove_redundant_parts(ExPolygons &parts)
|
void remove_redundant_parts(ExPolygons &parts)
|
||||||
{
|
{
|
||||||
@ -641,7 +642,7 @@ private:
|
|||||||
[this](const ExPolygon &p) {
|
[this](const ExPolygon &p) {
|
||||||
return !m_intersector.intersects(p);
|
return !m_intersector.intersects(p);
|
||||||
});
|
});
|
||||||
|
|
||||||
parts.erase(endit, parts.end());
|
parts.erase(endit, parts.end());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -661,9 +662,9 @@ public:
|
|||||||
|
|
||||||
for (auto &ep : support_blueprint) outer.emplace_back(ep.contour);
|
for (auto &ep : support_blueprint) outer.emplace_back(ep.contour);
|
||||||
for (auto &ep : model_blueprint) outer.emplace_back(ep.contour);
|
for (auto &ep : model_blueprint) outer.emplace_back(ep.contour);
|
||||||
|
|
||||||
ConcaveHull ochull{outer, ConcaveHull::get_merge_distance(cfg), thr};
|
ConcaveHull ochull{outer, ConcaveHull::get_merge_distance(cfg), thr};
|
||||||
|
|
||||||
ochull.offset_waffle_style(ConcaveHull::get_waffle_offset(cfg));
|
ochull.offset_waffle_style(ConcaveHull::get_waffle_offset(cfg));
|
||||||
outer = ochull.to_expolygons();
|
outer = ochull.to_expolygons();
|
||||||
}
|
}
|
||||||
@ -673,17 +674,17 @@ public:
|
|||||||
template<class...Args>
|
template<class...Args>
|
||||||
ExPolygon offset_contour_only(const ExPolygon &poly, coord_t delta, Args...args)
|
ExPolygon offset_contour_only(const ExPolygon &poly, coord_t delta, Args...args)
|
||||||
{
|
{
|
||||||
ExPolygons tmp = offset_ex(poly.contour, delta, args...);
|
ExPolygons tmp = offset_ex(poly.contour, float(delta), args...);
|
||||||
|
|
||||||
if (tmp.empty()) return {};
|
if (tmp.empty()) return {};
|
||||||
|
|
||||||
Polygons holes = poly.holes;
|
Polygons holes = poly.holes;
|
||||||
for (auto &h : holes) h.reverse();
|
for (auto &h : holes) h.reverse();
|
||||||
|
|
||||||
tmp = diff_ex(to_polygons(tmp), holes);
|
tmp = diff_ex(to_polygons(tmp), holes);
|
||||||
|
|
||||||
if (tmp.empty()) return {};
|
if (tmp.empty()) return {};
|
||||||
|
|
||||||
return tmp.front();
|
return tmp.front();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -691,17 +692,17 @@ bool add_cavity(Contour3D &pad, ExPolygon &top_poly, const PadConfig3D &cfg,
|
|||||||
ThrowOnCancel thr)
|
ThrowOnCancel thr)
|
||||||
{
|
{
|
||||||
auto logerr = []{BOOST_LOG_TRIVIAL(error)<<"Could not create pad cavity";};
|
auto logerr = []{BOOST_LOG_TRIVIAL(error)<<"Could not create pad cavity";};
|
||||||
|
|
||||||
double wing_distance = cfg.wing_height / std::tan(cfg.slope);
|
double wing_distance = cfg.wing_height / std::tan(cfg.slope);
|
||||||
coord_t delta_inner = -scaled(cfg.thickness + wing_distance);
|
coord_t delta_inner = -scaled(cfg.thickness + wing_distance);
|
||||||
coord_t delta_middle = -scaled(cfg.thickness);
|
coord_t delta_middle = -scaled(cfg.thickness);
|
||||||
ExPolygon inner_base = offset_contour_only(top_poly, delta_inner);
|
ExPolygon inner_base = offset_contour_only(top_poly, delta_inner);
|
||||||
ExPolygon middle_base = offset_contour_only(top_poly, delta_middle);
|
ExPolygon middle_base = offset_contour_only(top_poly, delta_middle);
|
||||||
|
|
||||||
if (inner_base.empty() || middle_base.empty()) { logerr(); return false; }
|
if (inner_base.empty() || middle_base.empty()) { logerr(); return false; }
|
||||||
|
|
||||||
ExPolygons pdiff = diff_ex(top_poly, middle_base.contour);
|
ExPolygons pdiff = diff_ex(top_poly, middle_base.contour);
|
||||||
|
|
||||||
if (pdiff.size() != 1) { logerr(); return false; }
|
if (pdiff.size() != 1) { logerr(); return false; }
|
||||||
|
|
||||||
top_poly = pdiff.front();
|
top_poly = pdiff.front();
|
||||||
@ -721,28 +722,28 @@ Contour3D create_outer_pad_geometry(const ExPolygons & skeleton,
|
|||||||
ThrowOnCancel thr)
|
ThrowOnCancel thr)
|
||||||
{
|
{
|
||||||
Contour3D ret;
|
Contour3D ret;
|
||||||
|
|
||||||
for (const ExPolygon &pad_part : skeleton) {
|
for (const ExPolygon &pad_part : skeleton) {
|
||||||
ExPolygon top_poly{pad_part};
|
ExPolygon top_poly{pad_part};
|
||||||
ExPolygon bottom_poly =
|
ExPolygon bottom_poly =
|
||||||
offset_contour_only(pad_part, -scaled(cfg.bottom_offset()));
|
offset_contour_only(pad_part, -scaled(cfg.bottom_offset()));
|
||||||
|
|
||||||
if (bottom_poly.empty()) continue;
|
if (bottom_poly.empty()) continue;
|
||||||
|
|
||||||
double z_min = -cfg.height, z_max = 0;
|
double z_min = -cfg.height, z_max = 0;
|
||||||
ret.merge(walls(top_poly.contour, bottom_poly.contour, z_max, z_min,
|
ret.merge(walls(top_poly.contour, bottom_poly.contour, z_max, z_min,
|
||||||
cfg.bottom_offset(), thr));
|
cfg.bottom_offset(), thr));
|
||||||
|
|
||||||
if (cfg.wing_height > 0. && add_cavity(ret, top_poly, cfg, thr))
|
if (cfg.wing_height > 0. && add_cavity(ret, top_poly, cfg, thr))
|
||||||
z_max = -cfg.wing_height;
|
z_max = -cfg.wing_height;
|
||||||
|
|
||||||
for (auto &h : bottom_poly.holes)
|
for (auto &h : bottom_poly.holes)
|
||||||
ret.merge(straight_walls(h, z_max, z_min, thr));
|
ret.merge(straight_walls(h, z_max, z_min, thr));
|
||||||
|
|
||||||
ret.merge(triangulate_expolygon_3d(bottom_poly, z_min, NORMALS_DOWN));
|
ret.merge(triangulate_expolygon_3d(bottom_poly, z_min, NORMALS_DOWN));
|
||||||
ret.merge(triangulate_expolygon_3d(top_poly, NORMALS_UP));
|
ret.merge(triangulate_expolygon_3d(top_poly, NORMALS_UP));
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -751,18 +752,18 @@ Contour3D create_inner_pad_geometry(const ExPolygons & skeleton,
|
|||||||
ThrowOnCancel thr)
|
ThrowOnCancel thr)
|
||||||
{
|
{
|
||||||
Contour3D ret;
|
Contour3D ret;
|
||||||
|
|
||||||
double z_max = 0., z_min = -cfg.height;
|
double z_max = 0., z_min = -cfg.height;
|
||||||
for (const ExPolygon &pad_part : skeleton) {
|
for (const ExPolygon &pad_part : skeleton) {
|
||||||
ret.merge(straight_walls(pad_part.contour, z_max, z_min,thr));
|
ret.merge(straight_walls(pad_part.contour, z_max, z_min,thr));
|
||||||
|
|
||||||
for (auto &h : pad_part.holes)
|
for (auto &h : pad_part.holes)
|
||||||
ret.merge(straight_walls(h, z_max, z_min, thr));
|
ret.merge(straight_walls(h, z_max, z_min, thr));
|
||||||
|
|
||||||
ret.merge(triangulate_expolygon_3d(pad_part, z_min, NORMALS_DOWN));
|
ret.merge(triangulate_expolygon_3d(pad_part, z_min, NORMALS_DOWN));
|
||||||
ret.merge(triangulate_expolygon_3d(pad_part, z_max, NORMALS_UP));
|
ret.merge(triangulate_expolygon_3d(pad_part, z_max, NORMALS_UP));
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -776,7 +777,7 @@ Contour3D create_pad_geometry(const PadSkeleton &skelet,
|
|||||||
svg.draw(skelet.inner, "blue");
|
svg.draw(skelet.inner, "blue");
|
||||||
svg.Close();
|
svg.Close();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
PadConfig3D cfg3d(cfg);
|
PadConfig3D cfg3d(cfg);
|
||||||
return create_outer_pad_geometry(skelet.outer, cfg3d, thr)
|
return create_outer_pad_geometry(skelet.outer, cfg3d, thr)
|
||||||
.merge(create_inner_pad_geometry(skelet.inner, cfg3d, thr));
|
.merge(create_inner_pad_geometry(skelet.inner, cfg3d, thr));
|
||||||
@ -809,13 +810,13 @@ void pad_blueprint(const TriangleMesh & mesh,
|
|||||||
{
|
{
|
||||||
if (mesh.empty()) return;
|
if (mesh.empty()) return;
|
||||||
TriangleMeshSlicer slicer(&mesh);
|
TriangleMeshSlicer slicer(&mesh);
|
||||||
|
|
||||||
auto out = reserve_vector<ExPolygons>(heights.size());
|
auto out = reserve_vector<ExPolygons>(heights.size());
|
||||||
slicer.slice(heights, 0.f, &out, thrfn);
|
slicer.slice(heights, 0.f, &out, thrfn);
|
||||||
|
|
||||||
size_t count = 0;
|
size_t count = 0;
|
||||||
for(auto& o : out) count += o.size();
|
for(auto& o : out) count += o.size();
|
||||||
|
|
||||||
// Unification is expensive, a simplify also speeds up the pad generation
|
// Unification is expensive, a simplify also speeds up the pad generation
|
||||||
auto tmp = reserve_vector<ExPolygon>(count);
|
auto tmp = reserve_vector<ExPolygon>(count);
|
||||||
for(ExPolygons& o : out)
|
for(ExPolygons& o : out)
|
||||||
@ -823,9 +824,9 @@ void pad_blueprint(const TriangleMesh & mesh,
|
|||||||
auto&& exss = e.simplify(scaled<double>(0.1));
|
auto&& exss = e.simplify(scaled<double>(0.1));
|
||||||
for(ExPolygon& ep : exss) tmp.emplace_back(std::move(ep));
|
for(ExPolygon& ep : exss) tmp.emplace_back(std::move(ep));
|
||||||
}
|
}
|
||||||
|
|
||||||
ExPolygons utmp = union_ex(tmp);
|
ExPolygons utmp = union_ex(tmp);
|
||||||
|
|
||||||
for(auto& o : utmp) {
|
for(auto& o : utmp) {
|
||||||
auto&& smp = o.simplify(scaled<double>(0.1));
|
auto&& smp = o.simplify(scaled<double>(0.1));
|
||||||
output.insert(output.end(), smp.begin(), smp.end());
|
output.insert(output.end(), smp.begin(), smp.end());
|
||||||
@ -839,7 +840,7 @@ void pad_blueprint(const TriangleMesh &mesh,
|
|||||||
ThrowOnCancel thrfn)
|
ThrowOnCancel thrfn)
|
||||||
{
|
{
|
||||||
float gnd = float(mesh.bounding_box().min(Z));
|
float gnd = float(mesh.bounding_box().min(Z));
|
||||||
|
|
||||||
std::vector<float> slicegrid = grid(gnd, gnd + h, layerh);
|
std::vector<float> slicegrid = grid(gnd, gnd + h, layerh);
|
||||||
pad_blueprint(mesh, output, slicegrid, thrfn);
|
pad_blueprint(mesh, output, slicegrid, thrfn);
|
||||||
}
|
}
|
||||||
@ -855,10 +856,14 @@ void create_pad(const ExPolygons &sup_blueprint,
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::string PadConfig::validate() const
|
std::string PadConfig::validate() const
|
||||||
{
|
{
|
||||||
if (bottom_offset() > brim_size_mm + wing_distance())
|
static const double constexpr MIN_BRIM_SIZE_MM = .1;
|
||||||
return L("Pad brim size is too low for the current slope.");
|
|
||||||
|
if (brim_size_mm < MIN_BRIM_SIZE_MM ||
|
||||||
|
bottom_offset() > brim_size_mm + wing_distance() ||
|
||||||
|
ConcaveHull::get_waffle_offset(*this) <= MIN_BRIM_SIZE_MM)
|
||||||
|
return L("Pad brim size is too small for the current configuration.");
|
||||||
|
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
||||||
@ -26,8 +27,8 @@ void pad_blueprint(
|
|||||||
ThrowOnCancel thrfn = [] {}); // Function that throws if cancel was requested
|
ThrowOnCancel thrfn = [] {}); // Function that throws if cancel was requested
|
||||||
|
|
||||||
void pad_blueprint(
|
void pad_blueprint(
|
||||||
const TriangleMesh &mesh,
|
const TriangleMesh &mesh,
|
||||||
ExPolygons & output,
|
ExPolygons & output,
|
||||||
float samplingheight = 0.1f, // The height range to sample
|
float samplingheight = 0.1f, // The height range to sample
|
||||||
float layerheight = 0.05f, // The sampling height
|
float layerheight = 0.05f, // The sampling height
|
||||||
ThrowOnCancel thrfn = [] {});
|
ThrowOnCancel thrfn = [] {});
|
||||||
@ -38,7 +39,7 @@ struct PadConfig {
|
|||||||
double max_merge_dist_mm = 50;
|
double max_merge_dist_mm = 50;
|
||||||
double wall_slope = std::atan(1.0); // Universal constant for Pi/4
|
double wall_slope = std::atan(1.0); // Universal constant for Pi/4
|
||||||
double brim_size_mm = 1.6;
|
double brim_size_mm = 1.6;
|
||||||
|
|
||||||
struct EmbedObject {
|
struct EmbedObject {
|
||||||
double object_gap_mm = 1.;
|
double object_gap_mm = 1.;
|
||||||
double stick_stride_mm = 10.;
|
double stick_stride_mm = 10.;
|
||||||
@ -48,7 +49,7 @@ struct PadConfig {
|
|||||||
bool everywhere = false;
|
bool everywhere = false;
|
||||||
operator bool() const { return enabled; }
|
operator bool() const { return enabled; }
|
||||||
} embed_object;
|
} embed_object;
|
||||||
|
|
||||||
inline PadConfig() = default;
|
inline PadConfig() = default;
|
||||||
inline PadConfig(double thickness,
|
inline PadConfig(double thickness,
|
||||||
double height,
|
double height,
|
||||||
@ -74,10 +75,10 @@ struct PadConfig {
|
|||||||
{
|
{
|
||||||
return wall_height_mm + wall_thickness_mm;
|
return wall_height_mm + wall_thickness_mm;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the elevation needed for compensating the pad.
|
/// Returns the elevation needed for compensating the pad.
|
||||||
inline double required_elevation() const { return wall_thickness_mm; }
|
inline double required_elevation() const { return wall_thickness_mm; }
|
||||||
|
|
||||||
std::string validate() const;
|
std::string validate() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -15,8 +15,6 @@ namespace ClipperLib { struct Polygon; }
|
|||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
namespace sla {
|
namespace sla {
|
||||||
|
|
||||||
class Raster;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Raster captures an anti-aliased monochrome canvas where vectorial
|
* @brief Raster captures an anti-aliased monochrome canvas where vectorial
|
||||||
* polygons can be rasterized. Fill color is always white and the background is
|
* polygons can be rasterized. Fill color is always white and the background is
|
||||||
|
@ -70,7 +70,7 @@ std::vector<ExPolygons> SupportTree::slice(
|
|||||||
auto bb = pad_mesh.bounding_box();
|
auto bb = pad_mesh.bounding_box();
|
||||||
auto maxzit = std::upper_bound(grid.begin(), grid.end(), bb.max.z());
|
auto maxzit = std::upper_bound(grid.begin(), grid.end(), bb.max.z());
|
||||||
|
|
||||||
long cap = grid.end() - maxzit;
|
auto cap = grid.end() - maxzit;
|
||||||
auto padgrid = reserve_vector<float>(size_t(cap > 0 ? cap : 0));
|
auto padgrid = reserve_vector<float>(size_t(cap > 0 ? cap : 0));
|
||||||
std::copy(grid.begin(), maxzit, std::back_inserter(padgrid));
|
std::copy(grid.begin(), maxzit, std::back_inserter(padgrid));
|
||||||
|
|
||||||
|
@ -96,6 +96,8 @@ struct Head {
|
|||||||
// If there is a pillar connecting to this head, then the id will be set.
|
// If there is a pillar connecting to this head, then the id will be set.
|
||||||
long pillar_id = ID_UNSET;
|
long pillar_id = ID_UNSET;
|
||||||
|
|
||||||
|
long bridge_id = ID_UNSET;
|
||||||
|
|
||||||
inline void invalidate() { id = ID_UNSET; }
|
inline void invalidate() { id = ID_UNSET; }
|
||||||
inline bool is_valid() const { return id >= 0; }
|
inline bool is_valid() const { return id >= 0; }
|
||||||
|
|
||||||
@ -331,10 +333,16 @@ public:
|
|||||||
std::lock_guard<Mutex> lk(m_mutex);
|
std::lock_guard<Mutex> lk(m_mutex);
|
||||||
assert(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size());
|
assert(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size());
|
||||||
|
|
||||||
if(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size())
|
if(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size())
|
||||||
m_pillars[size_t(pillar.id)].links++;
|
m_pillars[size_t(pillar.id)].links++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsigned bridgecount(const Pillar &pillar) const {
|
||||||
|
std::lock_guard<Mutex> lk(m_mutex);
|
||||||
|
assert(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size());
|
||||||
|
return pillar.bridges;
|
||||||
|
}
|
||||||
|
|
||||||
template<class...Args> Pillar& add_pillar(Args&&...args)
|
template<class...Args> Pillar& add_pillar(Args&&...args)
|
||||||
{
|
{
|
||||||
std::lock_guard<Mutex> lk(m_mutex);
|
std::lock_guard<Mutex> lk(m_mutex);
|
||||||
|
@ -345,7 +345,7 @@ EigenMesh3D::hit_result SupportTreeBuildsteps::bridge_mesh_intersect(
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool SupportTreeBuildsteps::interconnect(const Pillar &pillar,
|
bool SupportTreeBuildsteps::interconnect(const Pillar &pillar,
|
||||||
const Pillar &nextpillar)
|
const Pillar &nextpillar)
|
||||||
{
|
{
|
||||||
// We need to get the starting point of the zig-zag pattern. We have to
|
// We need to get the starting point of the zig-zag pattern. We have to
|
||||||
// be aware that the two head junctions are at different heights. We
|
// be aware that the two head junctions are at different heights. We
|
||||||
@ -438,13 +438,14 @@ bool SupportTreeBuildsteps::interconnect(const Pillar &pillar,
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool SupportTreeBuildsteps::connect_to_nearpillar(const Head &head,
|
bool SupportTreeBuildsteps::connect_to_nearpillar(const Head &head,
|
||||||
long nearpillar_id)
|
long nearpillar_id)
|
||||||
{
|
{
|
||||||
auto nearpillar = [this, nearpillar_id]() {
|
auto nearpillar = [this, nearpillar_id]() {
|
||||||
return m_builder.pillar(nearpillar_id);
|
return m_builder.pillar(nearpillar_id);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (nearpillar().bridges > m_cfg.max_bridges_on_pillar) return false;
|
if (m_builder.bridgecount(nearpillar()) > 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();
|
||||||
@ -502,14 +503,21 @@ bool SupportTreeBuildsteps::connect_to_nearpillar(const Head &head,
|
|||||||
// Cannot insert the bridge. (further search might not worth the hassle)
|
// Cannot insert the bridge. (further search might not worth the hassle)
|
||||||
if(t < distance(bridgestart, bridgeend)) return false;
|
if(t < distance(bridgestart, bridgeend)) return false;
|
||||||
|
|
||||||
// A partial pillar is needed under the starting head.
|
std::lock_guard<ccr::BlockingMutex> lk(m_bridge_mutex);
|
||||||
if(zdiff > 0) {
|
|
||||||
m_builder.add_pillar(unsigned(head.id), bridgestart, r);
|
|
||||||
m_builder.add_junction(bridgestart, r);
|
|
||||||
}
|
|
||||||
|
|
||||||
m_builder.add_bridge(bridgestart, bridgeend, r);
|
if (m_builder.bridgecount(nearpillar()) < m_cfg.max_bridges_on_pillar) {
|
||||||
m_builder.increment_bridges(nearpillar());
|
// A partial pillar is needed under the starting head.
|
||||||
|
if(zdiff > 0) {
|
||||||
|
m_builder.add_pillar(unsigned(head.id), bridgestart, r);
|
||||||
|
m_builder.add_junction(bridgestart, r);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto &br = m_builder.add_bridge(bridgestart, bridgeend, r);
|
||||||
|
m_builder.increment_bridges(nearpillar());
|
||||||
|
if (head.pillar_id == ID_UNSET)
|
||||||
|
m_builder.head(unsigned(head.id)).bridge_id = br.id;
|
||||||
|
|
||||||
|
} else return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -550,9 +558,9 @@ bool SupportTreeBuildsteps::search_pillar_and_connect(const Head &head)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp,
|
void SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp,
|
||||||
const Vec3d &sourcedir,
|
const Vec3d &sourcedir,
|
||||||
double radius,
|
double radius,
|
||||||
long head_id)
|
long head_id)
|
||||||
{
|
{
|
||||||
// People were killed for this number (seriously)
|
// People were killed for this number (seriously)
|
||||||
static const double SQR2 = std::sqrt(2.0);
|
static const double SQR2 = std::sqrt(2.0);
|
||||||
@ -646,10 +654,8 @@ void SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp,
|
|||||||
|
|
||||||
if (normal_mode) {
|
if (normal_mode) {
|
||||||
Pillar &plr = head_id >= 0
|
Pillar &plr = head_id >= 0
|
||||||
? m_builder.add_pillar(unsigned(head_id),
|
? m_builder.add_pillar(unsigned(head_id), endp, radius)
|
||||||
endp,
|
: m_builder.add_pillar(jp, endp, radius);
|
||||||
radius)
|
|
||||||
: m_builder.add_pillar(jp, endp, radius);
|
|
||||||
|
|
||||||
if (can_add_base)
|
if (can_add_base)
|
||||||
plr.add_base(m_cfg.base_height_mm, m_cfg.base_radius_mm);
|
plr.add_base(m_cfg.base_height_mm, m_cfg.base_radius_mm);
|
||||||
@ -963,7 +969,7 @@ void SupportTreeBuildsteps::routing_to_model()
|
|||||||
m_builder.add_bridge(hjp, endp, head.r_back_mm);
|
m_builder.add_bridge(hjp, endp, head.r_back_mm);
|
||||||
m_builder.add_junction(endp, head.r_back_mm);
|
m_builder.add_junction(endp, head.r_back_mm);
|
||||||
|
|
||||||
this->create_ground_pillar(endp, dir, head.r_back_mm);
|
this->create_ground_pillar(endp, dir, head.r_back_mm, head.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
std::vector<unsigned> modelpillars;
|
std::vector<unsigned> modelpillars;
|
||||||
@ -1240,12 +1246,15 @@ void SupportTreeBuildsteps::interconnect_pillars()
|
|||||||
needpillars = 3;
|
needpillars = 3;
|
||||||
else if (pillar().links < 2 && pillar().height > H2) {
|
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;
|
||||||
} 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
needpillars = std::max(pillar().links, needpillars) - pillar().links;
|
||||||
|
if (needpillars == 0) continue;
|
||||||
|
|
||||||
// Search for new pillar locations:
|
// Search for new pillar locations:
|
||||||
|
|
||||||
bool found = false;
|
bool found = false;
|
||||||
@ -1318,6 +1327,7 @@ void SupportTreeBuildsteps::interconnect_pillars()
|
|||||||
|
|
||||||
newpills.emplace_back(pp.id);
|
newpills.emplace_back(pp.id);
|
||||||
m_builder.increment_links(pillar());
|
m_builder.increment_links(pillar());
|
||||||
|
m_builder.increment_links(pp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ long cluster_centroid(const ClusterEl& clust,
|
|||||||
case 2: /* if two elements, there is no center */ return 0;
|
case 2: /* if two elements, there is no center */ return 0;
|
||||||
default: ;
|
default: ;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The function works by calculating for each point the average distance
|
// The function works by calculating for each point the average distance
|
||||||
// from all the other points in the cluster. We create a selector bitmask of
|
// from all the other points in the cluster. We create a selector bitmask of
|
||||||
// the same size as the cluster. The bitmask will have two true bits and
|
// the same size as the cluster. The bitmask will have two true bits and
|
||||||
@ -40,30 +40,30 @@ long cluster_centroid(const ClusterEl& clust,
|
|||||||
// permutations of the bitmask (combinations of two points). Get the
|
// permutations of the bitmask (combinations of two points). Get the
|
||||||
// distance for the two points and add the distance to the averages.
|
// distance for the two points and add the distance to the averages.
|
||||||
// The point with the smallest average than wins.
|
// The point with the smallest average than wins.
|
||||||
|
|
||||||
// The complexity should be O(n^2) but we will mostly apply this function
|
// The complexity should be O(n^2) but we will mostly apply this function
|
||||||
// for small clusters only (cca 3 elements)
|
// for small clusters only (cca 3 elements)
|
||||||
|
|
||||||
std::vector<bool> sel(clust.size(), false); // create full zero bitmask
|
std::vector<bool> sel(clust.size(), false); // create full zero bitmask
|
||||||
std::fill(sel.end() - 2, sel.end(), true); // insert the two ones
|
std::fill(sel.end() - 2, sel.end(), true); // insert the two ones
|
||||||
std::vector<double> avgs(clust.size(), 0.0); // store the average distances
|
std::vector<double> avgs(clust.size(), 0.0); // store the average distances
|
||||||
|
|
||||||
do {
|
do {
|
||||||
std::array<size_t, 2> idx;
|
std::array<size_t, 2> idx;
|
||||||
for(size_t i = 0, j = 0; i < clust.size(); i++) if(sel[i]) idx[j++] = i;
|
for(size_t i = 0, j = 0; i < clust.size(); i++) if(sel[i]) idx[j++] = i;
|
||||||
|
|
||||||
double d = df(pointfn(clust[idx[0]]),
|
double d = df(pointfn(clust[idx[0]]),
|
||||||
pointfn(clust[idx[1]]));
|
pointfn(clust[idx[1]]));
|
||||||
|
|
||||||
// add the distance to the sums for both associated points
|
// add the distance to the sums for both associated points
|
||||||
for(auto i : idx) avgs[i] += d;
|
for(auto i : idx) avgs[i] += d;
|
||||||
|
|
||||||
// now continue with the next permutation of the bitmask with two 1s
|
// now continue with the next permutation of the bitmask with two 1s
|
||||||
} while(std::next_permutation(sel.begin(), sel.end()));
|
} while(std::next_permutation(sel.begin(), sel.end()));
|
||||||
|
|
||||||
// Divide by point size in the cluster to get the average (may be redundant)
|
// Divide by point size in the cluster to get the average (may be redundant)
|
||||||
for(auto& a : avgs) a /= clust.size();
|
for(auto& a : avgs) a /= clust.size();
|
||||||
|
|
||||||
// get the lowest average distance and return the index
|
// get the lowest average distance and return the index
|
||||||
auto minit = std::min_element(avgs.begin(), avgs.end());
|
auto minit = std::min_element(avgs.begin(), avgs.end());
|
||||||
return long(minit - avgs.begin());
|
return long(minit - avgs.begin());
|
||||||
@ -77,45 +77,45 @@ class PillarIndex {
|
|||||||
PointIndex m_index;
|
PointIndex m_index;
|
||||||
using Mutex = ccr::BlockingMutex;
|
using Mutex = ccr::BlockingMutex;
|
||||||
mutable Mutex m_mutex;
|
mutable Mutex m_mutex;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
template<class...Args> inline void guarded_insert(Args&&...args)
|
template<class...Args> inline void guarded_insert(Args&&...args)
|
||||||
{
|
{
|
||||||
std::lock_guard<Mutex> lck(m_mutex);
|
std::lock_guard<Mutex> lck(m_mutex);
|
||||||
m_index.insert(std::forward<Args>(args)...);
|
m_index.insert(std::forward<Args>(args)...);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class...Args>
|
template<class...Args>
|
||||||
inline std::vector<PointIndexEl> guarded_query(Args&&...args) const
|
inline std::vector<PointIndexEl> guarded_query(Args&&...args) const
|
||||||
{
|
{
|
||||||
std::lock_guard<Mutex> lck(m_mutex);
|
std::lock_guard<Mutex> lck(m_mutex);
|
||||||
return m_index.query(std::forward<Args>(args)...);
|
return m_index.query(std::forward<Args>(args)...);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class...Args> inline void insert(Args&&...args)
|
template<class...Args> inline void insert(Args&&...args)
|
||||||
{
|
{
|
||||||
m_index.insert(std::forward<Args>(args)...);
|
m_index.insert(std::forward<Args>(args)...);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class...Args>
|
template<class...Args>
|
||||||
inline std::vector<PointIndexEl> query(Args&&...args) const
|
inline std::vector<PointIndexEl> query(Args&&...args) const
|
||||||
{
|
{
|
||||||
return m_index.query(std::forward<Args>(args)...);
|
return m_index.query(std::forward<Args>(args)...);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class Fn> inline void foreach(Fn fn) { m_index.foreach(fn); }
|
template<class Fn> inline void foreach(Fn fn) { m_index.foreach(fn); }
|
||||||
template<class Fn> inline void guarded_foreach(Fn fn)
|
template<class Fn> inline void guarded_foreach(Fn fn)
|
||||||
{
|
{
|
||||||
std::lock_guard<Mutex> lck(m_mutex);
|
std::lock_guard<Mutex> lck(m_mutex);
|
||||||
m_index.foreach(fn);
|
m_index.foreach(fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
PointIndex guarded_clone()
|
PointIndex guarded_clone()
|
||||||
{
|
{
|
||||||
std::lock_guard<Mutex> lck(m_mutex);
|
std::lock_guard<Mutex> lck(m_mutex);
|
||||||
return m_index;
|
return m_index;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Helper function for pillar interconnection where pairs of already connected
|
// Helper function for pillar interconnection where pairs of already connected
|
||||||
@ -132,13 +132,13 @@ IntegerOnly<DoubleI> pairhash(I a, I b)
|
|||||||
static const auto constexpr Ibits = int(sizeof(I) * CHAR_BIT);
|
static const auto constexpr Ibits = int(sizeof(I) * CHAR_BIT);
|
||||||
static const auto constexpr DoubleIbits = int(sizeof(DoubleI) * CHAR_BIT);
|
static const auto constexpr DoubleIbits = int(sizeof(DoubleI) * CHAR_BIT);
|
||||||
static const auto constexpr shift = DoubleIbits / 2 < Ibits ? Ibits / 2 : Ibits;
|
static const auto constexpr shift = DoubleIbits / 2 < Ibits ? Ibits / 2 : Ibits;
|
||||||
|
|
||||||
I g = min(a, b), l = max(a, b);
|
I g = min(a, b), l = max(a, b);
|
||||||
|
|
||||||
// Assume the hash will fit into the output variable
|
// Assume the hash will fit into the output variable
|
||||||
assert((g ? (ceil(log2(g))) : 0) < shift);
|
assert((g ? (ceil(log2(g))) : 0) <= shift);
|
||||||
assert((l ? (ceil(log2(l))) : 0) < shift);
|
assert((l ? (ceil(log2(l))) : 0) <= shift);
|
||||||
|
|
||||||
return (DoubleI(g) << shift) + l;
|
return (DoubleI(g) << shift) + l;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,42 +146,45 @@ class SupportTreeBuildsteps {
|
|||||||
const SupportConfig& m_cfg;
|
const SupportConfig& m_cfg;
|
||||||
const EigenMesh3D& m_mesh;
|
const EigenMesh3D& m_mesh;
|
||||||
const std::vector<SupportPoint>& m_support_pts;
|
const std::vector<SupportPoint>& m_support_pts;
|
||||||
|
|
||||||
using PtIndices = std::vector<unsigned>;
|
using PtIndices = std::vector<unsigned>;
|
||||||
|
|
||||||
PtIndices m_iheads; // support points with pinhead
|
PtIndices m_iheads; // support points with pinhead
|
||||||
PtIndices m_iheadless; // headless support points
|
PtIndices m_iheadless; // headless support points
|
||||||
|
|
||||||
// supp. pts. connecting to model: point index and the ray hit data
|
// supp. pts. connecting to model: point index and the ray hit data
|
||||||
std::vector<std::pair<unsigned, EigenMesh3D::hit_result>> m_iheads_onmodel;
|
std::vector<std::pair<unsigned, EigenMesh3D::hit_result>> m_iheads_onmodel;
|
||||||
|
|
||||||
// normals for support points from model faces.
|
// normals for support points from model faces.
|
||||||
PointSet m_support_nmls;
|
PointSet m_support_nmls;
|
||||||
|
|
||||||
// Clusters of points which can reach the ground directly and can be
|
// Clusters of points which can reach the ground directly and can be
|
||||||
// bridged to one central pillar
|
// bridged to one central pillar
|
||||||
std::vector<PtIndices> m_pillar_clusters;
|
std::vector<PtIndices> m_pillar_clusters;
|
||||||
|
|
||||||
// This algorithm uses the SupportTreeBuilder class to fill gradually
|
// This algorithm uses the SupportTreeBuilder class to fill gradually
|
||||||
// the support elements (heads, pillars, bridges, ...)
|
// the support elements (heads, pillars, bridges, ...)
|
||||||
SupportTreeBuilder& m_builder;
|
SupportTreeBuilder& m_builder;
|
||||||
|
|
||||||
// support points in Eigen/IGL format
|
// support points in Eigen/IGL format
|
||||||
PointSet m_points;
|
PointSet m_points;
|
||||||
|
|
||||||
// throw if canceled: It will be called many times so a shorthand will
|
// throw if canceled: It will be called many times so a shorthand will
|
||||||
// come in handy.
|
// come in handy.
|
||||||
ThrowOnCancel m_thr;
|
ThrowOnCancel m_thr;
|
||||||
|
|
||||||
// A spatial index to easily find strong pillars to connect to.
|
// A spatial index to easily find strong pillars to connect to.
|
||||||
PillarIndex m_pillar_index;
|
PillarIndex m_pillar_index;
|
||||||
|
|
||||||
|
// When bridging heads to pillars... TODO: find a cleaner solution
|
||||||
|
ccr::BlockingMutex m_bridge_mutex;
|
||||||
|
|
||||||
inline double ray_mesh_intersect(const Vec3d& s,
|
inline double ray_mesh_intersect(const Vec3d& s,
|
||||||
const Vec3d& dir)
|
const Vec3d& dir)
|
||||||
{
|
{
|
||||||
return m_mesh.query_ray_hit(s, dir).distance();
|
return m_mesh.query_ray_hit(s, dir).distance();
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function will test if a future pinhead would not collide with the
|
// 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
|
// model geometry. It does not take a 'Head' object because those are
|
||||||
// created after this test. Parameters: s: The touching point on the model
|
// created after this test. Parameters: s: The touching point on the model
|
||||||
@ -199,7 +202,7 @@ class SupportTreeBuildsteps {
|
|||||||
double r_pin,
|
double r_pin,
|
||||||
double r_back,
|
double r_back,
|
||||||
double width);
|
double width);
|
||||||
|
|
||||||
// Checking bridge (pillar and stick as well) intersection with the model.
|
// Checking bridge (pillar and stick as well) intersection with the model.
|
||||||
// If the function is used for headless sticks, the ins_check parameter
|
// 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
|
// have to be true as the beginning of the stick might be inside the model
|
||||||
@ -213,37 +216,37 @@ class SupportTreeBuildsteps {
|
|||||||
const Vec3d& dir,
|
const Vec3d& dir,
|
||||||
double r,
|
double r,
|
||||||
bool ins_check = false);
|
bool ins_check = false);
|
||||||
|
|
||||||
// Helper function for interconnecting two pillars with zig-zag bridges.
|
// Helper function for interconnecting two pillars with zig-zag bridges.
|
||||||
bool interconnect(const Pillar& pillar, const Pillar& nextpillar);
|
bool interconnect(const Pillar& pillar, const Pillar& nextpillar);
|
||||||
|
|
||||||
// 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);
|
||||||
|
|
||||||
bool search_pillar_and_connect(const Head& head);
|
bool search_pillar_and_connect(const Head& head);
|
||||||
|
|
||||||
// This is a proxy function for pillar creation which will mind the gap
|
// This is a proxy function for pillar creation which will mind the gap
|
||||||
// between the pad and the model bottom in zero elevation mode.
|
// between the pad and the model bottom in zero elevation mode.
|
||||||
void create_ground_pillar(const Vec3d &jp,
|
void create_ground_pillar(const Vec3d &jp,
|
||||||
const Vec3d &sourcedir,
|
const Vec3d &sourcedir,
|
||||||
double radius,
|
double radius,
|
||||||
long head_id = ID_UNSET);
|
long head_id = ID_UNSET);
|
||||||
public:
|
public:
|
||||||
SupportTreeBuildsteps(SupportTreeBuilder & builder, const SupportableMesh &sm);
|
SupportTreeBuildsteps(SupportTreeBuilder & builder, const SupportableMesh &sm);
|
||||||
|
|
||||||
// Now let's define the individual steps of the support generation algorithm
|
// Now let's define the individual steps of the support generation algorithm
|
||||||
|
|
||||||
// Filtering step: here we will discard inappropriate support points
|
// Filtering step: here we will discard inappropriate support points
|
||||||
// and decide the future of the appropriate ones. We will check if a
|
// 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
|
// 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
|
// will also merge the support points that are just too close and can
|
||||||
// be considered as one.
|
// be considered as one.
|
||||||
void filter();
|
void filter();
|
||||||
|
|
||||||
// Pinhead creation: based on the filtering results, the Head objects
|
// Pinhead creation: based on the filtering results, the Head objects
|
||||||
// will be constructed (together with their triangle meshes).
|
// will be constructed (together with their triangle meshes).
|
||||||
void add_pinheads();
|
void add_pinheads();
|
||||||
|
|
||||||
// Further classification of the support points with pinheads. If the
|
// Further classification of the support points with pinheads. If the
|
||||||
// ground is directly reachable through a vertical line parallel to the
|
// ground is directly reachable through a vertical line parallel to the
|
||||||
// Z axis we consider a support point as pillar candidate. If touches
|
// Z axis we consider a support point as pillar candidate. If touches
|
||||||
@ -253,28 +256,28 @@ public:
|
|||||||
// these groups may or may not be interconnected. Here we only run the
|
// these groups may or may not be interconnected. Here we only run the
|
||||||
// clustering algorithm.
|
// clustering algorithm.
|
||||||
void classify();
|
void classify();
|
||||||
|
|
||||||
// Step: Routing the ground connected pinheads, and interconnecting
|
// Step: Routing the ground connected pinheads, and interconnecting
|
||||||
// them with additional (angled) bridges. Not all of these pinheads
|
// them with additional (angled) bridges. Not all of these pinheads
|
||||||
// will be a full pillar (ground connected). Some will connect to a
|
// 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
|
// nearby pillar using a bridge. The max number of such side-heads for
|
||||||
// a central pillar is limited to avoid bad weight distribution.
|
// a central pillar is limited to avoid bad weight distribution.
|
||||||
void routing_to_ground();
|
void routing_to_ground();
|
||||||
|
|
||||||
// Step: routing the pinheads that would connect to the model surface
|
// Step: routing the pinheads that would connect to the model surface
|
||||||
// along the Z axis downwards. For now these will actually be connected with
|
// 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
|
// 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
|
// some smart algorithms to search for a safe path to the ground or to a
|
||||||
// nearby pillar that can hold the supported weight.
|
// nearby pillar that can hold the supported weight.
|
||||||
void routing_to_model();
|
void routing_to_model();
|
||||||
|
|
||||||
void interconnect_pillars();
|
void interconnect_pillars();
|
||||||
|
|
||||||
// Step: process the support points where there is not enough space for a
|
// 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
|
// 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).
|
// point and use a thinner bridge (let's call it a stick).
|
||||||
void routing_headless ();
|
void routing_headless ();
|
||||||
|
|
||||||
inline void merge_result() { m_builder.merged_mesh(); }
|
inline void merge_result() { m_builder.merged_mesh(); }
|
||||||
|
|
||||||
static bool execute(SupportTreeBuilder & builder, const SupportableMesh &sm);
|
static bool execute(SupportTreeBuilder & builder, const SupportableMesh &sm);
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
#include <system_error>
|
||||||
|
|
||||||
#include "libslic3r.h"
|
#include "libslic3r.h"
|
||||||
|
|
||||||
|
@ -7,7 +7,10 @@ set(TEST_DATA_DIR ${CMAKE_CURRENT_SOURCE_DIR}/data)
|
|||||||
file(TO_NATIVE_PATH "${TEST_DATA_DIR}" TEST_DATA_DIR)
|
file(TO_NATIVE_PATH "${TEST_DATA_DIR}" TEST_DATA_DIR)
|
||||||
|
|
||||||
add_library(test_common INTERFACE)
|
add_library(test_common INTERFACE)
|
||||||
target_compile_definitions(test_common INTERFACE TEST_DATA_DIR="${TEST_DATA_DIR}")
|
target_compile_definitions(test_common INTERFACE TEST_DATA_DIR=R"\(${TEST_DATA_DIR}\)")
|
||||||
target_link_libraries(test_common INTERFACE GTest::GTest GTest::Main)
|
target_link_libraries(test_common INTERFACE GTest::GTest GTest::Main)
|
||||||
|
if (APPLE)
|
||||||
|
target_link_libraries(test_common INTERFACE "-liconv -framework IOKit" "-framework CoreFoundation" -lc++)
|
||||||
|
endif()
|
||||||
|
|
||||||
add_subdirectory(sla_print)
|
add_subdirectory(sla_print)
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
#include <map>
|
#include <unordered_set>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <random>
|
||||||
|
|
||||||
// Debug
|
// Debug
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
@ -18,10 +20,10 @@
|
|||||||
|
|
||||||
#include "libslic3r/SVG.hpp"
|
#include "libslic3r/SVG.hpp"
|
||||||
|
|
||||||
#if defined(WIN32) || defined(_WIN32)
|
#if defined(WIN32) || defined(_WIN32)
|
||||||
#define PATH_SEPARATOR "\\"
|
#define PATH_SEPARATOR R"(\)"
|
||||||
#else
|
#else
|
||||||
#define PATH_SEPARATOR "/"
|
#define PATH_SEPARATOR R"(/)"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@ -30,7 +32,7 @@ using namespace Slic3r;
|
|||||||
TriangleMesh load_model(const std::string &obj_filename)
|
TriangleMesh load_model(const std::string &obj_filename)
|
||||||
{
|
{
|
||||||
TriangleMesh mesh;
|
TriangleMesh mesh;
|
||||||
auto fpath = std::string(TEST_DATA_DIR PATH_SEPARATOR) + obj_filename;
|
auto fpath = TEST_DATA_DIR PATH_SEPARATOR + obj_filename;
|
||||||
load_obj(fpath.c_str(), &mesh);
|
load_obj(fpath.c_str(), &mesh);
|
||||||
return mesh;
|
return mesh;
|
||||||
}
|
}
|
||||||
@ -46,21 +48,21 @@ void check_validity(const TriangleMesh &input_mesh,
|
|||||||
ASSUME_NO_REPAIR)
|
ASSUME_NO_REPAIR)
|
||||||
{
|
{
|
||||||
TriangleMesh mesh{input_mesh};
|
TriangleMesh mesh{input_mesh};
|
||||||
|
|
||||||
if (flags & ASSUME_NO_EMPTY) {
|
if (flags & ASSUME_NO_EMPTY) {
|
||||||
ASSERT_FALSE(mesh.empty());
|
ASSERT_FALSE(mesh.empty());
|
||||||
} else if (mesh.empty())
|
} else if (mesh.empty())
|
||||||
return; // If it can be empty and it is, there is nothing left to do.
|
return; // If it can be empty and it is, there is nothing left to do.
|
||||||
|
|
||||||
ASSERT_TRUE(stl_validate(&mesh.stl));
|
ASSERT_TRUE(stl_validate(&mesh.stl));
|
||||||
|
|
||||||
bool do_update_shared_vertices = false;
|
bool do_update_shared_vertices = false;
|
||||||
mesh.repair(do_update_shared_vertices);
|
mesh.repair(do_update_shared_vertices);
|
||||||
|
|
||||||
if (flags & ASSUME_NO_REPAIR) {
|
if (flags & ASSUME_NO_REPAIR) {
|
||||||
ASSERT_FALSE(mesh.needed_repair());
|
ASSERT_FALSE(mesh.needed_repair());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (flags & ASSUME_MANIFOLD) {
|
if (flags & ASSUME_MANIFOLD) {
|
||||||
mesh.require_shared_vertices();
|
mesh.require_shared_vertices();
|
||||||
if (!mesh.is_manifold()) mesh.WriteOBJFile("non_manifold.obj");
|
if (!mesh.is_manifold()) mesh.WriteOBJFile("non_manifold.obj");
|
||||||
@ -80,21 +82,21 @@ void test_pad(const std::string & obj_filename,
|
|||||||
PadByproducts & out)
|
PadByproducts & out)
|
||||||
{
|
{
|
||||||
ASSERT_TRUE(padcfg.validate().empty());
|
ASSERT_TRUE(padcfg.validate().empty());
|
||||||
|
|
||||||
TriangleMesh mesh = load_model(obj_filename);
|
TriangleMesh mesh = load_model(obj_filename);
|
||||||
|
|
||||||
ASSERT_FALSE(mesh.empty());
|
ASSERT_FALSE(mesh.empty());
|
||||||
|
|
||||||
// Create pad skeleton only from the model
|
// Create pad skeleton only from the model
|
||||||
Slic3r::sla::pad_blueprint(mesh, out.model_contours);
|
Slic3r::sla::pad_blueprint(mesh, out.model_contours);
|
||||||
|
|
||||||
ASSERT_FALSE(out.model_contours.empty());
|
ASSERT_FALSE(out.model_contours.empty());
|
||||||
|
|
||||||
// Create the pad geometry for the model contours only
|
// Create the pad geometry for the model contours only
|
||||||
Slic3r::sla::create_pad({}, out.model_contours, out.mesh, padcfg);
|
Slic3r::sla::create_pad({}, out.model_contours, out.mesh, padcfg);
|
||||||
|
|
||||||
check_validity(out.mesh);
|
check_validity(out.mesh);
|
||||||
|
|
||||||
auto bb = out.mesh.bounding_box();
|
auto bb = out.mesh.bounding_box();
|
||||||
ASSERT_DOUBLE_EQ(bb.max.z() - bb.min.z(),
|
ASSERT_DOUBLE_EQ(bb.max.z() - bb.min.z(),
|
||||||
padcfg.full_height());
|
padcfg.full_height());
|
||||||
@ -122,19 +124,25 @@ void check_support_tree_integrity(const sla::SupportTreeBuilder &stree,
|
|||||||
double gnd = stree.ground_level;
|
double gnd = stree.ground_level;
|
||||||
double H1 = cfg.max_solo_pillar_height_mm;
|
double H1 = cfg.max_solo_pillar_height_mm;
|
||||||
double H2 = cfg.max_dual_pillar_height_mm;
|
double H2 = cfg.max_dual_pillar_height_mm;
|
||||||
|
|
||||||
|
for (const sla::Head &head : stree.heads()) {
|
||||||
|
ASSERT_TRUE(!head.is_valid() ||
|
||||||
|
head.pillar_id != sla::ID_UNSET ||
|
||||||
|
head.bridge_id != sla::ID_UNSET);
|
||||||
|
}
|
||||||
|
|
||||||
for (const sla::Pillar &pillar : stree.pillars()) {
|
for (const sla::Pillar &pillar : stree.pillars()) {
|
||||||
if (std::abs(pillar.endpoint().z() - gnd) < EPSILON) {
|
if (std::abs(pillar.endpoint().z() - gnd) < EPSILON) {
|
||||||
double h = pillar.height;
|
double h = pillar.height;
|
||||||
|
|
||||||
if (h > H1) ASSERT_GE(pillar.links, 1);
|
if (h > H1) ASSERT_GE(pillar.links, 1);
|
||||||
else if(h > H2) { ASSERT_GE(pillar.links, 2); }
|
else if(h > H2) { ASSERT_GE(pillar.links, 2); }
|
||||||
}
|
}
|
||||||
|
|
||||||
ASSERT_LE(pillar.links, cfg.pillar_cascade_neighbors);
|
ASSERT_LE(pillar.links, cfg.pillar_cascade_neighbors);
|
||||||
ASSERT_LE(pillar.bridges, cfg.max_bridges_on_pillar);
|
ASSERT_LE(pillar.bridges, cfg.max_bridges_on_pillar);
|
||||||
}
|
}
|
||||||
|
|
||||||
double max_bridgelen = 0.;
|
double max_bridgelen = 0.;
|
||||||
auto chck_bridge = [&cfg](const sla::Bridge &bridge, double &max_brlen) {
|
auto chck_bridge = [&cfg](const sla::Bridge &bridge, double &max_brlen) {
|
||||||
Vec3d n = bridge.endp - bridge.startp;
|
Vec3d n = bridge.endp - bridge.startp;
|
||||||
@ -144,15 +152,15 @@ void check_support_tree_integrity(const sla::SupportTreeBuilder &stree,
|
|||||||
double z = n.z();
|
double z = n.z();
|
||||||
double polar = std::acos(z / d);
|
double polar = std::acos(z / d);
|
||||||
double slope = -polar + PI / 2.;
|
double slope = -polar + PI / 2.;
|
||||||
ASSERT_TRUE(slope >= cfg.bridge_slope || slope <= -cfg.bridge_slope);
|
ASSERT_GE(std::abs(slope), cfg.bridge_slope - EPSILON);
|
||||||
};
|
};
|
||||||
|
|
||||||
for (auto &bridge : stree.bridges()) chck_bridge(bridge, max_bridgelen);
|
for (auto &bridge : stree.bridges()) chck_bridge(bridge, max_bridgelen);
|
||||||
ASSERT_LE(max_bridgelen, cfg.max_bridge_length_mm);
|
ASSERT_LE(max_bridgelen, cfg.max_bridge_length_mm);
|
||||||
|
|
||||||
max_bridgelen = 0;
|
max_bridgelen = 0;
|
||||||
for (auto &bridge : stree.crossbridges()) chck_bridge(bridge, max_bridgelen);
|
for (auto &bridge : stree.crossbridges()) chck_bridge(bridge, max_bridgelen);
|
||||||
|
|
||||||
double md = cfg.max_pillar_link_distance_mm / std::cos(-cfg.bridge_slope);
|
double md = cfg.max_pillar_link_distance_mm / std::cos(-cfg.bridge_slope);
|
||||||
ASSERT_LE(max_bridgelen, md);
|
ASSERT_LE(max_bridgelen, md);
|
||||||
}
|
}
|
||||||
@ -163,9 +171,9 @@ void test_supports(const std::string & obj_filename,
|
|||||||
{
|
{
|
||||||
using namespace Slic3r;
|
using namespace Slic3r;
|
||||||
TriangleMesh mesh = load_model(obj_filename);
|
TriangleMesh mesh = load_model(obj_filename);
|
||||||
|
|
||||||
ASSERT_FALSE(mesh.empty());
|
ASSERT_FALSE(mesh.empty());
|
||||||
|
|
||||||
TriangleMeshSlicer slicer{&mesh};
|
TriangleMeshSlicer slicer{&mesh};
|
||||||
|
|
||||||
auto bb = mesh.bounding_box();
|
auto bb = mesh.bounding_box();
|
||||||
@ -176,50 +184,50 @@ void test_supports(const std::string & obj_filename,
|
|||||||
|
|
||||||
out.slicegrid = grid(float(gnd), float(zmax), layer_h);
|
out.slicegrid = grid(float(gnd), float(zmax), layer_h);
|
||||||
slicer.slice(out.slicegrid , CLOSING_RADIUS, &out.model_slices, []{});
|
slicer.slice(out.slicegrid , CLOSING_RADIUS, &out.model_slices, []{});
|
||||||
|
|
||||||
// Create the special index-triangle mesh with spatial indexing which
|
// Create the special index-triangle mesh with spatial indexing which
|
||||||
// is the input of the support point and support mesh generators
|
// is the input of the support point and support mesh generators
|
||||||
sla::EigenMesh3D emesh{mesh};
|
sla::EigenMesh3D emesh{mesh};
|
||||||
|
|
||||||
// Create the support point generator
|
// Create the support point generator
|
||||||
sla::SLAAutoSupports::Config autogencfg;
|
sla::SLAAutoSupports::Config autogencfg;
|
||||||
autogencfg.head_diameter = float(2 * supportcfg.head_front_radius_mm);
|
autogencfg.head_diameter = float(2 * supportcfg.head_front_radius_mm);
|
||||||
sla::SLAAutoSupports point_gen{emesh, out.model_slices, out.slicegrid,
|
sla::SLAAutoSupports point_gen{emesh, out.model_slices, out.slicegrid,
|
||||||
autogencfg, [] {}, [](int) {}};
|
autogencfg, [] {}, [](int) {}};
|
||||||
|
|
||||||
// Get the calculated support points.
|
// Get the calculated support points.
|
||||||
std::vector<sla::SupportPoint> support_points = point_gen.output();
|
std::vector<sla::SupportPoint> support_points = point_gen.output();
|
||||||
|
|
||||||
int validityflags = ASSUME_NO_REPAIR;
|
int validityflags = ASSUME_NO_REPAIR;
|
||||||
|
|
||||||
// If there is no elevation, support points shall be removed from the
|
// If there is no elevation, support points shall be removed from the
|
||||||
// bottom of the object.
|
// bottom of the object.
|
||||||
if (supportcfg.object_elevation_mm < EPSILON) {
|
if (supportcfg.object_elevation_mm < EPSILON) {
|
||||||
sla::remove_bottom_points(support_points, zmin,
|
sla::remove_bottom_points(support_points, zmin,
|
||||||
supportcfg.base_height_mm);
|
supportcfg.base_height_mm);
|
||||||
} else {
|
} else {
|
||||||
// Should be support points at least on the bottom of the model
|
// Should be support points at least on the bottom of the model
|
||||||
ASSERT_FALSE(support_points.empty());
|
ASSERT_FALSE(support_points.empty());
|
||||||
|
|
||||||
// Also the support mesh should not be empty.
|
// Also the support mesh should not be empty.
|
||||||
validityflags |= ASSUME_NO_EMPTY;
|
validityflags |= ASSUME_NO_EMPTY;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate the actual support tree
|
// Generate the actual support tree
|
||||||
sla::SupportTreeBuilder treebuilder;
|
sla::SupportTreeBuilder treebuilder;
|
||||||
treebuilder.build(sla::SupportableMesh{emesh, support_points, supportcfg});
|
treebuilder.build(sla::SupportableMesh{emesh, support_points, supportcfg});
|
||||||
|
|
||||||
check_support_tree_integrity(treebuilder, supportcfg);
|
check_support_tree_integrity(treebuilder, supportcfg);
|
||||||
|
|
||||||
const TriangleMesh &output_mesh = treebuilder.retrieve_mesh();
|
const TriangleMesh &output_mesh = treebuilder.retrieve_mesh();
|
||||||
|
|
||||||
check_validity(output_mesh, validityflags);
|
check_validity(output_mesh, validityflags);
|
||||||
|
|
||||||
// Quick check if the dimensions and placement of supports are correct
|
// Quick check if the dimensions and placement of supports are correct
|
||||||
auto obb = output_mesh.bounding_box();
|
auto obb = output_mesh.bounding_box();
|
||||||
ASSERT_DOUBLE_EQ(obb.min.z(), zmin - supportcfg.object_elevation_mm);
|
ASSERT_DOUBLE_EQ(obb.min.z(), zmin - supportcfg.object_elevation_mm);
|
||||||
ASSERT_LE(obb.max.z(), zmax);
|
ASSERT_LE(obb.max.z(), zmax);
|
||||||
|
|
||||||
// Move out the support tree into the byproducts, we can examine it further
|
// Move out the support tree into the byproducts, we can examine it further
|
||||||
// in various tests.
|
// in various tests.
|
||||||
out.supporttree = std::move(treebuilder);
|
out.supporttree = std::move(treebuilder);
|
||||||
@ -237,31 +245,31 @@ void test_support_model_collision(
|
|||||||
const sla::SupportConfig &input_supportcfg = {})
|
const sla::SupportConfig &input_supportcfg = {})
|
||||||
{
|
{
|
||||||
SupportByproducts byproducts;
|
SupportByproducts byproducts;
|
||||||
|
|
||||||
sla::SupportConfig supportcfg = input_supportcfg;
|
sla::SupportConfig supportcfg = input_supportcfg;
|
||||||
|
|
||||||
// Set head penetration to a small negative value which should ensure that
|
// Set head penetration to a small negative value which should ensure that
|
||||||
// the supports will not touch the model body.
|
// the supports will not touch the model body.
|
||||||
supportcfg.head_penetration_mm = -0.1;
|
supportcfg.head_penetration_mm = -0.1;
|
||||||
|
|
||||||
test_supports(obj_filename, supportcfg, byproducts);
|
test_supports(obj_filename, supportcfg, byproducts);
|
||||||
|
|
||||||
// Slice the support mesh given the slice grid of the model.
|
// Slice the support mesh given the slice grid of the model.
|
||||||
std::vector<ExPolygons> support_slices =
|
std::vector<ExPolygons> support_slices =
|
||||||
byproducts.supporttree.slice(byproducts.slicegrid, CLOSING_RADIUS);
|
byproducts.supporttree.slice(byproducts.slicegrid, CLOSING_RADIUS);
|
||||||
|
|
||||||
// The slices originate from the same slice grid so the numbers must match
|
// The slices originate from the same slice grid so the numbers must match
|
||||||
ASSERT_EQ(support_slices.size(), byproducts.model_slices.size());
|
ASSERT_EQ(support_slices.size(), byproducts.model_slices.size());
|
||||||
|
|
||||||
bool notouch = true;
|
bool notouch = true;
|
||||||
for (size_t n = 0; notouch && n < support_slices.size(); ++n) {
|
for (size_t n = 0; notouch && n < support_slices.size(); ++n) {
|
||||||
const ExPolygons &sup_slice = support_slices[n];
|
const ExPolygons &sup_slice = support_slices[n];
|
||||||
const ExPolygons &mod_slice = byproducts.model_slices[n];
|
const ExPolygons &mod_slice = byproducts.model_slices[n];
|
||||||
|
|
||||||
Polygons intersections = intersection(sup_slice, mod_slice);
|
Polygons intersections = intersection(sup_slice, mod_slice);
|
||||||
notouch = notouch && intersections.empty();
|
notouch = notouch && intersections.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
ASSERT_TRUE(notouch);
|
ASSERT_TRUE(notouch);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -283,25 +291,52 @@ const char *const SUPPORT_TEST_MODELS[] = {
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
// Test pair hash for 'nums' random number pairs.
|
||||||
template <class I, class II> void test_pairhash()
|
template <class I, class II> void test_pairhash()
|
||||||
{
|
{
|
||||||
std::map<II, std::pair<I, I> > ints;
|
const constexpr size_t nums = 1000;
|
||||||
for (I i = 0; i < 1000; ++i)
|
I A[nums] = {0}, B[nums] = {0};
|
||||||
for (I j = 0; j < 1000; ++j) {
|
std::unordered_set<I> CH;
|
||||||
if (j != i) {
|
std::unordered_map<II, std::pair<I, I>> ints;
|
||||||
II hash_ij = sla::pairhash<I, II>(i, j);
|
|
||||||
II hash_ji = sla::pairhash<I, II>(j, i);
|
std::random_device rd;
|
||||||
ASSERT_EQ(hash_ij, hash_ji);
|
std::mt19937 gen(rd());
|
||||||
|
|
||||||
auto it = ints.find(hash_ij);
|
const I Ibits = int(sizeof(I) * CHAR_BIT);
|
||||||
|
const II IIbits = int(sizeof(II) * CHAR_BIT);
|
||||||
if (it != ints.end()) {
|
const int bits = IIbits / 2 < Ibits ? Ibits / 2 : Ibits;
|
||||||
ASSERT_TRUE(
|
|
||||||
(it->second.first == i && it->second.second == j) ||
|
const I Imax = I(std::pow(2., bits) - 1);
|
||||||
(it->second.first == j && it->second.second == i));
|
std::uniform_int_distribution<I> dis(0, Imax);
|
||||||
} else ints[hash_ij] = std::make_pair(i, j);
|
|
||||||
}
|
for (size_t i = 0; i < nums;) {
|
||||||
}
|
I a = dis(gen);
|
||||||
|
if (CH.find(a) == CH.end()) { CH.insert(a); A[i] = a; ++i; }
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < nums;) {
|
||||||
|
I b = dis(gen);
|
||||||
|
if (CH.find(b) == CH.end()) { CH.insert(b); B[i] = b; ++i; }
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < nums; ++i) {
|
||||||
|
I a = A[i], b = B[i];
|
||||||
|
|
||||||
|
ASSERT_TRUE(a != b);
|
||||||
|
|
||||||
|
II hash_ab = sla::pairhash<I, II>(a, b);
|
||||||
|
II hash_ba = sla::pairhash<I, II>(b, a);
|
||||||
|
ASSERT_EQ(hash_ab, hash_ba);
|
||||||
|
|
||||||
|
auto it = ints.find(hash_ab);
|
||||||
|
|
||||||
|
if (it != ints.end()) {
|
||||||
|
ASSERT_TRUE(
|
||||||
|
(it->second.first == a && it->second.second == b) ||
|
||||||
|
(it->second.first == b && it->second.second == a));
|
||||||
|
} else
|
||||||
|
ints[hash_ab] = std::make_pair(a, b);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(SLASupportGeneration, PillarPairHashShouldBeUnique) {
|
TEST(SLASupportGeneration, PillarPairHashShouldBeUnique) {
|
||||||
@ -312,61 +347,61 @@ TEST(SLASupportGeneration, PillarPairHashShouldBeUnique) {
|
|||||||
|
|
||||||
TEST(SLASupportGeneration, FlatPadGeometryIsValid) {
|
TEST(SLASupportGeneration, FlatPadGeometryIsValid) {
|
||||||
sla::PadConfig padcfg;
|
sla::PadConfig padcfg;
|
||||||
|
|
||||||
// Disable wings
|
// Disable wings
|
||||||
padcfg.wall_height_mm = .0;
|
padcfg.wall_height_mm = .0;
|
||||||
|
|
||||||
for (auto &fname : BELOW_PAD_TEST_OBJECTS) test_pad(fname, padcfg);
|
for (auto &fname : BELOW_PAD_TEST_OBJECTS) test_pad(fname, padcfg);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(SLASupportGeneration, WingedPadGeometryIsValid) {
|
TEST(SLASupportGeneration, WingedPadGeometryIsValid) {
|
||||||
sla::PadConfig padcfg;
|
sla::PadConfig padcfg;
|
||||||
|
|
||||||
// Add some wings to the pad to test the cavity
|
// Add some wings to the pad to test the cavity
|
||||||
padcfg.wall_height_mm = 1.;
|
padcfg.wall_height_mm = 1.;
|
||||||
|
|
||||||
for (auto &fname : BELOW_PAD_TEST_OBJECTS) test_pad(fname, padcfg);
|
for (auto &fname : BELOW_PAD_TEST_OBJECTS) test_pad(fname, padcfg);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(SLASupportGeneration, FlatPadAroundObjectIsValid) {
|
TEST(SLASupportGeneration, FlatPadAroundObjectIsValid) {
|
||||||
sla::PadConfig padcfg;
|
sla::PadConfig padcfg;
|
||||||
|
|
||||||
// Add some wings to the pad to test the cavity
|
// Add some wings to the pad to test the cavity
|
||||||
padcfg.wall_height_mm = 0.;
|
padcfg.wall_height_mm = 0.;
|
||||||
// padcfg.embed_object.stick_stride_mm = 0.;
|
// padcfg.embed_object.stick_stride_mm = 0.;
|
||||||
padcfg.embed_object.enabled = true;
|
padcfg.embed_object.enabled = true;
|
||||||
padcfg.embed_object.everywhere = true;
|
padcfg.embed_object.everywhere = true;
|
||||||
|
|
||||||
for (auto &fname : AROUND_PAD_TEST_OBJECTS) test_pad(fname, padcfg);
|
for (auto &fname : AROUND_PAD_TEST_OBJECTS) test_pad(fname, padcfg);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(SLASupportGeneration, WingedPadAroundObjectIsValid) {
|
TEST(SLASupportGeneration, WingedPadAroundObjectIsValid) {
|
||||||
sla::PadConfig padcfg;
|
sla::PadConfig padcfg;
|
||||||
|
|
||||||
// Add some wings to the pad to test the cavity
|
// Add some wings to the pad to test the cavity
|
||||||
padcfg.wall_height_mm = 1.;
|
padcfg.wall_height_mm = 1.;
|
||||||
padcfg.embed_object.enabled = true;
|
padcfg.embed_object.enabled = true;
|
||||||
padcfg.embed_object.everywhere = true;
|
padcfg.embed_object.everywhere = true;
|
||||||
|
|
||||||
for (auto &fname : AROUND_PAD_TEST_OBJECTS) test_pad(fname, padcfg);
|
for (auto &fname : AROUND_PAD_TEST_OBJECTS) test_pad(fname, padcfg);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(SLASupportGeneration, ElevatedSupportGeometryIsValid) {
|
TEST(SLASupportGeneration, ElevatedSupportGeometryIsValid) {
|
||||||
sla::SupportConfig supportcfg;
|
sla::SupportConfig supportcfg;
|
||||||
supportcfg.object_elevation_mm = 5.;
|
supportcfg.object_elevation_mm = 5.;
|
||||||
|
|
||||||
for (auto fname : SUPPORT_TEST_MODELS) test_supports(fname);
|
for (auto fname : SUPPORT_TEST_MODELS) test_supports(fname);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(SLASupportGeneration, FloorSupportGeometryIsValid) {
|
TEST(SLASupportGeneration, FloorSupportGeometryIsValid) {
|
||||||
sla::SupportConfig supportcfg;
|
sla::SupportConfig supportcfg;
|
||||||
supportcfg.object_elevation_mm = 0;
|
supportcfg.object_elevation_mm = 0;
|
||||||
|
|
||||||
for (auto &fname: SUPPORT_TEST_MODELS) test_supports(fname, supportcfg);
|
for (auto &fname: SUPPORT_TEST_MODELS) test_supports(fname, supportcfg);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(SLASupportGeneration, SupportsDoNotPierceModel) {
|
TEST(SLASupportGeneration, SupportsDoNotPierceModel) {
|
||||||
|
|
||||||
sla::SupportConfig supportcfg;
|
sla::SupportConfig supportcfg;
|
||||||
|
|
||||||
for (auto fname : SUPPORT_TEST_MODELS)
|
for (auto fname : SUPPORT_TEST_MODELS)
|
||||||
@ -382,7 +417,7 @@ TEST(SLARasterOutput, InitializedRasterShouldBeNONEmpty) {
|
|||||||
// Default Prusa SL1 display parameters
|
// Default Prusa SL1 display parameters
|
||||||
sla::Raster::Resolution res{2560, 1440};
|
sla::Raster::Resolution res{2560, 1440};
|
||||||
sla::Raster::PixelDim pixdim{120. / res.width_px, 68. / res.height_px};
|
sla::Raster::PixelDim pixdim{120. / res.width_px, 68. / res.height_px};
|
||||||
|
|
||||||
sla::Raster raster;
|
sla::Raster raster;
|
||||||
raster.reset(res, pixdim);
|
raster.reset(res, pixdim);
|
||||||
ASSERT_FALSE(raster.empty());
|
ASSERT_FALSE(raster.empty());
|
||||||
@ -404,54 +439,54 @@ static void check_raster_transformations(sla::Raster::Orientation o,
|
|||||||
double disp_w = 120., disp_h = 68.;
|
double disp_w = 120., disp_h = 68.;
|
||||||
sla::Raster::Resolution res{2560, 1440};
|
sla::Raster::Resolution res{2560, 1440};
|
||||||
sla::Raster::PixelDim pixdim{disp_w / res.width_px, disp_h / res.height_px};
|
sla::Raster::PixelDim pixdim{disp_w / res.width_px, disp_h / res.height_px};
|
||||||
|
|
||||||
auto bb = BoundingBox({0, 0}, {scaled(disp_w), scaled(disp_h)});
|
auto bb = BoundingBox({0, 0}, {scaled(disp_w), scaled(disp_h)});
|
||||||
sla::Raster::Trafo trafo{o, mirroring};
|
sla::Raster::Trafo trafo{o, mirroring};
|
||||||
trafo.origin_x = bb.center().x();
|
trafo.origin_x = bb.center().x();
|
||||||
trafo.origin_y = bb.center().y();
|
trafo.origin_y = bb.center().y();
|
||||||
|
|
||||||
sla::Raster raster{res, pixdim, trafo};
|
sla::Raster raster{res, pixdim, trafo};
|
||||||
|
|
||||||
// create box of size 32x32 pixels (not 1x1 to avoid antialiasing errors)
|
// create box of size 32x32 pixels (not 1x1 to avoid antialiasing errors)
|
||||||
coord_t pw = 32 * coord_t(std::ceil(scaled<double>(pixdim.w_mm)));
|
coord_t pw = 32 * coord_t(std::ceil(scaled<double>(pixdim.w_mm)));
|
||||||
coord_t ph = 32 * coord_t(std::ceil(scaled<double>(pixdim.h_mm)));
|
coord_t ph = 32 * coord_t(std::ceil(scaled<double>(pixdim.h_mm)));
|
||||||
ExPolygon box;
|
ExPolygon box;
|
||||||
box.contour.points = {{-pw, -ph}, {pw, -ph}, {pw, ph}, {-pw, ph}};
|
box.contour.points = {{-pw, -ph}, {pw, -ph}, {pw, ph}, {-pw, ph}};
|
||||||
|
|
||||||
double tr_x = scaled<double>(20.), tr_y = tr_x;
|
double tr_x = scaled<double>(20.), tr_y = tr_x;
|
||||||
|
|
||||||
box.translate(tr_x, tr_y);
|
box.translate(tr_x, tr_y);
|
||||||
ExPolygon expected_box = box;
|
ExPolygon expected_box = box;
|
||||||
|
|
||||||
// Now calculate the position of the translated box according to output
|
// Now calculate the position of the translated box according to output
|
||||||
// trafo.
|
// trafo.
|
||||||
if (o == sla::Raster::Orientation::roPortrait) expected_box.rotate(PI / 2.);
|
if (o == sla::Raster::Orientation::roPortrait) expected_box.rotate(PI / 2.);
|
||||||
|
|
||||||
if (mirroring[X])
|
if (mirroring[X])
|
||||||
for (auto &p : expected_box.contour.points) p.x() = -p.x();
|
for (auto &p : expected_box.contour.points) p.x() = -p.x();
|
||||||
|
|
||||||
if (mirroring[Y])
|
if (mirroring[Y])
|
||||||
for (auto &p : expected_box.contour.points) p.y() = -p.y();
|
for (auto &p : expected_box.contour.points) p.y() = -p.y();
|
||||||
|
|
||||||
raster.draw(box);
|
raster.draw(box);
|
||||||
|
|
||||||
Point expected_coords = expected_box.contour.bounding_box().center();
|
Point expected_coords = expected_box.contour.bounding_box().center();
|
||||||
double rx = unscaled(expected_coords.x() + bb.center().x()) / pixdim.w_mm;
|
double rx = unscaled(expected_coords.x() + bb.center().x()) / pixdim.w_mm;
|
||||||
double ry = unscaled(expected_coords.y() + bb.center().y()) / pixdim.h_mm;
|
double ry = unscaled(expected_coords.y() + bb.center().y()) / pixdim.h_mm;
|
||||||
auto w = size_t(std::floor(rx));
|
auto w = size_t(std::floor(rx));
|
||||||
auto h = res.height_px - size_t(std::floor(ry));
|
auto h = res.height_px - size_t(std::floor(ry));
|
||||||
|
|
||||||
ASSERT_TRUE(w < res.width_px && h < res.height_px);
|
ASSERT_TRUE(w < res.width_px && h < res.height_px);
|
||||||
|
|
||||||
auto px = raster.read_pixel(w, h);
|
auto px = raster.read_pixel(w, h);
|
||||||
|
|
||||||
if (px != FullWhite) {
|
if (px != FullWhite) {
|
||||||
sla::PNGImage img;
|
sla::PNGImage img;
|
||||||
std::fstream outf("out.png", std::ios::out);
|
std::fstream outf("out.png", std::ios::out);
|
||||||
|
|
||||||
outf << img.serialize(raster);
|
outf << img.serialize(raster);
|
||||||
}
|
}
|
||||||
|
|
||||||
ASSERT_EQ(px, FullWhite);
|
ASSERT_EQ(px, FullWhite);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -472,7 +507,7 @@ static ExPolygon square_with_hole(double v)
|
|||||||
{
|
{
|
||||||
ExPolygon poly;
|
ExPolygon poly;
|
||||||
coord_t V = scaled(v / 2.);
|
coord_t V = scaled(v / 2.);
|
||||||
|
|
||||||
poly.contour.points = {{-V, -V}, {V, -V}, {V, V}, {-V, V}};
|
poly.contour.points = {{-V, -V}, {V, -V}, {V, V}, {-V, V}};
|
||||||
poly.holes.emplace_back();
|
poly.holes.emplace_back();
|
||||||
V = V / 2;
|
V = V / 2;
|
||||||
@ -488,16 +523,16 @@ static double pixel_area(TPixel px, const sla::Raster::PixelDim &pxdim)
|
|||||||
static double raster_white_area(const sla::Raster &raster)
|
static double raster_white_area(const sla::Raster &raster)
|
||||||
{
|
{
|
||||||
if (raster.empty()) return std::nan("");
|
if (raster.empty()) return std::nan("");
|
||||||
|
|
||||||
auto res = raster.resolution();
|
auto res = raster.resolution();
|
||||||
double a = 0;
|
double a = 0;
|
||||||
|
|
||||||
for (size_t x = 0; x < res.width_px; ++x)
|
for (size_t x = 0; x < res.width_px; ++x)
|
||||||
for (size_t y = 0; y < res.height_px; ++y) {
|
for (size_t y = 0; y < res.height_px; ++y) {
|
||||||
auto px = raster.read_pixel(x, y);
|
auto px = raster.read_pixel(x, y);
|
||||||
a += pixel_area(px, raster.pixel_dimensions());
|
a += pixel_area(px, raster.pixel_dimensions());
|
||||||
}
|
}
|
||||||
|
|
||||||
return a;
|
return a;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -505,15 +540,15 @@ static double predict_error(const ExPolygon &p, const sla::Raster::PixelDim &pd)
|
|||||||
{
|
{
|
||||||
auto lines = p.lines();
|
auto lines = p.lines();
|
||||||
double pix_err = pixel_area(FullWhite, pd) / 2.;
|
double pix_err = pixel_area(FullWhite, pd) / 2.;
|
||||||
|
|
||||||
// Worst case is when a line is parallel to the shorter axis of one pixel,
|
// Worst case is when a line is parallel to the shorter axis of one pixel,
|
||||||
// when the line will be composed of the max number of pixels
|
// when the line will be composed of the max number of pixels
|
||||||
double pix_l = std::min(pd.h_mm, pd.w_mm);
|
double pix_l = std::min(pd.h_mm, pd.w_mm);
|
||||||
|
|
||||||
double error = 0.;
|
double error = 0.;
|
||||||
for (auto &l : lines)
|
for (auto &l : lines)
|
||||||
error += (unscaled(l.length()) / pix_l) * pix_err;
|
error += (unscaled(l.length()) / pix_l) * pix_err;
|
||||||
|
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -524,26 +559,26 @@ TEST(SLARasterOutput, RasterizedPolygonAreaShouldMatch) {
|
|||||||
|
|
||||||
sla::Raster raster{res, pixdim};
|
sla::Raster raster{res, pixdim};
|
||||||
auto bb = BoundingBox({0, 0}, {scaled(disp_w), scaled(disp_h)});
|
auto bb = BoundingBox({0, 0}, {scaled(disp_w), scaled(disp_h)});
|
||||||
|
|
||||||
ExPolygon poly = square_with_hole(10.);
|
ExPolygon poly = square_with_hole(10.);
|
||||||
poly.translate(bb.center().x(), bb.center().y());
|
poly.translate(bb.center().x(), bb.center().y());
|
||||||
raster.draw(poly);
|
raster.draw(poly);
|
||||||
|
|
||||||
double a = poly.area() / (scaled<double>(1.) * scaled(1.));
|
double a = poly.area() / (scaled<double>(1.) * scaled(1.));
|
||||||
double ra = raster_white_area(raster);
|
double ra = raster_white_area(raster);
|
||||||
double diff = std::abs(a - ra);
|
double diff = std::abs(a - ra);
|
||||||
|
|
||||||
ASSERT_LE(diff, predict_error(poly, pixdim));
|
ASSERT_LE(diff, predict_error(poly, pixdim));
|
||||||
|
|
||||||
raster.clear();
|
raster.clear();
|
||||||
poly = square_with_hole(60.);
|
poly = square_with_hole(60.);
|
||||||
poly.translate(bb.center().x(), bb.center().y());
|
poly.translate(bb.center().x(), bb.center().y());
|
||||||
raster.draw(poly);
|
raster.draw(poly);
|
||||||
|
|
||||||
a = poly.area() / (scaled<double>(1.) * scaled(1.));
|
a = poly.area() / (scaled<double>(1.) * scaled(1.));
|
||||||
ra = raster_white_area(raster);
|
ra = raster_white_area(raster);
|
||||||
diff = std::abs(a - ra);
|
diff = std::abs(a - ra);
|
||||||
|
|
||||||
ASSERT_LE(diff, predict_error(poly, pixdim));
|
ASSERT_LE(diff, predict_error(poly, pixdim));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user