Merge branch 'tm_fix_concave_hull'

This commit is contained in:
tamasmeszaros 2019-10-10 09:43:14 +02:00
commit def254ddb8
6 changed files with 292 additions and 201 deletions

View File

@ -194,6 +194,8 @@ add_library(libslic3r STATIC
SLA/SLARaster.cpp SLA/SLARaster.cpp
SLA/SLARasterWriter.hpp SLA/SLARasterWriter.hpp
SLA/SLARasterWriter.cpp SLA/SLARasterWriter.cpp
SLA/ConcaveHull.hpp
SLA/ConcaveHull.cpp
) )
encoding_check(libslic3r) encoding_check(libslic3r)

View File

@ -0,0 +1,171 @@
#include "ConcaveHull.hpp"
#include <libslic3r/MTUtils.hpp>
#include <libslic3r/ClipperUtils.hpp>
#include "SLASpatIndex.hpp"
#include <boost/log/trivial.hpp>
namespace Slic3r {
namespace sla {
inline Vec3d to_vec3(const Vec2crd &v2) { return {double(v2(X)), double(v2(Y)), 0.}; }
inline Vec3d to_vec3(const Vec2d &v2) { return {v2(X), v2(Y), 0.}; }
inline Vec2crd to_vec2(const Vec3d &v3) { return {coord_t(v3(X)), coord_t(v3(Y))}; }
Point ConcaveHull::centroid(const Points &pp)
{
Point c;
switch(pp.size()) {
case 0: break;
case 1: c = pp.front(); break;
case 2: c = (pp[0] + pp[1]) / 2; break;
default: {
auto MAX = std::numeric_limits<Point::coord_type>::max();
auto MIN = std::numeric_limits<Point::coord_type>::min();
Point min = {MAX, MAX}, max = {MIN, MIN};
for(auto& p : pp) {
if(p(0) < min(0)) min(0) = p(0);
if(p(1) < min(1)) min(1) = p(1);
if(p(0) > max(0)) max(0) = p(0);
if(p(1) > max(1)) max(1) = p(1);
}
c(0) = min(0) + (max(0) - min(0)) / 2;
c(1) = min(1) + (max(1) - min(1)) / 2;
break;
}
}
return c;
}
// As it shows, the current offset_ex in ClipperUtils hangs if used in jtRound
// mode
ClipperLib::Paths fast_offset(const ClipperLib::Paths &paths,
coord_t delta,
ClipperLib::JoinType jointype)
{
using ClipperLib::ClipperOffset;
using ClipperLib::etClosedPolygon;
using ClipperLib::Paths;
using ClipperLib::Path;
ClipperOffset offs;
offs.ArcTolerance = scaled<double>(0.01);
for (auto &p : paths)
// If the input is not at least a triangle, we can not do this algorithm
if(p.size() < 3) {
BOOST_LOG_TRIVIAL(error) << "Invalid geometry for offsetting!";
return {};
}
offs.AddPaths(paths, jointype, etClosedPolygon);
Paths result;
offs.Execute(result, static_cast<double>(delta));
return result;
}
Points ConcaveHull::calculate_centroids() const
{
// We get the centroids of all the islands in the 2D slice
Points centroids = reserve_vector<Point>(m_polys.size());
std::transform(m_polys.begin(), m_polys.end(),
std::back_inserter(centroids),
[this](const Polygon &poly) { return centroid(poly); });
return centroids;
}
void ConcaveHull::merge_polygons() { m_polys = get_contours(union_ex(m_polys)); }
void ConcaveHull::add_connector_rectangles(const Points &centroids,
coord_t max_dist,
ThrowOnCancel thr)
{
// Centroid of the centroids of islands. This is where the additional
// connector sticks are routed.
Point cc = centroid(centroids);
PointIndex ctrindex;
unsigned idx = 0;
for(const Point &ct : centroids) ctrindex.insert(to_vec3(ct), idx++);
m_polys.reserve(m_polys.size() + centroids.size());
idx = 0;
for (const Point &c : centroids) {
thr();
double dx = c.x() - cc.x(), dy = c.y() - cc.y();
double l = std::sqrt(dx * dx + dy * dy);
double nx = dx / l, ny = dy / l;
const Point &ct = centroids[idx];
std::vector<PointIndexEl> result = ctrindex.nearest(to_vec3(ct), 2);
double dist = max_dist;
for (const PointIndexEl &el : result)
if (el.second != idx) {
dist = Line(to_vec2(el.first), ct).length();
break;
}
idx++;
if (dist >= max_dist) return;
Polygon r;
r.points.reserve(3);
r.points.emplace_back(cc);
Point n(scaled(nx), scaled(ny));
r.points.emplace_back(c + Point(n.y(), -n.x()));
r.points.emplace_back(c + Point(-n.y(), n.x()));
offset(r, scaled<float>(1.));
m_polys.emplace_back(r);
}
}
ConcaveHull::ConcaveHull(const Polygons &polys, double mergedist, ThrowOnCancel thr)
{
if(polys.empty()) return;
m_polys = polys;
merge_polygons();
if(m_polys.size() == 1) return;
Points centroids = calculate_centroids();
add_connector_rectangles(centroids, scaled(mergedist), thr);
merge_polygons();
}
ExPolygons ConcaveHull::to_expolygons() const
{
auto ret = reserve_vector<ExPolygon>(m_polys.size());
for (const Polygon &p : m_polys) ret.emplace_back(ExPolygon(p));
return ret;
}
ExPolygons offset_waffle_style_ex(const ConcaveHull &hull, coord_t delta)
{
ClipperLib::Paths paths = Slic3rMultiPoints_to_ClipperPaths(hull.polygons());
paths = fast_offset(paths, 2 * delta, ClipperLib::jtRound);
paths = fast_offset(paths, -delta, ClipperLib::jtRound);
ExPolygons ret = ClipperPaths_to_Slic3rExPolygons(paths);
for (ExPolygon &p : ret) p.holes = {};
return ret;
}
Polygons offset_waffle_style(const ConcaveHull &hull, coord_t delta)
{
return to_polygons(offset_waffle_style_ex(hull, delta));
}
}} // namespace Slic3r::sla

