#include #include #include #include #include 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::max(); auto MIN = std::numeric_limits::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 static 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(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(delta)); return result; } Points ConcaveHull::calculate_centroids() const { // We get the centroids of all the islands in the 2D slice Points centroids = reserve_vector(m_polys.size()); std::transform(m_polys.begin(), m_polys.end(), std::back_inserter(centroids), [](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 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(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(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