Separate support tree routing and meshing, remove Common.hpp/.cpp .
* Remove Common.hpp and Common.cpp, move things into their respective modules in sla.
This commit is contained in:
parent
2ff04e6f68
commit
184f64f828
27 changed files with 897 additions and 912 deletions
|
@ -204,11 +204,11 @@ add_library(libslic3r STATIC
|
|||
SimplifyMesh.cpp
|
||||
MarchingSquares.hpp
|
||||
${OpenVDBUtils_SOURCES}
|
||||
SLA/Common.hpp
|
||||
SLA/Common.cpp
|
||||
SLA/Pad.hpp
|
||||
SLA/Pad.cpp
|
||||
SLA/SupportTreeBuilder.hpp
|
||||
SLA/SupportTreeMesher.hpp
|
||||
SLA/SupportTreeMesher.cpp
|
||||
SLA/SupportTreeBuildsteps.hpp
|
||||
SLA/SupportTreeBuildsteps.cpp
|
||||
SLA/SupportTreeBuilder.cpp
|
||||
|
@ -220,6 +220,7 @@ add_library(libslic3r STATIC
|
|||
SLA/Rotfinder.cpp
|
||||
SLA/BoostAdapter.hpp
|
||||
SLA/SpatIndex.hpp
|
||||
SLA/SpatIndex.cpp
|
||||
SLA/RasterBase.hpp
|
||||
SLA/RasterBase.cpp
|
||||
SLA/AGGRaster.hpp
|
||||
|
@ -236,7 +237,9 @@ add_library(libslic3r STATIC
|
|||
SLA/Contour3D.hpp
|
||||
SLA/Contour3D.cpp
|
||||
SLA/EigenMesh3D.hpp
|
||||
SLA/EigenMesh3D.cpp
|
||||
SLA/Clustering.hpp
|
||||
SLA/Clustering.cpp
|
||||
SLA/ReprojectPointsOnMesh.hpp
|
||||
)
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
#define OPENVDBUTILS_HPP
|
||||
|
||||
#include <libslic3r/TriangleMesh.hpp>
|
||||
#include <libslic3r/SLA/Common.hpp>
|
||||
#include <libslic3r/SLA/Contour3D.hpp>
|
||||
#include <openvdb/openvdb.h>
|
||||
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
#ifndef SLA_BOOSTADAPTER_HPP
|
||||
#define SLA_BOOSTADAPTER_HPP
|
||||
|
||||
#include <libslic3r/SLA/Common.hpp>
|
||||
#include <libslic3r/Point.hpp>
|
||||
#include <libslic3r/BoundingBox.hpp>
|
||||
|
||||
#include <boost/geometry.hpp>
|
||||
|
||||
namespace boost {
|
||||
|
|
152
src/libslic3r/SLA/Clustering.cpp
Normal file
152
src/libslic3r/SLA/Clustering.cpp
Normal file
|
@ -0,0 +1,152 @@
|
|||
#include "Clustering.hpp"
|
||||
#include "boost/geometry/index/rtree.hpp"
|
||||
|
||||
#include <libslic3r/SLA/SpatIndex.hpp>
|
||||
#include <libslic3r/SLA/BoostAdapter.hpp>
|
||||
|
||||
namespace Slic3r { namespace sla {
|
||||
|
||||
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>(
|
||||
const Index3D &, const PointIndexEl &)> qfn)
|
||||
{
|
||||
using Elems = std::vector<PointIndexEl>;
|
||||
|
||||
// Recursive function for visiting all the points in a given distance to
|
||||
// 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);
|
||||
|
||||
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_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_ptidx_elements);
|
||||
|
||||
if(!newpts.empty() && (!max_points || cluster.size() < max_points))
|
||||
group(newpts, cluster);
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<Elems> clusters;
|
||||
for(auto it = sindex.begin(); it != sindex.end();) {
|
||||
Elems cluster = {};
|
||||
Elems pts = {*it};
|
||||
group(pts, cluster);
|
||||
|
||||
for(auto& c : cluster) sindex.remove(c);
|
||||
it = sindex.begin();
|
||||
|
||||
clusters.emplace_back(cluster);
|
||||
}
|
||||
|
||||
ClusteredPoints result;
|
||||
for(auto& cluster : clusters) {
|
||||
result.emplace_back();
|
||||
for(auto c : cluster) result.back().emplace_back(c.second);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<PointIndexEl> distance_queryfn(const Index3D& sindex,
|
||||
const PointIndexEl& p,
|
||||
double dist,
|
||||
unsigned max_points)
|
||||
{
|
||||
std::vector<PointIndexEl> tmp; tmp.reserve(max_points);
|
||||
sindex.query(
|
||||
bgi::nearest(p.first, max_points),
|
||||
std::back_inserter(tmp)
|
||||
);
|
||||
|
||||
for(auto it = tmp.begin(); it < tmp.end(); ++it)
|
||||
if((p.first - it->first).norm() > dist) it = tmp.erase(it);
|
||||
|
||||
return tmp;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Clustering a set of points by the given criteria
|
||||
ClusteredPoints cluster(
|
||||
const std::vector<unsigned>& indices,
|
||||
std::function<Vec3d(unsigned)> pointfn,
|
||||
double dist,
|
||||
unsigned max_points)
|
||||
{
|
||||
// A spatial index for querying the nearest points
|
||||
Index3D sindex;
|
||||
|
||||
// Build the index
|
||||
for(auto idx : indices) sindex.insert( std::make_pair(pointfn(idx), idx));
|
||||
|
||||
return cluster(sindex, max_points,
|
||||
[dist, max_points](const Index3D& sidx, const PointIndexEl& p)
|
||||
{
|
||||
return distance_queryfn(sidx, p, dist, max_points);
|
||||
});
|
||||
}
|
||||
|
||||
// Clustering a set of points by the given criteria
|
||||
ClusteredPoints cluster(
|
||||
const std::vector<unsigned>& indices,
|
||||
std::function<Vec3d(unsigned)> pointfn,
|
||||
std::function<bool(const PointIndexEl&, const PointIndexEl&)> predicate,
|
||||
unsigned max_points)
|
||||
{
|
||||
// A spatial index for querying the nearest points
|
||||
Index3D sindex;
|
||||
|
||||
// Build the index
|
||||
for(auto idx : indices) sindex.insert( std::make_pair(pointfn(idx), idx));
|
||||
|
||||
return cluster(sindex, max_points,
|
||||
[max_points, predicate](const Index3D& sidx, const PointIndexEl& p)
|
||||
{
|
||||
std::vector<PointIndexEl> tmp; tmp.reserve(max_points);
|
||||
sidx.query(bgi::satisfies([p, predicate](const PointIndexEl& e){
|
||||
return predicate(p, e);
|
||||
}), std::back_inserter(tmp));
|
||||
return tmp;
|
||||
});
|
||||
}
|
||||
|
||||
ClusteredPoints cluster(const Eigen::MatrixXd& pts, double dist, unsigned max_points)
|
||||
{
|
||||
// A spatial index for querying the nearest points
|
||||
Index3D sindex;
|
||||
|
||||
// Build the index
|
||||
for(Eigen::Index i = 0; i < pts.rows(); i++)
|
||||
sindex.insert(std::make_pair(Vec3d(pts.row(i)), unsigned(i)));
|
||||
|
||||
return cluster(sindex, max_points,
|
||||
[dist, max_points](const Index3D& sidx, const PointIndexEl& p)
|
||||
{
|
||||
return distance_queryfn(sidx, p, dist, max_points);
|
||||
});
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::sla
|
|
@ -2,7 +2,8 @@
|
|||
#define SLA_CLUSTERING_HPP
|
||||
|
||||
#include <vector>
|
||||
#include <libslic3r/SLA/Common.hpp>
|
||||
|
||||
#include <libslic3r/Point.hpp>
|
||||
#include <libslic3r/SLA/SpatIndex.hpp>
|
||||
|
||||
namespace Slic3r { namespace sla {
|
||||
|
@ -16,7 +17,7 @@ ClusteredPoints cluster(const std::vector<unsigned>& indices,
|
|||
double dist,
|
||||
unsigned max_points);
|
||||
|
||||
ClusteredPoints cluster(const PointSet& points,
|
||||
ClusteredPoints cluster(const Eigen::MatrixXd& points,
|
||||
double dist,
|
||||
unsigned max_points);
|
||||
|
||||
|
@ -26,5 +27,56 @@ ClusteredPoints cluster(
|
|||
std::function<bool(const PointIndexEl&, const PointIndexEl&)> predicate,
|
||||
unsigned max_points);
|
||||
|
||||
}}
|
||||
// This function returns the position of the centroid in the input 'clust'
|
||||
// vector of point indices.
|
||||
template<class DistFn, class PointFn>
|
||||
long cluster_centroid(const ClusterEl &clust, PointFn pointfn, DistFn df)
|
||||
{
|
||||
switch(clust.size()) {
|
||||
case 0: /* empty cluster */ return -1;
|
||||
case 1: /* only one element */ return 0;
|
||||
case 2: /* if two elements, there is no center */ return 0;
|
||||
default: ;
|
||||
}
|
||||
|
||||
// The function works by calculating for each point the average distance
|
||||
// from all the other points in the cluster. We create a selector bitmask of
|
||||
// the same size as the cluster. The bitmask will have two true bits and
|
||||
// false bits for the rest of items and we will loop through all the
|
||||
// permutations of the bitmask (combinations of two points). Get the
|
||||
// distance for the two points and add the distance to the averages.
|
||||
// The point with the smallest average than wins.
|
||||
|
||||
// The complexity should be O(n^2) but we will mostly apply this function
|
||||
// for small clusters only (cca 3 elements)
|
||||
|
||||
std::vector<bool> sel(clust.size(), false); // create full zero bitmask
|
||||
std::fill(sel.end() - 2, sel.end(), true); // insert the two ones
|
||||
std::vector<double> avgs(clust.size(), 0.0); // store the average distances
|
||||
|
||||
do {
|
||||
std::array<size_t, 2> idx;
|
||||
for(size_t i = 0, j = 0; i < clust.size(); i++)
|
||||
if(sel[i]) idx[j++] = i;
|
||||
|
||||
double d = df(pointfn(clust[idx[0]]),
|
||||
pointfn(clust[idx[1]]));
|
||||
|
||||
// add the distance to the sums for both associated points
|
||||
for(auto i : idx) avgs[i] += d;
|
||||
|
||||
// now continue with the next permutation of the bitmask with two 1s
|
||||
} while(std::next_permutation(sel.begin(), sel.end()));
|
||||
|
||||
// Divide by point size in the cluster to get the average (may be redundant)
|
||||
for(auto& a : avgs) a /= clust.size();
|
||||
|
||||
// get the lowest average distance and return the index
|
||||
auto minit = std::min_element(avgs.begin(), avgs.end());
|
||||
return long(minit - avgs.begin());
|
||||
}
|
||||
|
||||
|
||||
}} // namespace Slic3r::sla
|
||||
|
||||
#endif // CLUSTERING_HPP
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
#ifndef SLA_COMMON_HPP
|
||||
#define SLA_COMMON_HPP
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <numeric>
|
||||
#include <functional>
|
||||
#include <Eigen/Geometry>
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// Typedefs from Point.hpp
|
||||
typedef Eigen::Matrix<float, 3, 1, Eigen::DontAlign> Vec3f;
|
||||
typedef Eigen::Matrix<double, 3, 1, Eigen::DontAlign> Vec3d;
|
||||
typedef Eigen::Matrix<int, 3, 1, Eigen::DontAlign> Vec3i;
|
||||
typedef Eigen::Matrix<int, 4, 1, Eigen::DontAlign> Vec4i;
|
||||
|
||||
namespace sla {
|
||||
|
||||
using PointSet = Eigen::MatrixXd;
|
||||
|
||||
} // namespace sla
|
||||
} // namespace Slic3r
|
||||
|
||||
|
||||
#endif // SLASUPPORTTREE_HPP
|
|
@ -1,11 +1,14 @@
|
|||
#ifndef SLA_CONTOUR3D_HPP
|
||||
#define SLA_CONTOUR3D_HPP
|
||||
|
||||
#include <libslic3r/SLA/Common.hpp>
|
||||
|
||||
#include <libslic3r/TriangleMesh.hpp>
|
||||
|
||||
namespace Slic3r { namespace sla {
|
||||
namespace Slic3r {
|
||||
|
||||
// Used for quads (TODO: remove this, and convert quads to triangles in OpenVDBUtils)
|
||||
using Vec4i = Eigen::Matrix<int, 4, 1, Eigen::DontAlign>;
|
||||
|
||||
namespace sla {
|
||||
|
||||
class EigenMesh3D;
|
||||
|
||||
|
|
|
@ -1,185 +1,16 @@
|
|||
#include <cmath>
|
||||
#include <libslic3r/SLA/Common.hpp>
|
||||
#include <libslic3r/SLA/Concurrency.hpp>
|
||||
#include <libslic3r/SLA/SpatIndex.hpp>
|
||||
#include <libslic3r/SLA/EigenMesh3D.hpp>
|
||||
#include <libslic3r/SLA/Contour3D.hpp>
|
||||
#include <libslic3r/SLA/Clustering.hpp>
|
||||
#include "EigenMesh3D.hpp"
|
||||
#include "Concurrency.hpp"
|
||||
|
||||
#include <libslic3r/AABBTreeIndirect.hpp>
|
||||
#include <libslic3r/TriangleMesh.hpp>
|
||||
|
||||
// for concave hull merging decisions
|
||||
#include <libslic3r/SLA/BoostAdapter.hpp>
|
||||
#include "boost/geometry/index/rtree.hpp"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable: 4244)
|
||||
#pragma warning(disable: 4267)
|
||||
#endif
|
||||
|
||||
|
||||
#include <igl/remove_duplicate_vertices.h>
|
||||
#include <numeric>
|
||||
|
||||
#ifdef SLIC3R_HOLE_RAYCASTER
|
||||
#include <libslic3r/SLA/Hollowing.hpp>
|
||||
#include <libslic3r/SLA/Hollowing.hpp>
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
namespace sla {
|
||||
|
||||
|
||||
/* **************************************************************************
|
||||
* PointIndex implementation
|
||||
* ************************************************************************** */
|
||||
|
||||
class PointIndex::Impl {
|
||||
public:
|
||||
using BoostIndex = boost::geometry::index::rtree< PointIndexEl,
|
||||
boost::geometry::index::rstar<16, 4> /* ? */ >;
|
||||
|
||||
BoostIndex m_store;
|
||||
};
|
||||
|
||||
PointIndex::PointIndex(): m_impl(new Impl()) {}
|
||||
PointIndex::~PointIndex() {}
|
||||
|
||||
PointIndex::PointIndex(const PointIndex &cpy): m_impl(new Impl(*cpy.m_impl)) {}
|
||||
PointIndex::PointIndex(PointIndex&& cpy): m_impl(std::move(cpy.m_impl)) {}
|
||||
|
||||
PointIndex& PointIndex::operator=(const PointIndex &cpy)
|
||||
{
|
||||
m_impl.reset(new Impl(*cpy.m_impl));
|
||||
return *this;
|
||||
}
|
||||
|
||||
PointIndex& PointIndex::operator=(PointIndex &&cpy)
|
||||
{
|
||||
m_impl.swap(cpy.m_impl);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void PointIndex::insert(const PointIndexEl &el)
|
||||
{
|
||||
m_impl->m_store.insert(el);
|
||||
}
|
||||
|
||||
bool PointIndex::remove(const PointIndexEl& el)
|
||||
{
|
||||
return m_impl->m_store.remove(el) == 1;
|
||||
}
|
||||
|
||||
std::vector<PointIndexEl>
|
||||
PointIndex::query(std::function<bool(const PointIndexEl &)> fn) const
|
||||
{
|
||||
namespace bgi = boost::geometry::index;
|
||||
|
||||
std::vector<PointIndexEl> ret;
|
||||
m_impl->m_store.query(bgi::satisfies(fn), std::back_inserter(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<PointIndexEl> PointIndex::nearest(const Vec3d &el, unsigned k = 1) const
|
||||
{
|
||||
namespace bgi = boost::geometry::index;
|
||||
std::vector<PointIndexEl> ret; ret.reserve(k);
|
||||
m_impl->m_store.query(bgi::nearest(el, k), std::back_inserter(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t PointIndex::size() const
|
||||
{
|
||||
return m_impl->m_store.size();
|
||||
}
|
||||
|
||||
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
|
||||
* ************************************************************************** */
|
||||
|
||||
class BoxIndex::Impl {
|
||||
public:
|
||||
using BoostIndex = boost::geometry::index::
|
||||
rtree<BoxIndexEl, boost::geometry::index::rstar<16, 4> /* ? */>;
|
||||
|
||||
BoostIndex m_store;
|
||||
};
|
||||
|
||||
BoxIndex::BoxIndex(): m_impl(new Impl()) {}
|
||||
BoxIndex::~BoxIndex() {}
|
||||
|
||||
BoxIndex::BoxIndex(const BoxIndex &cpy): m_impl(new Impl(*cpy.m_impl)) {}
|
||||
BoxIndex::BoxIndex(BoxIndex&& cpy): m_impl(std::move(cpy.m_impl)) {}
|
||||
|
||||
BoxIndex& BoxIndex::operator=(const BoxIndex &cpy)
|
||||
{
|
||||
m_impl.reset(new Impl(*cpy.m_impl));
|
||||
return *this;
|
||||
}
|
||||
|
||||
BoxIndex& BoxIndex::operator=(BoxIndex &&cpy)
|
||||
{
|
||||
m_impl.swap(cpy.m_impl);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void BoxIndex::insert(const BoxIndexEl &el)
|
||||
{
|
||||
m_impl->m_store.insert(el);
|
||||
}
|
||||
|
||||
bool BoxIndex::remove(const BoxIndexEl& el)
|
||||
{
|
||||
return m_impl->m_store.remove(el) == 1;
|
||||
}
|
||||
|
||||
std::vector<BoxIndexEl> BoxIndex::query(const BoundingBox &qrbb,
|
||||
BoxIndex::QueryType qt)
|
||||
{
|
||||
namespace bgi = boost::geometry::index;
|
||||
|
||||
std::vector<BoxIndexEl> ret; ret.reserve(m_impl->m_store.size());
|
||||
|
||||
switch (qt) {
|
||||
case qtIntersects:
|
||||
m_impl->m_store.query(bgi::intersects(qrbb), std::back_inserter(ret));
|
||||
break;
|
||||
case qtWithin:
|
||||
m_impl->m_store.query(bgi::within(qrbb), std::back_inserter(ret));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t BoxIndex::size() const
|
||||
{
|
||||
return m_impl->m_store.size();
|
||||
}
|
||||
|
||||
void BoxIndex::foreach(std::function<void (const BoxIndexEl &)> fn)
|
||||
{
|
||||
for(auto& el : m_impl->m_store) fn(el);
|
||||
}
|
||||
|
||||
|
||||
/* ****************************************************************************
|
||||
* EigenMesh3D implementation
|
||||
* ****************************************************************************/
|
||||
|
||||
namespace Slic3r { namespace sla {
|
||||
|
||||
class EigenMesh3D::AABBImpl {
|
||||
private:
|
||||
|
@ -189,7 +20,7 @@ public:
|
|||
void init(const TriangleMesh& tm)
|
||||
{
|
||||
m_tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(
|
||||
tm.its.vertices, tm.its.indices);
|
||||
tm.its.vertices, tm.its.indices);
|
||||
}
|
||||
|
||||
void intersect_ray(const TriangleMesh& tm,
|
||||
|
@ -215,9 +46,9 @@ public:
|
|||
size_t idx_unsigned = 0;
|
||||
Vec3d closest_vec3d(closest);
|
||||
double dist = AABBTreeIndirect::squared_distance_to_indexed_triangle_set(
|
||||
tm.its.vertices,
|
||||
tm.its.indices,
|
||||
m_tree, point, idx_unsigned, closest_vec3d);
|
||||
tm.its.vertices,
|
||||
tm.its.indices,
|
||||
m_tree, point, idx_unsigned, closest_vec3d);
|
||||
i = int(idx_unsigned);
|
||||
closest = closest_vec3d;
|
||||
return dist;
|
||||
|
@ -289,7 +120,6 @@ Vec3d EigenMesh3D::normal_by_face_id(int face_id) const {
|
|||
}
|
||||
|
||||
|
||||
|
||||
EigenMesh3D::hit_result
|
||||
EigenMesh3D::query_ray_hit(const Vec3d &s, const Vec3d &dir) const
|
||||
{
|
||||
|
@ -334,7 +164,7 @@ EigenMesh3D::query_ray_hits(const Vec3d &s, const Vec3d &dir) const
|
|||
// along an axis of a cube due to floating-point approximations in igl (?)
|
||||
hits.erase(std::unique(hits.begin(), hits.end(),
|
||||
[](const igl::Hit& a, const igl::Hit& b)
|
||||
{ return a.t == b.t; }),
|
||||
{ return a.t == b.t; }),
|
||||
hits.end());
|
||||
|
||||
// Convert the igl::Hit into hit_result
|
||||
|
@ -356,7 +186,7 @@ EigenMesh3D::query_ray_hits(const Vec3d &s, const Vec3d &dir) const
|
|||
|
||||
#ifdef SLIC3R_HOLE_RAYCASTER
|
||||
EigenMesh3D::hit_result EigenMesh3D::filter_hits(
|
||||
const std::vector<EigenMesh3D::hit_result>& object_hits) const
|
||||
const std::vector<EigenMesh3D::hit_result>& object_hits) const
|
||||
{
|
||||
assert(! m_holes.empty());
|
||||
hit_result out(*this);
|
||||
|
@ -461,14 +291,9 @@ double EigenMesh3D::squared_distance(const Vec3d &p, int& i, Vec3d& c) const {
|
|||
return sqdst;
|
||||
}
|
||||
|
||||
/* ****************************************************************************
|
||||
* Misc functions
|
||||
* ****************************************************************************/
|
||||
|
||||
namespace {
|
||||
|
||||
bool point_on_edge(const Vec3d& p, const Vec3d& e1, const Vec3d& e2,
|
||||
double eps = 0.05)
|
||||
static bool point_on_edge(const Vec3d& p, const Vec3d& e1, const Vec3d& e2,
|
||||
double eps = 0.05)
|
||||
{
|
||||
using Line3D = Eigen::ParametrizedLine<double, 3>;
|
||||
|
||||
|
@ -477,13 +302,6 @@ bool point_on_edge(const Vec3d& p, const Vec3d& e1, const Vec3d& e2,
|
|||
return std::abs(d) < eps;
|
||||
}
|
||||
|
||||
template<class Vec> double distance(const Vec& pp1, const Vec& pp2) {
|
||||
auto p = pp2 - pp1;
|
||||
return std::sqrt(p.transpose() * p);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
PointSet normals(const PointSet& points,
|
||||
const EigenMesh3D& mesh,
|
||||
double eps,
|
||||
|
@ -531,11 +349,11 @@ PointSet normals(const PointSet& points,
|
|||
// ic will mark a single vertex.
|
||||
int ia = -1, ib = -1, ic = -1;
|
||||
|
||||
if (std::abs(distance(p, p1)) < eps) {
|
||||
if (std::abs((p - p1).norm()) < eps) {
|
||||
ic = trindex(0);
|
||||
} else if (std::abs(distance(p, p2)) < eps) {
|
||||
} else if (std::abs((p - p2).norm()) < eps) {
|
||||
ic = trindex(1);
|
||||
} else if (std::abs(distance(p, p3)) < eps) {
|
||||
} else if (std::abs((p - p3).norm()) < eps) {
|
||||
ic = trindex(2);
|
||||
} else if (point_on_edge(p, p1, p2, eps)) {
|
||||
ia = trindex(0);
|
||||
|
@ -612,148 +430,4 @@ 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>(
|
||||
const Index3D &, const PointIndexEl &)> qfn)
|
||||
{
|
||||
using Elems = std::vector<PointIndexEl>;
|
||||
|
||||
// Recursive function for visiting all the points in a given distance to
|
||||
// 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);
|
||||
|
||||
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_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_ptidx_elements);
|
||||
|
||||
if(!newpts.empty() && (!max_points || cluster.size() < max_points))
|
||||
group(newpts, cluster);
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<Elems> clusters;
|
||||
for(auto it = sindex.begin(); it != sindex.end();) {
|
||||
Elems cluster = {};
|
||||
Elems pts = {*it};
|
||||
group(pts, cluster);
|
||||
|
||||
for(auto& c : cluster) sindex.remove(c);
|
||||
it = sindex.begin();
|
||||
|
||||
clusters.emplace_back(cluster);
|
||||
}
|
||||
|
||||
ClusteredPoints result;
|
||||
for(auto& cluster : clusters) {
|
||||
result.emplace_back();
|
||||
for(auto c : cluster) result.back().emplace_back(c.second);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<PointIndexEl> distance_queryfn(const Index3D& sindex,
|
||||
const PointIndexEl& p,
|
||||
double dist,
|
||||
unsigned max_points)
|
||||
{
|
||||
std::vector<PointIndexEl> tmp; tmp.reserve(max_points);
|
||||
sindex.query(
|
||||
bgi::nearest(p.first, max_points),
|
||||
std::back_inserter(tmp)
|
||||
);
|
||||
|
||||
for(auto it = tmp.begin(); it < tmp.end(); ++it)
|
||||
if(distance(p.first, it->first) > dist) it = tmp.erase(it);
|
||||
|
||||
return tmp;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Clustering a set of points by the given criteria
|
||||
ClusteredPoints cluster(
|
||||
const std::vector<unsigned>& indices,
|
||||
std::function<Vec3d(unsigned)> pointfn,
|
||||
double dist,
|
||||
unsigned max_points)
|
||||
{
|
||||
// A spatial index for querying the nearest points
|
||||
Index3D sindex;
|
||||
|
||||
// Build the index
|
||||
for(auto idx : indices) sindex.insert( std::make_pair(pointfn(idx), idx));
|
||||
|
||||
return cluster(sindex, max_points,
|
||||
[dist, max_points](const Index3D& sidx, const PointIndexEl& p)
|
||||
{
|
||||
return distance_queryfn(sidx, p, dist, max_points);
|
||||
});
|
||||
}
|
||||
|
||||
// Clustering a set of points by the given criteria
|
||||
ClusteredPoints cluster(
|
||||
const std::vector<unsigned>& indices,
|
||||
std::function<Vec3d(unsigned)> pointfn,
|
||||
std::function<bool(const PointIndexEl&, const PointIndexEl&)> predicate,
|
||||
unsigned max_points)
|
||||
{
|
||||
// A spatial index for querying the nearest points
|
||||
Index3D sindex;
|
||||
|
||||
// Build the index
|
||||
for(auto idx : indices) sindex.insert( std::make_pair(pointfn(idx), idx));
|
||||
|
||||
return cluster(sindex, max_points,
|
||||
[max_points, predicate](const Index3D& sidx, const PointIndexEl& p)
|
||||
{
|
||||
std::vector<PointIndexEl> tmp; tmp.reserve(max_points);
|
||||
sidx.query(bgi::satisfies([p, predicate](const PointIndexEl& e){
|
||||
return predicate(p, e);
|
||||
}), std::back_inserter(tmp));
|
||||
return tmp;
|
||||
});
|
||||
}
|
||||
|
||||
ClusteredPoints cluster(const PointSet& pts, double dist, unsigned max_points)
|
||||
{
|
||||
// A spatial index for querying the nearest points
|
||||
Index3D sindex;
|
||||
|
||||
// Build the index
|
||||
for(Eigen::Index i = 0; i < pts.rows(); i++)
|
||||
sindex.insert(std::make_pair(Vec3d(pts.row(i)), unsigned(i)));
|
||||
|
||||
return cluster(sindex, max_points,
|
||||
[dist, max_points](const Index3D& sidx, const PointIndexEl& p)
|
||||
{
|
||||
return distance_queryfn(sidx, p, dist, max_points);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace sla
|
||||
} // namespace Slic3r
|
||||
}} // namespace Slic3r::sla
|
|
@ -1,8 +1,10 @@
|
|||
#ifndef SLA_EIGENMESH3D_H
|
||||
#define SLA_EIGENMESH3D_H
|
||||
|
||||
#include <libslic3r/SLA/Common.hpp>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include <libslic3r/Point.hpp>
|
||||
|
||||
// There is an implementation of a hole-aware raycaster that was eventually
|
||||
// not used in production version. It is now hidden under following define
|
||||
|
@ -19,6 +21,8 @@ class TriangleMesh;
|
|||
|
||||
namespace sla {
|
||||
|
||||
using PointSet = Eigen::MatrixXd;
|
||||
|
||||
/// An index-triangle structure for libIGL functions. Also serves as an
|
||||
/// alternative (raw) input format for the SLASupportTree.
|
||||
// Implemented in libslic3r/SLA/Common.cpp
|
||||
|
|
|
@ -3,11 +3,10 @@
|
|||
#include <libslic3r/OpenVDBUtils.hpp>
|
||||
#include <libslic3r/TriangleMesh.hpp>
|
||||
#include <libslic3r/SLA/Hollowing.hpp>
|
||||
#include <libslic3r/SLA/Contour3D.hpp>
|
||||
#include <libslic3r/SLA/EigenMesh3D.hpp>
|
||||
#include <libslic3r/SLA/SupportTreeBuilder.hpp>
|
||||
#include <libslic3r/ClipperUtils.hpp>
|
||||
#include <libslic3r/SimplifyMesh.hpp>
|
||||
#include <libslic3r/SLA/SupportTreeMesher.hpp>
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
#define SLA_HOLLOWING_HPP
|
||||
|
||||
#include <memory>
|
||||
#include <libslic3r/SLA/Common.hpp>
|
||||
#include <libslic3r/SLA/Contour3D.hpp>
|
||||
#include <libslic3r/SLA/JobController.hpp>
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#define SLA_JOBCONTROLLER_HPP
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
namespace Slic3r { namespace sla {
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#include <libslic3r/SLA/Pad.hpp>
|
||||
#include <libslic3r/SLA/Common.hpp>
|
||||
#include <libslic3r/SLA/SpatIndex.hpp>
|
||||
#include <libslic3r/SLA/BoostAdapter.hpp>
|
||||
#include <libslic3r/SLA/Contour3D.hpp>
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
#include <exception>
|
||||
|
||||
#include <libnest2d/optimizers/nlopt/genetic.hpp>
|
||||
#include <libslic3r/SLA/Common.hpp>
|
||||
#include <libslic3r/SLA/Rotfinder.hpp>
|
||||
#include <libslic3r/SLA/SupportTree.hpp>
|
||||
#include "Model.hpp"
|
||||
|
|
161
src/libslic3r/SLA/SpatIndex.cpp
Normal file
161
src/libslic3r/SLA/SpatIndex.cpp
Normal file
|
@ -0,0 +1,161 @@
|
|||
#include "SpatIndex.hpp"
|
||||
|
||||
// for concave hull merging decisions
|
||||
#include <libslic3r/SLA/BoostAdapter.hpp>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable: 4244)
|
||||
#pragma warning(disable: 4267)
|
||||
#endif
|
||||
|
||||
#include "boost/geometry/index/rtree.hpp"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
namespace Slic3r { namespace sla {
|
||||
|
||||
/* **************************************************************************
|
||||
* PointIndex implementation
|
||||
* ************************************************************************** */
|
||||
|
||||
class PointIndex::Impl {
|
||||
public:
|
||||
using BoostIndex = boost::geometry::index::rtree< PointIndexEl,
|
||||
boost::geometry::index::rstar<16, 4> /* ? */ >;
|
||||
|
||||
BoostIndex m_store;
|
||||
};
|
||||
|
||||
PointIndex::PointIndex(): m_impl(new Impl()) {}
|
||||
PointIndex::~PointIndex() {}
|
||||
|
||||
PointIndex::PointIndex(const PointIndex &cpy): m_impl(new Impl(*cpy.m_impl)) {}
|
||||
PointIndex::PointIndex(PointIndex&& cpy): m_impl(std::move(cpy.m_impl)) {}
|
||||
|
||||
PointIndex& PointIndex::operator=(const PointIndex &cpy)
|
||||
{
|
||||
m_impl.reset(new Impl(*cpy.m_impl));
|
||||
return *this;
|
||||
}
|
||||
|
||||
PointIndex& PointIndex::operator=(PointIndex &&cpy)
|
||||
{
|
||||
m_impl.swap(cpy.m_impl);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void PointIndex::insert(const PointIndexEl &el)
|
||||
{
|
||||
m_impl->m_store.insert(el);
|
||||
}
|
||||
|
||||
bool PointIndex::remove(const PointIndexEl& el)
|
||||
{
|
||||
return m_impl->m_store.remove(el) == 1;
|
||||
}
|
||||
|
||||
std::vector<PointIndexEl>
|
||||
PointIndex::query(std::function<bool(const PointIndexEl &)> fn) const
|
||||
{
|
||||
namespace bgi = boost::geometry::index;
|
||||
|
||||
std::vector<PointIndexEl> ret;
|
||||
m_impl->m_store.query(bgi::satisfies(fn), std::back_inserter(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<PointIndexEl> PointIndex::nearest(const Vec3d &el, unsigned k = 1) const
|
||||
{
|
||||
namespace bgi = boost::geometry::index;
|
||||
std::vector<PointIndexEl> ret; ret.reserve(k);
|
||||
m_impl->m_store.query(bgi::nearest(el, k), std::back_inserter(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t PointIndex::size() const
|
||||
{
|
||||
return m_impl->m_store.size();
|
||||
}
|
||||
|
||||
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
|
||||
* ************************************************************************** */
|
||||
|
||||
class BoxIndex::Impl {
|
||||
public:
|
||||
using BoostIndex = boost::geometry::index::
|
||||
rtree<BoxIndexEl, boost::geometry::index::rstar<16, 4> /* ? */>;
|
||||
|
||||
BoostIndex m_store;
|
||||
};
|
||||
|
||||
BoxIndex::BoxIndex(): m_impl(new Impl()) {}
|
||||
BoxIndex::~BoxIndex() {}
|
||||
|
||||
BoxIndex::BoxIndex(const BoxIndex &cpy): m_impl(new Impl(*cpy.m_impl)) {}
|
||||
BoxIndex::BoxIndex(BoxIndex&& cpy): m_impl(std::move(cpy.m_impl)) {}
|
||||
|
||||
BoxIndex& BoxIndex::operator=(const BoxIndex &cpy)
|
||||
{
|
||||
m_impl.reset(new Impl(*cpy.m_impl));
|
||||
return *this;
|
||||
}
|
||||
|
||||
BoxIndex& BoxIndex::operator=(BoxIndex &&cpy)
|
||||
{
|
||||
m_impl.swap(cpy.m_impl);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void BoxIndex::insert(const BoxIndexEl &el)
|
||||
{
|
||||
m_impl->m_store.insert(el);
|
||||
}
|
||||
|
||||
bool BoxIndex::remove(const BoxIndexEl& el)
|
||||
{
|
||||
return m_impl->m_store.remove(el) == 1;
|
||||
}
|
||||
|
||||
std::vector<BoxIndexEl> BoxIndex::query(const BoundingBox &qrbb,
|
||||
BoxIndex::QueryType qt)
|
||||
{
|
||||
namespace bgi = boost::geometry::index;
|
||||
|
||||
std::vector<BoxIndexEl> ret; ret.reserve(m_impl->m_store.size());
|
||||
|
||||
switch (qt) {
|
||||
case qtIntersects:
|
||||
m_impl->m_store.query(bgi::intersects(qrbb), std::back_inserter(ret));
|
||||
break;
|
||||
case qtWithin:
|
||||
m_impl->m_store.query(bgi::within(qrbb), std::back_inserter(ret));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t BoxIndex::size() const
|
||||
{
|
||||
return m_impl->m_store.size();
|
||||
}
|
||||
|
||||
void BoxIndex::foreach(std::function<void (const BoxIndexEl &)> fn)
|
||||
{
|
||||
for(auto& el : m_impl->m_store) fn(el);
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::sla
|
|
@ -73,7 +73,7 @@ public:
|
|||
BoxIndex& operator=(BoxIndex&&);
|
||||
|
||||
void insert(const BoxIndexEl&);
|
||||
inline void insert(const BoundingBox& bb, unsigned idx)
|
||||
void insert(const BoundingBox& bb, unsigned idx)
|
||||
{
|
||||
insert(std::make_pair(bb, unsigned(idx)));
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
#define SLA_SUPPORTPOINT_HPP
|
||||
|
||||
#include <vector>
|
||||
#include <libslic3r/SLA/Common.hpp>
|
||||
#include <libslic3r/ExPolygon.hpp>
|
||||
|
||||
namespace Slic3r { namespace sla {
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
#include <random>
|
||||
|
||||
#include <libslic3r/SLA/Common.hpp>
|
||||
#include <libslic3r/SLA/SupportPoint.hpp>
|
||||
#include <libslic3r/SLA/EigenMesh3D.hpp>
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
|
||||
#include <numeric>
|
||||
#include <libslic3r/SLA/SupportTree.hpp>
|
||||
#include <libslic3r/SLA/Common.hpp>
|
||||
#include <libslic3r/SLA/SpatIndex.hpp>
|
||||
#include <libslic3r/SLA/SupportTreeBuilder.hpp>
|
||||
#include <libslic3r/SLA/SupportTreeBuildsteps.hpp>
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
#include <memory>
|
||||
#include <Eigen/Geometry>
|
||||
|
||||
#include <libslic3r/SLA/Common.hpp>
|
||||
#include <libslic3r/SLA/Pad.hpp>
|
||||
#include <libslic3r/SLA/EigenMesh3D.hpp>
|
||||
#include <libslic3r/SLA/SupportPoint.hpp>
|
||||
|
|
|
@ -1,278 +1,18 @@
|
|||
#include <libslic3r/SLA/SupportTreeBuilder.hpp>
|
||||
#include <libslic3r/SLA/SupportTreeBuildsteps.hpp>
|
||||
#include <libslic3r/SLA/SupportTreeMesher.hpp>
|
||||
#include <libslic3r/SLA/Contour3D.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace sla {
|
||||
|
||||
Contour3D sphere(double rho, Portion portion, double fa) {
|
||||
|
||||
Contour3D ret;
|
||||
|
||||
// prohibit close to zero radius
|
||||
if(rho <= 1e-6 && rho >= -1e-6) return ret;
|
||||
|
||||
auto& vertices = ret.points;
|
||||
auto& facets = ret.faces3;
|
||||
|
||||
// Algorithm:
|
||||
// Add points one-by-one to the sphere grid and form facets using relative
|
||||
// coordinates. Sphere is composed effectively of a mesh of stacked circles.
|
||||
|
||||
// adjust via rounding to get an even multiple for any provided angle.
|
||||
double angle = (2*PI / floor(2*PI / fa));
|
||||
|
||||
// Ring to be scaled to generate the steps of the sphere
|
||||
std::vector<double> ring;
|
||||
|
||||
for (double i = 0; i < 2*PI; i+=angle) ring.emplace_back(i);
|
||||
|
||||
const auto sbegin = size_t(2*std::get<0>(portion)/angle);
|
||||
const auto send = size_t(2*std::get<1>(portion)/angle);
|
||||
|
||||
const size_t steps = ring.size();
|
||||
const double increment = 1.0 / double(steps);
|
||||
|
||||
// special case: first ring connects to 0,0,0
|
||||
// insert and form facets.
|
||||
if(sbegin == 0)
|
||||
vertices.emplace_back(Vec3d(0.0, 0.0, -rho + increment*sbegin*2.0*rho));
|
||||
|
||||
auto id = coord_t(vertices.size());
|
||||
for (size_t i = 0; i < ring.size(); i++) {
|
||||
// Fixed scaling
|
||||
const double z = -rho + increment*rho*2.0 * (sbegin + 1.0);
|
||||
// radius of the circle for this step.
|
||||
const double r = std::sqrt(std::abs(rho*rho - z*z));
|
||||
Vec2d b = Eigen::Rotation2Dd(ring[i]) * Eigen::Vector2d(0, r);
|
||||
vertices.emplace_back(Vec3d(b(0), b(1), z));
|
||||
|
||||
if (sbegin == 0)
|
||||
(i == 0) ? facets.emplace_back(coord_t(ring.size()), 0, 1) :
|
||||
facets.emplace_back(id - 1, 0, id);
|
||||
++id;
|
||||
}
|
||||
|
||||
// General case: insert and form facets for each step,
|
||||
// joining it to the ring below it.
|
||||
for (size_t s = sbegin + 2; s < send - 1; s++) {
|
||||
const double z = -rho + increment*double(s*2.0*rho);
|
||||
const double r = std::sqrt(std::abs(rho*rho - z*z));
|
||||
|
||||
for (size_t i = 0; i < ring.size(); i++) {
|
||||
Vec2d b = Eigen::Rotation2Dd(ring[i]) * Eigen::Vector2d(0, r);
|
||||
vertices.emplace_back(Vec3d(b(0), b(1), z));
|
||||
auto id_ringsize = coord_t(id - int(ring.size()));
|
||||
if (i == 0) {
|
||||
// wrap around
|
||||
facets.emplace_back(id - 1, id, id + coord_t(ring.size() - 1) );
|
||||
facets.emplace_back(id - 1, id_ringsize, id);
|
||||
} else {
|
||||
facets.emplace_back(id_ringsize - 1, id_ringsize, id);
|
||||
facets.emplace_back(id - 1, id_ringsize - 1, id);
|
||||
}
|
||||
id++;
|
||||
}
|
||||
}
|
||||
|
||||
// special case: last ring connects to 0,0,rho*2.0
|
||||
// only form facets.
|
||||
if(send >= size_t(2*PI / angle)) {
|
||||
vertices.emplace_back(Vec3d(0.0, 0.0, -rho + increment*send*2.0*rho));
|
||||
for (size_t i = 0; i < ring.size(); i++) {
|
||||
auto id_ringsize = coord_t(id - int(ring.size()));
|
||||
if (i == 0) {
|
||||
// third vertex is on the other side of the ring.
|
||||
facets.emplace_back(id - 1, id_ringsize, id);
|
||||
} else {
|
||||
auto ci = coord_t(id_ringsize + coord_t(i));
|
||||
facets.emplace_back(ci - 1, ci, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
id++;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Contour3D cylinder(double r, double h, size_t ssteps, const Vec3d &sp)
|
||||
{
|
||||
Contour3D ret;
|
||||
|
||||
auto steps = int(ssteps);
|
||||
auto& points = ret.points;
|
||||
auto& indices = ret.faces3;
|
||||
points.reserve(2*ssteps);
|
||||
double a = 2*PI/steps;
|
||||
|
||||
Vec3d jp = sp;
|
||||
Vec3d endp = {sp(X), sp(Y), sp(Z) + h};
|
||||
|
||||
// Upper circle points
|
||||
for(int i = 0; i < steps; ++i) {
|
||||
double phi = i*a;
|
||||
double ex = endp(X) + r*std::cos(phi);
|
||||
double ey = endp(Y) + r*std::sin(phi);
|
||||
points.emplace_back(ex, ey, endp(Z));
|
||||
}
|
||||
|
||||
// Lower circle points
|
||||
for(int i = 0; i < steps; ++i) {
|
||||
double phi = i*a;
|
||||
double x = jp(X) + r*std::cos(phi);
|
||||
double y = jp(Y) + r*std::sin(phi);
|
||||
points.emplace_back(x, y, jp(Z));
|
||||
}
|
||||
|
||||
// Now create long triangles connecting upper and lower circles
|
||||
indices.reserve(2*ssteps);
|
||||
auto offs = steps;
|
||||
for(int i = 0; i < steps - 1; ++i) {
|
||||
indices.emplace_back(i, i + offs, offs + i + 1);
|
||||
indices.emplace_back(i, offs + i + 1, i + 1);
|
||||
}
|
||||
|
||||
// Last triangle connecting the first and last vertices
|
||||
auto last = steps - 1;
|
||||
indices.emplace_back(0, last, offs);
|
||||
indices.emplace_back(last, offs + last, offs);
|
||||
|
||||
// According to the slicing algorithms, we need to aid them with generating
|
||||
// a watertight body. So we create a triangle fan for the upper and lower
|
||||
// ending of the cylinder to close the geometry.
|
||||
points.emplace_back(jp); int ci = int(points.size() - 1);
|
||||
for(int i = 0; i < steps - 1; ++i)
|
||||
indices.emplace_back(i + offs + 1, i + offs, ci);
|
||||
|
||||
indices.emplace_back(offs, steps + offs - 1, ci);
|
||||
|
||||
points.emplace_back(endp); ci = int(points.size() - 1);
|
||||
for(int i = 0; i < steps - 1; ++i)
|
||||
indices.emplace_back(ci, i, i + 1);
|
||||
|
||||
indices.emplace_back(steps - 1, 0, ci);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Contour3D pinhead(double r_pin, double r_back, double length, size_t steps)
|
||||
{
|
||||
assert(length > 0.);
|
||||
assert(r_back > 0.);
|
||||
assert(r_pin > 0.);
|
||||
|
||||
Contour3D mesh;
|
||||
|
||||
// We create two spheres which will be connected with a robe that fits
|
||||
// both circles perfectly.
|
||||
|
||||
// Set up the model detail level
|
||||
const double detail = 2*PI/steps;
|
||||
|
||||
// We don't generate whole circles. Instead, we generate only the
|
||||
// portions which are visible (not covered by the robe) To know the
|
||||
// exact portion of the bottom and top circles we need to use some
|
||||
// rules of tangent circles from which we can derive (using simple
|
||||
// triangles the following relations:
|
||||
|
||||
// The height of the whole mesh
|
||||
const double h = r_back + r_pin + length;
|
||||
double phi = PI / 2. - std::acos((r_back - r_pin) / h);
|
||||
|
||||
// To generate a whole circle we would pass a portion of (0, Pi)
|
||||
// To generate only a half horizontal circle we can pass (0, Pi/2)
|
||||
// The calculated phi is an offset to the half circles needed to smooth
|
||||
// the transition from the circle to the robe geometry
|
||||
|
||||
auto&& s1 = sphere(r_back, make_portion(0, PI/2 + phi), detail);
|
||||
auto&& s2 = sphere(r_pin, make_portion(PI/2 + phi, PI), detail);
|
||||
|
||||
for(auto& p : s2.points) p.z() += h;
|
||||
|
||||
mesh.merge(s1);
|
||||
mesh.merge(s2);
|
||||
|
||||
for(size_t idx1 = s1.points.size() - steps, idx2 = s1.points.size();
|
||||
idx1 < s1.points.size() - 1;
|
||||
idx1++, idx2++)
|
||||
{
|
||||
coord_t i1s1 = coord_t(idx1), i1s2 = coord_t(idx2);
|
||||
coord_t i2s1 = i1s1 + 1, i2s2 = i1s2 + 1;
|
||||
|
||||
mesh.faces3.emplace_back(i1s1, i2s1, i2s2);
|
||||
mesh.faces3.emplace_back(i1s1, i2s2, i1s2);
|
||||
}
|
||||
|
||||
auto i1s1 = coord_t(s1.points.size()) - coord_t(steps);
|
||||
auto i2s1 = coord_t(s1.points.size()) - 1;
|
||||
auto i1s2 = coord_t(s1.points.size());
|
||||
auto i2s2 = coord_t(s1.points.size()) + coord_t(steps) - 1;
|
||||
|
||||
mesh.faces3.emplace_back(i2s2, i2s1, i1s1);
|
||||
mesh.faces3.emplace_back(i1s2, i2s2, i1s1);
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
||||
|
||||
Contour3D pedestal(const Vec3d &endpt, double baseheight, double radius, size_t steps)
|
||||
{
|
||||
if(baseheight <= 0) return {};
|
||||
|
||||
assert(steps >= 0);
|
||||
auto last = int(steps - 1);
|
||||
|
||||
Contour3D base;
|
||||
|
||||
double a = 2*PI/steps;
|
||||
double z = endpt(Z) + baseheight;
|
||||
|
||||
for(size_t i = 0; i < steps; ++i) {
|
||||
double phi = i*a;
|
||||
double x = endpt(X) + radius * std::cos(phi);
|
||||
double y = endpt(Y) + radius * std::sin(phi);
|
||||
base.points.emplace_back(x, y, z);
|
||||
}
|
||||
|
||||
for(size_t i = 0; i < steps; ++i) {
|
||||
double phi = i*a;
|
||||
double x = endpt(X) + radius*std::cos(phi);
|
||||
double y = endpt(Y) + radius*std::sin(phi);
|
||||
base.points.emplace_back(x, y, z - baseheight);
|
||||
}
|
||||
|
||||
auto ep = endpt; ep(Z) += baseheight;
|
||||
base.points.emplace_back(endpt);
|
||||
base.points.emplace_back(ep);
|
||||
|
||||
auto& indices = base.faces3;
|
||||
auto hcenter = int(base.points.size() - 1);
|
||||
auto lcenter = int(base.points.size() - 2);
|
||||
auto offs = int(steps);
|
||||
for(int i = 0; i < last; ++i) {
|
||||
indices.emplace_back(i, i + offs, offs + i + 1);
|
||||
indices.emplace_back(i, offs + i + 1, i + 1);
|
||||
indices.emplace_back(i, i + 1, hcenter);
|
||||
indices.emplace_back(lcenter, offs + i + 1, offs + i);
|
||||
}
|
||||
|
||||
indices.emplace_back(0, last, offs);
|
||||
indices.emplace_back(last, offs + last, offs);
|
||||
indices.emplace_back(hcenter, last, 0);
|
||||
indices.emplace_back(offs, offs + last, lcenter);
|
||||
|
||||
return base;
|
||||
}
|
||||
|
||||
Head::Head(double r_big_mm,
|
||||
double r_small_mm,
|
||||
double length_mm,
|
||||
double penetration,
|
||||
const Vec3d &direction,
|
||||
const Vec3d &offset,
|
||||
const size_t circlesteps)
|
||||
: steps(circlesteps)
|
||||
, dir(direction)
|
||||
const Vec3d &offset)
|
||||
: dir(direction)
|
||||
, pos(offset)
|
||||
, r_back_mm(r_big_mm)
|
||||
, r_pin_mm(r_small_mm)
|
||||
|
@ -350,35 +90,6 @@ Head::Head(double r_big_mm,
|
|||
// return *this;
|
||||
//}
|
||||
|
||||
Bridge::Bridge(const Vec3d &j1, const Vec3d &j2, double r_mm, size_t steps):
|
||||
r(r_mm), startp(j1), endp(j2)
|
||||
{
|
||||
using Quaternion = Eigen::Quaternion<double>;
|
||||
Vec3d dir = (j2 - j1).normalized();
|
||||
double d = distance(j2, j1);
|
||||
|
||||
mesh = cylinder(r, d, steps);
|
||||
|
||||
auto quater = Quaternion::FromTwoVectors(Vec3d{0,0,1}, dir);
|
||||
for(auto& p : mesh.points) p = quater * p + j1;
|
||||
}
|
||||
|
||||
Bridge::Bridge(const Vec3d &j1,
|
||||
const Vec3d &j2,
|
||||
double r1_mm,
|
||||
double r2_mm,
|
||||
size_t steps)
|
||||
{
|
||||
Vec3d dir = (j2 - j1);
|
||||
mesh = pinhead(r1_mm, r2_mm, dir.norm(), steps);
|
||||
dir.normalize();
|
||||
|
||||
using Quaternion = Eigen::Quaternion<double>;
|
||||
auto quater = Quaternion::FromTwoVectors(Vec3d{0,0,1}, dir);
|
||||
|
||||
for(auto& p : mesh.points) p = quater * p + j1;
|
||||
}
|
||||
|
||||
Pad::Pad(const TriangleMesh &support_mesh,
|
||||
const ExPolygons & model_contours,
|
||||
double ground_level,
|
||||
|
@ -464,7 +175,7 @@ SupportTreeBuilder &SupportTreeBuilder::operator=(const SupportTreeBuilder &o)
|
|||
return *this;
|
||||
}
|
||||
|
||||
const TriangleMesh &SupportTreeBuilder::merged_mesh() const
|
||||
const TriangleMesh &SupportTreeBuilder::merged_mesh(size_t steps) const
|
||||
{
|
||||
if (m_meshcache_valid) return m_meshcache;
|
||||
|
||||
|
@ -472,28 +183,31 @@ const TriangleMesh &SupportTreeBuilder::merged_mesh() const
|
|||
|
||||
for (auto &head : m_heads) {
|
||||
if (ctl().stopcondition()) break;
|
||||
if (head.is_valid()) merged.merge(get_mesh(head));
|
||||
if (head.is_valid()) merged.merge(get_mesh(head, steps));
|
||||
}
|
||||
|
||||
for (auto &stick : m_pillars) {
|
||||
for (auto &pill : m_pillars) {
|
||||
if (ctl().stopcondition()) break;
|
||||
merged.merge(stick.mesh);
|
||||
merged.merge(stick.base);
|
||||
merged.merge(get_mesh(pill, steps));
|
||||
}
|
||||
|
||||
for (auto &pedest : m_pedestals) {
|
||||
merged.merge(get_mesh(pedest, steps));
|
||||
}
|
||||
|
||||
for (auto &j : m_junctions) {
|
||||
if (ctl().stopcondition()) break;
|
||||
merged.merge(j.mesh);
|
||||
merged.merge(get_mesh(j, steps));
|
||||
}
|
||||
|
||||
for (auto &bs : m_bridges) {
|
||||
if (ctl().stopcondition()) break;
|
||||
merged.merge(bs.mesh);
|
||||
merged.merge(get_mesh(bs, steps));
|
||||
}
|
||||
|
||||
for (auto &bs : m_crossbridges) {
|
||||
if (ctl().stopcondition()) break;
|
||||
merged.merge(bs.mesh);
|
||||
merged.merge(get_mesh(bs, steps));
|
||||
}
|
||||
|
||||
if (ctl().stopcondition()) {
|
||||
|
@ -550,16 +264,4 @@ const TriangleMesh &SupportTreeBuilder::retrieve_mesh(MeshType meshtype) const
|
|||
return m_meshcache;
|
||||
}
|
||||
|
||||
template<class C, class Hit = EigenMesh3D::hit_result>
|
||||
static Hit min_hit(const C &hits)
|
||||
{
|
||||
auto mit = std::min_element(hits.begin(), hits.end(),
|
||||
[](const Hit &h1, const Hit &h2) {
|
||||
return h1.distance() < h2.distance();
|
||||
});
|
||||
|
||||
return *mit;
|
||||
}
|
||||
|
||||
|
||||
}} // namespace Slic3r::sla
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
#define SLA_SUPPORTTREEBUILDER_HPP
|
||||
|
||||
#include <libslic3r/SLA/Concurrency.hpp>
|
||||
#include <libslic3r/SLA/Common.hpp>
|
||||
#include <libslic3r/SLA/SupportTree.hpp>
|
||||
#include <libslic3r/SLA/Contour3D.hpp>
|
||||
#include <libslic3r/SLA/Pad.hpp>
|
||||
|
@ -50,13 +49,6 @@ namespace sla {
|
|||
* nearby pillar.
|
||||
*/
|
||||
|
||||
using Coordf = double;
|
||||
using Portion = std::tuple<double, double>;
|
||||
|
||||
inline Portion make_portion(double a, double b) {
|
||||
return std::make_tuple(a, b);
|
||||
}
|
||||
|
||||
template<class Vec> double distance(const Vec& p) {
|
||||
return std::sqrt(p.transpose() * p);
|
||||
}
|
||||
|
@ -66,27 +58,13 @@ template<class Vec> double distance(const Vec& pp1, const Vec& pp2) {
|
|||
return distance(p);
|
||||
}
|
||||
|
||||
Contour3D sphere(double rho, Portion portion = make_portion(0.0, 2.0*PI),
|
||||
double fa=(2*PI/360));
|
||||
|
||||
// Down facing cylinder in Z direction with arguments:
|
||||
// r: radius
|
||||
// h: Height
|
||||
// ssteps: how many edges will create the base circle
|
||||
// sp: starting point
|
||||
Contour3D cylinder(double r, double h, size_t steps = 45, const Vec3d &sp = Vec3d::Zero());
|
||||
|
||||
Contour3D pinhead(double r_pin, double r_back, double length, size_t steps = 45);
|
||||
|
||||
Contour3D pedestal(const Vec3d &pt, double baseheight, double radius, size_t steps = 45);
|
||||
|
||||
const constexpr long ID_UNSET = -1;
|
||||
|
||||
struct Head {
|
||||
Contour3D mesh;
|
||||
const Vec3d DOWN = {0.0, 0.0, -1.0};
|
||||
|
||||
size_t steps = 45;
|
||||
Vec3d dir = {0, 0, -1};
|
||||
// A pinhead originating from a support point
|
||||
struct Head {
|
||||
Vec3d dir = DOWN;
|
||||
Vec3d pos = {0, 0, 0};
|
||||
|
||||
double r_back_mm = 1;
|
||||
|
@ -110,9 +88,9 @@ struct Head {
|
|||
double r_small_mm,
|
||||
double length_mm,
|
||||
double penetration,
|
||||
const Vec3d &direction = {0, 0, -1}, // direction (normal to the dull end)
|
||||
const Vec3d &offset = {0, 0, 0}, // displacement
|
||||
const size_t circlesteps = 45);
|
||||
const Vec3d &direction = DOWN, // direction (normal to the dull end)
|
||||
const Vec3d &offset = {0, 0, 0} // displacement
|
||||
);
|
||||
|
||||
void transform()
|
||||
{
|
||||
|
@ -141,29 +119,27 @@ struct Head {
|
|||
}
|
||||
};
|
||||
|
||||
struct Join {
|
||||
enum Types {
|
||||
jtPillarBrigde, jtHeadPillar, jtPillarPedestal, jtBridgePedestal,
|
||||
jtPillarAnchor, jtBridgeAnchor
|
||||
};
|
||||
};
|
||||
|
||||
// A junction connecting bridges and pillars
|
||||
struct Junction {
|
||||
Contour3D mesh;
|
||||
double r = 1;
|
||||
size_t steps = 45;
|
||||
Vec3d pos;
|
||||
|
||||
long id = ID_UNSET;
|
||||
|
||||
Junction(const Vec3d& tr, double r_mm, size_t stepnum = 45):
|
||||
r(r_mm), steps(stepnum), pos(tr)
|
||||
{
|
||||
mesh = sphere(r_mm, make_portion(0, PI), 2*PI/steps);
|
||||
for(auto& p : mesh.points) p += tr;
|
||||
}
|
||||
Junction(const Vec3d &tr, double r_mm) : r(r_mm), pos(tr) {}
|
||||
};
|
||||
|
||||
|
||||
struct Pillar {
|
||||
// Contour3D mesh;
|
||||
// Contour3D base;
|
||||
double r = 1;
|
||||
size_t steps = 0;
|
||||
double height, r;
|
||||
Vec3d endpt;
|
||||
double height = 0;
|
||||
|
||||
long id = ID_UNSET;
|
||||
|
||||
|
@ -177,60 +153,47 @@ struct Pillar {
|
|||
// How many pillars are cascaded with this one
|
||||
unsigned links = 0;
|
||||
|
||||
Pillar(const Vec3d &endp, double h, double radius = 1, size_t st = 45):
|
||||
height{h}, r(radius), steps(st), endpt(endp), starts_from_head(false) {}
|
||||
Pillar(const Vec3d &endp, double h, double radius = 1.):
|
||||
height{h}, r(radius), endpt(endp), starts_from_head(false) {}
|
||||
|
||||
|
||||
// Pillar(const Junction &junc, const Vec3d &endp)
|
||||
// : Pillar(junc.pos, endp, junc.r, junc.steps)
|
||||
// {}
|
||||
|
||||
inline Vec3d startpoint() const
|
||||
Vec3d startpoint() const
|
||||
{
|
||||
return {endpt.x(), endpt.y(), endpt.z() + height};
|
||||
}
|
||||
|
||||
inline const Vec3d& endpoint() const { return endpt; }
|
||||
const Vec3d& endpoint() const { return endpt; }
|
||||
|
||||
// Pillar& add_base(double baseheight = 3, double radius = 2);
|
||||
};
|
||||
|
||||
// A base for pillars or bridges that end on the ground
|
||||
struct Pedestal {
|
||||
Vec3d pos;
|
||||
double height, radius;
|
||||
size_t steps = 45;
|
||||
long id = ID_UNSET;
|
||||
|
||||
Pedestal() = default;
|
||||
Pedestal(const Vec3d &p, double h = 3., double r = 2., size_t stps = 45)
|
||||
: pos{p}, height{h}, radius{r}, steps{stps}
|
||||
{}
|
||||
|
||||
Pedestal(const Pillar &p, double h = 3., double r = 2.)
|
||||
: Pedestal{p.endpt, std::min(h, p.height), std::max(r, p.r), p.steps}
|
||||
Pedestal(const Vec3d &p, double h = 3., double r = 2.)
|
||||
: pos{p}, height{h}, radius{r}
|
||||
{}
|
||||
};
|
||||
|
||||
struct PinJoin {
|
||||
|
||||
};
|
||||
// This is the thing that anchors a pillar or bridge to the model body.
|
||||
// It is actually a reverse pinhead.
|
||||
struct Anchor: public Head { using Head::Head; };
|
||||
|
||||
// A Bridge between two pillars (with junction endpoints)
|
||||
struct Bridge {
|
||||
Contour3D mesh;
|
||||
double r = 0.8;
|
||||
long id = ID_UNSET;
|
||||
Vec3d startp = Vec3d::Zero(), endp = Vec3d::Zero();
|
||||
|
||||
Bridge(const Vec3d &j1,
|
||||
const Vec3d &j2,
|
||||
double r_mm = 0.8,
|
||||
size_t steps = 45);
|
||||
double r_mm = 0.8): r{r_mm}, startp{j1}, endp{j2}
|
||||
{}
|
||||
|
||||
Bridge(const Vec3d &j1,
|
||||
const Vec3d &j2,
|
||||
double r1_mm,
|
||||
double r2_mm,
|
||||
size_t steps = 45);
|
||||
double get_length() const { return (endp - startp).norm(); }
|
||||
Vec3d get_dir() const { return (endp - startp).normalized(); }
|
||||
};
|
||||
|
||||
// A wrapper struct around the pad
|
||||
|
@ -250,40 +213,6 @@ struct Pad {
|
|||
bool empty() const { return tmesh.facets_count() == 0; }
|
||||
};
|
||||
|
||||
inline Contour3D get_mesh(const Head &h)
|
||||
{
|
||||
Contour3D mesh = pinhead(h.r_pin_mm, h.r_back_mm, h.width_mm, h.steps);
|
||||
|
||||
using Quaternion = Eigen::Quaternion<double>;
|
||||
|
||||
// We rotate the head to the specified direction The head's pointing
|
||||
// side is facing upwards so this means that it would hold a support
|
||||
// point with a normal pointing straight down. This is the reason of
|
||||
// the -1 z coordinate
|
||||
auto quatern = Quaternion::FromTwoVectors(Vec3d{0, 0, -1}, h.dir);
|
||||
|
||||
for(auto& p : mesh.points) p = quatern * p + h.pos;
|
||||
}
|
||||
|
||||
inline Contour3D get_mesh(const Pillar &p)
|
||||
{
|
||||
assert(p.steps > 0);
|
||||
|
||||
if(p.height > EPSILON) { // Endpoint is below the starting point
|
||||
// We just create a bridge geometry with the pillar parameters and
|
||||
// move the data.
|
||||
return cylinder(p.r, p.height, p.steps, p.endpoint());
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
inline Contour3D get_mesh(const Pedestal &p, double h, double r)
|
||||
{
|
||||
return pedestal(p.pos, p.height, p.radius, p.steps);
|
||||
}
|
||||
|
||||
|
||||
// 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 merged when the caller queries the merged mesh. The
|
||||
|
@ -300,12 +229,15 @@ inline Contour3D get_mesh(const Pedestal &p, double h, double r)
|
|||
// merged mesh. It can be retrieved using a dedicated method (pad())
|
||||
class SupportTreeBuilder: public SupportTree {
|
||||
// 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<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<Bridge> m_crossbridges;
|
||||
std::vector<Bridge> m_bridges;
|
||||
std::vector<Bridge> m_crossbridges;
|
||||
std::vector<Pedestal> m_pedestals;
|
||||
std::vector<Anchor> m_anchors;
|
||||
|
||||
Pad m_pad;
|
||||
|
||||
using Mutex = ccr::SpinningMutex;
|
||||
|
@ -347,7 +279,7 @@ public:
|
|||
return m_heads.back();
|
||||
}
|
||||
|
||||
template<class...Args> long add_pillar(long headid, Args&&... args)
|
||||
template<class...Args> long add_pillar(long headid, double length)
|
||||
{
|
||||
std::lock_guard<Mutex> lk(m_mutex);
|
||||
if (m_pillars.capacity() < m_heads.size())
|
||||
|
@ -356,7 +288,9 @@ public:
|
|||
assert(headid >= 0 && size_t(headid) < m_head_indices.size());
|
||||
Head &head = m_heads[m_head_indices[size_t(headid)]];
|
||||
|
||||
m_pillars.emplace_back(head, std::forward<Args>(args)...);
|
||||
Vec3d hjp = head.junction_point() - Vec3d{0, 0, length};
|
||||
m_pillars.emplace_back(hjp, length, head.r_back_mm);
|
||||
|
||||
Pillar& pillar = m_pillars.back();
|
||||
pillar.id = long(m_pillars.size() - 1);
|
||||
head.pillar_id = pillar.id;
|
||||
|
@ -371,7 +305,19 @@ public:
|
|||
{
|
||||
std::lock_guard<Mutex> lk(m_mutex);
|
||||
assert(pid >= 0 && size_t(pid) < m_pillars.size());
|
||||
m_pillars[size_t(pid)].add_base(baseheight, radius);
|
||||
m_pedestals.emplace_back(m_pillars[size_t(pid)].endpt, baseheight, radius);
|
||||
m_pedestals.back().id = m_pedestals.size() - 1;
|
||||
m_meshcache_valid = false;
|
||||
// m_pillars[size_t(pid)].add_base(baseheight, radius);
|
||||
}
|
||||
|
||||
template<class...Args> const Anchor& add_anchor(Args&&...args)
|
||||
{
|
||||
std::lock_guard<Mutex> lk(m_mutex);
|
||||
m_anchors.emplace_back(std::forward<Args>(args)...);
|
||||
m_anchors.back().id = long(m_junctions.size() - 1);
|
||||
m_meshcache_valid = false;
|
||||
return m_anchors.back();
|
||||
}
|
||||
|
||||
void increment_bridges(const Pillar& pillar)
|
||||
|
@ -432,18 +378,18 @@ public:
|
|||
return m_junctions.back();
|
||||
}
|
||||
|
||||
const Bridge& add_bridge(const Vec3d &s, const Vec3d &e, double r, size_t n = 45)
|
||||
const Bridge& add_bridge(const Vec3d &s, const Vec3d &e, double r)
|
||||
{
|
||||
return _add_bridge(m_bridges, s, e, r, n);
|
||||
return _add_bridge(m_bridges, s, e, r);
|
||||
}
|
||||
|
||||
const Bridge& add_bridge(long headid, const Vec3d &endp, size_t s = 45)
|
||||
const Bridge& add_bridge(long headid, const Vec3d &endp)
|
||||
{
|
||||
std::lock_guard<Mutex> lk(m_mutex);
|
||||
assert(headid >= 0 && size_t(headid) < m_head_indices.size());
|
||||
|
||||
Head &h = m_heads[m_head_indices[size_t(headid)]];
|
||||
m_bridges.emplace_back(h.junction_point(), endp, h.r_back_mm, s);
|
||||
m_bridges.emplace_back(h.junction_point(), endp, h.r_back_mm);
|
||||
m_bridges.back().id = long(m_bridges.size() - 1);
|
||||
|
||||
h.bridge_id = m_bridges.back().id;
|
||||
|
@ -471,7 +417,7 @@ public:
|
|||
}
|
||||
|
||||
inline const std::vector<Pillar> &pillars() const { return m_pillars; }
|
||||
inline const std::vector<Head> &heads() const { return m_heads; }
|
||||
inline const std::vector<Head> &heads() const { return m_heads; }
|
||||
inline const std::vector<Bridge> &bridges() const { return m_bridges; }
|
||||
inline const std::vector<Bridge> &crossbridges() const { return m_crossbridges; }
|
||||
|
||||
|
@ -496,7 +442,7 @@ public:
|
|||
const Pad& pad() const { return m_pad; }
|
||||
|
||||
// WITHOUT THE PAD!!!
|
||||
const TriangleMesh &merged_mesh() const;
|
||||
const TriangleMesh &merged_mesh(size_t steps = 45) const;
|
||||
|
||||
// WITH THE PAD
|
||||
double full_height() const;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include <libslic3r/SLA/SupportTreeBuildsteps.hpp>
|
||||
|
||||
#include <libslic3r/SLA/SpatIndex.hpp>
|
||||
#include <libnest2d/optimizers/nlopt/genetic.hpp>
|
||||
#include <libnest2d/optimizers/nlopt/subplex.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
@ -7,14 +8,23 @@
|
|||
namespace Slic3r {
|
||||
namespace sla {
|
||||
|
||||
static const Vec3d DOWN = {0.0, 0.0, -1.0};
|
||||
|
||||
using libnest2d::opt::initvals;
|
||||
using libnest2d::opt::bound;
|
||||
using libnest2d::opt::StopCriteria;
|
||||
using libnest2d::opt::GeneticOptimizer;
|
||||
using libnest2d::opt::SubplexOptimizer;
|
||||
|
||||
template<class C, class Hit = EigenMesh3D::hit_result>
|
||||
static Hit min_hit(const C &hits)
|
||||
{
|
||||
auto mit = std::min_element(hits.begin(), hits.end(),
|
||||
[](const Hit &h1, const Hit &h2) {
|
||||
return h1.distance() < h2.distance();
|
||||
});
|
||||
|
||||
return *mit;
|
||||
}
|
||||
|
||||
EigenMesh3D::hit_result query_hit(const SupportableMesh &msh, const Head &h)
|
||||
{
|
||||
static const size_t SAMPLES = 8;
|
||||
|
@ -271,17 +281,6 @@ bool SupportTreeBuildsteps::execute(SupportTreeBuilder & builder,
|
|||
return pc == ABORT;
|
||||
}
|
||||
|
||||
template<class C, class Hit = EigenMesh3D::hit_result>
|
||||
static Hit min_hit(const C &hits)
|
||||
{
|
||||
auto mit = std::min_element(hits.begin(), hits.end(),
|
||||
[](const Hit &h1, const Hit &h2) {
|
||||
return h1.distance() < h2.distance();
|
||||
});
|
||||
|
||||
return *mit;
|
||||
}
|
||||
|
||||
EigenMesh3D::hit_result SupportTreeBuildsteps::pinhead_mesh_intersect(
|
||||
const Vec3d &s, const Vec3d &dir, double r_pin, double r_back, double width)
|
||||
{
|
||||
|
@ -552,7 +551,7 @@ bool SupportTreeBuildsteps::connect_to_nearpillar(const Head &head,
|
|||
if (m_builder.bridgecount(nearpillar()) < m_cfg.max_bridges_on_pillar) {
|
||||
// A partial pillar is needed under the starting head.
|
||||
if(zdiff > 0) {
|
||||
m_builder.add_pillar(head.id, bridgestart, r);
|
||||
m_builder.add_pillar(head.id, headjp.z() - bridgestart.z());
|
||||
m_builder.add_junction(bridgestart, r);
|
||||
m_builder.add_bridge(bridgestart, bridgeend, r);
|
||||
} else {
|
||||
|
@ -607,7 +606,7 @@ bool SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp,
|
|||
normal_mode = false;
|
||||
|
||||
if (t > m_cfg.max_bridge_length_mm || endp(Z) < gndlvl) {
|
||||
if (head_id >= 0) m_builder.add_pillar(head_id, jp, radius);
|
||||
if (head_id >= 0) m_builder.add_pillar(head_id, 0.);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -615,14 +614,15 @@ bool SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp,
|
|||
|
||||
// Check if the deduced route is sane and exit with error if not.
|
||||
if (bridge_mesh_distance(jp, dir, radius) < (endp - jp).norm()) {
|
||||
if (head_id >= 0) m_builder.add_pillar(head_id, jp, radius);
|
||||
if (head_id >= 0) m_builder.add_pillar(head_id, 0.);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Straigh path down, no area to dodge
|
||||
if (normal_mode) {
|
||||
pillar_id = head_id >= 0 ? m_builder.add_pillar(head_id, endp, radius) :
|
||||
m_builder.add_pillar(jp, endp, radius);
|
||||
double h = jp.z() - endp.z();
|
||||
pillar_id = head_id >= 0 ? m_builder.add_pillar(head_id, h) :
|
||||
m_builder.add_pillar(jp, h, radius);
|
||||
|
||||
if (can_add_base)
|
||||
m_builder.add_pillar_base(pillar_id, m_cfg.base_height_mm,
|
||||
|
@ -630,8 +630,9 @@ bool SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp,
|
|||
} else {
|
||||
|
||||
// Insert the bridge to get around the forbidden area
|
||||
Vec3d pgnd{endp.x(), endp.y(), gndlvl};
|
||||
pillar_id = m_builder.add_pillar(endp, pgnd, radius);
|
||||
// Vec3d pgnd{endp.x(), endp.y(), gndlvl};
|
||||
double h = endp.z() - gndlvl;
|
||||
pillar_id = m_builder.add_pillar(endp, h, radius);
|
||||
|
||||
if (can_add_base)
|
||||
m_builder.add_pillar_base(pillar_id, m_cfg.base_height_mm,
|
||||
|
@ -645,7 +646,7 @@ bool SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp,
|
|||
// prevent from queries of head_pillar() to have non-existing
|
||||
// pillar when the head should have one.
|
||||
if (head_id >= 0)
|
||||
m_builder.add_pillar(head_id, jp, radius);
|
||||
m_builder.add_pillar(head_id, 0.);
|
||||
}
|
||||
|
||||
if(pillar_id >= 0) // Save the pillar endpoint in the spatial index
|
||||
|
@ -1034,7 +1035,7 @@ bool SupportTreeBuildsteps::connect_to_model_body(Head &head)
|
|||
|
||||
head.transform();
|
||||
|
||||
long pillar_id = m_builder.add_pillar(head.id, endp, head.r_back_mm);
|
||||
long pillar_id = m_builder.add_pillar(head.id, hit.distance() + h);
|
||||
Pillar &pill = m_builder.pillar(pillar_id);
|
||||
|
||||
Vec3d taildir = endp - hitp;
|
||||
|
@ -1046,11 +1047,14 @@ bool SupportTreeBuildsteps::connect_to_model_body(Head &head)
|
|||
w = 0.;
|
||||
}
|
||||
|
||||
Head tailhead(head.r_back_mm, head.r_pin_mm, w,
|
||||
m_cfg.head_penetration_mm, taildir, hitp);
|
||||
m_builder.add_anchor(head.r_back_mm, head.r_pin_mm, w,
|
||||
m_cfg.head_penetration_mm, taildir, hitp);
|
||||
|
||||
tailhead.transform();
|
||||
pill.base = tailhead.mesh;
|
||||
// Head tailhead(head.r_back_mm, head.r_pin_mm, w,
|
||||
// m_cfg.head_penetration_mm, taildir, hitp);
|
||||
|
||||
// tailhead.transform();
|
||||
// pill.base = tailhead.mesh;
|
||||
|
||||
m_pillar_index.guarded_insert(pill.endpoint(), pill.id);
|
||||
|
||||
|
@ -1297,8 +1301,8 @@ void SupportTreeBuildsteps::interconnect_pillars()
|
|||
if (found)
|
||||
for (unsigned n = 0; n < needpillars; n++) {
|
||||
Vec3d s = spts[n];
|
||||
Pillar p(s, Vec3d(s(X), s(Y), gnd), pillar().r);
|
||||
p.add_base(m_cfg.base_height_mm, m_cfg.base_radius_mm);
|
||||
Pillar p(s, s.z() - gnd, pillar().r);
|
||||
// p.add_base(m_cfg.base_height_mm, m_cfg.base_radius_mm);
|
||||
|
||||
if (interconnect(pillar(), p)) {
|
||||
Pillar &pp = m_builder.pillar(m_builder.add_pillar(p));
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
#include <libslic3r/SLA/SupportTreeBuilder.hpp>
|
||||
#include <libslic3r/SLA/Clustering.hpp>
|
||||
#include <libslic3r/SLA/SpatIndex.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace sla {
|
||||
|
@ -108,55 +109,6 @@ public:
|
|||
EigenMesh3D::hit_result query_hit(const SupportableMesh &msh, const Bridge &br, double safety_d = std::nan(""));
|
||||
EigenMesh3D::hit_result query_hit(const SupportableMesh &msh, const Head &br, double safety_d = std::nan(""));
|
||||
|
||||
// This function returns the position of the centroid in the input 'clust'
|
||||
// vector of point indices.
|
||||
template<class DistFn>
|
||||
long cluster_centroid(const ClusterEl& clust,
|
||||
const std::function<Vec3d(size_t)> &pointfn,
|
||||
DistFn df)
|
||||
{
|
||||
switch(clust.size()) {
|
||||
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: ;
|
||||
}
|
||||
|
||||
// The function works by calculating for each point the average distance
|
||||
// from all the other points in the cluster. We create a selector bitmask of
|
||||
// the same size as the cluster. The bitmask will have two true bits and
|
||||
// false bits for the rest of items and we will loop through all the
|
||||
// permutations of the bitmask (combinations of two points). Get the
|
||||
// distance for the two points and add the distance to the averages.
|
||||
// The point with the smallest average than wins.
|
||||
|
||||
// The complexity should be O(n^2) but we will mostly apply this function
|
||||
// for small clusters only (cca 3 elements)
|
||||
|
||||
std::vector<bool> sel(clust.size(), false); // create full zero bitmask
|
||||
std::fill(sel.end() - 2, sel.end(), true); // insert the two ones
|
||||
std::vector<double> avgs(clust.size(), 0.0); // store the average distances
|
||||
|
||||
do {
|
||||
std::array<size_t, 2> idx;
|
||||
for(size_t i = 0, j = 0; i < clust.size(); i++) if(sel[i]) idx[j++] = i;
|
||||
|
||||
double d = df(pointfn(clust[idx[0]]),
|
||||
pointfn(clust[idx[1]]));
|
||||
|
||||
// add the distance to the sums for both associated points
|
||||
for(auto i : idx) avgs[i] += d;
|
||||
|
||||
// now continue with the next permutation of the bitmask with two 1s
|
||||
} while(std::next_permutation(sel.begin(), sel.end()));
|
||||
|
||||
// Divide by point size in the cluster to get the average (may be redundant)
|
||||
for(auto& a : avgs) a /= clust.size();
|
||||
|
||||
// get the lowest average distance and return the index
|
||||
auto minit = std::min_element(avgs.begin(), avgs.end());
|
||||
return long(minit - avgs.begin());
|
||||
}
|
||||
|
||||
inline Vec3d dirv(const Vec3d& startp, const Vec3d& endp) {
|
||||
return (endp - startp).normalized();
|
||||
|
|
268
src/libslic3r/SLA/SupportTreeMesher.cpp
Normal file
268
src/libslic3r/SLA/SupportTreeMesher.cpp
Normal file
|
@ -0,0 +1,268 @@
|
|||
#include "SupportTreeMesher.hpp"
|
||||
|
||||
namespace Slic3r { namespace sla {
|
||||
|
||||
Contour3D sphere(double rho, Portion portion, double fa) {
|
||||
|
||||
Contour3D ret;
|
||||
|
||||
// prohibit close to zero radius
|
||||
if(rho <= 1e-6 && rho >= -1e-6) return ret;
|
||||
|
||||
auto& vertices = ret.points;
|
||||
auto& facets = ret.faces3;
|
||||
|
||||
// Algorithm:
|
||||
// Add points one-by-one to the sphere grid and form facets using relative
|
||||
// coordinates. Sphere is composed effectively of a mesh of stacked circles.
|
||||
|
||||
// adjust via rounding to get an even multiple for any provided angle.
|
||||
double angle = (2*PI / floor(2*PI / fa));
|
||||
|
||||
// Ring to be scaled to generate the steps of the sphere
|
||||
std::vector<double> ring;
|
||||
|
||||
for (double i = 0; i < 2*PI; i+=angle) ring.emplace_back(i);
|
||||
|
||||
const auto sbegin = size_t(2*std::get<0>(portion)/angle);
|
||||
const auto send = size_t(2*std::get<1>(portion)/angle);
|
||||
|
||||
const size_t steps = ring.size();
|
||||
const double increment = 1.0 / double(steps);
|
||||
|
||||
// special case: first ring connects to 0,0,0
|
||||
// insert and form facets.
|
||||
if(sbegin == 0)
|
||||
vertices.emplace_back(Vec3d(0.0, 0.0, -rho + increment*sbegin*2.0*rho));
|
||||
|
||||
auto id = coord_t(vertices.size());
|
||||
for (size_t i = 0; i < ring.size(); i++) {
|
||||
// Fixed scaling
|
||||
const double z = -rho + increment*rho*2.0 * (sbegin + 1.0);
|
||||
// radius of the circle for this step.
|
||||
const double r = std::sqrt(std::abs(rho*rho - z*z));
|
||||
Vec2d b = Eigen::Rotation2Dd(ring[i]) * Eigen::Vector2d(0, r);
|
||||
vertices.emplace_back(Vec3d(b(0), b(1), z));
|
||||
|
||||
if (sbegin == 0)
|
||||
(i == 0) ? facets.emplace_back(coord_t(ring.size()), 0, 1) :
|
||||
facets.emplace_back(id - 1, 0, id);
|
||||
++id;
|
||||
}
|
||||
|
||||
// General case: insert and form facets for each step,
|
||||
// joining it to the ring below it.
|
||||
for (size_t s = sbegin + 2; s < send - 1; s++) {
|
||||
const double z = -rho + increment*double(s*2.0*rho);
|
||||
const double r = std::sqrt(std::abs(rho*rho - z*z));
|
||||
|
||||
for (size_t i = 0; i < ring.size(); i++) {
|
||||
Vec2d b = Eigen::Rotation2Dd(ring[i]) * Eigen::Vector2d(0, r);
|
||||
vertices.emplace_back(Vec3d(b(0), b(1), z));
|
||||
auto id_ringsize = coord_t(id - int(ring.size()));
|
||||
if (i == 0) {
|
||||
// wrap around
|
||||
facets.emplace_back(id - 1, id, id + coord_t(ring.size() - 1) );
|
||||
facets.emplace_back(id - 1, id_ringsize, id);
|
||||
} else {
|
||||
facets.emplace_back(id_ringsize - 1, id_ringsize, id);
|
||||
facets.emplace_back(id - 1, id_ringsize - 1, id);
|
||||
}
|
||||
id++;
|
||||
}
|
||||
}
|
||||
|
||||
// special case: last ring connects to 0,0,rho*2.0
|
||||
// only form facets.
|
||||
if(send >= size_t(2*PI / angle)) {
|
||||
vertices.emplace_back(Vec3d(0.0, 0.0, -rho + increment*send*2.0*rho));
|
||||
for (size_t i = 0; i < ring.size(); i++) {
|
||||
auto id_ringsize = coord_t(id - int(ring.size()));
|
||||
if (i == 0) {
|
||||
// third vertex is on the other side of the ring.
|
||||
facets.emplace_back(id - 1, id_ringsize, id);
|
||||
} else {
|
||||
auto ci = coord_t(id_ringsize + coord_t(i));
|
||||
facets.emplace_back(ci - 1, ci, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
id++;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Contour3D cylinder(double r, double h, size_t ssteps, const Vec3d &sp)
|
||||
{
|
||||
assert(steps > 0);
|
||||
|
||||
Contour3D ret;
|
||||
|
||||
auto steps = int(ssteps);
|
||||
auto& points = ret.points;
|
||||
auto& indices = ret.faces3;
|
||||
points.reserve(2*ssteps);
|
||||
double a = 2*PI/steps;
|
||||
|
||||
Vec3d jp = sp;
|
||||
Vec3d endp = {sp(X), sp(Y), sp(Z) + h};
|
||||
|
||||
// Upper circle points
|
||||
for(int i = 0; i < steps; ++i) {
|
||||
double phi = i*a;
|
||||
double ex = endp(X) + r*std::cos(phi);
|
||||
double ey = endp(Y) + r*std::sin(phi);
|
||||
points.emplace_back(ex, ey, endp(Z));
|
||||
}
|
||||
|
||||
// Lower circle points
|
||||
for(int i = 0; i < steps; ++i) {
|
||||
double phi = i*a;
|
||||
double x = jp(X) + r*std::cos(phi);
|
||||
double y = jp(Y) + r*std::sin(phi);
|
||||
points.emplace_back(x, y, jp(Z));
|
||||
}
|
||||
|
||||
// Now create long triangles connecting upper and lower circles
|
||||
indices.reserve(2*ssteps);
|
||||
auto offs = steps;
|
||||
for(int i = 0; i < steps - 1; ++i) {
|
||||
indices.emplace_back(i, i + offs, offs + i + 1);
|
||||
indices.emplace_back(i, offs + i + 1, i + 1);
|
||||
}
|
||||
|
||||
// Last triangle connecting the first and last vertices
|
||||
auto last = steps - 1;
|
||||
indices.emplace_back(0, last, offs);
|
||||
indices.emplace_back(last, offs + last, offs);
|
||||
|
||||
// According to the slicing algorithms, we need to aid them with generating
|
||||
// a watertight body. So we create a triangle fan for the upper and lower
|
||||
// ending of the cylinder to close the geometry.
|
||||
points.emplace_back(jp); int ci = int(points.size() - 1);
|
||||
for(int i = 0; i < steps - 1; ++i)
|
||||
indices.emplace_back(i + offs + 1, i + offs, ci);
|
||||
|
||||
indices.emplace_back(offs, steps + offs - 1, ci);
|
||||
|
||||
points.emplace_back(endp); ci = int(points.size() - 1);
|
||||
for(int i = 0; i < steps - 1; ++i)
|
||||
indices.emplace_back(ci, i, i + 1);
|
||||
|
||||
indices.emplace_back(steps - 1, 0, ci);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Contour3D pinhead(double r_pin, double r_back, double length, size_t steps)
|
||||
{
|
||||
assert(steps > 0);
|
||||
assert(length > 0.);
|
||||
assert(r_back > 0.);
|
||||
assert(r_pin > 0.);
|
||||
|
||||
Contour3D mesh;
|
||||
|
||||
// We create two spheres which will be connected with a robe that fits
|
||||
// both circles perfectly.
|
||||
|
||||
// Set up the model detail level
|
||||
const double detail = 2*PI/steps;
|
||||
|
||||
// We don't generate whole circles. Instead, we generate only the
|
||||
// portions which are visible (not covered by the robe) To know the
|
||||
// exact portion of the bottom and top circles we need to use some
|
||||
// rules of tangent circles from which we can derive (using simple
|
||||
// triangles the following relations:
|
||||
|
||||
// The height of the whole mesh
|
||||
const double h = r_back + r_pin + length;
|
||||
double phi = PI / 2. - std::acos((r_back - r_pin) / h);
|
||||
|
||||
// To generate a whole circle we would pass a portion of (0, Pi)
|
||||
// To generate only a half horizontal circle we can pass (0, Pi/2)
|
||||
// The calculated phi is an offset to the half circles needed to smooth
|
||||
// the transition from the circle to the robe geometry
|
||||
|
||||
auto&& s1 = sphere(r_back, make_portion(0, PI/2 + phi), detail);
|
||||
auto&& s2 = sphere(r_pin, make_portion(PI/2 + phi, PI), detail);
|
||||
|
||||
for(auto& p : s2.points) p.z() += h;
|
||||
|
||||
mesh.merge(s1);
|
||||
mesh.merge(s2);
|
||||
|
||||
for(size_t idx1 = s1.points.size() - steps, idx2 = s1.points.size();
|
||||
idx1 < s1.points.size() - 1;
|
||||
idx1++, idx2++)
|
||||
{
|
||||
coord_t i1s1 = coord_t(idx1), i1s2 = coord_t(idx2);
|
||||
coord_t i2s1 = i1s1 + 1, i2s2 = i1s2 + 1;
|
||||
|
||||
mesh.faces3.emplace_back(i1s1, i2s1, i2s2);
|
||||
mesh.faces3.emplace_back(i1s1, i2s2, i1s2);
|
||||
}
|
||||
|
||||
auto i1s1 = coord_t(s1.points.size()) - coord_t(steps);
|
||||
auto i2s1 = coord_t(s1.points.size()) - 1;
|
||||
auto i1s2 = coord_t(s1.points.size());
|
||||
auto i2s2 = coord_t(s1.points.size()) + coord_t(steps) - 1;
|
||||
|
||||
mesh.faces3.emplace_back(i2s2, i2s1, i1s1);
|
||||
mesh.faces3.emplace_back(i1s2, i2s2, i1s1);
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
||||
Contour3D pedestal(const Vec3d &endpt, double baseheight, double radius, size_t steps)
|
||||
{
|
||||
assert(steps > 0);
|
||||
|
||||
if(baseheight <= 0) return {};
|
||||
|
||||
assert(steps >= 0);
|
||||
auto last = int(steps - 1);
|
||||
|
||||
Contour3D base;
|
||||
|
||||
double a = 2*PI/steps;
|
||||
double z = endpt(Z) + baseheight;
|
||||
|
||||
for(size_t i = 0; i < steps; ++i) {
|
||||
double phi = i*a;
|
||||
double x = endpt(X) + radius * std::cos(phi);
|
||||
double y = endpt(Y) + radius * std::sin(phi);
|
||||
base.points.emplace_back(x, y, z);
|
||||
}
|
||||
|
||||
for(size_t i = 0; i < steps; ++i) {
|
||||
double phi = i*a;
|
||||
double x = endpt(X) + radius*std::cos(phi);
|
||||
double y = endpt(Y) + radius*std::sin(phi);
|
||||
base.points.emplace_back(x, y, z - baseheight);
|
||||
}
|
||||
|
||||
auto ep = endpt; ep(Z) += baseheight;
|
||||
base.points.emplace_back(endpt);
|
||||
base.points.emplace_back(ep);
|
||||
|
||||
auto& indices = base.faces3;
|
||||
auto hcenter = int(base.points.size() - 1);
|
||||
auto lcenter = int(base.points.size() - 2);
|
||||
auto offs = int(steps);
|
||||
for(int i = 0; i < last; ++i) {
|
||||
indices.emplace_back(i, i + offs, offs + i + 1);
|
||||
indices.emplace_back(i, offs + i + 1, i + 1);
|
||||
indices.emplace_back(i, i + 1, hcenter);
|
||||
indices.emplace_back(lcenter, offs + i + 1, offs + i);
|
||||
}
|
||||
|
||||
indices.emplace_back(0, last, offs);
|
||||
indices.emplace_back(last, offs + last, offs);
|
||||
indices.emplace_back(hcenter, last, 0);
|
||||
indices.emplace_back(offs, offs + last, lcenter);
|
||||
|
||||
return base;
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::sla
|
94
src/libslic3r/SLA/SupportTreeMesher.hpp
Normal file
94
src/libslic3r/SLA/SupportTreeMesher.hpp
Normal file
|
@ -0,0 +1,94 @@
|
|||
#ifndef SUPPORTTREEMESHER_HPP
|
||||
#define SUPPORTTREEMESHER_HPP
|
||||
|
||||
#include "libslic3r/Point.hpp"
|
||||
|
||||
#include "libslic3r/SLA/SupportTreeBuilder.hpp"
|
||||
#include "libslic3r/SLA/Contour3D.hpp"
|
||||
|
||||
namespace Slic3r { namespace sla {
|
||||
|
||||
using Portion = std::tuple<double, double>;
|
||||
|
||||
inline Portion make_portion(double a, double b)
|
||||
{
|
||||
return std::make_tuple(a, b);
|
||||
}
|
||||
|
||||
Contour3D sphere(double rho,
|
||||
Portion portion = make_portion(0., 2. * PI),
|
||||
double fa = (2. * PI / 360.));
|
||||
|
||||
// Down facing cylinder in Z direction with arguments:
|
||||
// r: radius
|
||||
// h: Height
|
||||
// ssteps: how many edges will create the base circle
|
||||
// sp: starting point
|
||||
Contour3D cylinder(double r, double h, size_t steps = 45, const Vec3d &sp = Vec3d::Zero());
|
||||
|
||||
Contour3D pinhead(double r_pin, double r_back, double length, size_t steps = 45);
|
||||
|
||||
Contour3D pedestal(const Vec3d &pt, double baseheight, double radius, size_t steps = 45);
|
||||
|
||||
inline Contour3D get_mesh(const Head &h, size_t steps)
|
||||
{
|
||||
Contour3D mesh = pinhead(h.r_pin_mm, h.r_back_mm, h.width_mm, steps);
|
||||
|
||||
// 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) p.z() -= (h.fullwidth() - h.r_back_mm);
|
||||
|
||||
using Quaternion = Eigen::Quaternion<double>;
|
||||
|
||||
// We rotate the head to the specified direction The head's pointing
|
||||
// side is facing upwards so this means that it would hold a support
|
||||
// point with a normal pointing straight down. This is the reason of
|
||||
// the -1 z coordinate
|
||||
auto quatern = Quaternion::FromTwoVectors(Vec3d{0, 0, -1}, h.dir);
|
||||
|
||||
for(auto& p : mesh.points) p = quatern * p + h.pos;
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
||||
inline Contour3D get_mesh(const Pillar &p, size_t steps)
|
||||
{
|
||||
if(p.height > EPSILON) { // Endpoint is below the starting point
|
||||
// We just create a bridge geometry with the pillar parameters and
|
||||
// move the data.
|
||||
return cylinder(p.r, p.height, steps, p.endpoint());
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
inline Contour3D get_mesh(const Pedestal &p, size_t steps)
|
||||
{
|
||||
return pedestal(p.pos, p.height, p.radius, steps);
|
||||
}
|
||||
|
||||
inline Contour3D get_mesh(const Junction &j, size_t steps)
|
||||
{
|
||||
Contour3D mesh = sphere(j.r, make_portion(0, PI), 2 *PI / steps);
|
||||
for(auto& p : mesh.points) p += j.pos;
|
||||
return mesh;
|
||||
}
|
||||
|
||||
inline Contour3D get_mesh(const Bridge &br, size_t steps)
|
||||
{
|
||||
using Quaternion = Eigen::Quaternion<double>;
|
||||
Vec3d v = (br.endp - br.startp);
|
||||
Vec3d dir = v.normalized();
|
||||
double d = v.norm();
|
||||
|
||||
Contour3D mesh = cylinder(br.r, d, steps);
|
||||
|
||||
auto quater = Quaternion::FromTwoVectors(Vec3d{0,0,1}, dir);
|
||||
for(auto& p : mesh.points) p = quater * p + br.startp;
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
||||
}}
|
||||
|
||||
#endif // SUPPORTTREEMESHER_HPP
|
|
@ -2,7 +2,8 @@
|
|||
#include <test_utils.hpp>
|
||||
|
||||
#include "libslic3r/TriangleMesh.hpp"
|
||||
#include "libslic3r/SLA/SupportTreeBuilder.hpp"
|
||||
#include "libslic3r/SLA/SupportTreeBuildsteps.hpp"
|
||||
#include "libslic3r/SLA/SupportTreeMesher.hpp"
|
||||
|
||||
TEST_CASE("Test bridge_mesh_intersect on a cube's wall", "[SLABridgeMeshInters]") {
|
||||
using namespace Slic3r;
|
||||
|
@ -13,6 +14,7 @@ TEST_CASE("Test bridge_mesh_intersect on a cube's wall", "[SLABridgeMeshInters]"
|
|||
sla::SupportPoints pts = {{10.f, 5.f, 5.f, float(cfg.head_front_radius_mm), false}};
|
||||
sla::SupportableMesh sm{cube, pts, cfg};
|
||||
|
||||
size_t steps = 45;
|
||||
SECTION("Bridge is straight horizontal and pointing away from the cube") {
|
||||
|
||||
sla::Bridge bridge(pts[0].pos.cast<double>(), Vec3d{15., 5., 5.},
|
||||
|
@ -22,7 +24,7 @@ TEST_CASE("Test bridge_mesh_intersect on a cube's wall", "[SLABridgeMeshInters]"
|
|||
|
||||
REQUIRE(std::isinf(hit.distance()));
|
||||
|
||||
cube.merge(sla::to_triangle_mesh(bridge.mesh));
|
||||
cube.merge(sla::to_triangle_mesh(get_mesh(bridge, steps)));
|
||||
cube.require_shared_vertices();
|
||||
cube.WriteOBJFile("cube1.obj");
|
||||
}
|
||||
|
@ -35,7 +37,7 @@ TEST_CASE("Test bridge_mesh_intersect on a cube's wall", "[SLABridgeMeshInters]"
|
|||
|
||||
REQUIRE(std::isinf(hit.distance()));
|
||||
|
||||
cube.merge(sla::to_triangle_mesh(bridge.mesh));
|
||||
cube.merge(sla::to_triangle_mesh(get_mesh(bridge, steps)));
|
||||
cube.require_shared_vertices();
|
||||
cube.WriteOBJFile("cube2.obj");
|
||||
}
|
||||
|
@ -52,6 +54,7 @@ TEST_CASE("Test bridge_mesh_intersect on a sphere", "[SLABridgeMeshInters]") {
|
|||
sla::SupportPoints pts = {{1.f, 0.f, 0.f, float(cfg.head_front_radius_mm), false}};
|
||||
sla::SupportableMesh sm{sphere, pts, cfg};
|
||||
|
||||
size_t steps = 45;
|
||||
SECTION("Bridge is straight horizontal and pointing away from the sphere") {
|
||||
|
||||
sla::Bridge bridge(pts[0].pos.cast<double>(), Vec3d{2., 0., 0.},
|
||||
|
@ -59,7 +62,7 @@ TEST_CASE("Test bridge_mesh_intersect on a sphere", "[SLABridgeMeshInters]") {
|
|||
|
||||
auto hit = sla::query_hit(sm, bridge);
|
||||
|
||||
sphere.merge(sla::to_triangle_mesh(bridge.mesh));
|
||||
sphere.merge(sla::to_triangle_mesh(get_mesh(bridge, steps)));
|
||||
sphere.require_shared_vertices();
|
||||
sphere.WriteOBJFile("sphere1.obj");
|
||||
|
||||
|
@ -73,7 +76,7 @@ TEST_CASE("Test bridge_mesh_intersect on a sphere", "[SLABridgeMeshInters]") {
|
|||
|
||||
auto hit = sla::query_hit(sm, bridge);
|
||||
|
||||
sphere.merge(sla::to_triangle_mesh(bridge.mesh));
|
||||
sphere.merge(sla::to_triangle_mesh(get_mesh(bridge, steps)));
|
||||
sphere.require_shared_vertices();
|
||||
sphere.WriteOBJFile("sphere2.obj");
|
||||
|
||||
|
@ -87,7 +90,7 @@ TEST_CASE("Test bridge_mesh_intersect on a sphere", "[SLABridgeMeshInters]") {
|
|||
|
||||
auto hit = sla::query_hit(sm, bridge);
|
||||
|
||||
sphere.merge(sla::to_triangle_mesh(bridge.mesh));
|
||||
sphere.merge(sla::to_triangle_mesh(get_mesh(bridge, steps)));
|
||||
sphere.require_shared_vertices();
|
||||
sphere.WriteOBJFile("sphere3.obj");
|
||||
|
||||
|
|
Loading…
Reference in a new issue