View File

@ -0,0 +1,53 @@
#ifndef CONCAVEHULL_HPP
#define CONCAVEHULL_HPP
#include <libslic3r/ExPolygon.hpp>
namespace Slic3r {
namespace sla {
inline Polygons get_contours(const ExPolygons &poly)
{
Polygons ret; ret.reserve(poly.size());
for (const ExPolygon &p : poly) ret.emplace_back(p.contour);
return ret;
}
using ThrowOnCancel = std::function<void()>;
/// A fake concave hull that is constructed by connecting separate shapes
/// 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
/// centroids (a star is created...)
class ConcaveHull {
Polygons m_polys;
static Point centroid(const Points& pp);
static inline Point centroid(const Polygon &poly) { return poly.centroid(); }
Points calculate_centroids() const;
void merge_polygons();
void add_connector_rectangles(const Points &centroids,
coord_t max_dist,
ThrowOnCancel thr);
public:
ConcaveHull(const ExPolygons& polys, double merge_dist, ThrowOnCancel thr)
: ConcaveHull{to_polygons(polys), merge_dist, thr} {}
ConcaveHull(const Polygons& polys, double mergedist, ThrowOnCancel thr);
const Polygons & polygons() const { return m_polys; }
ExPolygons to_expolygons() const;
};
ExPolygons offset_waffle_style_ex(const ConcaveHull &ccvhull, coord_t delta);
Polygons offset_waffle_style(const ConcaveHull &polys, coord_t delta);
}} // namespace Slic3r::sla
#endif // CONCAVEHULL_HPP

View File

