Reworked pad creation algorithm with new parameters:
* brim size * force pad around object everywhere
This commit is contained in:
parent
9d775d0a43
commit
e675a5d5c6
@ -16,9 +16,9 @@ const std::string USAGE_STR = {
|
||||
|
||||
namespace Slic3r { namespace sla {
|
||||
|
||||
Contour3D create_base_pool(const Polygons &ground_layer,
|
||||
Contour3D create_pad(const Polygons &ground_layer,
|
||||
const ExPolygons &holes = {},
|
||||
const PoolConfig& cfg = PoolConfig());
|
||||
const PadConfig& cfg = PadConfig());
|
||||
|
||||
Contour3D walls(const Polygon& floor_plate, const Polygon& ceiling,
|
||||
double floor_z_mm, double ceiling_z_mm,
|
||||
@ -45,7 +45,7 @@ int main(const int argc, const char *argv[]) {
|
||||
model.align_to_origin();
|
||||
|
||||
ExPolygons ground_slice;
|
||||
sla::base_plate(model, ground_slice, 0.1f);
|
||||
sla::pad_plate(model, ground_slice, 0.1f);
|
||||
if(ground_slice.empty()) return EXIT_FAILURE;
|
||||
|
||||
ground_slice = offset_ex(ground_slice, 0.5);
|
||||
@ -56,10 +56,10 @@ int main(const int argc, const char *argv[]) {
|
||||
|
||||
bench.start();
|
||||
|
||||
sla::PoolConfig cfg;
|
||||
sla::PadConfig cfg;
|
||||
cfg.min_wall_height_mm = 0;
|
||||
cfg.edge_radius_mm = 0;
|
||||
mesh = sla::create_base_pool(to_polygons(ground_slice), {}, cfg);
|
||||
mesh = sla::create_pad(to_polygons(ground_slice), {}, cfg);
|
||||
|
||||
bench.stop();
|
||||
|
||||
|
@ -176,8 +176,8 @@ add_library(libslic3r STATIC
|
||||
miniz_extension.cpp
|
||||
SLA/SLACommon.hpp
|
||||
SLA/SLABoilerPlate.hpp
|
||||
SLA/SLABasePool.hpp
|
||||
SLA/SLABasePool.cpp
|
||||
SLA/SLAPad.hpp
|
||||
SLA/SLAPad.cpp
|
||||
SLA/SLASupportTree.hpp
|
||||
SLA/SLASupportTree.cpp
|
||||
SLA/SLASupportTreeIGL.cpp
|
||||
@ -215,6 +215,7 @@ target_link_libraries(libslic3r
|
||||
qhull
|
||||
semver
|
||||
tbb
|
||||
${CMAKE_DL_LIBS}
|
||||
)
|
||||
|
||||
if(WIN32)
|
||||
|
@ -252,22 +252,15 @@ template<class T> struct remove_cvref
|
||||
|
||||
template<class T> using remove_cvref_t = typename remove_cvref<T>::type;
|
||||
|
||||
template<template<class> class C, class T>
|
||||
class Container : public C<remove_cvref_t<T>>
|
||||
{
|
||||
public:
|
||||
explicit Container(size_t count, T &&initval)
|
||||
: C<remove_cvref_t<T>>(count, initval)
|
||||
{}
|
||||
};
|
||||
|
||||
template<class T> using DefaultContainer = std::vector<T>;
|
||||
|
||||
/// Exactly like Matlab https://www.mathworks.com/help/matlab/ref/linspace.html
|
||||
template<class T, class I, template<class> class C = DefaultContainer>
|
||||
inline C<remove_cvref_t<T>> linspace(const T &start, const T &stop, const I &n)
|
||||
template<class T, class I, template<class> class Container = DefaultContainer>
|
||||
inline Container<remove_cvref_t<T>> linspace(const T &start,
|
||||
const T &stop,
|
||||
const I &n)
|
||||
{
|
||||
Container<C, T> vals(n, T());
|
||||
Container<remove_cvref_t<T>> vals(n, T());
|
||||
|
||||
T stride = (stop - start) / n;
|
||||
size_t i = 0;
|
||||
@ -282,10 +275,13 @@ inline C<remove_cvref_t<T>> linspace(const T &start, const T &stop, const I &n)
|
||||
/// in the closest multiple of 'stride' less than or equal to 'end' and
|
||||
/// leaving 'stride' space between each value.
|
||||
/// Very similar to Matlab [start:stride:end] notation.
|
||||
template<class T, template<class> class C = DefaultContainer>
|
||||
inline C<remove_cvref_t<T>> grid(const T &start, const T &stop, const T &stride)
|
||||
template<class T, template<class> class Container = DefaultContainer>
|
||||
inline Container<remove_cvref_t<T>> grid(const T &start,
|
||||
const T &stop,
|
||||
const T &stride)
|
||||
{
|
||||
Container<C, T> vals(size_t(std::ceil((stop - start) / stride)), T());
|
||||
Container<remove_cvref_t<T>>
|
||||
vals(size_t(std::ceil((stop - start) / stride)), T());
|
||||
|
||||
int i = 0;
|
||||
std::generate(vals.begin(), vals.end(), [&i, start, stride] {
|
||||
@ -387,10 +383,12 @@ unscaled(const Eigen::Matrix<Tin, N, EigenArgs...> &v) noexcept
|
||||
return v.template cast<Tout>() * SCALING_FACTOR;
|
||||
}
|
||||
|
||||
template<class T> inline std::vector<T> reserve_vector(size_t capacity)
|
||||
template<class T, class I, class... Args> // Arbitrary allocator can be used
|
||||
inline IntegerOnly<I, std::vector<T, Args...>> reserve_vector(I capacity)
|
||||
{
|
||||
std::vector<T> ret;
|
||||
ret.reserve(capacity);
|
||||
std::vector<T, Args...> ret;
|
||||
if (capacity > I(0)) ret.reserve(size_t(capacity));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -2694,6 +2694,17 @@ void PrintConfigDef::init_sla_params()
|
||||
def->max = 30;
|
||||
def->mode = comExpert;
|
||||
def->set_default_value(new ConfigOptionFloat(0.));
|
||||
|
||||
def = this->add("pad_brim_size", coFloat);
|
||||
def->label = L("Pad brim size");
|
||||
def->tooltip = L("How far should the pad extend around the contained geometry");
|
||||
def->category = L("Pad");
|
||||
// def->tooltip = L("");
|
||||
def->sidetext = L("mm");
|
||||
def->min = 0;
|
||||
def->max = 30;
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloat(1.6));
|
||||
|
||||
def = this->add("pad_max_merge_distance", coFloat);
|
||||
def->label = L("Max merge distance");
|
||||
@ -2734,6 +2745,13 @@ void PrintConfigDef::init_sla_params()
|
||||
def->tooltip = L("Create pad around object and ignore the support elevation");
|
||||
def->mode = comSimple;
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
|
||||
def = this->add("pad_around_object_everywhere", coBool);
|
||||
def->label = L("Pad around object everywhere");
|
||||
def->category = L("Pad");
|
||||
def->tooltip = L("Force pad around object everywhere");
|
||||
def->mode = comSimple;
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
|
||||
def = this->add("pad_object_gap", coFloat);
|
||||
def->label = L("Pad object gap");
|
||||
|
@ -1022,6 +1022,9 @@ public:
|
||||
|
||||
// The height of the pad from the bottom to the top not considering the pit
|
||||
ConfigOptionFloat pad_wall_height /*= 5*/;
|
||||
|
||||
// How far should the pad extend around the contained geometry
|
||||
ConfigOptionFloat pad_brim_size;
|
||||
|
||||
// The greatest distance where two individual pads are merged into one. The
|
||||
// distance is measured roughly from the centroids of the pads.
|
||||
@ -1042,7 +1045,9 @@ public:
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Disable the elevation (ignore its value) and use the zero elevation mode
|
||||
ConfigOptionBool pad_around_object;
|
||||
ConfigOptionBool pad_around_object;
|
||||
|
||||
ConfigOptionBool pad_around_object_everywhere;
|
||||
|
||||
// This is the gap between the object bottom and the generated pad
|
||||
ConfigOptionFloat pad_object_gap;
|
||||
@ -1082,10 +1087,12 @@ protected:
|
||||
OPT_PTR(pad_enable);
|
||||
OPT_PTR(pad_wall_thickness);
|
||||
OPT_PTR(pad_wall_height);
|
||||
OPT_PTR(pad_brim_size);
|
||||
OPT_PTR(pad_max_merge_distance);
|
||||
// OPT_PTR(pad_edge_radius);
|
||||
OPT_PTR(pad_wall_slope);
|
||||
OPT_PTR(pad_around_object);
|
||||
OPT_PTR(pad_around_object_everywhere);
|
||||
OPT_PTR(pad_object_gap);
|
||||
OPT_PTR(pad_object_connector_stride);
|
||||
OPT_PTR(pad_object_connector_width);
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include <random>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace sla {
|
||||
|
||||
/*float SLAAutoSupports::approximate_geodesic_distance(const Vec3d& p1, const Vec3d& p2, Vec3d& n1, Vec3d& n2)
|
||||
{
|
||||
@ -48,9 +49,16 @@ float SLAAutoSupports::distance_limit(float angle) const
|
||||
return 1./(2.4*get_required_density(angle));
|
||||
}*/
|
||||
|
||||
SLAAutoSupports::SLAAutoSupports(const TriangleMesh& mesh, const sla::EigenMesh3D& emesh, const std::vector<ExPolygons>& slices, const std::vector<float>& heights,
|
||||
const Config& config, std::function<void(void)> throw_on_cancel, std::function<void(int)> statusfn)
|
||||
: m_config(config), m_emesh(emesh), m_throw_on_cancel(throw_on_cancel), m_statusfn(statusfn)
|
||||
SLAAutoSupports::SLAAutoSupports(const sla::EigenMesh3D & emesh,
|
||||
const std::vector<ExPolygons> &slices,
|
||||
const std::vector<float> & heights,
|
||||
const Config & config,
|
||||
std::function<void(void)> throw_on_cancel,
|
||||
std::function<void(int)> statusfn)
|
||||
: m_config(config)
|
||||
, m_emesh(emesh)
|
||||
, m_throw_on_cancel(throw_on_cancel)
|
||||
, m_statusfn(statusfn)
|
||||
{
|
||||
process(slices, heights);
|
||||
project_onto_mesh(m_output);
|
||||
@ -505,6 +513,21 @@ void SLAAutoSupports::uniformly_cover(const ExPolygons& islands, Structure& stru
|
||||
}
|
||||
}
|
||||
|
||||
void remove_bottom_points(std::vector<SupportPoint> &pts, double gnd_lvl, double tolerance)
|
||||
{
|
||||
// get iterator to the reorganized vector end
|
||||
auto endit =
|
||||
std::remove_if(pts.begin(), pts.end(),
|
||||
[tolerance, gnd_lvl](const sla::SupportPoint &sp) {
|
||||
double diff = std::abs(gnd_lvl -
|
||||
double(sp.pos(Z)));
|
||||
return diff <= tolerance;
|
||||
});
|
||||
|
||||
// erase all elements after the new end
|
||||
pts.erase(endit, pts.end());
|
||||
}
|
||||
|
||||
#ifdef SLA_AUTOSUPPORTS_DEBUG
|
||||
void SLAAutoSupports::output_structures(const std::vector<Structure>& structures)
|
||||
{
|
||||
@ -533,4 +556,5 @@ void SLAAutoSupports::output_expolygons(const ExPolygons& expolys, const std::st
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace sla
|
||||
} // namespace Slic3r
|
||||
|
@ -11,20 +11,22 @@
|
||||
// #define SLA_AUTOSUPPORTS_DEBUG
|
||||
|
||||
namespace Slic3r {
|
||||
namespace sla {
|
||||
|
||||
class SLAAutoSupports {
|
||||
public:
|
||||
struct Config {
|
||||
float density_relative;
|
||||
float minimal_distance;
|
||||
float head_diameter;
|
||||
float density_relative {1.f};
|
||||
float minimal_distance {1.f};
|
||||
float head_diameter {0.4f};
|
||||
///////////////
|
||||
inline float support_force() const { return 7.7f / density_relative; } // a force one point can support (arbitrary force unit)
|
||||
inline float tear_pressure() const { return 1.f; } // pressure that the display exerts (the force unit per mm2)
|
||||
};
|
||||
|
||||
SLAAutoSupports(const TriangleMesh& mesh, const sla::EigenMesh3D& emesh, const std::vector<ExPolygons>& slices,
|
||||
SLAAutoSupports(const sla::EigenMesh3D& emesh, const std::vector<ExPolygons>& slices,
|
||||
const std::vector<float>& heights, const Config& config, std::function<void(void)> throw_on_cancel, std::function<void(int)> statusfn);
|
||||
|
||||
const std::vector<sla::SupportPoint>& output() { return m_output; }
|
||||
|
||||
struct MyLayer;
|
||||
@ -199,7 +201,9 @@ private:
|
||||
std::function<void(int)> m_statusfn;
|
||||
};
|
||||
|
||||
void remove_bottom_points(std::vector<SupportPoint> &pts, double gnd_lvl, double tolerance);
|
||||
|
||||
} // namespace sla
|
||||
} // namespace Slic3r
|
||||
|
||||
|
||||
|
@ -1,922 +0,0 @@
|
||||
#include "SLABasePool.hpp"
|
||||
#include "SLABoilerPlate.hpp"
|
||||
|
||||
#include "boost/log/trivial.hpp"
|
||||
#include "SLABoostAdapter.hpp"
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "Tesselate.hpp"
|
||||
#include "MTUtils.hpp"
|
||||
|
||||
// For debugging:
|
||||
// #include <fstream>
|
||||
// #include <libnest2d/tools/benchmark.h>
|
||||
// #include "SVG.hpp"
|
||||
|
||||
namespace Slic3r { namespace sla {
|
||||
|
||||
/// This function will return a triangulation of a sheet connecting an upper
|
||||
/// and a lower plate given as input polygons. It will not triangulate the
|
||||
/// plates themselves only the sheet. The caller has to specify the lower and
|
||||
/// upper z levels in world coordinates as well as the offset difference
|
||||
/// between the sheets. If the lower_z_mm is higher than upper_z_mm or the
|
||||
/// offset difference is negative, the resulting triangle orientation will be
|
||||
/// reversed.
|
||||
///
|
||||
/// IMPORTANT: This is not a universal triangulation algorithm. It assumes
|
||||
/// that the lower and upper polygons are offsetted versions of the same
|
||||
/// original polygon. In general, it assumes that one of the polygons is
|
||||
/// completely inside the other. The offset difference is the reference
|
||||
/// distance from the inner polygon's perimeter to the outer polygon's
|
||||
/// perimeter. The real distance will be variable as the clipper offset has
|
||||
/// different strategies (rounding, etc...). This algorithm should have
|
||||
/// O(2n + 3m) complexity where n is the number of upper vertices and m is the
|
||||
/// number of lower vertices.
|
||||
Contour3D walls(const Polygon& lower, const Polygon& upper,
|
||||
double lower_z_mm, double upper_z_mm,
|
||||
double offset_difference_mm, ThrowOnCancel thr)
|
||||
{
|
||||
Contour3D ret;
|
||||
|
||||
if(upper.points.size() < 3 || lower.size() < 3) return ret;
|
||||
|
||||
// The concept of the algorithm is relatively simple. It will try to find
|
||||
// the closest vertices from the upper and the lower polygon and use those
|
||||
// as starting points. Then it will create the triangles sequentially using
|
||||
// an edge from the upper polygon and a vertex from the lower or vice versa,
|
||||
// depending on the resulting triangle's quality.
|
||||
// The quality is measured by a scalar value. So far it looks like it is
|
||||
// enough to derive it from the slope of the triangle's two edges connecting
|
||||
// the upper and the lower part. A reference slope is calculated from the
|
||||
// height and the offset difference.
|
||||
|
||||
// Offset in the index array for the ceiling
|
||||
const auto offs = upper.points.size();
|
||||
|
||||
// Shorthand for the vertex arrays
|
||||
auto& upoints = upper.points, &lpoints = lower.points;
|
||||
auto& rpts = ret.points; auto& ind = ret.indices;
|
||||
|
||||
// If the Z levels are flipped, or the offset difference is negative, we
|
||||
// will interpret that as the triangles normals should be inverted.
|
||||
bool inverted = upper_z_mm < lower_z_mm || offset_difference_mm < 0;
|
||||
|
||||
// Copy the points into the mesh, convert them from 2D to 3D
|
||||
rpts.reserve(upoints.size() + lpoints.size());
|
||||
ind.reserve(2 * upoints.size() + 2 * lpoints.size());
|
||||
for (auto &p : upoints)
|
||||
rpts.emplace_back(unscaled(p.x()), unscaled(p.y()), upper_z_mm);
|
||||
for (auto &p : lpoints)
|
||||
rpts.emplace_back(unscaled(p.x()), unscaled(p.y()), lower_z_mm);
|
||||
|
||||
// Create pointing indices into vertex arrays. u-upper, l-lower
|
||||
size_t uidx = 0, lidx = offs, unextidx = 1, lnextidx = offs + 1;
|
||||
|
||||
// Simple squared distance calculation.
|
||||
auto distfn = [](const Vec3d& p1, const Vec3d& p2) {
|
||||
auto p = p1 - p2; return p.transpose() * p;
|
||||
};
|
||||
|
||||
// We need to find the closest point on lower polygon to the first point on
|
||||
// the upper polygon. These will be our starting points.
|
||||
double distmin = std::numeric_limits<double>::max();
|
||||
for(size_t l = lidx; l < rpts.size(); ++l) {
|
||||
thr();
|
||||
double d = distfn(rpts[l], rpts[uidx]);
|
||||
if(d < distmin) { lidx = l; distmin = d; }
|
||||
}
|
||||
|
||||
// Set up lnextidx to be ahead of lidx in cyclic mode
|
||||
lnextidx = lidx + 1;
|
||||
if(lnextidx == rpts.size()) lnextidx = offs;
|
||||
|
||||
// This will be the flip switch to toggle between upper and lower triangle
|
||||
// creation mode
|
||||
enum class Proceed {
|
||||
UPPER, // A segment from the upper polygon and one vertex from the lower
|
||||
LOWER // A segment from the lower polygon and one vertex from the upper
|
||||
} proceed = Proceed::UPPER;
|
||||
|
||||
// Flags to help evaluating loop termination.
|
||||
bool ustarted = false, lstarted = false;
|
||||
|
||||
// The variables for the fitness values, one for the actual and one for the
|
||||
// previous.
|
||||
double current_fit = 0, prev_fit = 0;
|
||||
|
||||
// Every triangle of the wall has two edges connecting the upper plate with
|
||||
// the lower plate. From the length of these two edges and the zdiff we
|
||||
// can calculate the momentary squared offset distance at a particular
|
||||
// position on the wall. The average of the differences from the reference
|
||||
// (squared) offset distance will give us the driving fitness value.
|
||||
const double offsdiff2 = std::pow(offset_difference_mm, 2);
|
||||
const double zdiff2 = std::pow(upper_z_mm - lower_z_mm, 2);
|
||||
|
||||
// Mark the current vertex iterator positions. If the iterators return to
|
||||
// the same position, the loop can be terminated.
|
||||
size_t uendidx = uidx, lendidx = lidx;
|
||||
|
||||
do { thr(); // check throw if canceled
|
||||
|
||||
prev_fit = current_fit;
|
||||
|
||||
switch(proceed) { // proceed depending on the current state
|
||||
case Proceed::UPPER:
|
||||
if(!ustarted || uidx != uendidx) { // there are vertices remaining
|
||||
// Get the 3D vertices in order
|
||||
const Vec3d& p_up1 = rpts[uidx];
|
||||
const Vec3d& p_low = rpts[lidx];
|
||||
const Vec3d& p_up2 = rpts[unextidx];
|
||||
|
||||
// Calculate fitness: the average of the two connecting edges
|
||||
double a = offsdiff2 - (distfn(p_up1, p_low) - zdiff2);
|
||||
double b = offsdiff2 - (distfn(p_up2, p_low) - zdiff2);
|
||||
current_fit = (std::abs(a) + std::abs(b)) / 2;
|
||||
|
||||
if(current_fit > prev_fit) { // fit is worse than previously
|
||||
proceed = Proceed::LOWER;
|
||||
} else { // good to go, create the triangle
|
||||
inverted
|
||||
? ind.emplace_back(int(unextidx), int(lidx), int(uidx))
|
||||
: ind.emplace_back(int(uidx), int(lidx), int(unextidx));
|
||||
|
||||
// Increment the iterators, rotate if necessary
|
||||
++uidx; ++unextidx;
|
||||
if(unextidx == offs) unextidx = 0;
|
||||
if(uidx == offs) uidx = 0;
|
||||
|
||||
ustarted = true; // mark the movement of the iterators
|
||||
// so that the comparison to uendidx can be made correctly
|
||||
}
|
||||
} else proceed = Proceed::LOWER;
|
||||
|
||||
break;
|
||||
case Proceed::LOWER:
|
||||
// Mode with lower segment, upper vertex. Same structure:
|
||||
if(!lstarted || lidx != lendidx) {
|
||||
const Vec3d& p_low1 = rpts[lidx];
|
||||
const Vec3d& p_low2 = rpts[lnextidx];
|
||||
const Vec3d& p_up = rpts[uidx];
|
||||
|
||||
double a = offsdiff2 - (distfn(p_up, p_low1) - zdiff2);
|
||||
double b = offsdiff2 - (distfn(p_up, p_low2) - zdiff2);
|
||||
current_fit = (std::abs(a) + std::abs(b)) / 2;
|
||||
|
||||
if(current_fit > prev_fit) {
|
||||
proceed = Proceed::UPPER;
|
||||
} else {
|
||||
inverted
|
||||
? ind.emplace_back(int(uidx), int(lnextidx), int(lidx))
|
||||
: ind.emplace_back(int(lidx), int(lnextidx), int(uidx));
|
||||
|
||||
++lidx; ++lnextidx;
|
||||
if(lnextidx == rpts.size()) lnextidx = offs;
|
||||
if(lidx == rpts.size()) lidx = offs;
|
||||
|
||||
lstarted = true;
|
||||
}
|
||||
} else proceed = Proceed::UPPER;
|
||||
|
||||
break;
|
||||
} // end of switch
|
||||
} while(!ustarted || !lstarted || uidx != uendidx || lidx != lendidx);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// Offsetting with clipper and smoothing the edges into a curvature.
|
||||
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;
|
||||
|
||||
auto&& ctour = Slic3rMultiPoint_to_ClipperPath(sh.contour);
|
||||
auto&& holes = Slic3rMultiPoints_to_ClipperPaths(sh.holes);
|
||||
|
||||
// If the input is not at least a triangle, we can not do this algorithm
|
||||
if(ctour.size() < 3 ||
|
||||
std::any_of(holes.begin(), holes.end(),
|
||||
[](const Path& p) { return p.size() < 3; })
|
||||
) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Invalid geometry for offsetting!";
|
||||
return;
|
||||
}
|
||||
|
||||
auto jointype = edgerounding? jtRound : jtMiter;
|
||||
|
||||
ClipperOffset offs;
|
||||
offs.ArcTolerance = scaled<double>(0.01);
|
||||
Paths result;
|
||||
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
|
||||
// so boost will not have a closed polygon.
|
||||
|
||||
bool found_the_contour = false;
|
||||
sh.holes.clear();
|
||||
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.contour.points.swap(rr.points);
|
||||
found_the_contour = true;
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(warning)
|
||||
<< "Warning: offsetting result is invalid!";
|
||||
}
|
||||
} else {
|
||||
// TODO If there are multiple contours we can't be sure which hole
|
||||
// belongs to the first contour. (But in this case the situation is
|
||||
// bad enough to let it go...)
|
||||
sh.holes.emplace_back(ClipperPath_to_Slic3rPolygon(r));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 * scaled(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;
|
||||
|
||||
ExPolygons retv;
|
||||
|
||||
bool closed = true;
|
||||
bool valid = true;
|
||||
|
||||
ClipperLib::Clipper clipper;
|
||||
|
||||
for(auto& path : shapes) {
|
||||
auto clipperpath = Slic3rMultiPoint_to_ClipperPath(path.contour);
|
||||
|
||||
if(!clipperpath.empty())
|
||||
valid &= clipper.AddPath(clipperpath, ptSubject, closed);
|
||||
|
||||
auto clipperholes = Slic3rMultiPoints_to_ClipperPaths(path.holes);
|
||||
|
||||
for(auto& hole : clipperholes) {
|
||||
if(!hole.empty())
|
||||
valid &= clipper.AddPath(hole, ptSubject, closed);
|
||||
}
|
||||
}
|
||||
|
||||
if(!valid) BOOST_LOG_TRIVIAL(warning) << "Unification of invalid shapes!";
|
||||
|
||||
ClipperLib::PolyTree result;
|
||||
clipper.Execute(ClipperLib::ctUnion, result, ClipperLib::pftNonZero);
|
||||
|
||||
retv.reserve(static_cast<size_t>(result.Total()));
|
||||
|
||||
// Now we will recursively traverse the polygon tree and serialize it
|
||||
// into an ExPolygon with holes. The polygon tree has the clipper-ish
|
||||
// PolyTree structure which alternates its nodes as contours and holes
|
||||
|
||||
// A "declaration" of function for traversing leafs which are holes
|
||||
std::function<void(ClipperLib::PolyNode*, ExPolygon&)> processHole;
|
||||
|
||||
// Process polygon which calls processHoles which than calls processPoly
|
||||
// again until no leafs are left.
|
||||
auto processPoly = [&retv, &processHole](ClipperLib::PolyNode *pptr) {
|
||||
ExPolygon poly;
|
||||
poly.contour.points = ClipperPath_to_Slic3rPolygon(pptr->Contour);
|
||||
for(auto h : pptr->Childs) { processHole(h, poly); }
|
||||
retv.push_back(poly);
|
||||
};
|
||||
|
||||
// Body of the processHole function
|
||||
processHole = [&processPoly](ClipperLib::PolyNode *pptr, ExPolygon& poly)
|
||||
{
|
||||
poly.holes.emplace_back();
|
||||
poly.holes.back().points = ClipperPath_to_Slic3rPolygon(pptr->Contour);
|
||||
for(auto c : pptr->Childs) processPoly(c);
|
||||
};
|
||||
|
||||
// Wrapper for traversing.
|
||||
auto traverse = [&processPoly] (ClipperLib::PolyNode *node)
|
||||
{
|
||||
for(auto ch : node->Childs) {
|
||||
processPoly(ch);
|
||||
}
|
||||
};
|
||||
|
||||
// Here is the actual traverse
|
||||
traverse(&result);
|
||||
|
||||
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 breakstick_holes(ExPolygon& poly,
|
||||
double padding,
|
||||
double stride,
|
||||
double stick_width,
|
||||
double penetration)
|
||||
{
|
||||
// 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 = scaled(stick_width);
|
||||
double sright = scaled(penetration + padding);
|
||||
|
||||
// scaled stride distance
|
||||
double sstride = scaled(stride);
|
||||
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 < sbottom) t += sbottom;
|
||||
double tend = nrm - sbottom;
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
if(stride > 0.0 && stick_width > 0.0 && padding > 0.0) {
|
||||
transf(poly.contour.points);
|
||||
for (auto &h : poly.holes) transf(h.points);
|
||||
}
|
||||
|
||||
// svg.draw(poly);
|
||||
// svg.Close();
|
||||
}
|
||||
|
||||
/// This method will create a rounded edge around a flat polygon in 3d space.
|
||||
/// 'base_plate' parameter is the target plate.
|
||||
/// 'radius' is the radius of the edges.
|
||||
/// 'degrees' is tells how much of a circle should be created as the rounding.
|
||||
/// It should be in degrees, not radians.
|
||||
/// 'ceilheight_mm' is the Z coordinate of the flat polygon in 3D space.
|
||||
/// 'dir' Is the direction of the round edges: inward or outward
|
||||
/// 'thr' Throws if a cancel signal was received
|
||||
/// 'last_offset' An auxiliary output variable to save the last offsetted
|
||||
/// version of 'base_plate'
|
||||
/// 'last_height' An auxiliary output to save the last z coordinate of the
|
||||
/// offsetted base_plate. In other words, where the rounded edges end.
|
||||
Contour3D round_edges(const ExPolygon& base_plate,
|
||||
double radius_mm,
|
||||
double degrees,
|
||||
double ceilheight_mm,
|
||||
bool dir,
|
||||
ThrowOnCancel thr,
|
||||
ExPolygon& last_offset, double& last_height)
|
||||
{
|
||||
auto ob = base_plate;
|
||||
auto ob_prev = ob;
|
||||
double wh = ceilheight_mm, wh_prev = wh;
|
||||
Contour3D curvedwalls;
|
||||
|
||||
int steps = 30;
|
||||
double stepx = radius_mm / steps;
|
||||
coord_t s = dir? 1 : -1;
|
||||
degrees = std::fmod(degrees, 180);
|
||||
|
||||
// we use sin for x distance because we interpret the angle starting from
|
||||
// PI/2
|
||||
int tos = degrees < 90?
|
||||
int(radius_mm*std::cos(degrees * PI / 180 - PI/2) / stepx) : steps;
|
||||
|
||||
for(int i = 1; i <= tos; ++i) {
|
||||
thr();
|
||||
|
||||
ob = base_plate;
|
||||
|
||||
double r2 = radius_mm * radius_mm;
|
||||
double xx = i*stepx;
|
||||
double x2 = xx*xx;
|
||||
double stepy = std::sqrt(r2 - x2);
|
||||
|
||||
offset(ob, s * scaled(xx));
|
||||
wh = ceilheight_mm - radius_mm + stepy;
|
||||
|
||||
Contour3D pwalls;
|
||||
double prev_x = xx - (i - 1) * stepx;
|
||||
pwalls = walls(ob.contour, ob_prev.contour, wh, wh_prev, s*prev_x, thr);
|
||||
|
||||
curvedwalls.merge(pwalls);
|
||||
ob_prev = ob;
|
||||
wh_prev = wh;
|
||||
}
|
||||
|
||||
if(degrees > 90) {
|
||||
double tox = radius_mm - radius_mm*std::cos(degrees * PI / 180 - PI/2);
|
||||
int tos = int(tox / stepx);
|
||||
|
||||
for(int i = 1; i <= tos; ++i) {
|
||||
thr();
|
||||
ob = base_plate;
|
||||
|
||||
double r2 = radius_mm * radius_mm;
|
||||
double xx = radius_mm - i*stepx;
|
||||
double x2 = xx*xx;
|
||||
double stepy = std::sqrt(r2 - x2);
|
||||
offset(ob, s * scaled(xx));
|
||||
wh = ceilheight_mm - radius_mm - stepy;
|
||||
|
||||
Contour3D pwalls;
|
||||
double prev_x = xx - radius_mm + (i - 1)*stepx;
|
||||
pwalls =
|
||||
walls(ob_prev.contour, ob.contour, wh_prev, wh, s*prev_x, thr);
|
||||
|
||||
curvedwalls.merge(pwalls);
|
||||
ob_prev = ob;
|
||||
wh_prev = wh;
|
||||
}
|
||||
}
|
||||
|
||||
last_offset = std::move(ob);
|
||||
last_height = wh;
|
||||
|
||||
return curvedwalls;
|
||||
}
|
||||
|
||||
inline Point centroid(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;
|
||||
|
||||
// TODO: fails for non convex cluster
|
||||
// c = std::accumulate(pp.begin(), pp.end(), Point{0, 0});
|
||||
// x(c) /= coord_t(pp.size()); y(c) /= coord_t(pp.size());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
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...)
|
||||
Polygons concave_hull(const Polygons& polys, double maxd_mm, ThrowOnCancel thr)
|
||||
{
|
||||
namespace bgi = boost::geometry::index;
|
||||
using SpatElement = std::pair<Point, unsigned>;
|
||||
using SpatIndex = bgi::rtree< SpatElement, bgi::rstar<16, 4> >;
|
||||
|
||||
if(polys.empty()) return Polygons();
|
||||
|
||||
const double max_dist = scaled(maxd_mm);
|
||||
|
||||
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 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);
|
||||
|
||||
punion.reserve(punion.size() + centroids.size());
|
||||
|
||||
idx = 0;
|
||||
std::transform(centroids.begin(), centroids.end(),
|
||||
std::back_inserter(punion),
|
||||
[¢roids, &ctrindex, cc, max_dist, &idx, thr]
|
||||
(const Point& c)
|
||||
{
|
||||
thr();
|
||||
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;
|
||||
|
||||
Point& ct = centroids[idx];
|
||||
|
||||
std::vector<SpatElement> result;
|
||||
ctrindex.query(bgi::nearest(ct, 2), std::back_inserter(result));
|
||||
|
||||
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);
|
||||
|
||||
Point d(scaled(nx), scaled(ny));
|
||||
ctour.emplace_back(c + Point( -y(d), x(d) ));
|
||||
ctour.emplace_back(c + Point( y(d), -x(d) ));
|
||||
offset(r, scaled(1.));
|
||||
|
||||
return r;
|
||||
});
|
||||
|
||||
// This is unavoidable...
|
||||
punion = unify(punion);
|
||||
|
||||
return punion;
|
||||
}
|
||||
|
||||
void base_plate(const TriangleMesh & mesh,
|
||||
ExPolygons & output,
|
||||
const std::vector<float> &heights,
|
||||
ThrowOnCancel thrfn)
|
||||
{
|
||||
if (mesh.empty()) return;
|
||||
// m.require_shared_vertices(); // TriangleMeshSlicer needs this
|
||||
TriangleMeshSlicer slicer(&mesh);
|
||||
|
||||
std::vector<ExPolygons> out; out.reserve(heights.size());
|
||||
slicer.slice(heights, 0.f, &out, thrfn);
|
||||
|
||||
size_t count = 0; for(auto& o : out) count += o.size();
|
||||
|
||||
// Now we have to unify all slice layers which can be an expensive operation
|
||||
// so we will try to simplify the polygons
|
||||
ExPolygons tmp; tmp.reserve(count);
|
||||
for(ExPolygons& o : out)
|
||||
for(ExPolygon& e : o) {
|
||||
auto&& exss = e.simplify(scaled<double>(0.1));
|
||||
for(ExPolygon& ep : exss) tmp.emplace_back(std::move(ep));
|
||||
}
|
||||
|
||||
ExPolygons utmp = unify(tmp);
|
||||
|
||||
for(auto& o : utmp) {
|
||||
auto&& smp = o.simplify(scaled<double>(0.1));
|
||||
output.insert(output.end(), smp.begin(), smp.end());
|
||||
}
|
||||
}
|
||||
|
||||
void base_plate(const TriangleMesh &mesh,
|
||||
ExPolygons & output,
|
||||
float h,
|
||||
float layerh,
|
||||
ThrowOnCancel thrfn)
|
||||
{
|
||||
auto bb = mesh.bounding_box();
|
||||
float gnd = float(bb.min(Z));
|
||||
std::vector<float> heights = {float(bb.min(Z))};
|
||||
|
||||
for(float hi = gnd + layerh; hi <= gnd + h; hi += layerh)
|
||||
heights.emplace_back(hi);
|
||||
|
||||
base_plate(mesh, output, heights, thrfn);
|
||||
}
|
||||
|
||||
Contour3D create_base_pool(const Polygons &ground_layer,
|
||||
const ExPolygons &obj_self_pad = {},
|
||||
const PoolConfig& cfg = PoolConfig())
|
||||
{
|
||||
// for debugging:
|
||||
// Benchmark bench;
|
||||
// bench.start();
|
||||
|
||||
double mergedist = 2*(1.8*cfg.min_wall_thickness_mm + 4*cfg.edge_radius_mm)+
|
||||
cfg.max_merge_distance_mm;
|
||||
|
||||
// Here we get the base polygon from which the pad has to be generated.
|
||||
// We create an artificial concave hull from this polygon and that will
|
||||
// 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.
|
||||
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;
|
||||
const double fullheight = wingheight + thickness;
|
||||
const double slope = cfg.wall_slope;
|
||||
const double wingdist = wingheight / std::tan(slope);
|
||||
const double bottom_offs = (thickness + wingheight) / std::tan(slope);
|
||||
|
||||
// scaled values
|
||||
const coord_t s_thickness = scaled(thickness);
|
||||
const coord_t s_eradius = scaled(cfg.edge_radius_mm);
|
||||
const coord_t s_safety_dist = 2*s_eradius + coord_t(0.8*s_thickness);
|
||||
const coord_t s_wingdist = scaled(wingdist);
|
||||
const coord_t s_bottom_offs = scaled(bottom_offs);
|
||||
|
||||
auto& thrcl = cfg.throw_on_cancel;
|
||||
|
||||
Contour3D pool;
|
||||
|
||||
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;
|
||||
offset(outer_base, s_safety_dist + s_wingdist + s_thickness);
|
||||
|
||||
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;
|
||||
|
||||
if(wingheight > 0) {
|
||||
inner_base.contour = outer_base;
|
||||
offset(inner_base, -(s_thickness + s_wingdist + s_eradius));
|
||||
|
||||
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; 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.
|
||||
// this is a tangent point calculation problem and the equation can
|
||||
// be found for example here:
|
||||
// http://www.ambrsoft.com/TrigoCalc/Circles2/CirclePoint/CirclePointDistance.htm
|
||||
// the y coordinate would be:
|
||||
// y = cy + (r^2*py - r*px*sqrt(px^2 + py^2 - r^2) / (px^2 + py^2)
|
||||
// where px and py are the coordinates of the point outside the circle
|
||||
// cx and cy are the circle center, r is the radius
|
||||
// We place the circle center to (0, 0) in the calculation the make
|
||||
// things easier.
|
||||
// to get the angle we use arcsin function and subtract 90 degrees then
|
||||
// flip the sign to get the right input to the round_edge function.
|
||||
double r = cfg.edge_radius_mm;
|
||||
double cy = 0;
|
||||
double cx = 0;
|
||||
double px = thickness + wingdist;
|
||||
double py = r - fullheight;
|
||||
|
||||
double pxcx = px - cx;
|
||||
double pycy = py - cy;
|
||||
double b_2 = pxcx*pxcx + pycy*pycy;
|
||||
double r_2 = r*r;
|
||||
double D = std::sqrt(b_2 - r_2);
|
||||
double vy = (r_2*pycy - r*pxcx*D) / b_2;
|
||||
double phi = -(std::asin(vy/r) * 180 / PI - 90);
|
||||
|
||||
|
||||
// Generate the smoothed edge geometry
|
||||
if(s_eradius > 0) pool.merge(round_edges(ob,
|
||||
r,
|
||||
phi,
|
||||
0, // z position of the input plane
|
||||
true,
|
||||
thrcl,
|
||||
ob, wh));
|
||||
|
||||
// Now that we have the rounded edge connecting the top plate with
|
||||
// the outer side walls, we can generate and merge the sidewall geometry
|
||||
pool.merge(walls(ob.contour, bottom_poly.contour, wh, -fullheight,
|
||||
bottom_offs, thrcl));
|
||||
|
||||
if(wingheight > 0) {
|
||||
// Generate the smoothed edge geometry
|
||||
wh = 0;
|
||||
ob = middle_base;
|
||||
if(s_eradius) pool.merge(round_edges(middle_base,
|
||||
r,
|
||||
phi - 90, // from tangent lines
|
||||
0, // z position of the input plane
|
||||
false,
|
||||
thrcl,
|
||||
ob, wh));
|
||||
|
||||
// Next is the cavity walls connecting to the top plate's
|
||||
// artificially created hole.
|
||||
pool.merge(walls(inner_base.contour, ob.contour, -wingheight,
|
||||
wh, -wingdist, thrcl));
|
||||
}
|
||||
|
||||
if (cfg.embed_object) {
|
||||
ExPolygons bttms = diff_ex(to_polygons(bottom_poly),
|
||||
to_polygons(obj_self_pad));
|
||||
|
||||
assert(!bttms.empty());
|
||||
|
||||
std::sort(bttms.begin(), bttms.end(),
|
||||
[](const ExPolygon& e1, const ExPolygon& e2) {
|
||||
return e1.contour.area() > e2.contour.area();
|
||||
});
|
||||
|
||||
if(wingheight > 0) inner_base.holes = bttms.front().holes;
|
||||
else top_poly.holes = bttms.front().holes;
|
||||
|
||||
auto straight_walls =
|
||||
[&pool](const Polygon &cntr, coord_t z_low, coord_t z_high) {
|
||||
|
||||
auto lines = cntr.lines();
|
||||
|
||||
for (auto &l : lines) {
|
||||
auto s = coord_t(pool.points.size());
|
||||
auto& pts = pool.points;
|
||||
pts.emplace_back(unscale(l.a.x(), l.a.y(), z_low));
|
||||
pts.emplace_back(unscale(l.b.x(), l.b.y(), z_low));
|
||||
pts.emplace_back(unscale(l.a.x(), l.a.y(), z_high));
|
||||
pts.emplace_back(unscale(l.b.x(), l.b.y(), z_high));
|
||||
|
||||
pool.indices.emplace_back(s, s + 1, s + 3);
|
||||
pool.indices.emplace_back(s, s + 3, s + 2);
|
||||
}
|
||||
};
|
||||
|
||||
coord_t z_lo = -scaled(fullheight), z_hi = -scaled(wingheight);
|
||||
for (ExPolygon &ep : bttms) {
|
||||
pool.merge(triangulate_expolygon_3d(ep, -fullheight, true));
|
||||
for (auto &h : ep.holes) straight_walls(h, z_lo, z_hi);
|
||||
}
|
||||
|
||||
// Skip the outer contour, triangulate the holes
|
||||
for (auto it = std::next(bttms.begin()); it != bttms.end(); ++it) {
|
||||
pool.merge(triangulate_expolygon_3d(*it, -wingheight));
|
||||
straight_walls(it->contour, z_lo, z_hi);
|
||||
}
|
||||
|
||||
} 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(bottom_poly, -fullheight, true));
|
||||
}
|
||||
|
||||
pool.merge(triangulate_expolygon_3d(top_poly));
|
||||
|
||||
if(wingheight > 0)
|
||||
pool.merge(triangulate_expolygon_3d(inner_base, -wingheight));
|
||||
|
||||
}
|
||||
|
||||
return pool;
|
||||
}
|
||||
|
||||
void create_base_pool(const Polygons &ground_layer, TriangleMesh& out,
|
||||
const ExPolygons &holes, const PoolConfig& cfg)
|
||||
{
|
||||
|
||||
|
||||
// For debugging:
|
||||
// bench.stop();
|
||||
// std::cout << "Pad creation time: " << bench.getElapsedSec() << std::endl;
|
||||
// std::fstream fout("pad_debug.obj", std::fstream::out);
|
||||
// if(fout.good()) pool.to_obj(fout);
|
||||
|
||||
out.merge(mesh(create_base_pool(ground_layer, holes, cfg)));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -1,92 +0,0 @@
|
||||
#ifndef SLABASEPOOL_HPP
|
||||
#define SLABASEPOOL_HPP
|
||||
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <cmath>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class ExPolygon;
|
||||
class Polygon;
|
||||
using ExPolygons = std::vector<ExPolygon>;
|
||||
using Polygons = std::vector<Polygon>;
|
||||
|
||||
class TriangleMesh;
|
||||
|
||||
namespace sla {
|
||||
|
||||
using ThrowOnCancel = std::function<void(void)>;
|
||||
|
||||
/// Calculate the polygon representing the silhouette from the specified height
|
||||
void base_plate(const TriangleMesh& mesh, // input mesh
|
||||
ExPolygons& output, // Output will be merged with
|
||||
float samplingheight = 0.1f, // The height range to sample
|
||||
float layerheight = 0.05f, // The sampling height
|
||||
ThrowOnCancel thrfn = [](){}); // Will be called frequently
|
||||
|
||||
void base_plate(const TriangleMesh& mesh, // input mesh
|
||||
ExPolygons& output, // Output will be merged with
|
||||
const std::vector<float>&, // Exact Z levels to sample
|
||||
ThrowOnCancel thrfn = [](){}); // Will be called frequently
|
||||
|
||||
// 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 breakstick_holes(ExPolygon &poly,
|
||||
double padding,
|
||||
double stride,
|
||||
double stick_width,
|
||||
double penetration = 0.0);
|
||||
|
||||
Polygons concave_hull(const Polygons& polys, double max_dist_mm = 50,
|
||||
ThrowOnCancel throw_on_cancel = [](){});
|
||||
|
||||
struct PoolConfig {
|
||||
double min_wall_thickness_mm = 2;
|
||||
double min_wall_height_mm = 5;
|
||||
double max_merge_distance_mm = 50;
|
||||
double edge_radius_mm = 1;
|
||||
double wall_slope = std::atan(1.0); // Universal constant for Pi/4
|
||||
struct EmbedObject {
|
||||
double object_gap_mm = 0.5;
|
||||
double stick_stride_mm = 10;
|
||||
double stick_width_mm = 0.3;
|
||||
double stick_penetration_mm = 0.1;
|
||||
bool enabled = false;
|
||||
operator bool() const { return enabled; }
|
||||
} embed_object;
|
||||
|
||||
ThrowOnCancel throw_on_cancel = [](){};
|
||||
|
||||
inline PoolConfig() {}
|
||||
inline PoolConfig(double wt, double wh, double md, double er, double slope):
|
||||
min_wall_thickness_mm(wt),
|
||||
min_wall_height_mm(wh),
|
||||
max_merge_distance_mm(md),
|
||||
edge_radius_mm(er),
|
||||
wall_slope(slope) {}
|
||||
};
|
||||
|
||||
/// Calculate the pool for the mesh for SLA printing
|
||||
void create_base_pool(const Polygons& base_plate,
|
||||
TriangleMesh& output_mesh,
|
||||
const ExPolygons& holes,
|
||||
const PoolConfig& = PoolConfig());
|
||||
|
||||
/// Returns the elevation needed for compensating the pad.
|
||||
inline double get_pad_elevation(const PoolConfig& cfg) {
|
||||
return cfg.min_wall_thickness_mm;
|
||||
}
|
||||
|
||||
inline double get_pad_fullheight(const PoolConfig& cfg) {
|
||||
return cfg.min_wall_height_mm + cfg.min_wall_thickness_mm;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // SLABASEPOOL_HPP
|
@ -8,35 +8,19 @@
|
||||
#include <libslic3r/ExPolygon.hpp>
|
||||
#include <libslic3r/TriangleMesh.hpp>
|
||||
|
||||
#include "SLACommon.hpp"
|
||||
#include "SLASpatIndex.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace sla {
|
||||
|
||||
/// Get x and y coordinates (because we are eigenizing...)
|
||||
inline coord_t x(const Point& p) { return p(0); }
|
||||
inline coord_t y(const Point& p) { return p(1); }
|
||||
inline coord_t& x(Point& p) { return p(0); }
|
||||
inline coord_t& y(Point& p) { return p(1); }
|
||||
|
||||
inline coordf_t x(const Vec3d& p) { return p(0); }
|
||||
inline coordf_t y(const Vec3d& p) { return p(1); }
|
||||
inline coordf_t z(const Vec3d& p) { return p(2); }
|
||||
inline coordf_t& x(Vec3d& p) { return p(0); }
|
||||
inline coordf_t& y(Vec3d& p) { return p(1); }
|
||||
inline coordf_t& z(Vec3d& p) { return p(2); }
|
||||
|
||||
inline coord_t& x(Vec3crd& p) { return p(0); }
|
||||
inline coord_t& y(Vec3crd& p) { return p(1); }
|
||||
inline coord_t& z(Vec3crd& p) { return p(2); }
|
||||
inline coord_t x(const Vec3crd& p) { return p(0); }
|
||||
inline coord_t y(const Vec3crd& p) { return p(1); }
|
||||
inline coord_t z(const Vec3crd& p) { return p(2); }
|
||||
|
||||
/// Intermediate struct for a 3D mesh
|
||||
struct Contour3D {
|
||||
Pointf3s points;
|
||||
std::vector<Vec3i> indices;
|
||||
|
||||
void merge(const Contour3D& ctr) {
|
||||
Contour3D& merge(const Contour3D& ctr)
|
||||
{
|
||||
auto s3 = coord_t(points.size());
|
||||
auto s = indices.size();
|
||||
|
||||
@ -44,21 +28,27 @@ struct Contour3D {
|
||||
indices.insert(indices.end(), ctr.indices.begin(), ctr.indices.end());
|
||||
|
||||
for(size_t n = s; n < indices.size(); n++) {
|
||||
auto& idx = indices[n]; x(idx) += s3; y(idx) += s3; z(idx) += s3;
|
||||
auto& idx = indices[n]; idx.x() += s3; idx.y() += s3; idx.z() += s3;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
void merge(const Pointf3s& triangles) {
|
||||
Contour3D& merge(const Pointf3s& triangles)
|
||||
{
|
||||
const size_t offs = points.size();
|
||||
points.insert(points.end(), triangles.begin(), triangles.end());
|
||||
indices.reserve(indices.size() + points.size() / 3);
|
||||
|
||||
for(int i = (int)offs; i < (int)points.size(); i += 3)
|
||||
|
||||
for(int i = int(offs); i < int(points.size()); i += 3)
|
||||
indices.emplace_back(i, i + 1, i + 2);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Write the index triangle structure to OBJ file for debugging purposes.
|
||||
void to_obj(std::ostream& stream) {
|
||||
void to_obj(std::ostream& stream)
|
||||
{
|
||||
for(auto& p : points) {
|
||||
stream << "v " << p.transpose() << "\n";
|
||||
}
|
||||
@ -72,6 +62,31 @@ struct Contour3D {
|
||||
using ClusterEl = std::vector<unsigned>;
|
||||
using ClusteredPoints = std::vector<ClusterEl>;
|
||||
|
||||
// Clustering a set of points by the given distance.
|
||||
ClusteredPoints cluster(const std::vector<unsigned>& indices,
|
||||
std::function<Vec3d(unsigned)> pointfn,
|
||||
double dist,
|
||||
unsigned max_points);
|
||||
|
||||
ClusteredPoints cluster(const PointSet& points,
|
||||
double dist,
|
||||
unsigned max_points);
|
||||
|
||||
ClusteredPoints cluster(
|
||||
const std::vector<unsigned>& indices,
|
||||
std::function<Vec3d(unsigned)> pointfn,
|
||||
std::function<bool(const PointIndexEl&, const PointIndexEl&)> predicate,
|
||||
unsigned max_points);
|
||||
|
||||
|
||||
// Calculate the normals for the selected points (from 'points' set) on the
|
||||
// mesh. This will call squared distance for each point.
|
||||
PointSet normals(const PointSet& points,
|
||||
const EigenMesh3D& mesh,
|
||||
double eps = 0.05, // min distance from edges
|
||||
std::function<void()> throw_on_cancel = [](){},
|
||||
const std::vector<unsigned>& selected_points = {});
|
||||
|
||||
/// Mesh from an existing contour.
|
||||
inline TriangleMesh mesh(const Contour3D& ctour) {
|
||||
return {ctour.points, ctour.indices};
|
||||
|
@ -175,6 +175,8 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
using PointSet = Eigen::MatrixXd;
|
||||
|
||||
} // namespace sla
|
||||
} // namespace Slic3r
|
||||
|
||||
|
865
src/libslic3r/SLA/SLAPad.cpp
Normal file
865
src/libslic3r/SLA/SLAPad.cpp
Normal file
@ -0,0 +1,865 @@
|
||||
#include "SLAPad.hpp"
|
||||
#include "SLABoilerPlate.hpp"
|
||||
#include "SLASpatIndex.hpp"
|
||||
|
||||
#include "boost/log/trivial.hpp"
|
||||
#include "SLABoostAdapter.hpp"
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "Tesselate.hpp"
|
||||
#include "MTUtils.hpp"
|
||||
|
||||
// For debugging:
|
||||
// #include <fstream>
|
||||
// #include <libnest2d/tools/benchmark.h>
|
||||
#include "SVG.hpp"
|
||||
|
||||
#include "I18N.hpp"
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
//! macro used to mark string used at localization,
|
||||
//! return same string
|
||||
#define L(s) Slic3r::I18N::translate(s)
|
||||
|
||||
namespace Slic3r { namespace sla {
|
||||
|
||||
namespace {
|
||||
|
||||
/// This function will return a triangulation of a sheet connecting an upper
|
||||
/// and a lower plate given as input polygons. It will not triangulate the
|
||||
/// plates themselves only the sheet. The caller has to specify the lower and
|
||||
/// upper z levels in world coordinates as well as the offset difference
|
||||
/// between the sheets. If the lower_z_mm is higher than upper_z_mm or the
|
||||
/// offset difference is negative, the resulting triangle orientation will be
|
||||
/// reversed.
|
||||
///
|
||||
/// IMPORTANT: This is not a universal triangulation algorithm. It assumes
|
||||
/// that the lower and upper polygons are offsetted versions of the same
|
||||
/// original polygon. In general, it assumes that one of the polygons is
|
||||
/// completely inside the other. The offset difference is the reference
|
||||
/// distance from the inner polygon's perimeter to the outer polygon's
|
||||
/// perimeter. The real distance will be variable as the clipper offset has
|
||||
/// different strategies (rounding, etc...). This algorithm should have
|
||||
/// O(2n + 3m) complexity where n is the number of upper vertices and m is the
|
||||
/// number of lower vertices.
|
||||
Contour3D walls(
|
||||
const Polygon &lower,
|
||||
const Polygon &upper,
|
||||
double lower_z_mm,
|
||||
double upper_z_mm,
|
||||
double offset_difference_mm,
|
||||
ThrowOnCancel thr = [] {})
|
||||
{
|
||||
Contour3D ret;
|
||||
|
||||
if(upper.points.size() < 3 || lower.size() < 3) return ret;
|
||||
|
||||
// The concept of the algorithm is relatively simple. It will try to find
|
||||
// the closest vertices from the upper and the lower polygon and use those
|
||||
// as starting points. Then it will create the triangles sequentially using
|
||||
// an edge from the upper polygon and a vertex from the lower or vice versa,
|
||||
// depending on the resulting triangle's quality.
|
||||
// The quality is measured by a scalar value. So far it looks like it is
|
||||
// enough to derive it from the slope of the triangle's two edges connecting
|
||||
// the upper and the lower part. A reference slope is calculated from the
|
||||
// height and the offset difference.
|
||||
|
||||
// Offset in the index array for the ceiling
|
||||
const auto offs = upper.points.size();
|
||||
|
||||
// Shorthand for the vertex arrays
|
||||
auto& upts = upper.points, &lpts = lower.points;
|
||||
auto& rpts = ret.points; auto& ind = ret.indices;
|
||||
|
||||
// If the Z levels are flipped, or the offset difference is negative, we
|
||||
// will interpret that as the triangles normals should be inverted.
|
||||
bool inverted = upper_z_mm < lower_z_mm || offset_difference_mm < 0;
|
||||
|
||||
// Copy the points into the mesh, convert them from 2D to 3D
|
||||
rpts.reserve(upts.size() + lpts.size());
|
||||
ind.reserve(2 * upts.size() + 2 * lpts.size());
|
||||
for (auto &p : upts)
|
||||
rpts.emplace_back(unscaled(p.x()), unscaled(p.y()), upper_z_mm);
|
||||
for (auto &p : lpts)
|
||||
rpts.emplace_back(unscaled(p.x()), unscaled(p.y()), lower_z_mm);
|
||||
|
||||
// Create pointing indices into vertex arrays. u-upper, l-lower
|
||||
size_t uidx = 0, lidx = offs, unextidx = 1, lnextidx = offs + 1;
|
||||
|
||||
// Simple squared distance calculation.
|
||||
auto distfn = [](const Vec3d& p1, const Vec3d& p2) {
|
||||
auto p = p1 - p2; return p.transpose() * p;
|
||||
};
|
||||
|
||||
// We need to find the closest point on lower polygon to the first point on
|
||||
// the upper polygon. These will be our starting points.
|
||||
double distmin = std::numeric_limits<double>::max();
|
||||
for(size_t l = lidx; l < rpts.size(); ++l) {
|
||||
thr();
|
||||
double d = distfn(rpts[l], rpts[uidx]);
|
||||
if(d < distmin) { lidx = l; distmin = d; }
|
||||
}
|
||||
|
||||
// Set up lnextidx to be ahead of lidx in cyclic mode
|
||||
lnextidx = lidx + 1;
|
||||
if(lnextidx == rpts.size()) lnextidx = offs;
|
||||
|
||||
// This will be the flip switch to toggle between upper and lower triangle
|
||||
// creation mode
|
||||
enum class Proceed {
|
||||
UPPER, // A segment from the upper polygon and one vertex from the lower
|
||||
LOWER // A segment from the lower polygon and one vertex from the upper
|
||||
} proceed = Proceed::UPPER;
|
||||
|
||||
// Flags to help evaluating loop termination.
|
||||
bool ustarted = false, lstarted = false;
|
||||
|
||||
// The variables for the fitness values, one for the actual and one for the
|
||||
// previous.
|
||||
double current_fit = 0, prev_fit = 0;
|
||||
|
||||
// Every triangle of the wall has two edges connecting the upper plate with
|
||||
// the lower plate. From the length of these two edges and the zdiff we
|
||||
// can calculate the momentary squared offset distance at a particular
|
||||
// position on the wall. The average of the differences from the reference
|
||||
// (squared) offset distance will give us the driving fitness value.
|
||||
const double offsdiff2 = std::pow(offset_difference_mm, 2);
|
||||
const double zdiff2 = std::pow(upper_z_mm - lower_z_mm, 2);
|
||||
|
||||
// Mark the current vertex iterator positions. If the iterators return to
|
||||
// the same position, the loop can be terminated.
|
||||
size_t uendidx = uidx, lendidx = lidx;
|
||||
|
||||
do { thr(); // check throw if canceled
|
||||
|
||||
prev_fit = current_fit;
|
||||
|
||||
switch(proceed) { // proceed depending on the current state
|
||||
case Proceed::UPPER:
|
||||
if(!ustarted || uidx != uendidx) { // there are vertices remaining
|
||||
// Get the 3D vertices in order
|
||||
const Vec3d& p_up1 = rpts[uidx];
|
||||
const Vec3d& p_low = rpts[lidx];
|
||||
const Vec3d& p_up2 = rpts[unextidx];
|
||||
|
||||
// Calculate fitness: the average of the two connecting edges
|
||||
double a = offsdiff2 - (distfn(p_up1, p_low) - zdiff2);
|
||||
double b = offsdiff2 - (distfn(p_up2, p_low) - zdiff2);
|
||||
current_fit = (std::abs(a) + std::abs(b)) / 2;
|
||||
|
||||
if(current_fit > prev_fit) { // fit is worse than previously
|
||||
proceed = Proceed::LOWER;
|
||||
} else { // good to go, create the triangle
|
||||
inverted
|
||||
? ind.emplace_back(int(unextidx), int(lidx), int(uidx))
|
||||
: ind.emplace_back(int(uidx), int(lidx), int(unextidx));
|
||||
|
||||
// Increment the iterators, rotate if necessary
|
||||
++uidx; ++unextidx;
|
||||
if(unextidx == offs) unextidx = 0;
|
||||
if(uidx == offs) uidx = 0;
|
||||
|
||||
ustarted = true; // mark the movement of the iterators
|
||||
// so that the comparison to uendidx can be made correctly
|
||||
}
|
||||
} else proceed = Proceed::LOWER;
|
||||
|
||||
break;
|
||||
case Proceed::LOWER:
|
||||
// Mode with lower segment, upper vertex. Same structure:
|
||||
if(!lstarted || lidx != lendidx) {
|
||||
const Vec3d& p_low1 = rpts[lidx];
|
||||
const Vec3d& p_low2 = rpts[lnextidx];
|
||||
const Vec3d& p_up = rpts[uidx];
|
||||
|
||||
double a = offsdiff2 - (distfn(p_up, p_low1) - zdiff2);
|
||||
double b = offsdiff2 - (distfn(p_up, p_low2) - zdiff2);
|
||||
current_fit = (std::abs(a) + std::abs(b)) / 2;
|
||||
|
||||
if(current_fit > prev_fit) {
|
||||
proceed = Proceed::UPPER;
|
||||
} else {
|
||||
inverted
|
||||
? ind.emplace_back(int(uidx), int(lnextidx), int(lidx))
|
||||
: ind.emplace_back(int(lidx), int(lnextidx), int(uidx));
|
||||
|
||||
++lidx; ++lnextidx;
|
||||
if(lnextidx == rpts.size()) lnextidx = offs;
|
||||
if(lidx == rpts.size()) lidx = offs;
|
||||
|
||||
lstarted = true;
|
||||
}
|
||||
} else proceed = Proceed::UPPER;
|
||||
|
||||
break;
|
||||
} // end of switch
|
||||
} while(!ustarted || !lstarted || uidx != uendidx || lidx != lendidx);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Same as walls() but with identical higher and lower polygons.
|
||||
Contour3D inline straight_walls(const Polygon &plate,
|
||||
double lo_z,
|
||||
double hi_z,
|
||||
ThrowOnCancel 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
|
||||
// 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 breakstick_holes(Points& pts,
|
||||
double padding,
|
||||
double stride,
|
||||
double stick_width,
|
||||
double penetration)
|
||||
{
|
||||
if(stride <= EPSILON || stick_width <= EPSILON || padding <= EPSILON)
|
||||
return;
|
||||
|
||||
// SVG svg("bridgestick_plate.svg");
|
||||
// svg.draw(poly);
|
||||
|
||||
// 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 = scaled(stick_width);
|
||||
double sright = scaled(penetration + padding);
|
||||
|
||||
// scaled stride distance
|
||||
double sstride = scaled(stride);
|
||||
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 < sbottom) t += sbottom;
|
||||
double tend = nrm - sbottom;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
void breakstick_holes(Points &pts, const PadConfig::EmbedObject &c)
|
||||
{
|
||||
breakstick_holes(pts, c.object_gap_mm, c.stick_stride_mm,
|
||||
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) {
|
||||
breakstick_holes(p.contour.points, cfg);
|
||||
for (auto &h : p.holes) breakstick_holes(h.points, cfg);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// 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;
|
||||
|
||||
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 ¢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(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() + c.wall_thickness_mm);
|
||||
}
|
||||
|
||||
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
|
||||
struct PadConfig3D {
|
||||
double thickness, height, wing_height, slope;
|
||||
|
||||
explicit PadConfig3D(const PadConfig &cfg2d)
|
||||
: thickness{cfg2d.wall_thickness_mm}
|
||||
, height{cfg2d.full_height()}
|
||||
, wing_height{cfg2d.wall_height_mm}
|
||||
, slope{cfg2d.wall_slope}
|
||||
{}
|
||||
|
||||
inline double bottom_offset() const
|
||||
{
|
||||
return (thickness + wing_height) / std::tan(slope);
|
||||
}
|
||||
};
|
||||
|
||||
// Outer part of the skeleton is used to generate the waffled edges of the pad.
|
||||
// Inner parts will not be waffled or offsetted. Inner parts are only used if
|
||||
// pad is generated around the object and correspond to holes and inner polygons
|
||||
// in the model blueprint.
|
||||
struct PadSkeleton { ExPolygons inner, outer; };
|
||||
|
||||
PadSkeleton divide_blueprint(const ExPolygons &bp)
|
||||
{
|
||||
ClipperLib::PolyTree ptree = union_pt(bp);
|
||||
|
||||
PadSkeleton ret;
|
||||
ret.inner.reserve(size_t(ptree.Total()));
|
||||
ret.outer.reserve(size_t(ptree.Total()));
|
||||
|
||||
for (ClipperLib::PolyTree::PolyNode *node : ptree.Childs) {
|
||||
ExPolygon poly(ClipperPath_to_Slic3rPolygon(node->Contour));
|
||||
for (ClipperLib::PolyTree::PolyNode *child : node->Childs) {
|
||||
if (child->IsHole()) {
|
||||
poly.holes.emplace_back(
|
||||
ClipperPath_to_Slic3rPolygon(child->Contour));
|
||||
|
||||
traverse_pt_unordered(child->Childs, &ret.inner);
|
||||
}
|
||||
else traverse_pt_unordered(child, &ret.inner);
|
||||
}
|
||||
|
||||
ret.outer.emplace_back(poly);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// A helper class for storing polygons and maintaining a spatial index of their
|
||||
// bounding boxes.
|
||||
class Intersector {
|
||||
BoxIndex m_index;
|
||||
ExPolygons m_polys;
|
||||
|
||||
public:
|
||||
|
||||
// Add a new polygon to the index
|
||||
void add(const ExPolygon &ep)
|
||||
{
|
||||
m_polys.emplace_back(ep);
|
||||
m_index.insert(BoundingBox{ep}, unsigned(m_index.size()));
|
||||
}
|
||||
|
||||
// Check an arbitrary polygon for intersection with the indexed polygons
|
||||
bool intersects(const ExPolygon &poly)
|
||||
{
|
||||
// Create a suitable query bounding box.
|
||||
auto bb = poly.contour.bounding_box();
|
||||
|
||||
std::vector<BoxIndexEl> qres = m_index.query(bb, BoxIndex::qtIntersects);
|
||||
|
||||
// Now check intersections on the actual polygons (not just the boxes)
|
||||
bool is_overlap = false;
|
||||
auto qit = qres.begin();
|
||||
while (!is_overlap && qit != qres.end())
|
||||
is_overlap = is_overlap || poly.overlaps(m_polys[(qit++)->second]);
|
||||
|
||||
return is_overlap;
|
||||
}
|
||||
};
|
||||
|
||||
// This dummy intersector to implement the "force pad everywhere" feature
|
||||
struct DummyIntersector
|
||||
{
|
||||
inline void add(const ExPolygon &) {}
|
||||
inline bool intersects(const ExPolygon &) { return true; }
|
||||
};
|
||||
|
||||
template<class _Intersector>
|
||||
class _AroundPadSkeleton : public PadSkeleton
|
||||
{
|
||||
// A spatial index used to be able to efficiently find intersections of
|
||||
// support polygons with the model polygons.
|
||||
_Intersector m_intersector;
|
||||
|
||||
public:
|
||||
_AroundPadSkeleton(const ExPolygons &support_blueprint,
|
||||
const ExPolygons &model_blueprint,
|
||||
const PadConfig & cfg,
|
||||
ThrowOnCancel thr)
|
||||
{
|
||||
// We need to merge the support and the model contours in a special
|
||||
// way in which the model contours have to be substracted from the
|
||||
// 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
|
||||
// to be eliminated from areas where there is no need for a pad, due
|
||||
// to missing supports.
|
||||
|
||||
add_supports_to_index(support_blueprint);
|
||||
|
||||
ConcaveHull fullcvh =
|
||||
wafflized_concave_hull(support_blueprint, model_blueprint, cfg, thr);
|
||||
|
||||
auto model_bp_sticks =
|
||||
breakstick_holes(model_blueprint, cfg.embed_object);
|
||||
|
||||
ExPolygons fullpad = diff_ex(fullcvh.to_expolygons(), model_bp_sticks);
|
||||
|
||||
remove_redundant_parts(fullpad);
|
||||
|
||||
PadSkeleton divided = divide_blueprint(fullpad);
|
||||
outer = std::move(divided.outer);
|
||||
inner = std::move(divided.inner);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
// Add the support blueprint to the search index to be queried later
|
||||
void add_supports_to_index(const ExPolygons &supp_bp)
|
||||
{
|
||||
for (auto &ep : supp_bp) m_intersector.add(ep);
|
||||
}
|
||||
|
||||
// Create the wafflized pad around all object in the scene. This pad doesnt
|
||||
// have any holes yet.
|
||||
ConcaveHull wafflized_concave_hull(const ExPolygons &supp_bp,
|
||||
const ExPolygons &model_bp,
|
||||
const PadConfig &cfg,
|
||||
ThrowOnCancel thr)
|
||||
{
|
||||
auto allin = reserve_vector<ExPolygon>(supp_bp.size() + model_bp.size());
|
||||
|
||||
for (auto &ep : supp_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};
|
||||
ret.offset_waffle_style(ConcaveHull::get_waffle_offset(cfg));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// To remove parts of the pad skeleton which do not host any supports
|
||||
void remove_redundant_parts(ExPolygons &parts)
|
||||
{
|
||||
auto endit = std::remove_if(parts.begin(), parts.end(),
|
||||
[this](const ExPolygon &p) {
|
||||
return !m_intersector.intersects(p);
|
||||
});
|
||||
|
||||
parts.erase(endit, parts.end());
|
||||
}
|
||||
};
|
||||
|
||||
using AroundPadSkeleton = _AroundPadSkeleton<Intersector>;
|
||||
using BrimPadSkeleton = _AroundPadSkeleton<DummyIntersector>;
|
||||
|
||||
class BelowPadSkeleton : public PadSkeleton
|
||||
{
|
||||
public:
|
||||
BelowPadSkeleton(const ExPolygons &support_blueprint,
|
||||
const ExPolygons &model_blueprint,
|
||||
const PadConfig & cfg,
|
||||
ThrowOnCancel thr)
|
||||
{
|
||||
outer.reserve(support_blueprint.size() + model_blueprint.size());
|
||||
|
||||
for (auto &ep : support_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};
|
||||
|
||||
ochull.offset_waffle_style(ConcaveHull::get_waffle_offset(cfg));
|
||||
outer = ochull.to_expolygons();
|
||||
}
|
||||
};
|
||||
|
||||
// Offset the contour only, leave the holes untouched
|
||||
template<class...Args>
|
||||
ExPolygon offset_contour_only(const ExPolygon &poly, coord_t delta, Args...args)
|
||||
{
|
||||
ExPolygons tmp = offset_ex(poly.contour, delta, args...);
|
||||
|
||||
if (tmp.empty()) return {};
|
||||
|
||||
Polygons holes = poly.holes;
|
||||
for (auto &h : holes) h.reverse();
|
||||
|
||||
tmp = diff_ex(to_polygons(tmp), holes);
|
||||
|
||||
if (tmp.empty()) return {};
|
||||
|
||||
return tmp.front();
|
||||
}
|
||||
|
||||
bool add_cavity(Contour3D &pad, ExPolygon &top_poly, const PadConfig3D &cfg,
|
||||
ThrowOnCancel thr)
|
||||
{
|
||||
auto logerr = []{BOOST_LOG_TRIVIAL(error)<<"Could not create pad cavity";};
|
||||
|
||||
double wing_distance = cfg.wing_height / std::tan(cfg.slope);
|
||||
coord_t delta_inner = -scaled(cfg.thickness + wing_distance);
|
||||
coord_t delta_middle = -scaled(cfg.thickness);
|
||||
ExPolygon inner_base = offset_contour_only(top_poly, delta_inner);
|
||||
ExPolygon middle_base = offset_contour_only(top_poly, delta_middle);
|
||||
|
||||
if (inner_base.empty() || middle_base.empty()) { logerr(); return false; }
|
||||
|
||||
ExPolygons pdiff = diff_ex(top_poly, middle_base.contour);
|
||||
|
||||
if (pdiff.size() != 1) { logerr(); return false; }
|
||||
|
||||
top_poly = pdiff.front();
|
||||
|
||||
double z_min = -cfg.wing_height, z_max = 0;
|
||||
double offset_difference = -wing_distance;
|
||||
pad.merge(walls(inner_base.contour, middle_base.contour, z_min, z_max,
|
||||
offset_difference, thr));
|
||||
|
||||
pad.merge(triangulate_expolygon_3d(inner_base, z_min, NORMALS_UP));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Contour3D create_outer_pad_geometry(const ExPolygons & skeleton,
|
||||
const PadConfig3D &cfg,
|
||||
ThrowOnCancel thr)
|
||||
{
|
||||
Contour3D ret;
|
||||
|
||||
for (const ExPolygon &pad_part : skeleton) {
|
||||
ExPolygon top_poly{pad_part};
|
||||
ExPolygon bottom_poly =
|
||||
offset_contour_only(pad_part, -scaled(cfg.bottom_offset()));
|
||||
|
||||
if (bottom_poly.empty()) continue;
|
||||
|
||||
double z_min = -cfg.height, z_max = 0;
|
||||
ret.merge(walls(top_poly.contour, bottom_poly.contour, z_max, z_min,
|
||||
cfg.bottom_offset(), thr));
|
||||
|
||||
if (cfg.wing_height > 0. && add_cavity(ret, top_poly, cfg, thr))
|
||||
z_max = -cfg.wing_height;
|
||||
|
||||
for (auto &h : bottom_poly.holes)
|
||||
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(top_poly, NORMALS_UP));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Contour3D create_inner_pad_geometry(const ExPolygons & skeleton,
|
||||
const PadConfig3D &cfg,
|
||||
ThrowOnCancel thr)
|
||||
{
|
||||
Contour3D ret;
|
||||
|
||||
double z_max = 0., z_min = -cfg.height;
|
||||
for (const ExPolygon &pad_part : skeleton) {
|
||||
ret.merge(straight_walls(pad_part.contour, z_max, z_min,thr));
|
||||
|
||||
for (auto &h : pad_part.holes)
|
||||
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_max, NORMALS_UP));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Contour3D create_pad_geometry(const PadSkeleton &skelet,
|
||||
const PadConfig & cfg,
|
||||
ThrowOnCancel thr)
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
SVG svg("pad_skeleton.svg");
|
||||
svg.draw(skelet.outer, "green");
|
||||
svg.draw(skelet.inner, "blue");
|
||||
svg.Close();
|
||||
#endif
|
||||
|
||||
PadConfig3D cfg3d(cfg);
|
||||
return create_outer_pad_geometry(skelet.outer, cfg3d, thr)
|
||||
.merge(create_inner_pad_geometry(skelet.inner, cfg3d, thr));
|
||||
}
|
||||
|
||||
Contour3D create_pad_geometry(const ExPolygons &supp_bp,
|
||||
const ExPolygons &model_bp,
|
||||
const PadConfig & cfg,
|
||||
ThrowOnCancel thr)
|
||||
{
|
||||
PadSkeleton skelet;
|
||||
|
||||
if (cfg.embed_object.enabled) {
|
||||
if (cfg.embed_object.everywhere)
|
||||
skelet = BrimPadSkeleton(supp_bp, model_bp, cfg, thr);
|
||||
else
|
||||
skelet = AroundPadSkeleton(supp_bp, model_bp, cfg, thr);
|
||||
} else
|
||||
skelet = BelowPadSkeleton(supp_bp, model_bp, cfg, thr);
|
||||
|
||||
return create_pad_geometry(skelet, cfg, thr);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void pad_blueprint(const TriangleMesh & mesh,
|
||||
ExPolygons & output,
|
||||
const std::vector<float> &heights,
|
||||
ThrowOnCancel thrfn)
|
||||
{
|
||||
if (mesh.empty()) return;
|
||||
TriangleMeshSlicer slicer(&mesh);
|
||||
|
||||
auto out = reserve_vector<ExPolygons>(heights.size());
|
||||
slicer.slice(heights, 0.f, &out, thrfn);
|
||||
|
||||
size_t count = 0;
|
||||
for(auto& o : out) count += o.size();
|
||||
|
||||
// Unification is expensive, a simplify also speeds up the pad generation
|
||||
auto tmp = reserve_vector<ExPolygon>(count);
|
||||
for(ExPolygons& o : out)
|
||||
for(ExPolygon& e : o) {
|
||||
auto&& exss = e.simplify(scaled<double>(0.1));
|
||||
for(ExPolygon& ep : exss) tmp.emplace_back(std::move(ep));
|
||||
}
|
||||
|
||||
ExPolygons utmp = union_ex(tmp);
|
||||
|
||||
for(auto& o : utmp) {
|
||||
auto&& smp = o.simplify(scaled<double>(0.1));
|
||||
output.insert(output.end(), smp.begin(), smp.end());
|
||||
}
|
||||
}
|
||||
|
||||
void pad_blueprint(const TriangleMesh &mesh,
|
||||
ExPolygons & output,
|
||||
float h,
|
||||
float layerh,
|
||||
ThrowOnCancel thrfn)
|
||||
{
|
||||
float gnd = float(mesh.bounding_box().min(Z));
|
||||
|
||||
std::vector<float> slicegrid = grid(gnd, gnd + h, layerh);
|
||||
pad_blueprint(mesh, output, slicegrid, thrfn);
|
||||
}
|
||||
|
||||
void create_pad(const ExPolygons &sup_blueprint,
|
||||
const ExPolygons &model_blueprint,
|
||||
TriangleMesh & out,
|
||||
const PadConfig & cfg,
|
||||
ThrowOnCancel thr)
|
||||
{
|
||||
Contour3D t = create_pad_geometry(sup_blueprint, model_blueprint, cfg, thr);
|
||||
out.merge(mesh(std::move(t)));
|
||||
}
|
||||
|
||||
std::string PadConfig::validate() const
|
||||
{
|
||||
if (bottom_offset() > brim_size_mm + wing_distance())
|
||||
return L("Pad brim size is too low for the current slope.");
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::sla
|
93
src/libslic3r/SLA/SLAPad.hpp
Normal file
93
src/libslic3r/SLA/SLAPad.hpp
Normal file
@ -0,0 +1,93 @@
|
||||
#ifndef SLABASEPOOL_HPP
|
||||
#define SLABASEPOOL_HPP
|
||||
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <cmath>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class ExPolygon;
|
||||
class Polygon;
|
||||
using ExPolygons = std::vector<ExPolygon>;
|
||||
using Polygons = std::vector<Polygon>;
|
||||
|
||||
class TriangleMesh;
|
||||
|
||||
namespace sla {
|
||||
|
||||
using ThrowOnCancel = std::function<void(void)>;
|
||||
|
||||
/// Calculate the polygon representing the silhouette.
|
||||
void pad_blueprint(
|
||||
const TriangleMesh &mesh, // input mesh
|
||||
ExPolygons & output, // Output will be merged with
|
||||
const std::vector<float> &, // Exact Z levels to sample
|
||||
ThrowOnCancel thrfn = [] {}); // Function that throws if cancel was requested
|
||||
|
||||
void pad_blueprint(
|
||||
const TriangleMesh &mesh,
|
||||
ExPolygons & output,
|
||||
float samplingheight = 0.1f, // The height range to sample
|
||||
float layerheight = 0.05f, // The sampling height
|
||||
ThrowOnCancel thrfn = [] {});
|
||||
|
||||
struct PadConfig {
|
||||
double wall_thickness_mm = 1.;
|
||||
double wall_height_mm = 1.;
|
||||
double max_merge_dist_mm = 50;
|
||||
double wall_slope = std::atan(1.0); // Universal constant for Pi/4
|
||||
double brim_size_mm = 1.6;
|
||||
|
||||
struct EmbedObject {
|
||||
double object_gap_mm = 1.;
|
||||
double stick_stride_mm = 10.;
|
||||
double stick_width_mm = 0.5;
|
||||
double stick_penetration_mm = 0.1;
|
||||
bool enabled = false;
|
||||
bool everywhere = false;
|
||||
operator bool() const { return enabled; }
|
||||
} embed_object;
|
||||
|
||||
inline PadConfig() = default;
|
||||
inline PadConfig(double thickness,
|
||||
double height,
|
||||
double mergedist,
|
||||
double slope)
|
||||
: wall_thickness_mm(thickness)
|
||||
, wall_height_mm(height)
|
||||
, max_merge_dist_mm(mergedist)
|
||||
, wall_slope(slope)
|
||||
{}
|
||||
|
||||
inline double bottom_offset() const
|
||||
{
|
||||
return (wall_thickness_mm + wall_height_mm) / std::tan(wall_slope);
|
||||
}
|
||||
|
||||
inline double wing_distance() const
|
||||
{
|
||||
return wall_height_mm / std::tan(wall_slope);
|
||||
}
|
||||
|
||||
inline double full_height() const
|
||||
{
|
||||
return wall_height_mm + wall_thickness_mm;
|
||||
}
|
||||
|
||||
/// Returns the elevation needed for compensating the pad.
|
||||
inline double required_elevation() const { return wall_thickness_mm; }
|
||||
|
||||
std::string validate() const;
|
||||
};
|
||||
|
||||
void create_pad(const ExPolygons &support_contours,
|
||||
const ExPolygons &model_contours,
|
||||
TriangleMesh & output_mesh,
|
||||
const PadConfig & = PadConfig(),
|
||||
ThrowOnCancel throw_on_cancel = []{});
|
||||
|
||||
} // namespace sla
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // SLABASEPOOL_HPP
|
@ -39,14 +39,19 @@ public:
|
||||
insert(std::make_pair(v, unsigned(idx)));
|
||||
}
|
||||
|
||||
std::vector<PointIndexEl> query(std::function<bool(const PointIndexEl&)>);
|
||||
std::vector<PointIndexEl> nearest(const Vec3d&, unsigned k);
|
||||
std::vector<PointIndexEl> query(std::function<bool(const PointIndexEl&)>) const;
|
||||
std::vector<PointIndexEl> nearest(const Vec3d&, unsigned k) const;
|
||||
std::vector<PointIndexEl> query(const Vec3d &v, unsigned k) const // wrapper
|
||||
{
|
||||
return nearest(v, k);
|
||||
}
|
||||
|
||||
// For testing
|
||||
size_t size() const;
|
||||
bool empty() const { return size() == 0; }
|
||||
|
||||
void foreach(std::function<void(const PointIndexEl& el)> fn);
|
||||
void foreach(std::function<void(const PointIndexEl& el)> fn) const;
|
||||
};
|
||||
|
||||
using BoxIndexEl = std::pair<Slic3r::BoundingBox, unsigned>;
|
||||
|
@ -7,7 +7,7 @@
|
||||
#include "SLASupportTree.hpp"
|
||||
#include "SLABoilerPlate.hpp"
|
||||
#include "SLASpatIndex.hpp"
|
||||
#include "SLABasePool.hpp"
|
||||
#include "SLAPad.hpp"
|
||||
|
||||
#include <libslic3r/MTUtils.hpp>
|
||||
#include <libslic3r/ClipperUtils.hpp>
|
||||
@ -17,6 +17,8 @@
|
||||
#include <libnest2d/optimizers/nlopt/subplex.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <tbb/mutex.h>
|
||||
#include <tbb/spin_mutex.h>
|
||||
#include <libslic3r/I18N.hpp>
|
||||
|
||||
//! macro used to mark string used at localization,
|
||||
@ -91,27 +93,34 @@ template<bool> struct _ccr {};
|
||||
|
||||
template<> struct _ccr<true>
|
||||
{
|
||||
using Mutex = SpinMutex;
|
||||
using SpinningMutex = tbb::spin_mutex;
|
||||
using LockingMutex = tbb::mutex;
|
||||
|
||||
template<class It, class Fn>
|
||||
static inline void enumerate(It from, It to, Fn fn)
|
||||
{
|
||||
using TN = size_t;
|
||||
auto iN = to - from;
|
||||
TN N = iN < 0 ? 0 : TN(iN);
|
||||
auto iN = to - from;
|
||||
size_t N = iN < 0 ? 0 : size_t(iN);
|
||||
|
||||
tbb::parallel_for(TN(0), N, [from, fn](TN n) { fn(*(from + n), n); });
|
||||
tbb::parallel_for(size_t(0), N, [from, fn](size_t n) {
|
||||
fn(*(from + decltype(iN)(n)), n);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
template<> struct _ccr<false>
|
||||
{
|
||||
struct Mutex { inline void lock() {} inline void unlock() {} };
|
||||
private:
|
||||
struct _Mtx { inline void lock() {} inline void unlock() {} };
|
||||
|
||||
public:
|
||||
using SpinningMutex = _Mtx;
|
||||
using LockingMutex = _Mtx;
|
||||
|
||||
template<class It, class Fn>
|
||||
static inline void enumerate(It from, It to, Fn fn)
|
||||
{
|
||||
for (auto it = from; it != to; ++it) fn(*it, it - from);
|
||||
for (auto it = from; it != to; ++it) fn(*it, size_t(it - from));
|
||||
}
|
||||
};
|
||||
|
||||
@ -132,6 +141,8 @@ template<class Vec> double distance(const Vec& pp1, const Vec& pp2) {
|
||||
return distance(p);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
Contour3D sphere(double rho, Portion portion = make_portion(0.0, 2.0*PI),
|
||||
double fa=(2*PI/360)) {
|
||||
|
||||
@ -175,10 +186,11 @@ Contour3D sphere(double rho, Portion portion = make_portion(0.0, 2.0*PI),
|
||||
Vec2d b = Eigen::Rotation2Dd(ring[i]) * Eigen::Vector2d(0, r);
|
||||
vertices.emplace_back(Vec3d(b(0), b(1), z));
|
||||
|
||||
if(sbegin == 0)
|
||||
facets.emplace_back((i == 0) ? Vec3crd(coord_t(ring.size()), 0, 1) :
|
||||
Vec3crd(id - 1, 0, id));
|
||||
++ id;
|
||||
if (sbegin == 0)
|
||||
facets.emplace_back((i == 0) ?
|
||||
Vec3crd(coord_t(ring.size()), 0, 1) :
|
||||
Vec3crd(id - 1, 0, id));
|
||||
++id;
|
||||
}
|
||||
|
||||
// General case: insert and form facets for each step,
|
||||
@ -229,7 +241,7 @@ Contour3D sphere(double rho, Portion portion = make_portion(0.0, 2.0*PI),
|
||||
// h: Height
|
||||
// ssteps: how many edges will create the base circle
|
||||
// sp: starting point
|
||||
Contour3D cylinder(double r, double h, size_t ssteps, const Vec3d sp = {0,0,0})
|
||||
Contour3D cylinder(double r, double h, size_t ssteps, const Vec3d &sp = {0,0,0})
|
||||
{
|
||||
Contour3D ret;
|
||||
|
||||
@ -289,6 +301,8 @@ Contour3D cylinder(double r, double h, size_t ssteps, const Vec3d sp = {0,0,0})
|
||||
return ret;
|
||||
}
|
||||
|
||||
const constexpr long ID_UNSET = -1;
|
||||
|
||||
struct Head {
|
||||
Contour3D mesh;
|
||||
|
||||
@ -300,25 +314,25 @@ struct Head {
|
||||
double r_pin_mm = 0.5;
|
||||
double width_mm = 2;
|
||||
double penetration_mm = 0.5;
|
||||
|
||||
|
||||
// For identification purposes. This will be used as the index into the
|
||||
// container holding the head structures. See SLASupportTree::Impl
|
||||
long id = -1;
|
||||
long id = ID_UNSET;
|
||||
|
||||
// If there is a pillar connecting to this head, then the id will be set.
|
||||
long pillar_id = -1;
|
||||
long pillar_id = ID_UNSET;
|
||||
|
||||
inline void invalidate() { id = -1; }
|
||||
inline void invalidate() { id = ID_UNSET; }
|
||||
inline bool is_valid() const { return id >= 0; }
|
||||
|
||||
Head(double r_big_mm,
|
||||
double r_small_mm,
|
||||
double length_mm,
|
||||
double penetration,
|
||||
Vec3d direction = {0, 0, -1}, // direction (normal to the dull end )
|
||||
Vec3d offset = {0, 0, 0}, // displacement
|
||||
const Vec3d &direction = {0, 0, -1}, // direction (normal to the dull end)
|
||||
const Vec3d &offset = {0, 0, 0}, // displacement
|
||||
const size_t circlesteps = 45):
|
||||
steps(circlesteps), dir(direction), tr(offset),
|
||||
steps(circlesteps), dir(direction), tr(offset),
|
||||
r_back_mm(r_big_mm), r_pin_mm(r_small_mm), width_mm(length_mm),
|
||||
penetration_mm(penetration)
|
||||
{
|
||||
@ -347,7 +361,7 @@ struct Head {
|
||||
auto&& s1 = sphere(r_big_mm, make_portion(0, PI/2 + phi), detail);
|
||||
auto&& s2 = sphere(r_small_mm, make_portion(PI/2 + phi, PI), detail);
|
||||
|
||||
for(auto& p : s2.points) z(p) += h;
|
||||
for(auto& p : s2.points) p.z() += h;
|
||||
|
||||
mesh.merge(s1);
|
||||
mesh.merge(s2);
|
||||
@ -373,7 +387,7 @@ struct Head {
|
||||
|
||||
// To simplify further processing, we translate the mesh so that the
|
||||
// last vertex of the pointing sphere (the pinpoint) will be at (0,0,0)
|
||||
for(auto& p : mesh.points) z(p) -= (h + r_small_mm - penetration_mm);
|
||||
for(auto& p : mesh.points) p.z() -= (h + r_small_mm - penetration_mm);
|
||||
}
|
||||
|
||||
void transform()
|
||||
@ -393,11 +407,6 @@ struct Head {
|
||||
return 2 * r_pin_mm + width_mm + 2*r_back_mm - penetration_mm;
|
||||
}
|
||||
|
||||
static double fullwidth(const SupportConfig& cfg) {
|
||||
return 2 * cfg.head_front_radius_mm + cfg.head_width_mm +
|
||||
2 * cfg.head_back_radius_mm - cfg.head_penetration_mm;
|
||||
}
|
||||
|
||||
Vec3d junction_point() const {
|
||||
return tr + ( 2 * r_pin_mm + width_mm + r_back_mm - penetration_mm)*dir;
|
||||
}
|
||||
@ -414,7 +423,7 @@ struct Junction {
|
||||
size_t steps = 45;
|
||||
Vec3d pos;
|
||||
|
||||
long id = -1;
|
||||
long id = ID_UNSET;
|
||||
|
||||
Junction(const Vec3d& tr, double r_mm, size_t stepnum = 45):
|
||||
r(r_mm), steps(stepnum), pos(tr)
|
||||
@ -432,11 +441,11 @@ struct Pillar {
|
||||
Vec3d endpt;
|
||||
double height = 0;
|
||||
|
||||
long id = -1;
|
||||
long id = ID_UNSET;
|
||||
|
||||
// If the pillar connects to a head, this is the id of that head
|
||||
bool starts_from_head = true; // Could start from a junction as well
|
||||
long start_junction_id = -1;
|
||||
long start_junction_id = ID_UNSET;
|
||||
|
||||
// How many bridges are connected to this pillar
|
||||
unsigned bridges = 0;
|
||||
@ -461,22 +470,24 @@ struct Pillar {
|
||||
}
|
||||
}
|
||||
|
||||
Pillar(const Junction& junc, const Vec3d& endp):
|
||||
Pillar(junc.pos, endp, junc.r, junc.steps){}
|
||||
Pillar(const Junction &junc, const Vec3d &endp)
|
||||
: Pillar(junc.pos, endp, junc.r, junc.steps)
|
||||
{}
|
||||
|
||||
Pillar(const Head& head, const Vec3d& endp, double radius = 1):
|
||||
Pillar(head.junction_point(), endp, head.request_pillar_radius(radius),
|
||||
head.steps)
|
||||
Pillar(const Head &head, const Vec3d &endp, double radius = 1)
|
||||
: Pillar(head.junction_point(), endp,
|
||||
head.request_pillar_radius(radius), head.steps)
|
||||
{}
|
||||
|
||||
inline Vec3d startpoint() const
|
||||
{
|
||||
}
|
||||
|
||||
inline Vec3d startpoint() const {
|
||||
return {endpt(X), endpt(Y), endpt(Z) + height};
|
||||
}
|
||||
|
||||
inline const Vec3d& endpoint() const { return endpt; }
|
||||
|
||||
Pillar& add_base(double baseheight = 3, double radius = 2) {
|
||||
Pillar& add_base(double baseheight = 3, double radius = 2)
|
||||
{
|
||||
if(baseheight <= 0) return *this;
|
||||
if(baseheight > height) baseheight = height;
|
||||
|
||||
@ -523,8 +534,6 @@ struct Pillar {
|
||||
indices.emplace_back(offs, offs + last, lcenter);
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool has_base() const { return !base.points.empty(); }
|
||||
};
|
||||
|
||||
// A Bridge between two pillars (with junction endpoints)
|
||||
@ -532,9 +541,9 @@ struct Bridge {
|
||||
Contour3D mesh;
|
||||
double r = 0.8;
|
||||
|
||||
long id = -1;
|
||||
long start_jid = -1;
|
||||
long end_jid = -1;
|
||||
long id = ID_UNSET;
|
||||
long start_jid = ID_UNSET;
|
||||
long end_jid = ID_UNSET;
|
||||
|
||||
// We should reduce the radius a tiny bit to help the convex hull algorithm
|
||||
Bridge(const Vec3d& j1, const Vec3d& j2,
|
||||
@ -550,17 +559,13 @@ struct Bridge {
|
||||
auto quater = Quaternion::FromTwoVectors(Vec3d{0,0,1}, dir);
|
||||
for(auto& p : mesh.points) p = quater * p + j1;
|
||||
}
|
||||
|
||||
Bridge(const Junction& j1, const Junction& j2, double r_mm = 0.8):
|
||||
Bridge(j1.pos, j2.pos, r_mm, j1.steps) {}
|
||||
|
||||
};
|
||||
|
||||
// A bridge that spans from model surface to model surface with small connecting
|
||||
// edges on the endpoints. Used for headless support points.
|
||||
struct CompactBridge {
|
||||
Contour3D mesh;
|
||||
long id = -1;
|
||||
long id = ID_UNSET;
|
||||
|
||||
CompactBridge(const Vec3d& sp,
|
||||
const Vec3d& ep,
|
||||
@ -594,123 +599,28 @@ struct CompactBridge {
|
||||
// A wrapper struct around the base pool (pad)
|
||||
struct Pad {
|
||||
TriangleMesh tmesh;
|
||||
PoolConfig cfg;
|
||||
PadConfig cfg;
|
||||
double zlevel = 0;
|
||||
|
||||
Pad() = default;
|
||||
|
||||
Pad(const TriangleMesh& support_mesh,
|
||||
const ExPolygons& modelbase,
|
||||
double ground_level,
|
||||
const PoolConfig& pcfg) :
|
||||
cfg(pcfg),
|
||||
zlevel(ground_level +
|
||||
sla::get_pad_fullheight(pcfg) -
|
||||
sla::get_pad_elevation(pcfg))
|
||||
Pad(const TriangleMesh &support_mesh,
|
||||
const ExPolygons & model_contours,
|
||||
double ground_level,
|
||||
const PadConfig & pcfg,
|
||||
ThrowOnCancel thr)
|
||||
: cfg(pcfg)
|
||||
, zlevel(ground_level + pcfg.full_height() - pcfg.required_elevation())
|
||||
{
|
||||
Polygons basep;
|
||||
auto &thr = cfg.throw_on_cancel;
|
||||
|
||||
thr();
|
||||
|
||||
// Get a sample for the pad from the support mesh
|
||||
{
|
||||
ExPolygons platetmp;
|
||||
ExPolygons sup_contours;
|
||||
|
||||
float zstart = float(zlevel);
|
||||
float zend = zstart + float(get_pad_fullheight(pcfg) + EPSILON);
|
||||
float zstart = float(zlevel);
|
||||
float zend = zstart + float(pcfg.full_height() + EPSILON);
|
||||
|
||||
base_plate(support_mesh, platetmp, grid(zstart, zend, 0.1f), thr);
|
||||
|
||||
// We don't need no... holes control...
|
||||
for (const ExPolygon &bp : platetmp)
|
||||
basep.emplace_back(std::move(bp.contour));
|
||||
}
|
||||
|
||||
if(pcfg.embed_object) {
|
||||
|
||||
// If the zero elevation mode is ON, we need to process the model
|
||||
// base silhouette. Create the offsetted version and punch the
|
||||
// breaksticks across its perimeter.
|
||||
|
||||
ExPolygons modelbase_offs = modelbase;
|
||||
|
||||
if (pcfg.embed_object.object_gap_mm > 0.0)
|
||||
modelbase_offs
|
||||
= offset_ex(modelbase_offs,
|
||||
float(scaled(pcfg.embed_object.object_gap_mm)));
|
||||
|
||||
// Create a spatial index of the support silhouette polygons.
|
||||
// This will be used to check for intersections with the model
|
||||
// silhouette polygons. If there is no intersection, then a certain
|
||||
// part of the pad is redundant as it does not host any supports.
|
||||
BoxIndex bindex;
|
||||
{
|
||||
unsigned idx = 0;
|
||||
for(auto &bp : basep) {
|
||||
auto bb = bp.bounding_box();
|
||||
bb.offset(float(scaled(pcfg.min_wall_thickness_mm)));
|
||||
bindex.insert(bb, idx++);
|
||||
}
|
||||
}
|
||||
|
||||
ExPolygons concaveh = offset_ex(
|
||||
concave_hull(basep, pcfg.max_merge_distance_mm, thr),
|
||||
scaled<float>(pcfg.min_wall_thickness_mm));
|
||||
|
||||
// Punching the breaksticks across the offsetted polygon perimeters
|
||||
auto pad_stickholes = reserve_vector<ExPolygon>(modelbase.size());
|
||||
for(auto& poly : modelbase_offs) {
|
||||
|
||||
bool overlap = false;
|
||||
for (const ExPolygon &p : concaveh)
|
||||
overlap = overlap || poly.overlaps(p);
|
||||
|
||||
auto bb = poly.contour.bounding_box();
|
||||
bb.offset(scaled<float>(pcfg.min_wall_thickness_mm));
|
||||
|
||||
std::vector<BoxIndexEl> qres =
|
||||
bindex.query(bb, BoxIndex::qtIntersects);
|
||||
|
||||
if (!qres.empty() || overlap) {
|
||||
|
||||
// The model silhouette polygon 'poly' HAS an intersection
|
||||
// with the support silhouettes. Include this polygon
|
||||
// in the pad holes with the breaksticks and merge the
|
||||
// original (offsetted) version with the rest of the pad
|
||||
// base plate.
|
||||
|
||||
basep.emplace_back(poly.contour);
|
||||
|
||||
// The holes of 'poly' will become positive parts of the
|
||||
// pad, so they has to be checked for intersections as well
|
||||
// and erased if there is no intersection with the supports
|
||||
auto it = poly.holes.begin();
|
||||
while(it != poly.holes.end()) {
|
||||
if (bindex.query(it->bounding_box(),
|
||||
BoxIndex::qtIntersects).empty())
|
||||
it = poly.holes.erase(it);
|
||||
else
|
||||
++it;
|
||||
}
|
||||
|
||||
// Punch the breaksticks
|
||||
sla::breakstick_holes(
|
||||
poly,
|
||||
pcfg.embed_object.object_gap_mm, // padding
|
||||
pcfg.embed_object.stick_stride_mm,
|
||||
pcfg.embed_object.stick_width_mm,
|
||||
pcfg.embed_object.stick_penetration_mm);
|
||||
|
||||
pad_stickholes.emplace_back(poly);
|
||||
}
|
||||
}
|
||||
|
||||
create_base_pool(basep, tmesh, pad_stickholes, cfg);
|
||||
} else {
|
||||
for (const ExPolygon &bp : modelbase) basep.emplace_back(bp.contour);
|
||||
create_base_pool(basep, tmesh, {}, cfg);
|
||||
}
|
||||
pad_blueprint(support_mesh, sup_contours, grid(zstart, zend, 0.1f), thr);
|
||||
create_pad(sup_contours, model_contours, tmesh, pcfg);
|
||||
|
||||
tmesh.translate(0, 0, float(zlevel));
|
||||
if (!tmesh.empty()) tmesh.require_shared_vertices();
|
||||
@ -720,43 +630,18 @@ struct Pad {
|
||||
};
|
||||
|
||||
// The minimum distance for two support points to remain valid.
|
||||
static const double /*constexpr*/ D_SP = 0.1;
|
||||
const double /*constexpr*/ D_SP = 0.1;
|
||||
|
||||
} // namespace
|
||||
|
||||
enum { // For indexing Eigen vectors as v(X), v(Y), v(Z) instead of numbers
|
||||
X, Y, Z
|
||||
};
|
||||
|
||||
// Calculate the normals for the selected points (from 'points' set) on the
|
||||
// mesh. This will call squared distance for each point.
|
||||
PointSet normals(const PointSet& points,
|
||||
const EigenMesh3D& mesh,
|
||||
double eps = 0.05, // min distance from edges
|
||||
std::function<void()> throw_on_cancel = [](){},
|
||||
const std::vector<unsigned>& selected_points = {});
|
||||
|
||||
inline Vec2d to_vec2(const Vec3d& v3) {
|
||||
return {v3(X), v3(Y)};
|
||||
}
|
||||
|
||||
bool operator==(const PointIndexEl& e1, const PointIndexEl& e2) {
|
||||
return e1.second == e2.second;
|
||||
}
|
||||
|
||||
// Clustering a set of points by the given distance.
|
||||
ClusteredPoints cluster(const std::vector<unsigned>& indices,
|
||||
std::function<Vec3d(unsigned)> pointfn,
|
||||
double dist,
|
||||
unsigned max_points);
|
||||
|
||||
ClusteredPoints cluster(const PointSet& points,
|
||||
double dist,
|
||||
unsigned max_points);
|
||||
|
||||
ClusteredPoints cluster(
|
||||
const std::vector<unsigned>& indices,
|
||||
std::function<Vec3d(unsigned)> pointfn,
|
||||
std::function<bool(const PointIndexEl&, const PointIndexEl&)> predicate,
|
||||
unsigned max_points);
|
||||
|
||||
// This class will hold the support tree meshes with some additional bookkeeping
|
||||
// as well. Various parts of the support geometry are stored separately and are
|
||||
@ -775,20 +660,20 @@ class SLASupportTree::Impl {
|
||||
// For heads it is beneficial to use the same IDs as for the support points.
|
||||
std::vector<Head> m_heads;
|
||||
std::vector<size_t> m_head_indices;
|
||||
|
||||
std::vector<Pillar> m_pillars;
|
||||
std::vector<Junction> m_junctions;
|
||||
std::vector<Bridge> m_bridges;
|
||||
std::vector<CompactBridge> m_compact_bridges;
|
||||
std::vector<CompactBridge> m_compact_bridges;
|
||||
Pad m_pad;
|
||||
|
||||
Controller m_ctl;
|
||||
|
||||
Pad m_pad;
|
||||
|
||||
using Mutex = ccr::Mutex;
|
||||
using Mutex = ccr::SpinningMutex;
|
||||
|
||||
mutable TriangleMesh m_meshcache;
|
||||
mutable Mutex m_mutex;
|
||||
mutable TriangleMesh meshcache; mutable bool meshcache_valid = false;
|
||||
mutable double model_height = 0; // the full height of the model
|
||||
mutable bool m_meshcache_valid = false;
|
||||
mutable double m_model_height = 0; // the full height of the model
|
||||
|
||||
public:
|
||||
double ground_level = 0;
|
||||
@ -807,7 +692,7 @@ public:
|
||||
if (id >= m_head_indices.size()) m_head_indices.resize(id + 1);
|
||||
m_head_indices[id] = m_heads.size() - 1;
|
||||
|
||||
meshcache_valid = false;
|
||||
m_meshcache_valid = false;
|
||||
return m_heads.back();
|
||||
}
|
||||
|
||||
@ -825,7 +710,7 @@ public:
|
||||
pillar.start_junction_id = head.id;
|
||||
pillar.starts_from_head = true;
|
||||
|
||||
meshcache_valid = false;
|
||||
m_meshcache_valid = false;
|
||||
return m_pillars.back();
|
||||
}
|
||||
|
||||
@ -854,22 +739,10 @@ public:
|
||||
Pillar& pillar = m_pillars.back();
|
||||
pillar.id = long(m_pillars.size() - 1);
|
||||
pillar.starts_from_head = false;
|
||||
meshcache_valid = false;
|
||||
m_meshcache_valid = false;
|
||||
return m_pillars.back();
|
||||
}
|
||||
|
||||
const Head& pillar_head(long pillar_id) const
|
||||
{
|
||||
std::lock_guard<Mutex> lk(m_mutex);
|
||||
assert(pillar_id >= 0 && pillar_id < long(m_pillars.size()));
|
||||
|
||||
const Pillar& p = m_pillars[size_t(pillar_id)];
|
||||
assert(p.starts_from_head && p.start_junction_id >= 0);
|
||||
assert(size_t(p.start_junction_id) < m_head_indices.size());
|
||||
|
||||
return m_heads[m_head_indices[p.start_junction_id]];
|
||||
}
|
||||
|
||||
const Pillar& head_pillar(unsigned headid) const
|
||||
{
|
||||
std::lock_guard<Mutex> lk(m_mutex);
|
||||
@ -886,7 +759,7 @@ public:
|
||||
std::lock_guard<Mutex> lk(m_mutex);
|
||||
m_junctions.emplace_back(std::forward<Args>(args)...);
|
||||
m_junctions.back().id = long(m_junctions.size() - 1);
|
||||
meshcache_valid = false;
|
||||
m_meshcache_valid = false;
|
||||
return m_junctions.back();
|
||||
}
|
||||
|
||||
@ -895,7 +768,7 @@ public:
|
||||
std::lock_guard<Mutex> lk(m_mutex);
|
||||
m_bridges.emplace_back(std::forward<Args>(args)...);
|
||||
m_bridges.back().id = long(m_bridges.size() - 1);
|
||||
meshcache_valid = false;
|
||||
m_meshcache_valid = false;
|
||||
return m_bridges.back();
|
||||
}
|
||||
|
||||
@ -904,7 +777,7 @@ public:
|
||||
std::lock_guard<Mutex> lk(m_mutex);
|
||||
m_compact_bridges.emplace_back(std::forward<Args>(args)...);
|
||||
m_compact_bridges.back().id = long(m_compact_bridges.size() - 1);
|
||||
meshcache_valid = false;
|
||||
m_meshcache_valid = false;
|
||||
return m_compact_bridges.back();
|
||||
}
|
||||
|
||||
@ -913,7 +786,7 @@ public:
|
||||
std::lock_guard<Mutex> lk(m_mutex);
|
||||
assert(id < m_head_indices.size());
|
||||
|
||||
meshcache_valid = false;
|
||||
m_meshcache_valid = false;
|
||||
return m_heads[m_head_indices[id]];
|
||||
}
|
||||
|
||||
@ -933,9 +806,10 @@ public:
|
||||
|
||||
const Pad &create_pad(const TriangleMesh &object_supports,
|
||||
const ExPolygons & modelbase,
|
||||
const PoolConfig & cfg)
|
||||
const PadConfig & cfg)
|
||||
{
|
||||
m_pad = Pad(object_supports, modelbase, ground_level, cfg);
|
||||
m_pad = Pad{object_supports, modelbase, ground_level, cfg, m_ctl.cancelfn};
|
||||
|
||||
return m_pad;
|
||||
}
|
||||
|
||||
@ -946,7 +820,7 @@ public:
|
||||
// WITHOUT THE PAD!!!
|
||||
const TriangleMesh &merged_mesh() const
|
||||
{
|
||||
if (meshcache_valid) return meshcache;
|
||||
if (m_meshcache_valid) return m_meshcache;
|
||||
|
||||
Contour3D merged;
|
||||
|
||||
@ -978,39 +852,39 @@ public:
|
||||
|
||||
if (m_ctl.stopcondition()) {
|
||||
// In case of failure we have to return an empty mesh
|
||||
meshcache = TriangleMesh();
|
||||
return meshcache;
|
||||
m_meshcache = TriangleMesh();
|
||||
return m_meshcache;
|
||||
}
|
||||
|
||||
meshcache = mesh(merged);
|
||||
m_meshcache = mesh(merged);
|
||||
|
||||
// The mesh will be passed by const-pointer to TriangleMeshSlicer,
|
||||
// which will need this.
|
||||
if (!meshcache.empty()) meshcache.require_shared_vertices();
|
||||
if (!m_meshcache.empty()) m_meshcache.require_shared_vertices();
|
||||
|
||||
BoundingBoxf3 &&bb = meshcache.bounding_box();
|
||||
model_height = bb.max(Z) - bb.min(Z);
|
||||
BoundingBoxf3 &&bb = m_meshcache.bounding_box();
|
||||
m_model_height = bb.max(Z) - bb.min(Z);
|
||||
|
||||
meshcache_valid = true;
|
||||
return meshcache;
|
||||
m_meshcache_valid = true;
|
||||
return m_meshcache;
|
||||
}
|
||||
|
||||
// WITH THE PAD
|
||||
double full_height() const
|
||||
{
|
||||
if (merged_mesh().empty() && !pad().empty())
|
||||
return get_pad_fullheight(pad().cfg);
|
||||
return pad().cfg.full_height();
|
||||
|
||||
double h = mesh_height();
|
||||
if (!pad().empty()) h += sla::get_pad_elevation(pad().cfg);
|
||||
if (!pad().empty()) h += pad().cfg.required_elevation();
|
||||
return h;
|
||||
}
|
||||
|
||||
// WITHOUT THE PAD!!!
|
||||
double mesh_height() const
|
||||
{
|
||||
if (!meshcache_valid) merged_mesh();
|
||||
return model_height;
|
||||
if (!m_meshcache_valid) merged_mesh();
|
||||
return m_model_height;
|
||||
}
|
||||
|
||||
// Intended to be called after the generation is fully complete
|
||||
@ -1032,11 +906,11 @@ public:
|
||||
// vector of point indices.
|
||||
template<class DistFn>
|
||||
long cluster_centroid(const ClusterEl& clust,
|
||||
std::function<Vec3d(size_t)> pointfn,
|
||||
const std::function<Vec3d(size_t)> &pointfn,
|
||||
DistFn df)
|
||||
{
|
||||
switch(clust.size()) {
|
||||
case 0: /* empty cluster */ return -1;
|
||||
case 0: /* empty cluster */ return ID_UNSET;
|
||||
case 1: /* only one element */ return 0;
|
||||
case 2: /* if two elements, there is no center */ return 0;
|
||||
default: ;
|
||||
@ -1116,8 +990,53 @@ class SLASupportTree::Algorithm {
|
||||
ThrowOnCancel m_thr;
|
||||
|
||||
// A spatial index to easily find strong pillars to connect to.
|
||||
PointIndex m_pillar_index;
|
||||
|
||||
|
||||
class PillarIndex {
|
||||
PointIndex m_index;
|
||||
mutable ccr::LockingMutex m_mutex;
|
||||
|
||||
public:
|
||||
|
||||
template<class...Args> inline void guarded_insert(Args&&...args)
|
||||
{
|
||||
std::lock_guard<ccr::LockingMutex> lck(m_mutex);
|
||||
m_index.insert(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template<class...Args>
|
||||
inline std::vector<PointIndexEl> guarded_query(Args&&...args) const
|
||||
{
|
||||
std::lock_guard<ccr::LockingMutex> lck(m_mutex);
|
||||
return m_index.query(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template<class...Args> inline void insert(Args&&...args)
|
||||
{
|
||||
m_index.insert(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template<class...Args>
|
||||
inline std::vector<PointIndexEl> query(Args&&...args) const
|
||||
{
|
||||
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 guarded_foreach(Fn fn)
|
||||
{
|
||||
std::lock_guard<ccr::LockingMutex> lck(m_mutex);
|
||||
m_index.foreach(fn);
|
||||
}
|
||||
|
||||
PointIndex guarded_clone()
|
||||
{
|
||||
std::lock_guard<ccr::LockingMutex> lck(m_mutex);
|
||||
return m_index;
|
||||
}
|
||||
|
||||
} m_pillar_index;
|
||||
|
||||
|
||||
inline double ray_mesh_intersect(const Vec3d& s,
|
||||
const Vec3d& dir)
|
||||
{
|
||||
@ -1382,7 +1301,7 @@ class SLASupportTree::Algorithm {
|
||||
// Align to center
|
||||
double available_dist = (startz - endz);
|
||||
double rounds = std::floor(available_dist / std::abs(zstep));
|
||||
startz -= 0.5 * (available_dist - rounds * std::abs(zstep));;
|
||||
startz -= 0.5 * (available_dist - rounds * std::abs(zstep));
|
||||
}
|
||||
|
||||
auto pcm = m_cfg.pillar_connection_mode;
|
||||
@ -1507,9 +1426,9 @@ class SLASupportTree::Algorithm {
|
||||
}
|
||||
|
||||
bool search_pillar_and_connect(const Head& head) {
|
||||
PointIndex spindex = m_pillar_index;
|
||||
PointIndex spindex = m_pillar_index.guarded_clone();
|
||||
|
||||
long nearest_id = -1;
|
||||
long nearest_id = ID_UNSET;
|
||||
|
||||
Vec3d querypoint = head.junction_point();
|
||||
|
||||
@ -1530,8 +1449,8 @@ class SLASupportTree::Algorithm {
|
||||
auto nearpillarID = unsigned(nearest_id);
|
||||
if(nearpillarID < m_result.pillarcount()) {
|
||||
if(!connect_to_nearpillar(head, nearpillarID)) {
|
||||
nearest_id = -1; // continue searching
|
||||
spindex.remove(ne); // without the current pillar
|
||||
nearest_id = ID_UNSET; // continue searching
|
||||
spindex.remove(ne); // without the current pillar
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1545,7 +1464,7 @@ class SLASupportTree::Algorithm {
|
||||
void create_ground_pillar(const Vec3d &jp,
|
||||
const Vec3d &sourcedir,
|
||||
double radius,
|
||||
int head_id = -1)
|
||||
long head_id = ID_UNSET)
|
||||
{
|
||||
// People were killed for this number (seriously)
|
||||
static const double SQR2 = std::sqrt(2.0);
|
||||
@ -1554,7 +1473,7 @@ class SLASupportTree::Algorithm {
|
||||
double gndlvl = m_result.ground_level;
|
||||
Vec3d endp = {jp(X), jp(Y), gndlvl};
|
||||
double sd = m_cfg.pillar_base_safety_distance_mm;
|
||||
int pillar_id = -1;
|
||||
long pillar_id = ID_UNSET;
|
||||
double min_dist = sd + m_cfg.base_radius_mm + EPSILON;
|
||||
double dist = 0;
|
||||
bool can_add_base = true;
|
||||
@ -1567,7 +1486,7 @@ class SLASupportTree::Algorithm {
|
||||
// the ground level only.
|
||||
|
||||
normal_mode = false;
|
||||
double mv = min_dist - dist;
|
||||
double mind = min_dist - dist;
|
||||
double azimuth = std::atan2(sourcedir(Y), sourcedir(X));
|
||||
double sinpolar = std::sin(PI - m_cfg.bridge_slope);
|
||||
double cospolar = std::cos(PI - m_cfg.bridge_slope);
|
||||
@ -1584,14 +1503,14 @@ class SLASupportTree::Algorithm {
|
||||
|
||||
auto result = solver.optimize_max(
|
||||
[this, dir, jp, gndlvl](double mv) {
|
||||
Vec3d endp = jp + SQR2 * mv * dir;
|
||||
endp(Z) = gndlvl;
|
||||
return std::sqrt(m_mesh.squared_distance(endp));
|
||||
Vec3d endpt = jp + SQR2 * mv * dir;
|
||||
endpt(Z) = gndlvl;
|
||||
return std::sqrt(m_mesh.squared_distance(endpt));
|
||||
},
|
||||
initvals(mv), bound(0.0, 2 * min_dist));
|
||||
initvals(mind), bound(0.0, 2 * min_dist));
|
||||
|
||||
mv = std::get<0>(result.optimum);
|
||||
endp = jp + SQR2 * mv * dir;
|
||||
mind = std::get<0>(result.optimum);
|
||||
endp = jp + SQR2 * mind * dir;
|
||||
Vec3d pgnd = {endp(X), endp(Y), gndlvl};
|
||||
can_add_base = result.score > min_dist;
|
||||
|
||||
@ -1651,7 +1570,7 @@ class SLASupportTree::Algorithm {
|
||||
}
|
||||
|
||||
if(pillar_id >= 0) // Save the pillar endpoint in the spatial index
|
||||
m_pillar_index.insert(endp, pillar_id);
|
||||
m_pillar_index.guarded_insert(endp, unsigned(pillar_id));
|
||||
}
|
||||
|
||||
public:
|
||||
@ -1717,9 +1636,9 @@ public:
|
||||
using libnest2d::opt::GeneticOptimizer;
|
||||
using libnest2d::opt::StopCriteria;
|
||||
|
||||
ccr::Mutex mutex;
|
||||
ccr::SpinningMutex mutex;
|
||||
auto addfn = [&mutex](PtIndices &container, unsigned val) {
|
||||
std::lock_guard<ccr::Mutex> lk(mutex);
|
||||
std::lock_guard<ccr::SpinningMutex> lk(mutex);
|
||||
container.emplace_back(val);
|
||||
};
|
||||
|
||||
@ -1727,8 +1646,8 @@ public:
|
||||
[this, &nmls, addfn](unsigned fidx, size_t i)
|
||||
{
|
||||
m_thr();
|
||||
|
||||
auto n = nmls.row(i);
|
||||
|
||||
auto n = nmls.row(Eigen::Index(i));
|
||||
|
||||
// for all normals we generate the spherical coordinates and
|
||||
// saturate the polar angle to 45 degrees from the bottom then
|
||||
@ -1786,19 +1705,20 @@ public:
|
||||
auto oresult = solver.optimize_max(
|
||||
[this, pin_r, w, hp](double plr, double azm)
|
||||
{
|
||||
auto n = Vec3d(std::cos(azm) * std::sin(plr),
|
||||
std::sin(azm) * std::sin(plr),
|
||||
std::cos(plr)).normalized();
|
||||
auto dir = Vec3d(std::cos(azm) * std::sin(plr),
|
||||
std::sin(azm) * std::sin(plr),
|
||||
std::cos(plr)).normalized();
|
||||
|
||||
double score = pinhead_mesh_intersect(
|
||||
hp, n, pin_r, m_cfg.head_back_radius_mm, w);
|
||||
hp, dir, pin_r, m_cfg.head_back_radius_mm, w);
|
||||
|
||||
return score;
|
||||
},
|
||||
initvals(polar, azimuth), // start with what we have
|
||||
bound(3*PI/4, PI), // Must not exceed the tilt limit
|
||||
bound(-PI, PI) // azimuth can be a full search
|
||||
);
|
||||
bound(3 * PI / 4,
|
||||
PI), // Must not exceed the tilt limit
|
||||
bound(-PI, PI) // azimuth can be a full search
|
||||
);
|
||||
|
||||
if(oresult.score > w) {
|
||||
polar = std::get<0>(oresult.optimum);
|
||||
@ -1921,7 +1841,9 @@ public:
|
||||
ClusterEl cl_centroids;
|
||||
cl_centroids.reserve(m_pillar_clusters.size());
|
||||
|
||||
for(auto& cl : m_pillar_clusters) { m_thr();
|
||||
for(auto& cl : m_pillar_clusters) {
|
||||
m_thr();
|
||||
|
||||
// place all the centroid head positions into the index. We
|
||||
// will query for alternative pillar positions. If a sidehead
|
||||
// cannot connect to the cluster centroid, we have to search
|
||||
@ -1957,7 +1879,8 @@ public:
|
||||
// sidepoints with the cluster centroid (which is a ground pillar)
|
||||
// or a nearby pillar if the centroid is unreachable.
|
||||
size_t ci = 0;
|
||||
for(auto cl : m_pillar_clusters) { m_thr();
|
||||
for(auto cl : m_pillar_clusters) {
|
||||
m_thr();
|
||||
|
||||
auto cidx = cl_centroids[ci++];
|
||||
|
||||
@ -2015,7 +1938,7 @@ public:
|
||||
};
|
||||
|
||||
std::vector<unsigned> modelpillars;
|
||||
ccr::Mutex mutex;
|
||||
ccr::SpinningMutex mutex;
|
||||
|
||||
// TODO: connect these to the ground pillars if possible
|
||||
ccr::enumerate(m_iheads_onmodel.begin(), m_iheads_onmodel.end(),
|
||||
@ -2161,7 +2084,7 @@ public:
|
||||
pill.base = tailhead.mesh;
|
||||
|
||||
// Experimental: add the pillar to the index for cascading
|
||||
std::lock_guard<ccr::Mutex> lk(mutex);
|
||||
std::lock_guard<ccr::SpinningMutex> lk(mutex);
|
||||
modelpillars.emplace_back(unsigned(pill.id));
|
||||
return;
|
||||
}
|
||||
@ -2184,13 +2107,10 @@ public:
|
||||
// in O(log) or even constant time with a set or an unordered set of hash
|
||||
// values uniquely representing a pair of integers. The order of numbers
|
||||
// within the pair should not matter, it has the same unique hash.
|
||||
template<class I> static I pairhash(I a, I b)
|
||||
template<class I> static IntegerOnly<I> pairhash(I a, I b)
|
||||
{
|
||||
using std::ceil; using std::log2; using std::max; using std::min;
|
||||
|
||||
static_assert(std::is_integral<I>::value,
|
||||
"This function works only for integral types.");
|
||||
|
||||
I g = min(a, b), l = max(a, b);
|
||||
|
||||
auto bits_g = g ? int(ceil(log2(g))) : 0;
|
||||
@ -2414,7 +2334,8 @@ public:
|
||||
|
||||
// We will sink the pins into the model surface for a distance of 1/3 of
|
||||
// the pin radius
|
||||
for(unsigned i : m_iheadless) { m_thr();
|
||||
for(unsigned i : m_iheadless) {
|
||||
m_thr();
|
||||
|
||||
const auto R = double(m_support_pts[i].head_front_radius);
|
||||
const double HWIDTH_MM = R/3;
|
||||
@ -2617,8 +2538,9 @@ std::vector<ExPolygons> SLASupportTree::slice(
|
||||
|
||||
auto bb = pad_mesh.bounding_box();
|
||||
auto maxzit = std::upper_bound(grid.begin(), grid.end(), bb.max.z());
|
||||
|
||||
auto padgrid = reserve_vector<float>(grid.end() - maxzit);
|
||||
|
||||
long cap = grid.end() - maxzit;
|
||||
auto padgrid = reserve_vector<float>(size_t(cap > 0 ? cap : 0));
|
||||
std::copy(grid.begin(), maxzit, std::back_inserter(padgrid));
|
||||
|
||||
TriangleMeshSlicer pad_slicer(&pad_mesh);
|
||||
@ -2645,7 +2567,7 @@ std::vector<ExPolygons> SLASupportTree::slice(
|
||||
}
|
||||
|
||||
const TriangleMesh &SLASupportTree::add_pad(const ExPolygons& modelbase,
|
||||
const PoolConfig& pcfg) const
|
||||
const PadConfig& pcfg) const
|
||||
{
|
||||
return m_impl->create_pad(merged_mesh(), modelbase, pcfg).tmesh;
|
||||
}
|
||||
@ -2670,6 +2592,9 @@ SLASupportTree::SLASupportTree(const std::vector<SupportPoint> &points,
|
||||
generate(points, emesh, cfg, ctl);
|
||||
}
|
||||
|
||||
SLASupportTree::SLASupportTree(SLASupportTree &&o) = default;
|
||||
SLASupportTree &SLASupportTree::operator=(SLASupportTree &&o) = default;
|
||||
|
||||
SLASupportTree::~SLASupportTree() {}
|
||||
|
||||
}
|
||||
|
@ -9,7 +9,6 @@
|
||||
|
||||
#include "SLACommon.hpp"
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// Needed types from Point.hpp
|
||||
@ -85,6 +84,11 @@ struct SupportConfig {
|
||||
// The shortest distance between a pillar base perimeter from the model
|
||||
// body. This is only useful when elevation is set to zero.
|
||||
double pillar_base_safety_distance_mm = 0.5;
|
||||
|
||||
double head_fullwidth() const {
|
||||
return 2 * head_front_radius_mm + head_width_mm +
|
||||
2 * head_back_radius_mm - head_penetration_mm;
|
||||
}
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
// Compile time configuration values (candidates for runtime)
|
||||
@ -104,7 +108,7 @@ struct SupportConfig {
|
||||
static const unsigned max_bridges_on_pillar;
|
||||
};
|
||||
|
||||
struct PoolConfig;
|
||||
struct PadConfig;
|
||||
|
||||
/// A Control structure for the support calculation. Consists of the status
|
||||
/// indicator callback and the stop condition predicate.
|
||||
@ -124,17 +128,6 @@ struct Controller {
|
||||
std::function<void(void)> cancelfn = [](){};
|
||||
};
|
||||
|
||||
using PointSet = Eigen::MatrixXd;
|
||||
|
||||
//EigenMesh3D to_eigenmesh(const TriangleMesh& m);
|
||||
|
||||
// needed for find best rotation
|
||||
//EigenMesh3D to_eigenmesh(const ModelObject& model);
|
||||
|
||||
// Simple conversion of 'vector of points' to an Eigen matrix
|
||||
//PointSet to_point_set(const std::vector<sla::SupportPoint>&);
|
||||
|
||||
|
||||
/* ************************************************************************** */
|
||||
|
||||
/// The class containing mesh data for the generated supports.
|
||||
@ -174,6 +167,9 @@ public:
|
||||
|
||||
SLASupportTree(const SLASupportTree&) = delete;
|
||||
SLASupportTree& operator=(const SLASupportTree&) = delete;
|
||||
|
||||
SLASupportTree(SLASupportTree &&o);
|
||||
SLASupportTree &operator=(SLASupportTree &&o);
|
||||
|
||||
~SLASupportTree();
|
||||
|
||||
@ -192,7 +188,7 @@ public:
|
||||
/// Otherwise, the modelbase will be unified with the base plate calculated
|
||||
/// from the supports.
|
||||
const TriangleMesh& add_pad(const ExPolygons& modelbase,
|
||||
const PoolConfig& pcfg) const;
|
||||
const PadConfig& pcfg) const;
|
||||
|
||||
/// Get the pad geometry
|
||||
const TriangleMesh& get_pad() const;
|
||||
|
@ -77,7 +77,7 @@ bool PointIndex::remove(const PointIndexEl& el)
|
||||
}
|
||||
|
||||
std::vector<PointIndexEl>
|
||||
PointIndex::query(std::function<bool(const PointIndexEl &)> fn)
|
||||
PointIndex::query(std::function<bool(const PointIndexEl &)> fn) const
|
||||
{
|
||||
namespace bgi = boost::geometry::index;
|
||||
|
||||
@ -86,7 +86,7 @@ PointIndex::query(std::function<bool(const PointIndexEl &)> fn)
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<PointIndexEl> PointIndex::nearest(const Vec3d &el, unsigned k = 1)
|
||||
std::vector<PointIndexEl> PointIndex::nearest(const Vec3d &el, unsigned k = 1) const
|
||||
{
|
||||
namespace bgi = boost::geometry::index;
|
||||
std::vector<PointIndexEl> ret; ret.reserve(k);
|
||||
@ -104,6 +104,11 @@ void PointIndex::foreach(std::function<void (const PointIndexEl &)> fn)
|
||||
for(auto& el : m_impl->m_store) fn(el);
|
||||
}
|
||||
|
||||
void PointIndex::foreach(std::function<void (const PointIndexEl &)> fn) const
|
||||
{
|
||||
for(const auto &el : m_impl->m_store) fn(el);
|
||||
}
|
||||
|
||||
/* **************************************************************************
|
||||
* BoxIndex implementation
|
||||
* ************************************************************************** */
|
||||
@ -274,6 +279,8 @@ double EigenMesh3D::squared_distance(const Vec3d &p, int& i, Vec3d& c) const {
|
||||
* Misc functions
|
||||
* ****************************************************************************/
|
||||
|
||||
namespace {
|
||||
|
||||
bool point_on_edge(const Vec3d& p, const Vec3d& e1, const Vec3d& e2,
|
||||
double eps = 0.05)
|
||||
{
|
||||
@ -289,11 +296,13 @@ template<class Vec> double distance(const Vec& pp1, const Vec& pp2) {
|
||||
return std::sqrt(p.transpose() * p);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
PointSet normals(const PointSet& points,
|
||||
const EigenMesh3D& mesh,
|
||||
double eps,
|
||||
std::function<void()> thr, // throw on cancel
|
||||
const std::vector<unsigned>& pt_indices = {})
|
||||
const std::vector<unsigned>& pt_indices)
|
||||
{
|
||||
if(points.rows() == 0 || mesh.V().rows() == 0 || mesh.F().rows() == 0)
|
||||
return {};
|
||||
@ -419,9 +428,17 @@ PointSet normals(const PointSet& points,
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
namespace bgi = boost::geometry::index;
|
||||
using Index3D = bgi::rtree< PointIndexEl, bgi::rstar<16, 4> /* ? */ >;
|
||||
|
||||
namespace {
|
||||
|
||||
bool cmp_ptidx_elements(const PointIndexEl& e1, const PointIndexEl& e2)
|
||||
{
|
||||
return e1.second < e2.second;
|
||||
};
|
||||
|
||||
ClusteredPoints cluster(Index3D &sindex,
|
||||
unsigned max_points,
|
||||
std::function<std::vector<PointIndexEl>(
|
||||
@ -433,25 +450,22 @@ ClusteredPoints cluster(Index3D &sindex,
|
||||
// each other
|
||||
std::function<void(Elems&, Elems&)> group =
|
||||
[&sindex, &group, max_points, qfn](Elems& pts, Elems& cluster)
|
||||
{
|
||||
{
|
||||
for(auto& p : pts) {
|
||||
std::vector<PointIndexEl> tmp = qfn(sindex, p);
|
||||
auto cmp = [](const PointIndexEl& e1, const PointIndexEl& e2){
|
||||
return e1.second < e2.second;
|
||||
};
|
||||
|
||||
std::sort(tmp.begin(), tmp.end(), cmp);
|
||||
|
||||
std::sort(tmp.begin(), tmp.end(), cmp_ptidx_elements);
|
||||
|
||||
Elems newpts;
|
||||
std::set_difference(tmp.begin(), tmp.end(),
|
||||
cluster.begin(), cluster.end(),
|
||||
std::back_inserter(newpts), cmp);
|
||||
std::back_inserter(newpts), cmp_ptidx_elements);
|
||||
|
||||
int c = max_points && newpts.size() + cluster.size() > max_points?
|
||||
int(max_points - cluster.size()) : int(newpts.size());
|
||||
|
||||
cluster.insert(cluster.end(), newpts.begin(), newpts.begin() + c);
|
||||
std::sort(cluster.begin(), cluster.end(), cmp);
|
||||
std::sort(cluster.begin(), cluster.end(), cmp_ptidx_elements);
|
||||
|
||||
if(!newpts.empty() && (!max_points || cluster.size() < max_points))
|
||||
group(newpts, cluster);
|
||||
@ -479,7 +493,6 @@ ClusteredPoints cluster(Index3D &sindex,
|
||||
return result;
|
||||
}
|
||||
|
||||
namespace {
|
||||
std::vector<PointIndexEl> distance_queryfn(const Index3D& sindex,
|
||||
const PointIndexEl& p,
|
||||
double dist,
|
||||
@ -496,7 +509,8 @@ std::vector<PointIndexEl> distance_queryfn(const Index3D& sindex,
|
||||
|
||||
return tmp;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Clustering a set of points by the given criteria
|
||||
ClusteredPoints cluster(
|
||||
@ -558,5 +572,5 @@ ClusteredPoints cluster(const PointSet& pts, double dist, unsigned max_points)
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
} // namespace sla
|
||||
} // namespace Slic3r
|
||||
|
@ -1,6 +1,6 @@
|
||||
#include "SLAPrint.hpp"
|
||||
#include "SLA/SLASupportTree.hpp"
|
||||
#include "SLA/SLABasePool.hpp"
|
||||
#include "SLA/SLAPad.hpp"
|
||||
#include "SLA/SLAAutoSupports.hpp"
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "Geometry.hpp"
|
||||
@ -53,7 +53,7 @@ const std::array<unsigned, slaposCount> OBJ_STEP_LEVELS =
|
||||
30, // slaposObjectSlice,
|
||||
20, // slaposSupportPoints,
|
||||
10, // slaposSupportTree,
|
||||
10, // slaposBasePool,
|
||||
10, // slaposPad,
|
||||
30, // slaposSliceSupports,
|
||||
};
|
||||
|
||||
@ -64,7 +64,7 @@ std::string OBJ_STEP_LABELS(size_t idx)
|
||||
case slaposObjectSlice: return L("Slicing model");
|
||||
case slaposSupportPoints: return L("Generating support points");
|
||||
case slaposSupportTree: return L("Generating support tree");
|
||||
case slaposBasePool: return L("Generating pad");
|
||||
case slaposPad: return L("Generating pad");
|
||||
case slaposSliceSupports: return L("Slicing supports");
|
||||
default:;
|
||||
}
|
||||
@ -612,12 +612,13 @@ sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c) {
|
||||
return scfg;
|
||||
}
|
||||
|
||||
sla::PoolConfig::EmbedObject builtin_pad_cfg(const SLAPrintObjectConfig& c) {
|
||||
sla::PoolConfig::EmbedObject ret;
|
||||
sla::PadConfig::EmbedObject builtin_pad_cfg(const SLAPrintObjectConfig& c) {
|
||||
sla::PadConfig::EmbedObject ret;
|
||||
|
||||
ret.enabled = is_zero_elevation(c);
|
||||
|
||||
if(ret.enabled) {
|
||||
ret.everywhere = c.pad_around_object_everywhere.getBool();
|
||||
ret.object_gap_mm = c.pad_object_gap.getFloat();
|
||||
ret.stick_width_mm = c.pad_object_connector_width.getFloat();
|
||||
ret.stick_stride_mm = c.pad_object_connector_stride.getFloat();
|
||||
@ -628,17 +629,15 @@ sla::PoolConfig::EmbedObject builtin_pad_cfg(const SLAPrintObjectConfig& c) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
sla::PoolConfig make_pool_config(const SLAPrintObjectConfig& c) {
|
||||
sla::PoolConfig pcfg;
|
||||
sla::PadConfig make_pad_cfg(const SLAPrintObjectConfig& c) {
|
||||
sla::PadConfig pcfg;
|
||||
|
||||
pcfg.min_wall_thickness_mm = c.pad_wall_thickness.getFloat();
|
||||
pcfg.wall_thickness_mm = c.pad_wall_thickness.getFloat();
|
||||
pcfg.wall_slope = c.pad_wall_slope.getFloat() * PI / 180.0;
|
||||
|
||||
// We do not support radius for now
|
||||
pcfg.edge_radius_mm = 0.0; //c.pad_edge_radius.getFloat();
|
||||
|
||||
pcfg.max_merge_distance_mm = c.pad_max_merge_distance.getFloat();
|
||||
pcfg.min_wall_height_mm = c.pad_wall_height.getFloat();
|
||||
pcfg.max_merge_dist_mm = c.pad_max_merge_distance.getFloat();
|
||||
pcfg.wall_height_mm = c.pad_wall_height.getFloat();
|
||||
pcfg.brim_size_mm = c.pad_brim_size.getFloat();
|
||||
|
||||
// set builtin pad implicitly ON
|
||||
pcfg.embed_object = builtin_pad_cfg(c);
|
||||
@ -646,6 +645,13 @@ sla::PoolConfig make_pool_config(const SLAPrintObjectConfig& c) {
|
||||
return pcfg;
|
||||
}
|
||||
|
||||
bool validate_pad(const TriangleMesh &pad, const sla::PadConfig &pcfg)
|
||||
{
|
||||
// An empty pad can only be created if embed_object mode is enabled
|
||||
// and the pad is not forced everywhere
|
||||
return !pad.empty() || (pcfg.embed_object.enabled && !pcfg.embed_object.everywhere);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
std::string SLAPrint::validate() const
|
||||
@ -663,17 +669,12 @@ std::string SLAPrint::validate() const
|
||||
|
||||
sla::SupportConfig cfg = make_support_cfg(po->config());
|
||||
|
||||
double pinhead_width =
|
||||
2 * cfg.head_front_radius_mm +
|
||||
cfg.head_width_mm +
|
||||
2 * cfg.head_back_radius_mm -
|
||||
cfg.head_penetration_mm;
|
||||
|
||||
double elv = cfg.object_elevation_mm;
|
||||
|
||||
sla::PoolConfig::EmbedObject builtinpad = builtin_pad_cfg(po->config());
|
||||
|
||||
if(supports_en && !builtinpad.enabled && elv < pinhead_width )
|
||||
sla::PadConfig padcfg = make_pad_cfg(po->config());
|
||||
sla::PadConfig::EmbedObject &builtinpad = padcfg.embed_object;
|
||||
|
||||
if(supports_en && !builtinpad.enabled && elv < cfg.head_fullwidth())
|
||||
return L(
|
||||
"Elevation is too low for object. Use the \"Pad around "
|
||||
"object\" feature to print the object without elevation.");
|
||||
@ -686,6 +687,9 @@ std::string SLAPrint::validate() const
|
||||
"distance' has to be greater than the 'Pad object gap' "
|
||||
"parameter to avoid this.");
|
||||
}
|
||||
|
||||
std::string pval = padcfg.validate();
|
||||
if (!pval.empty()) return pval;
|
||||
}
|
||||
|
||||
double expt_max = m_printer_config.max_exposure_time.getFloat();
|
||||
@ -876,8 +880,7 @@ void SLAPrint::process()
|
||||
|
||||
// Construction of this object does the calculation.
|
||||
this->throw_if_canceled();
|
||||
SLAAutoSupports auto_supports(po.transformed_mesh(),
|
||||
po.m_supportdata->emesh,
|
||||
SLAAutoSupports auto_supports(po.m_supportdata->emesh,
|
||||
po.get_model_slices(),
|
||||
heights,
|
||||
config,
|
||||
@ -908,23 +911,13 @@ void SLAPrint::process()
|
||||
// If the zero elevation mode is engaged, we have to filter out all the
|
||||
// points that are on the bottom of the object
|
||||
if (is_zero_elevation(po.config())) {
|
||||
double gnd = po.m_supportdata->emesh.ground_level();
|
||||
auto & pts = po.m_supportdata->support_points;
|
||||
double tolerance = po.config().pad_enable.getBool()
|
||||
? po.m_config.pad_wall_thickness.getFloat()
|
||||
: po.m_config.support_base_height.getFloat();
|
||||
|
||||
// get iterator to the reorganized vector end
|
||||
auto endit = std::remove_if(
|
||||
pts.begin(),
|
||||
pts.end(),
|
||||
[tolerance, gnd](const sla::SupportPoint &sp) {
|
||||
double diff = std::abs(gnd - double(sp.pos(Z)));
|
||||
return diff <= tolerance;
|
||||
});
|
||||
|
||||
// erase all elements after the new end
|
||||
pts.erase(endit, pts.end());
|
||||
remove_bottom_points(po.m_supportdata->support_points,
|
||||
po.m_supportdata->emesh.ground_level(),
|
||||
tolerance);
|
||||
}
|
||||
};
|
||||
|
||||
@ -933,11 +926,11 @@ void SLAPrint::process()
|
||||
{
|
||||
if(!po.m_supportdata) return;
|
||||
|
||||
sla::PoolConfig pcfg = make_pool_config(po.m_config);
|
||||
sla::PadConfig pcfg = make_pad_cfg(po.m_config);
|
||||
|
||||
if (pcfg.embed_object)
|
||||
po.m_supportdata->emesh.ground_level_offset(
|
||||
pcfg.min_wall_thickness_mm);
|
||||
pcfg.wall_thickness_mm);
|
||||
|
||||
if(!po.m_config.supports_enable.getBool()) {
|
||||
|
||||
@ -993,7 +986,7 @@ void SLAPrint::process()
|
||||
};
|
||||
|
||||
// This step generates the sla base pad
|
||||
auto base_pool = [this](SLAPrintObject& po) {
|
||||
auto generate_pad = [this](SLAPrintObject& po) {
|
||||
// this step can only go after the support tree has been created
|
||||
// and before the supports had been sliced. (or the slicing has to be
|
||||
// repeated)
|
||||
@ -1001,10 +994,10 @@ void SLAPrint::process()
|
||||
if(po.m_config.pad_enable.getBool())
|
||||
{
|
||||
// Get the distilled pad configuration from the config
|
||||
sla::PoolConfig pcfg = make_pool_config(po.m_config);
|
||||
sla::PadConfig pcfg = make_pad_cfg(po.m_config);
|
||||
|
||||
ExPolygons bp; // This will store the base plate of the pad.
|
||||
double pad_h = sla::get_pad_fullheight(pcfg);
|
||||
double pad_h = pcfg.full_height();
|
||||
const TriangleMesh &trmesh = po.transformed_mesh();
|
||||
|
||||
// This call can get pretty time consuming
|
||||
@ -1015,15 +1008,19 @@ void SLAPrint::process()
|
||||
// we sometimes call it "builtin pad" is enabled so we will
|
||||
// get a sample from the bottom of the mesh and use it for pad
|
||||
// creation.
|
||||
sla::base_plate(trmesh,
|
||||
bp,
|
||||
float(pad_h),
|
||||
float(po.m_config.layer_height.getFloat()),
|
||||
thrfn);
|
||||
sla::pad_blueprint(trmesh, bp, float(pad_h),
|
||||
float(po.m_config.layer_height.getFloat()),
|
||||
thrfn);
|
||||
}
|
||||
|
||||
pcfg.throw_on_cancel = thrfn;
|
||||
po.m_supportdata->support_tree_ptr->add_pad(bp, pcfg);
|
||||
auto &pad_mesh = po.m_supportdata->support_tree_ptr->get_pad();
|
||||
|
||||
if (!validate_pad(pad_mesh, pcfg))
|
||||
throw std::runtime_error(
|
||||
L("No pad can be generated for this model with the "
|
||||
"current configuration"));
|
||||
|
||||
} else if(po.m_supportdata && po.m_supportdata->support_tree_ptr) {
|
||||
po.m_supportdata->support_tree_ptr->remove_pad();
|
||||
}
|
||||
@ -1478,12 +1475,12 @@ void SLAPrint::process()
|
||||
|
||||
slaposFn pobj_program[] =
|
||||
{
|
||||
slice_model, support_points, support_tree, base_pool, slice_supports
|
||||
slice_model, support_points, support_tree, generate_pad, slice_supports
|
||||
};
|
||||
|
||||
// We want to first process all objects...
|
||||
std::vector<SLAPrintObjectStep> level1_obj_steps = {
|
||||
slaposObjectSlice, slaposSupportPoints, slaposSupportTree, slaposBasePool
|
||||
slaposObjectSlice, slaposSupportPoints, slaposSupportTree, slaposPad
|
||||
};
|
||||
|
||||
// and then slice all supports to allow preview to be displayed ASAP
|
||||
@ -1730,6 +1727,7 @@ bool SLAPrintObject::invalidate_state_by_config_options(const std::vector<t_conf
|
||||
|| opt_key == "supports_enable"
|
||||
|| opt_key == "support_object_elevation"
|
||||
|| opt_key == "pad_around_object"
|
||||
|| opt_key == "pad_around_object_everywhere"
|
||||
|| opt_key == "slice_closing_radius") {
|
||||
steps.emplace_back(slaposObjectSlice);
|
||||
} else if (
|
||||
@ -1754,6 +1752,7 @@ bool SLAPrintObject::invalidate_state_by_config_options(const std::vector<t_conf
|
||||
steps.emplace_back(slaposSupportTree);
|
||||
} else if (
|
||||
opt_key == "pad_wall_height"
|
||||
|| opt_key == "pad_brim_size"
|
||||
|| opt_key == "pad_max_merge_distance"
|
||||
|| opt_key == "pad_wall_slope"
|
||||
|| opt_key == "pad_edge_radius"
|
||||
@ -1762,7 +1761,7 @@ bool SLAPrintObject::invalidate_state_by_config_options(const std::vector<t_conf
|
||||
|| opt_key == "pad_object_connector_width"
|
||||
|| opt_key == "pad_object_connector_penetration"
|
||||
) {
|
||||
steps.emplace_back(slaposBasePool);
|
||||
steps.emplace_back(slaposPad);
|
||||
} else {
|
||||
// All keys should be covered.
|
||||
assert(false);
|
||||
@ -1782,12 +1781,12 @@ bool SLAPrintObject::invalidate_step(SLAPrintObjectStep step)
|
||||
if (step == slaposObjectSlice) {
|
||||
invalidated |= this->invalidate_all_steps();
|
||||
} else if (step == slaposSupportPoints) {
|
||||
invalidated |= this->invalidate_steps({ slaposSupportTree, slaposBasePool, slaposSliceSupports });
|
||||
invalidated |= this->invalidate_steps({ slaposSupportTree, slaposPad, slaposSliceSupports });
|
||||
invalidated |= m_print->invalidate_step(slapsMergeSlicesAndEval);
|
||||
} else if (step == slaposSupportTree) {
|
||||
invalidated |= this->invalidate_steps({ slaposBasePool, slaposSliceSupports });
|
||||
invalidated |= this->invalidate_steps({ slaposPad, slaposSliceSupports });
|
||||
invalidated |= m_print->invalidate_step(slapsMergeSlicesAndEval);
|
||||
} else if (step == slaposBasePool) {
|
||||
} else if (step == slaposPad) {
|
||||
invalidated |= this->invalidate_steps({slaposSliceSupports});
|
||||
invalidated |= m_print->invalidate_step(slapsMergeSlicesAndEval);
|
||||
} else if (step == slaposSliceSupports) {
|
||||
@ -1813,8 +1812,8 @@ double SLAPrintObject::get_elevation() const {
|
||||
// its walls but currently it is half of its thickness. Whatever it
|
||||
// will be in the future, we provide the config to the get_pad_elevation
|
||||
// method and we will have the correct value
|
||||
sla::PoolConfig pcfg = make_pool_config(m_config);
|
||||
if(!pcfg.embed_object) ret += sla::get_pad_elevation(pcfg);
|
||||
sla::PadConfig pcfg = make_pad_cfg(m_config);
|
||||
if(!pcfg.embed_object) ret += pcfg.required_elevation();
|
||||
}
|
||||
|
||||
return ret;
|
||||
@ -1825,7 +1824,7 @@ double SLAPrintObject::get_current_elevation() const
|
||||
if (is_zero_elevation(m_config)) return 0.;
|
||||
|
||||
bool has_supports = is_step_done(slaposSupportTree);
|
||||
bool has_pad = is_step_done(slaposBasePool);
|
||||
bool has_pad = is_step_done(slaposPad);
|
||||
|
||||
if(!has_supports && !has_pad)
|
||||
return 0;
|
||||
@ -1896,7 +1895,7 @@ bool SLAPrintObject::has_mesh(SLAPrintObjectStep step) const
|
||||
switch (step) {
|
||||
case slaposSupportTree:
|
||||
return ! this->support_mesh().empty();
|
||||
case slaposBasePool:
|
||||
case slaposPad:
|
||||
return ! this->pad_mesh().empty();
|
||||
default:
|
||||
return false;
|
||||
@ -1908,7 +1907,7 @@ TriangleMesh SLAPrintObject::get_mesh(SLAPrintObjectStep step) const
|
||||
switch (step) {
|
||||
case slaposSupportTree:
|
||||
return this->support_mesh();
|
||||
case slaposBasePool:
|
||||
case slaposPad:
|
||||
return this->pad_mesh();
|
||||
default:
|
||||
return TriangleMesh();
|
||||
|
@ -21,7 +21,7 @@ enum SLAPrintObjectStep : unsigned int {
|
||||
slaposObjectSlice,
|
||||
slaposSupportPoints,
|
||||
slaposSupportTree,
|
||||
slaposBasePool,
|
||||
slaposPad,
|
||||
slaposSliceSupports,
|
||||
slaposCount
|
||||
};
|
||||
@ -54,7 +54,7 @@ public:
|
||||
bool is_left_handed() const { return m_left_handed; }
|
||||
|
||||
struct Instance {
|
||||
Instance(ObjectID instance_id, const Point &shift, float rotation) : instance_id(instance_id), shift(shift), rotation(rotation) {}
|
||||
Instance(ObjectID inst_id, const Point &shft, float rot) : instance_id(inst_id), shift(shft), rotation(rot) {}
|
||||
bool operator==(const Instance &rhs) const { return this->instance_id == rhs.instance_id && this->shift == rhs.shift && this->rotation == rhs.rotation; }
|
||||
// ID of the corresponding ModelInstance.
|
||||
ObjectID instance_id;
|
||||
|
@ -10,12 +10,15 @@ namespace Slic3r {
|
||||
class ExPolygon;
|
||||
typedef std::vector<ExPolygon> ExPolygons;
|
||||
|
||||
extern std::vector<Vec3d> triangulate_expolygon_3d (const ExPolygon &poly, coordf_t z = 0, bool flip = false);
|
||||
extern std::vector<Vec3d> triangulate_expolygons_3d(const ExPolygons &polys, coordf_t z = 0, bool flip = false);
|
||||
extern std::vector<Vec2d> triangulate_expolygon_2d (const ExPolygon &poly, bool flip = false);
|
||||
extern std::vector<Vec2d> triangulate_expolygons_2d(const ExPolygons &polys, bool flip = false);
|
||||
extern std::vector<Vec2f> triangulate_expolygon_2f (const ExPolygon &poly, bool flip = false);
|
||||
extern std::vector<Vec2f> triangulate_expolygons_2f(const ExPolygons &polys, bool flip = false);
|
||||
const bool constexpr NORMALS_UP = false;
|
||||
const bool constexpr NORMALS_DOWN = true;
|
||||
|
||||
extern std::vector<Vec3d> triangulate_expolygon_3d (const ExPolygon &poly, coordf_t z = 0, bool flip = NORMALS_UP);
|
||||
extern std::vector<Vec3d> triangulate_expolygons_3d(const ExPolygons &polys, coordf_t z = 0, bool flip = NORMALS_UP);
|
||||
extern std::vector<Vec2d> triangulate_expolygon_2d (const ExPolygon &poly, bool flip = NORMALS_UP);
|
||||
extern std::vector<Vec2d> triangulate_expolygons_2d(const ExPolygons &polys, bool flip = NORMALS_UP);
|
||||
extern std::vector<Vec2f> triangulate_expolygon_2f (const ExPolygon &poly, bool flip = NORMALS_UP);
|
||||
extern std::vector<Vec2f> triangulate_expolygons_2f(const ExPolygons &polys, bool flip = NORMALS_UP);
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
|
@ -417,7 +417,7 @@ void GLVolume::render(int color_id, int detection_id, int worldmatrix_id) const
|
||||
}
|
||||
|
||||
bool GLVolume::is_sla_support() const { return this->composite_id.volume_id == -int(slaposSupportTree); }
|
||||
bool GLVolume::is_sla_pad() const { return this->composite_id.volume_id == -int(slaposBasePool); }
|
||||
bool GLVolume::is_sla_pad() const { return this->composite_id.volume_id == -int(slaposPad); }
|
||||
|
||||
std::vector<int> GLVolumeCollection::load_object(
|
||||
const ModelObject *model_object,
|
||||
@ -501,7 +501,7 @@ void GLVolumeCollection::load_object_auxiliary(
|
||||
TriangleMesh convex_hull = mesh.convex_hull_3d();
|
||||
for (const std::pair<size_t, size_t>& instance_idx : instances) {
|
||||
const ModelInstance& model_instance = *print_object->model_object()->instances[instance_idx.first];
|
||||
this->volumes.emplace_back(new GLVolume((milestone == slaposBasePool) ? GLVolume::SLA_PAD_COLOR : GLVolume::SLA_SUPPORT_COLOR));
|
||||
this->volumes.emplace_back(new GLVolume((milestone == slaposPad) ? GLVolume::SLA_PAD_COLOR : GLVolume::SLA_SUPPORT_COLOR));
|
||||
GLVolume& v = *this->volumes.back();
|
||||
v.indexed_vertex_array.load_mesh(mesh);
|
||||
v.indexed_vertex_array.finalize_geometry(opengl_initialized);
|
||||
|
@ -349,15 +349,18 @@ void ConfigManipulation::toggle_print_sla_options(DynamicPrintConfig* config)
|
||||
|
||||
toggle_field("pad_wall_thickness", pad_en);
|
||||
toggle_field("pad_wall_height", pad_en);
|
||||
toggle_field("pad_brim_size", pad_en);
|
||||
toggle_field("pad_max_merge_distance", pad_en);
|
||||
// toggle_field("pad_edge_radius", supports_en);
|
||||
toggle_field("pad_wall_slope", pad_en);
|
||||
toggle_field("pad_around_object", pad_en);
|
||||
toggle_field("pad_around_object_everywhere", pad_en);
|
||||
|
||||
bool zero_elev = config->opt_bool("pad_around_object") && pad_en;
|
||||
|
||||
toggle_field("support_object_elevation", supports_en && !zero_elev);
|
||||
toggle_field("pad_object_gap", zero_elev);
|
||||
toggle_field("pad_around_object_everywhere", zero_elev);
|
||||
toggle_field("pad_object_connector_stride", zero_elev);
|
||||
toggle_field("pad_object_connector_width", zero_elev);
|
||||
toggle_field("pad_object_connector_penetration", zero_elev);
|
||||
|
@ -1767,7 +1767,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
|
||||
|
||||
// SLA steps to pull the preview meshes for.
|
||||
typedef std::array<SLAPrintObjectStep, 2> SLASteps;
|
||||
SLASteps sla_steps = { slaposSupportTree, slaposBasePool };
|
||||
SLASteps sla_steps = { slaposSupportTree, slaposPad };
|
||||
struct SLASupportState {
|
||||
std::array<PrintStateBase::StateWithTimeStamp, std::tuple_size<SLASteps>::value> step;
|
||||
};
|
||||
@ -5340,8 +5340,8 @@ void GLCanvas3D::_load_sla_shells()
|
||||
m_volumes.volumes.back()->extruder_id = obj->model_object()->volumes.front()->extruder_id();
|
||||
if (obj->is_step_done(slaposSupportTree) && obj->has_mesh(slaposSupportTree))
|
||||
add_volume(*obj, -int(slaposSupportTree), instance, obj->support_mesh(), GLVolume::SLA_SUPPORT_COLOR, true);
|
||||
if (obj->is_step_done(slaposBasePool) && obj->has_mesh(slaposBasePool))
|
||||
add_volume(*obj, -int(slaposBasePool), instance, obj->pad_mesh(), GLVolume::SLA_PAD_COLOR, false);
|
||||
if (obj->is_step_done(slaposPad) && obj->has_mesh(slaposPad))
|
||||
add_volume(*obj, -int(slaposPad), instance, obj->pad_mesh(), GLVolume::SLA_PAD_COLOR, false);
|
||||
}
|
||||
double shift_z = obj->get_current_elevation();
|
||||
for (unsigned int i = initial_volumes_count; i < m_volumes.volumes.size(); ++ i) {
|
||||
|
@ -261,7 +261,7 @@ bool MainFrame::can_export_supports() const
|
||||
const PrintObjects& objects = m_plater->sla_print().objects();
|
||||
for (const SLAPrintObject* object : objects)
|
||||
{
|
||||
if (object->has_mesh(slaposBasePool) || object->has_mesh(slaposSupportTree))
|
||||
if (object->has_mesh(slaposPad) || object->has_mesh(slaposSupportTree))
|
||||
{
|
||||
can_export = true;
|
||||
break;
|
||||
|
@ -4467,10 +4467,10 @@ void Plater::export_stl(bool extended, bool selection_only)
|
||||
bool is_left_handed = object->is_left_handed();
|
||||
|
||||
TriangleMesh pad_mesh;
|
||||
bool has_pad_mesh = object->has_mesh(slaposBasePool);
|
||||
bool has_pad_mesh = object->has_mesh(slaposPad);
|
||||
if (has_pad_mesh)
|
||||
{
|
||||
pad_mesh = object->get_mesh(slaposBasePool);
|
||||
pad_mesh = object->get_mesh(slaposPad);
|
||||
pad_mesh.transform(mesh_trafo_inv);
|
||||
}
|
||||
|
||||
@ -4646,7 +4646,7 @@ void Plater::reslice_SLA_supports(const ModelObject &object, bool postpone_error
|
||||
// Otherwise calculate everything, but start with the provided object.
|
||||
if (!this->p->background_processing_enabled()) {
|
||||
task.single_model_instance_only = true;
|
||||
task.to_object_step = slaposBasePool;
|
||||
task.to_object_step = slaposPad;
|
||||
}
|
||||
this->p->background_process.set_task(task);
|
||||
// and let the background processing start.
|
||||
|
@ -476,11 +476,13 @@ const std::vector<std::string>& Preset::sla_print_options()
|
||||
"pad_enable",
|
||||
"pad_wall_thickness",
|
||||
"pad_wall_height",
|
||||
"pad_brim_size",
|
||||
"pad_max_merge_distance",
|
||||
// "pad_edge_radius",
|
||||
"pad_wall_slope",
|
||||
"pad_object_gap",
|
||||
"pad_around_object",
|
||||
"pad_around_object_everywhere",
|
||||
"pad_object_connector_stride",
|
||||
"pad_object_connector_width",
|
||||
"pad_object_connector_penetration",
|
||||
|
@ -3539,12 +3539,14 @@ void TabSLAPrint::build()
|
||||
optgroup->append_single_option_line("pad_enable");
|
||||
optgroup->append_single_option_line("pad_wall_thickness");
|
||||
optgroup->append_single_option_line("pad_wall_height");
|
||||
optgroup->append_single_option_line("pad_brim_size");
|
||||
optgroup->append_single_option_line("pad_max_merge_distance");
|
||||
// TODO: Disabling this parameter for the beta release
|
||||
// optgroup->append_single_option_line("pad_edge_radius");
|
||||
optgroup->append_single_option_line("pad_wall_slope");
|
||||
|
||||
optgroup->append_single_option_line("pad_around_object");
|
||||
optgroup->append_single_option_line("pad_around_object_everywhere");
|
||||
optgroup->append_single_option_line("pad_object_gap");
|
||||
optgroup->append_single_option_line("pad_object_connector_stride");
|
||||
optgroup->append_single_option_line("pad_object_connector_width");
|
||||
|
Loading…
Reference in New Issue
Block a user