mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-07-12 01:07:57 -06:00
SPE-742: Builtin pad feature in zero elevation mode.
This commit is contained in:
parent
c7ba8c4daa
commit
ddd0a9abb6
8 changed files with 685 additions and 232 deletions
|
@ -7,9 +7,9 @@
|
|||
#include "Tesselate.hpp"
|
||||
|
||||
// For debugging:
|
||||
//#include <fstream>
|
||||
//#include <libnest2d/tools/benchmark.h>
|
||||
//#include "SVG.hpp"
|
||||
// #include <fstream>
|
||||
// #include <libnest2d/tools/benchmark.h>
|
||||
#include "SVG.hpp"
|
||||
|
||||
namespace Slic3r { namespace sla {
|
||||
|
||||
|
@ -180,9 +180,10 @@ Contour3D walls(const Polygon& lower, const Polygon& upper,
|
|||
}
|
||||
|
||||
/// Offsetting with clipper and smoothing the edges into a curvature.
|
||||
void offset(ExPolygon& sh, coord_t distance) {
|
||||
void offset(ExPolygon& sh, coord_t distance, bool edgerounding = true) {
|
||||
using ClipperLib::ClipperOffset;
|
||||
using ClipperLib::jtRound;
|
||||
using ClipperLib::jtMiter;
|
||||
using ClipperLib::etClosedPolygon;
|
||||
using ClipperLib::Paths;
|
||||
using ClipperLib::Path;
|
||||
|
@ -199,11 +200,13 @@ void offset(ExPolygon& sh, coord_t distance) {
|
|||
return;
|
||||
}
|
||||
|
||||
auto jointype = edgerounding? jtRound : jtMiter;
|
||||
|
||||
ClipperOffset offs;
|
||||
offs.ArcTolerance = 0.01*mm(1);
|
||||
Paths result;
|
||||
offs.AddPath(ctour, jtRound, etClosedPolygon);
|
||||
offs.AddPaths(holes, jtRound, etClosedPolygon);
|
||||
offs.AddPath(ctour, jointype, etClosedPolygon);
|
||||
offs.AddPaths(holes, jointype, etClosedPolygon);
|
||||
offs.Execute(result, static_cast<double>(distance));
|
||||
|
||||
// Offsetting reverts the orientation and also removes the last vertex
|
||||
|
@ -233,6 +236,49 @@ void offset(ExPolygon& sh, coord_t distance) {
|
|||
}
|
||||
}
|
||||
|
||||
void offset(Polygon& sh, coord_t distance, bool edgerounding = true) {
|
||||
using ClipperLib::ClipperOffset;
|
||||
using ClipperLib::jtRound;
|
||||
using ClipperLib::jtMiter;
|
||||
using ClipperLib::etClosedPolygon;
|
||||
using ClipperLib::Paths;
|
||||
using ClipperLib::Path;
|
||||
|
||||
auto&& ctour = Slic3rMultiPoint_to_ClipperPath(sh);
|
||||
|
||||
// If the input is not at least a triangle, we can not do this algorithm
|
||||
if(ctour.size() < 3) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Invalid geometry for offsetting!";
|
||||
return;
|
||||
}
|
||||
|
||||
ClipperOffset offs;
|
||||
offs.ArcTolerance = 0.01*mm(1);
|
||||
Paths result;
|
||||
offs.AddPath(ctour, edgerounding ? jtRound : jtMiter, etClosedPolygon);
|
||||
offs.Execute(result, static_cast<double>(distance));
|
||||
|
||||
// Offsetting reverts the orientation and also removes the last vertex
|
||||
// so boost will not have a closed polygon.
|
||||
|
||||
bool found_the_contour = false;
|
||||
for(auto& r : result) {
|
||||
if(ClipperLib::Orientation(r)) {
|
||||
// We don't like if the offsetting generates more than one contour
|
||||
// but throwing would be an overkill. Instead, we should warn the
|
||||
// caller about the inability to create correct geometries
|
||||
if(!found_the_contour) {
|
||||
auto rr = ClipperPath_to_Slic3rPolygon(r);
|
||||
sh.points.swap(rr.points);
|
||||
found_the_contour = true;
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(warning)
|
||||
<< "Warning: offsetting result is invalid!";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Unification of polygons (with clipper) preserving holes as well.
|
||||
ExPolygons unify(const ExPolygons& shapes) {
|
||||
using ClipperLib::ptSubject;
|
||||
|
@ -303,6 +349,118 @@ ExPolygons unify(const ExPolygons& shapes) {
|
|||
return retv;
|
||||
}
|
||||
|
||||
Polygons unify(const Polygons& shapes) {
|
||||
using ClipperLib::ptSubject;
|
||||
|
||||
bool closed = true;
|
||||
bool valid = true;
|
||||
|
||||
ClipperLib::Clipper clipper;
|
||||
|
||||
for(auto& path : shapes) {
|
||||
auto clipperpath = Slic3rMultiPoint_to_ClipperPath(path);
|
||||
|
||||
if(!clipperpath.empty())
|
||||
valid &= clipper.AddPath(clipperpath, ptSubject, closed);
|
||||
}
|
||||
|
||||
if(!valid) BOOST_LOG_TRIVIAL(warning) << "Unification of invalid shapes!";
|
||||
|
||||
ClipperLib::Paths result;
|
||||
clipper.Execute(ClipperLib::ctUnion, result, ClipperLib::pftNonZero);
|
||||
|
||||
Polygons ret;
|
||||
for (ClipperLib::Path &p : result) {
|
||||
Polygon pp = ClipperPath_to_Slic3rPolygon(p);
|
||||
if (!pp.is_clockwise()) ret.emplace_back(std::move(pp));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Function to cut tiny connector cavities for a given polygon. The input poly
|
||||
// will be offsetted by "padding" and small rectangle shaped cavities will be
|
||||
// inserted along the perimeter in every "stride" distance. The stick rectangles
|
||||
// will have a with about "stick_width". The input dimensions are in world
|
||||
// measure, not the scaled clipper units.
|
||||
void offset_with_breakstick_holes(ExPolygon& poly,
|
||||
double padding,
|
||||
double stride,
|
||||
double stick_width,
|
||||
double penetration)
|
||||
{
|
||||
// We do the basic offsetting first
|
||||
const bool dont_round_edges = false;
|
||||
offset(poly, coord_t(padding / SCALING_FACTOR), dont_round_edges);
|
||||
|
||||
SVG svg("bridgestick_plate.svg");
|
||||
svg.draw(poly);
|
||||
|
||||
auto transf = [stick_width, penetration, padding, stride](Points &pts) {
|
||||
// The connector stick will be a small rectangle with dimensions
|
||||
// stick_width x (penetration + padding) to have some penetration
|
||||
// into the input polygon.
|
||||
|
||||
Points out;
|
||||
out.reserve(2 * pts.size()); // output polygon points
|
||||
|
||||
// stick bottom and right edge dimensions
|
||||
double sbottom = stick_width / SCALING_FACTOR;
|
||||
double sright = (penetration + padding) / SCALING_FACTOR;
|
||||
|
||||
// scaled stride distance
|
||||
double sstride = stride / SCALING_FACTOR;
|
||||
double t = 0;
|
||||
|
||||
// process pairs of vertices as an edge, start with the last and
|
||||
// first point
|
||||
for (size_t i = pts.size() - 1, j = 0; j < pts.size(); i = j, ++j) {
|
||||
// Get vertices and the direction vectors
|
||||
const Point &a = pts[i], &b = pts[j];
|
||||
Vec2d dir = b.cast<double>() - a.cast<double>();
|
||||
double nrm = dir.norm();
|
||||
dir /= nrm;
|
||||
Vec2d dirp(-dir(Y), dir(X));
|
||||
|
||||
// Insert start point
|
||||
out.emplace_back(a);
|
||||
|
||||
// dodge the start point, do not make sticks on the joins
|
||||
while (t < sright) t += sright;
|
||||
double tend = nrm - sright;
|
||||
|
||||
while (t < tend) { // insert the stick on the polygon perimeter
|
||||
|
||||
// calculate the stick rectangle vertices and insert them
|
||||
// into the output.
|
||||
Point p1 = a + (t * dir).cast<coord_t>();
|
||||
Point p2 = p1 + (sright * dirp).cast<coord_t>();
|
||||
Point p3 = p2 + (sbottom * dir).cast<coord_t>();
|
||||
Point p4 = p3 + (sright * -dirp).cast<coord_t>();
|
||||
out.insert(out.end(), {p1, p2, p3, p4});
|
||||
|
||||
// continue along the perimeter
|
||||
t += sstride;
|
||||
}
|
||||
|
||||
t = t - nrm;
|
||||
|
||||
// Insert edge endpoint
|
||||
out.emplace_back(b);
|
||||
}
|
||||
|
||||
// move the new points
|
||||
out.shrink_to_fit();
|
||||
pts.swap(out);
|
||||
};
|
||||
|
||||
transf(poly.contour.points);
|
||||
for (auto &h : poly.holes) transf(h.points);
|
||||
|
||||
svg.draw(poly);
|
||||
svg.Close();
|
||||
}
|
||||
|
||||
/// Only a debug function to generate top and bottom plates from a 2D shape.
|
||||
/// It is not used in the algorithm directly.
|
||||
inline Contour3D roofs(const ExPolygon& poly, coord_t z_distance) {
|
||||
|
@ -467,41 +625,38 @@ inline Point centroid(Points& pp) {
|
|||
return c;
|
||||
}
|
||||
|
||||
inline Point centroid(const ExPolygon& poly) {
|
||||
return poly.contour.centroid();
|
||||
inline Point centroid(const Polygon& poly) {
|
||||
return poly.centroid();
|
||||
}
|
||||
|
||||
/// 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...)
|
||||
ExPolygons concave_hull(const ExPolygons& polys, double max_dist_mm = 50,
|
||||
ThrowOnCancel throw_on_cancel = [](){})
|
||||
Polygons concave_hull(const Polygons& polys, double max_dist_mm = 50,
|
||||
ThrowOnCancel throw_on_cancel = [](){})
|
||||
{
|
||||
namespace bgi = boost::geometry::index;
|
||||
using SpatElement = std::pair<BoundingBox, unsigned>;
|
||||
using SpatElement = std::pair<Point, unsigned>;
|
||||
using SpatIndex = bgi::rtree< SpatElement, bgi::rstar<16, 4> >;
|
||||
|
||||
if(polys.empty()) return ExPolygons();
|
||||
if(polys.empty()) return Polygons();
|
||||
|
||||
const double max_dist = mm(max_dist_mm);
|
||||
|
||||
ExPolygons punion = unify(polys); // could be redundant
|
||||
Polygons punion = unify(polys); // could be redundant
|
||||
|
||||
if(punion.size() == 1) return punion;
|
||||
|
||||
// We get the centroids of all the islands in the 2D slice
|
||||
Points centroids; centroids.reserve(punion.size());
|
||||
std::transform(punion.begin(), punion.end(), std::back_inserter(centroids),
|
||||
[](const ExPolygon& poly) { return centroid(poly); });
|
||||
|
||||
|
||||
SpatIndex boxindex; unsigned idx = 0;
|
||||
std::for_each(punion.begin(), punion.end(),
|
||||
[&boxindex, &idx](const ExPolygon& expo) {
|
||||
BoundingBox bb(expo);
|
||||
boxindex.insert(std::make_pair(bb, idx++));
|
||||
});
|
||||
|
||||
[](const Polygon& poly) { return centroid(poly); });
|
||||
|
||||
SpatIndex ctrindex;
|
||||
unsigned idx = 0;
|
||||
for(const Point &ct : centroids) ctrindex.insert(std::make_pair(ct, idx++));
|
||||
|
||||
// Centroid of the centroids of islands. This is where the additional
|
||||
// connector sticks are routed.
|
||||
Point cc = centroid(centroids);
|
||||
|
@ -511,25 +666,32 @@ ExPolygons concave_hull(const ExPolygons& polys, double max_dist_mm = 50,
|
|||
idx = 0;
|
||||
std::transform(centroids.begin(), centroids.end(),
|
||||
std::back_inserter(punion),
|
||||
[&punion, &boxindex, cc, max_dist_mm, &idx, throw_on_cancel]
|
||||
[¢roids, &ctrindex, cc, max_dist, &idx, throw_on_cancel]
|
||||
(const Point& c)
|
||||
{
|
||||
throw_on_cancel();
|
||||
double dx = x(c) - x(cc), dy = y(c) - y(cc);
|
||||
double l = std::sqrt(dx * dx + dy * dy);
|
||||
double nx = dx / l, ny = dy / l;
|
||||
double max_dist = mm(max_dist_mm);
|
||||
|
||||
ExPolygon& expo = punion[idx++];
|
||||
BoundingBox querybb(expo);
|
||||
|
||||
querybb.offset(max_dist);
|
||||
|
||||
Point& ct = centroids[idx];
|
||||
|
||||
std::vector<SpatElement> result;
|
||||
boxindex.query(bgi::intersects(querybb), std::back_inserter(result));
|
||||
if(result.size() <= 1) return ExPolygon();
|
||||
ctrindex.query(bgi::nearest(ct, 2), std::back_inserter(result));
|
||||
|
||||
ExPolygon r;
|
||||
auto& ctour = r.contour.points;
|
||||
double dist = max_dist;
|
||||
for (const SpatElement &el : result)
|
||||
if (el.second != idx) {
|
||||
dist = Line(el.first, ct).length();
|
||||
break;
|
||||
}
|
||||
|
||||
idx++;
|
||||
|
||||
if (dist >= max_dist) return Polygon();
|
||||
|
||||
Polygon r;
|
||||
auto& ctour = r.points;
|
||||
|
||||
ctour.reserve(3);
|
||||
ctour.emplace_back(cc);
|
||||
|
@ -538,7 +700,7 @@ ExPolygons concave_hull(const ExPolygons& polys, double max_dist_mm = 50,
|
|||
ctour.emplace_back(c + Point( -y(d), x(d) ));
|
||||
ctour.emplace_back(c + Point( y(d), -x(d) ));
|
||||
offset(r, mm(1));
|
||||
|
||||
|
||||
return r;
|
||||
});
|
||||
|
||||
|
@ -576,13 +738,14 @@ void base_plate(const TriangleMesh &mesh, ExPolygons &output, float h,
|
|||
|
||||
ExPolygons utmp = unify(tmp);
|
||||
|
||||
for(auto& o : utmp) {
|
||||
auto&& smp = o.simplify(0.1/SCALING_FACTOR);
|
||||
for(ExPolygon& o : utmp) {
|
||||
auto&& smp = o.simplify(0.1/SCALING_FACTOR); // TODO: is this important?
|
||||
output.insert(output.end(), smp.begin(), smp.end());
|
||||
}
|
||||
}
|
||||
|
||||
Contour3D create_base_pool(const ExPolygons &ground_layer,
|
||||
Contour3D create_base_pool(const Polygons &ground_layer,
|
||||
const ExPolygons &obj_self_pad = {},
|
||||
const PoolConfig& cfg = PoolConfig())
|
||||
{
|
||||
// for debugging:
|
||||
|
@ -597,7 +760,7 @@ Contour3D create_base_pool(const ExPolygons &ground_layer,
|
|||
// serve as the bottom plate of the pad. We will offset this concave hull
|
||||
// and then offset back the result with clipper with rounding edges ON. This
|
||||
// trick will create a nice rounded pad shape.
|
||||
ExPolygons concavehs = concave_hull(ground_layer, mergedist, cfg.throw_on_cancel);
|
||||
Polygons concavehs = concave_hull(ground_layer, mergedist, cfg.throw_on_cancel);
|
||||
|
||||
const double thickness = cfg.min_wall_thickness_mm;
|
||||
const double wingheight = cfg.min_wall_height_mm;
|
||||
|
@ -617,42 +780,37 @@ Contour3D create_base_pool(const ExPolygons &ground_layer,
|
|||
|
||||
Contour3D pool;
|
||||
|
||||
for(ExPolygon& concaveh : concavehs) {
|
||||
if(concaveh.contour.points.empty()) return pool;
|
||||
|
||||
// Get rid of any holes in the concave hull output.
|
||||
concaveh.holes.clear();
|
||||
for(Polygon& concaveh : concavehs) {
|
||||
if(concaveh.points.empty()) return pool;
|
||||
|
||||
// Here lies the trick that does the smoothing only with clipper offset
|
||||
// calls. The offset is configured to round edges. Inner edges will
|
||||
// be rounded because we offset twice: ones to get the outer (top) plate
|
||||
// and again to get the inner (bottom) plate
|
||||
auto outer_base = concaveh;
|
||||
outer_base.holes.clear();
|
||||
offset(outer_base, s_safety_dist + s_wingdist + s_thickness);
|
||||
|
||||
ExPolygon bottom_poly = outer_base;
|
||||
bottom_poly.holes.clear();
|
||||
ExPolygon bottom_poly; bottom_poly.contour = outer_base;
|
||||
offset(bottom_poly, -s_bottom_offs);
|
||||
|
||||
// Punching a hole in the top plate for the cavity
|
||||
ExPolygon top_poly;
|
||||
ExPolygon middle_base;
|
||||
ExPolygon inner_base;
|
||||
top_poly.contour = outer_base.contour;
|
||||
top_poly.contour = outer_base;
|
||||
|
||||
if(wingheight > 0) {
|
||||
inner_base = outer_base;
|
||||
inner_base.contour = outer_base;
|
||||
offset(inner_base, -(s_thickness + s_wingdist + s_eradius));
|
||||
|
||||
middle_base = outer_base;
|
||||
middle_base.contour = outer_base;
|
||||
offset(middle_base, -s_thickness);
|
||||
top_poly.holes.emplace_back(middle_base.contour);
|
||||
auto& tph = top_poly.holes.back().points;
|
||||
std::reverse(tph.begin(), tph.end());
|
||||
}
|
||||
|
||||
ExPolygon ob = outer_base; double wh = 0;
|
||||
ExPolygon ob; ob.contour = outer_base; double wh = 0;
|
||||
|
||||
// now we will calculate the angle or portion of the circle from
|
||||
// pi/2 that will connect perfectly with the bottom plate.
|
||||
|
@ -713,11 +871,56 @@ Contour3D create_base_pool(const ExPolygons &ground_layer,
|
|||
wh, -wingdist, thrcl));
|
||||
}
|
||||
|
||||
// Now we need to triangulate the top and bottom plates as well as the
|
||||
// cavity bottom plate which is the same as the bottom plate but it is
|
||||
// elevated by the thickness.
|
||||
pool.merge(triangulate_expolygon_3d(top_poly));
|
||||
pool.merge(triangulate_expolygon_3d(bottom_poly, -fullheight, true));
|
||||
if (cfg.embed_object) {
|
||||
ExPolygons pp = diff_ex(to_polygons(bottom_poly),
|
||||
to_polygons(obj_self_pad));
|
||||
|
||||
// Generate outer walls
|
||||
auto fp = [](const Point &p, Point::coord_type z) {
|
||||
return unscale(x(p), y(p), z);
|
||||
};
|
||||
|
||||
auto straight_walls = [&pool, s_thickness, fp](const Polygon &cntr)
|
||||
{
|
||||
auto lines = cntr.lines();
|
||||
bool cclk = cntr.is_counter_clockwise();
|
||||
|
||||
for (auto &l : lines) {
|
||||
auto s = coord_t(pool.points.size());
|
||||
pool.points.emplace_back(fp(l.a, -s_thickness));
|
||||
pool.points.emplace_back(fp(l.b, -s_thickness));
|
||||
pool.points.emplace_back(fp(l.a, 0));
|
||||
pool.points.emplace_back(fp(l.b, 0));
|
||||
|
||||
if(cclk) {
|
||||
pool.indices.emplace_back(s + 3, s + 1, s);
|
||||
pool.indices.emplace_back(s + 2, s + 3, s);
|
||||
} else {
|
||||
pool.indices.emplace_back(s, s + 1, s + 3);
|
||||
pool.indices.emplace_back(s, s + 3, s + 2);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for (ExPolygon &ep : pp) {
|
||||
pool.merge(triangulate_expolygon_3d(ep));
|
||||
pool.merge(triangulate_expolygon_3d(ep, -fullheight, true));
|
||||
|
||||
for (auto &h : ep.holes) straight_walls(h);
|
||||
}
|
||||
|
||||
// Skip the outer contour. TODO: make sure the first in the list
|
||||
// IS the outer contour.
|
||||
for (auto it = std::next(pp.begin()); it != pp.end(); ++it)
|
||||
straight_walls(it->contour);
|
||||
|
||||
} else {
|
||||
// Now we need to triangulate the top and bottom plates as well as
|
||||
// the cavity bottom plate which is the same as the bottom plate
|
||||
// but it is elevated by the thickness.
|
||||
pool.merge(triangulate_expolygon_3d(top_poly));
|
||||
pool.merge(triangulate_expolygon_3d(bottom_poly, -fullheight, true));
|
||||
}
|
||||
|
||||
if(wingheight > 0)
|
||||
pool.merge(triangulate_expolygon_3d(inner_base, -wingheight));
|
||||
|
@ -727,8 +930,8 @@ Contour3D create_base_pool(const ExPolygons &ground_layer,
|
|||
return pool;
|
||||
}
|
||||
|
||||
void create_base_pool(const ExPolygons &ground_layer, TriangleMesh& out,
|
||||
const PoolConfig& cfg)
|
||||
void create_base_pool(const Polygons &ground_layer, TriangleMesh& out,
|
||||
const ExPolygons &holes, const PoolConfig& cfg)
|
||||
{
|
||||
|
||||
|
||||
|
@ -738,7 +941,7 @@ void create_base_pool(const ExPolygons &ground_layer, TriangleMesh& out,
|
|||
// std::fstream fout("pad_debug.obj", std::fstream::out);
|
||||
// if(fout.good()) pool.to_obj(fout);
|
||||
|
||||
out.merge(mesh(create_base_pool(ground_layer, cfg)));
|
||||
out.merge(mesh(create_base_pool(ground_layer, holes, cfg)));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue