Merge branch 'tm_fix_concave_hull'
This commit is contained in:
commit
def254ddb8
@ -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)
|
||||||
|
171
src/libslic3r/SLA/ConcaveHull.cpp
Normal file
171
src/libslic3r/SLA/ConcaveHull.cpp
Normal 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 ¢roids,
|
||||||
|
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
|
53
src/libslic3r/SLA/ConcaveHull.hpp
Normal file
53
src/libslic3r/SLA/ConcaveHull.hpp
Normal 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 ¢roids,
|
||||||
|
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
|
@ -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
|
return scaled(c.brim_size_mm + c.wing_distance());
|
||||||
/// centroids (a star is created...)
|
}
|
||||||
class ConcaveHull {
|
|
||||||
Polygons m_polys;
|
|
||||||
|
|
||||||
Point centroid(const Points& pp) const
|
static inline double get_merge_distance(const PadConfig &c)
|
||||||
{
|
{
|
||||||
Point c;
|
return 2. * (1.8 * c.wall_thickness_mm) + c.max_merge_dist_mm;
|
||||||
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 ¢roids,
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline double get_merge_distance(const PadConfig &c)
|
|
||||||
{
|
|
||||||
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 "";
|
||||||
|
@ -175,7 +175,7 @@ class SupportTreeBuildsteps {
|
|||||||
|
|
||||||
// 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
|
// When bridging heads to pillars... TODO: find a cleaner solution
|
||||||
ccr::BlockingMutex m_bridge_mutex;
|
ccr::BlockingMutex m_bridge_mutex;
|
||||||
|
|
||||||
|
@ -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
|
||||||
@ -257,7 +297,7 @@ void export_failed_case(const std::vector<ExPolygons> &support_slices,
|
|||||||
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);
|
||||||
|
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
if (!intersections.empty()) {
|
if (!intersections.empty()) {
|
||||||
ss << byproducts.obj_fname << std::setprecision(4) << n << ".svg";
|
ss << byproducts.obj_fname << std::setprecision(4) << n << ".svg";
|
||||||
@ -268,7 +308,7 @@ void export_failed_case(const std::vector<ExPolygons> &support_slices,
|
|||||||
svg.Close();
|
svg.Close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TriangleMesh m;
|
TriangleMesh m;
|
||||||
byproducts.supporttree.retrieve_full_mesh(m);
|
byproducts.supporttree.retrieve_full_mesh(m);
|
||||||
m.merge(byproducts.input_mesh);
|
m.merge(byproducts.input_mesh);
|
||||||
@ -288,7 +328,7 @@ void test_support_model_collision(
|
|||||||
// 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.15;
|
supportcfg.head_penetration_mm = -0.15;
|
||||||
|
|
||||||
// TODO: currently, the tailheads penetrating into the model body do not
|
// TODO: currently, the tailheads penetrating into the model body do not
|
||||||
// respect the penetration parameter properly. No issues were reported so
|
// respect the penetration parameter properly. No issues were reported so
|
||||||
// far but we should definitely fix this.
|
// far but we should definitely fix this.
|
||||||
@ -305,7 +345,7 @@ void test_support_model_collision(
|
|||||||
bool support_mesh_is_empty =
|
bool support_mesh_is_empty =
|
||||||
byproducts.supporttree.retrieve_mesh(sla::MeshType::Pad).empty() &&
|
byproducts.supporttree.retrieve_mesh(sla::MeshType::Pad).empty() &&
|
||||||
byproducts.supporttree.retrieve_mesh(sla::MeshType::Support).empty();
|
byproducts.supporttree.retrieve_mesh(sla::MeshType::Support).empty();
|
||||||
|
|
||||||
if (support_mesh_is_empty)
|
if (support_mesh_is_empty)
|
||||||
REQUIRE(support_slices.empty());
|
REQUIRE(support_slices.empty());
|
||||||
else
|
else
|
||||||
@ -320,7 +360,7 @@ void test_support_model_collision(
|
|||||||
|
|
||||||
notouch = notouch && intersections.empty();
|
notouch = notouch && intersections.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!notouch) export_failed_case(support_slices, byproducts);
|
if (!notouch) export_failed_case(support_slices, byproducts);
|
||||||
|
|
||||||
REQUIRE(notouch);
|
REQUIRE(notouch);
|
||||||
@ -359,12 +399,12 @@ template <class I, class II> void test_pairhash()
|
|||||||
|
|
||||||
const I Ibits = int(sizeof(I) * CHAR_BIT);
|
const I Ibits = int(sizeof(I) * CHAR_BIT);
|
||||||
const II IIbits = int(sizeof(II) * CHAR_BIT);
|
const II IIbits = int(sizeof(II) * CHAR_BIT);
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
for (size_t i = 0; i < nums;) {
|
for (size_t i = 0; i < nums;) {
|
||||||
|
Loading…
Reference in New Issue
Block a user