@ -1,6 +1,7 @@
#include "SLAPad.hpp" #include "SLAPad.hpp"
#include "SLABoilerPlate.hpp" #include "SLABoilerPlate.hpp"
#include "SLASpatIndex.hpp" #include "SLASpatIndex.hpp"
#include "ConcaveHull.hpp"
#include "boost/log/trivial.hpp" #include "boost/log/trivial.hpp"
#include "SLABoostAdapter.hpp" #include "SLABoostAdapter.hpp"
@ -206,36 +207,6 @@ Contour3D inline straight_walls(const Polygon &plate,
return walls(plate, plate, lo_z, hi_z, .0 /*offset_diff*/, thr); return walls(plate, plate, lo_z, hi_z, .0 /*offset_diff*/, thr);
} }
// As it shows, the current offset_ex in ClipperUtils hangs if used in jtRound
// mode
ClipperLib::Paths fast_offset(const ClipperLib::Paths &paths,
coord_t delta,
ClipperLib::JoinType jointype)
{
using ClipperLib::ClipperOffset;
using ClipperLib::etClosedPolygon;
using ClipperLib::Paths;
using ClipperLib::Path;
ClipperOffset offs;
offs.ArcTolerance = scaled<double>(0.01);
for (auto &p : paths)
// If the input is not at least a triangle, we can not do this algorithm
if(p.size() < 3) {
BOOST_LOG_TRIVIAL(error) << "Invalid geometry for offsetting!";
return {};
}
offs.AddPaths(paths, jointype, etClosedPolygon);
Paths result;
offs.Execute(result, static_cast<double>(delta));
return result;
}
// Function to cut tiny connector cavities for a given polygon. The input poly // 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 // will be offsetted by "padding" and small rectangle shaped cavities will be
// inserted along the perimeter in every "stride" distance. The stick rectangles // inserted along the perimeter in every "stride" distance. The stick rectangles
@ -322,158 +293,15 @@ ExPolygons breakstick_holes(const ExPolygons &input, Args...args)
return ret; return ret;
} }
/// A fake concave hull that is constructed by connecting separate shapes static inline coord_t get_waffle_offset(const PadConfig &c)
/// 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
/// centroids (a star is created...)
class ConcaveHull {
Polygons m_polys;
Point centroid(const Points& pp) const
{
Point c;
switch(pp.size()) {
case 0: break;
case 1: c = pp.front(); break;
case 2: c = (pp[0] + pp[1]) / 2; break;
default: {
auto MAX = std::numeric_limits<Point::coord_type>::max();
auto MIN = std::numeric_limits<Point::coord_type>::min();
Point min = {MAX, MAX}, max = {MIN, MIN};
for(auto& p : pp) {
if(p(0) < min(0)) min(0) = p(0);
if(p(1) < min(1)) min(1) = p(1);
if(p(0) > max(0)) max(0) = p(0);
if(p(1) > max(1)) max(1) = p(1);
}
c(0) = min(0) + (max(0) - min(0)) / 2;
c(1) = min(1) + (max(1) - min(1)) / 2;
break;
}
}
return c;
}
inline Point centroid(const Polygon &poly) const { return poly.centroid(); }
Points calculate_centroids() const
{
// We get the centroids of all the islands in the 2D slice
Points centroids = reserve_vector<Point>(m_polys.size());
std::transform(m_polys.begin(), m_polys.end(),
std::back_inserter(centroids),
[this](const Polygon &poly) { return centroid(poly); });
return centroids;
}
void merge_polygons() { m_polys = union_(m_polys); }
void add_connector_rectangles(const Points &centroids,
coord_t max_dist,
ThrowOnCancel thr)
{
namespace bgi = boost::geometry::index;
using PointIndexElement = std::pair<Point, unsigned>;
using PointIndex = bgi::rtree<PointIndexElement, bgi::rstar<16, 4>>;
// Centroid of the centroids of islands. This is where the additional
// connector sticks are routed.
Point cc = centroid(centroids);
PointIndex ctrindex;
unsigned idx = 0;
for(const Point &ct : centroids)
ctrindex.insert(std::make_pair(ct, idx++));
m_polys.reserve(m_polys.size() + centroids.size());
idx = 0;
for (const Point &c : centroids) {
thr();
double dx = c.x() - cc.x(), dy = c.y() - cc.y();
double l = std::sqrt(dx * dx + dy * dy);
double nx = dx / l, ny = dy / l;
const Point &ct = centroids[idx];
std::vector<PointIndexElement> result;
ctrindex.query(bgi::nearest(ct, 2), std::back_inserter(result));
double dist = max_dist;
for (const PointIndexElement &el : result)
if (el.second != idx) {
dist = Line(el.first, ct).length();
break;
}
idx++;
if (dist >= max_dist) return;
Polygon r;
r.points.reserve(3);
r.points.emplace_back(cc);
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()));
offset(r, scaled<float>(1.));
m_polys.emplace_back(r);
}
}
public:
ConcaveHull(const ExPolygons& polys, double merge_dist, ThrowOnCancel thr)
: ConcaveHull{to_polygons(polys), merge_dist, thr} {}
ConcaveHull(const Polygons& polys, double mergedist, ThrowOnCancel thr)
{
if(polys.empty()) return;
m_polys = polys;
merge_polygons();
if(m_polys.size() == 1) return;
Points centroids = calculate_centroids();
add_connector_rectangles(centroids, scaled(mergedist), thr);
merge_polygons();
}
// const Polygons & polygons() const { return m_polys; }
ExPolygons to_expolygons() const
{
auto ret = reserve_vector<ExPolygon>(m_polys.size());
for (const Polygon &p : m_polys) ret.emplace_back(ExPolygon(p));
return ret;
}
void offset_waffle_style(coord_t delta) {
ClipperLib::Paths paths = Slic3rMultiPoints_to_ClipperPaths(m_polys);
paths = fast_offset(paths, 2 * delta, ClipperLib::jtRound);
paths = fast_offset(paths, -delta, ClipperLib::jtRound);
m_polys = ClipperPaths_to_Slic3rPolygons(paths);
}
static inline coord_t get_waffle_offset(const PadConfig &c)
{
return scaled(c.brim_size_mm + c.wing_distance()); 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;
} }
};
// Part of the pad configuration that is used for 3D geometry generation // Part of the pad configuration that is used for 3D geometry generation
struct PadConfig3D { struct PadConfig3D {
@ -591,7 +419,7 @@ public:
scaled<float>(cfg.embed_object.object_gap_mm), scaled<float>(cfg.embed_object.object_gap_mm),
ClipperLib::jtMiter, 1); ClipperLib::jtMiter, 1);
ConcaveHull fullcvh = ExPolygons fullcvh =
wafflized_concave_hull(support_blueprint, model_bp_offs, cfg, thr); wafflized_concave_hull(support_blueprint, model_bp_offs, cfg, thr);
auto model_bp_sticks = auto model_bp_sticks =
@ -600,7 +428,7 @@ public:
cfg.embed_object.stick_width_mm, cfg.embed_object.stick_width_mm,
cfg.embed_object.stick_penetration_mm); cfg.embed_object.stick_penetration_mm);
ExPolygons fullpad = diff_ex(fullcvh.to_expolygons(), model_bp_sticks); ExPolygons fullpad = diff_ex(fullcvh, model_bp_sticks);
remove_redundant_parts(fullpad); remove_redundant_parts(fullpad);
@ -619,7 +447,7 @@ private:
// 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, ExPolygons wafflized_concave_hull(const ExPolygons &supp_bp,
const ExPolygons &model_bp, const ExPolygons &model_bp,
const PadConfig &cfg, const PadConfig &cfg,
ThrowOnCancel thr) ThrowOnCancel thr)
@ -629,10 +457,8 @@ private:
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 cchull{allin, get_merge_distance(cfg), thr};
ret.offset_waffle_style(ConcaveHull::get_waffle_offset(cfg)); return offset_waffle_style_ex(cchull, get_waffle_offset(cfg));
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
@ -663,10 +489,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, get_merge_distance(cfg), thr};
ochull.offset_waffle_style(ConcaveHull::get_waffle_offset(cfg)); outer = offset_waffle_style_ex(ochull, get_waffle_offset(cfg));
outer = ochull.to_expolygons();
} }
}; };
@ -861,7 +686,7 @@ std::string PadConfig::validate() const
if (brim_size_mm < MIN_BRIM_SIZE_MM || if (brim_size_mm < MIN_BRIM_SIZE_MM ||
bottom_offset() > brim_size_mm + wing_distance() || bottom_offset() > brim_size_mm + wing_distance() ||
ConcaveHull::get_waffle_offset(*this) <= MIN_BRIM_SIZE_MM) get_waffle_offset(*this) <= MIN_BRIM_SIZE_MM)
return L("Pad brim size is too small for the current configuration."); return L("Pad brim size is too small for the current configuration.");
return ""; return "";

View File

@ -17,6 +17,7 @@
#include "libslic3r/SLA/SLASupportTreeBuildsteps.hpp" #include "libslic3r/SLA/SLASupportTreeBuildsteps.hpp"
#include "libslic3r/SLA/SLAAutoSupports.hpp" #include "libslic3r/SLA/SLAAutoSupports.hpp"
#include "libslic3r/SLA/SLARaster.hpp" #include "libslic3r/SLA/SLARaster.hpp"
#include "libslic3r/SLA/ConcaveHull.hpp"
#include "libslic3r/MTUtils.hpp" #include "libslic3r/MTUtils.hpp"
#include "libslic3r/SVG.hpp" #include "libslic3r/SVG.hpp"
@ -79,6 +80,43 @@ struct PadByproducts
TriangleMesh mesh; TriangleMesh mesh;
}; };
void _test_concave_hull(const Polygons &hull, const ExPolygons &polys)
{
REQUIRE(polys.size() >=hull.size());
double polys_area = 0;
for (const ExPolygon &p : polys) polys_area += p.area();
double cchull_area = 0;
for (const Slic3r::Polygon &p : hull) cchull_area += p.area();
REQUIRE(cchull_area >= Approx(polys_area));
size_t cchull_holes = 0;
for (const Slic3r::Polygon &p : hull)
cchull_holes += p.is_clockwise() ? 1 : 0;
REQUIRE(cchull_holes == 0);
Polygons intr = diff(to_polygons(polys), hull);
REQUIRE(intr.empty());
}
void test_concave_hull(const ExPolygons &polys) {
sla::PadConfig pcfg;
Slic3r::sla::ConcaveHull cchull{polys, pcfg.max_merge_dist_mm, []{}};
_test_concave_hull(cchull.polygons(), polys);
coord_t delta = scaled(pcfg.brim_size_mm + pcfg.wing_distance());
ExPolygons wafflex = sla::offset_waffle_style_ex(cchull, delta);
Polygons waffl = sla::offset_waffle_style(cchull, delta);
_test_concave_hull(to_polygons(wafflex), polys);
_test_concave_hull(waffl, polys);
}
void test_pad(const std::string & obj_filename, void test_pad(const std::string & obj_filename,
const sla::PadConfig &padcfg, const sla::PadConfig &padcfg,
PadByproducts & out) PadByproducts & out)
@ -92,6 +130,8 @@ void test_pad(const std::string & obj_filename,
// 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);
test_concave_hull(out.model_contours);
REQUIRE_FALSE(out.model_contours.empty()); REQUIRE_FALSE(out.model_contours.empty());
// Create the pad geometry for the model contours only // Create the pad geometry for the model contours only
@ -362,7 +402,7 @@ template <class I, class II> void test_pairhash()
int bits = IIbits / 2 < Ibits ? Ibits / 2 : Ibits; int bits = IIbits / 2 < Ibits ? Ibits / 2 : Ibits;
if (std::is_signed<I>::value) bits -= 1; if (std::is_signed<I>::value) bits -= 1;
const I Imin = std::is_signed<I>::value ? -I(std::pow(2., bits)) : 0; const I Imin = 0;
const I Imax = I(std::pow(2., bits) - 1); const I Imax = I(std::pow(2., bits) - 1);
std::uniform_int_distribution<I> dis(Imin, Imax); std::uniform_int_distribution<I> dis(Imin, Imax);