mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-07-25 23:54:00 -06:00
Merge branch 'master' into materials
This commit is contained in:
commit
fd6d32135b
129 changed files with 78584 additions and 4740 deletions
|
@ -15,7 +15,7 @@ public:
|
|||
PointClass max;
|
||||
bool defined;
|
||||
|
||||
BoundingBoxBase() : defined(false), min(PointClass::Zero()), max(PointClass::Zero()) {}
|
||||
BoundingBoxBase() : min(PointClass::Zero()), max(PointClass::Zero()), defined(false) {}
|
||||
BoundingBoxBase(const PointClass &pmin, const PointClass &pmax) :
|
||||
min(pmin), max(pmax), defined(pmin(0) < pmax(0) && pmin(1) < pmax(1)) {}
|
||||
BoundingBoxBase(const std::vector<PointClass>& points) : min(PointClass::Zero()), max(PointClass::Zero())
|
||||
|
@ -59,7 +59,7 @@ template <class PointClass>
|
|||
class BoundingBox3Base : public BoundingBoxBase<PointClass>
|
||||
{
|
||||
public:
|
||||
BoundingBox3Base() : BoundingBoxBase<PointClass>() {};
|
||||
BoundingBox3Base() : BoundingBoxBase<PointClass>() {}
|
||||
BoundingBox3Base(const PointClass &pmin, const PointClass &pmax) :
|
||||
BoundingBoxBase<PointClass>(pmin, pmax)
|
||||
{ if (pmin(2) >= pmax(2)) BoundingBoxBase<PointClass>::defined = false; }
|
||||
|
@ -100,6 +100,33 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
// Will prevent warnings caused by non existing definition of template in hpp
|
||||
extern template void BoundingBoxBase<Point>::scale(double factor);
|
||||
extern template void BoundingBoxBase<Vec2d>::scale(double factor);
|
||||
extern template void BoundingBoxBase<Vec3d>::scale(double factor);
|
||||
extern template void BoundingBoxBase<Point>::offset(coordf_t delta);
|
||||
extern template void BoundingBoxBase<Vec2d>::offset(coordf_t delta);
|
||||
extern template void BoundingBoxBase<Point>::merge(const Point &point);
|
||||
extern template void BoundingBoxBase<Vec2d>::merge(const Vec2d &point);
|
||||
extern template void BoundingBoxBase<Point>::merge(const Points &points);
|
||||
extern template void BoundingBoxBase<Vec2d>::merge(const Pointfs &points);
|
||||
extern template void BoundingBoxBase<Point>::merge(const BoundingBoxBase<Point> &bb);
|
||||
extern template void BoundingBoxBase<Vec2d>::merge(const BoundingBoxBase<Vec2d> &bb);
|
||||
extern template Point BoundingBoxBase<Point>::size() const;
|
||||
extern template Vec2d BoundingBoxBase<Vec2d>::size() const;
|
||||
extern template double BoundingBoxBase<Point>::radius() const;
|
||||
extern template double BoundingBoxBase<Vec2d>::radius() const;
|
||||
extern template Point BoundingBoxBase<Point>::center() const;
|
||||
extern template Vec2d BoundingBoxBase<Vec2d>::center() const;
|
||||
extern template void BoundingBox3Base<Vec3d>::merge(const Vec3d &point);
|
||||
extern template void BoundingBox3Base<Vec3d>::merge(const Pointf3s &points);
|
||||
extern template void BoundingBox3Base<Vec3d>::merge(const BoundingBox3Base<Vec3d> &bb);
|
||||
extern template Vec3d BoundingBox3Base<Vec3d>::size() const;
|
||||
extern template double BoundingBox3Base<Vec3d>::radius() const;
|
||||
extern template void BoundingBox3Base<Vec3d>::offset(coordf_t delta);
|
||||
extern template Vec3d BoundingBox3Base<Vec3d>::center() const;
|
||||
extern template coordf_t BoundingBox3Base<Vec3d>::max_size() const;
|
||||
|
||||
class BoundingBox : public BoundingBoxBase<Point>
|
||||
{
|
||||
public:
|
||||
|
@ -113,9 +140,9 @@ public:
|
|||
// to encompass the original bounding box.
|
||||
void align_to_grid(const coord_t cell_size);
|
||||
|
||||
BoundingBox() : BoundingBoxBase<Point>() {};
|
||||
BoundingBox(const Point &pmin, const Point &pmax) : BoundingBoxBase<Point>(pmin, pmax) {};
|
||||
BoundingBox(const Points &points) : BoundingBoxBase<Point>(points) {};
|
||||
BoundingBox() : BoundingBoxBase<Point>() {}
|
||||
BoundingBox(const Point &pmin, const Point &pmax) : BoundingBoxBase<Point>(pmin, pmax) {}
|
||||
BoundingBox(const Points &points) : BoundingBoxBase<Point>(points) {}
|
||||
BoundingBox(const Lines &lines);
|
||||
|
||||
friend BoundingBox get_extents_rotated(const Points &points, double angle);
|
||||
|
@ -124,25 +151,25 @@ public:
|
|||
class BoundingBox3 : public BoundingBox3Base<Vec3crd>
|
||||
{
|
||||
public:
|
||||
BoundingBox3() : BoundingBox3Base<Vec3crd>() {};
|
||||
BoundingBox3(const Vec3crd &pmin, const Vec3crd &pmax) : BoundingBox3Base<Vec3crd>(pmin, pmax) {};
|
||||
BoundingBox3(const Points3& points) : BoundingBox3Base<Vec3crd>(points) {};
|
||||
BoundingBox3() : BoundingBox3Base<Vec3crd>() {}
|
||||
BoundingBox3(const Vec3crd &pmin, const Vec3crd &pmax) : BoundingBox3Base<Vec3crd>(pmin, pmax) {}
|
||||
BoundingBox3(const Points3& points) : BoundingBox3Base<Vec3crd>(points) {}
|
||||
};
|
||||
|
||||
class BoundingBoxf : public BoundingBoxBase<Vec2d>
|
||||
{
|
||||
public:
|
||||
BoundingBoxf() : BoundingBoxBase<Vec2d>() {};
|
||||
BoundingBoxf(const Vec2d &pmin, const Vec2d &pmax) : BoundingBoxBase<Vec2d>(pmin, pmax) {};
|
||||
BoundingBoxf(const std::vector<Vec2d> &points) : BoundingBoxBase<Vec2d>(points) {};
|
||||
BoundingBoxf() : BoundingBoxBase<Vec2d>() {}
|
||||
BoundingBoxf(const Vec2d &pmin, const Vec2d &pmax) : BoundingBoxBase<Vec2d>(pmin, pmax) {}
|
||||
BoundingBoxf(const std::vector<Vec2d> &points) : BoundingBoxBase<Vec2d>(points) {}
|
||||
};
|
||||
|
||||
class BoundingBoxf3 : public BoundingBox3Base<Vec3d>
|
||||
{
|
||||
public:
|
||||
BoundingBoxf3() : BoundingBox3Base<Vec3d>() {};
|
||||
BoundingBoxf3(const Vec3d &pmin, const Vec3d &pmax) : BoundingBox3Base<Vec3d>(pmin, pmax) {};
|
||||
BoundingBoxf3(const std::vector<Vec3d> &points) : BoundingBox3Base<Vec3d>(points) {};
|
||||
BoundingBoxf3() : BoundingBox3Base<Vec3d>() {}
|
||||
BoundingBoxf3(const Vec3d &pmin, const Vec3d &pmax) : BoundingBox3Base<Vec3d>(pmin, pmax) {}
|
||||
BoundingBoxf3(const std::vector<Vec3d> &points) : BoundingBox3Base<Vec3d>(points) {}
|
||||
|
||||
BoundingBoxf3 transformed(const Transform3d& matrix) const;
|
||||
};
|
||||
|
|
|
@ -6,9 +6,9 @@
|
|||
namespace Slic3r {
|
||||
|
||||
BridgeDetector::BridgeDetector(
|
||||
ExPolygon _expolygon,
|
||||
const ExPolygonCollection &_lower_slices,
|
||||
coord_t _spacing) :
|
||||
ExPolygon _expolygon,
|
||||
const ExPolygons &_lower_slices,
|
||||
coord_t _spacing) :
|
||||
// The original infill polygon, not inflated.
|
||||
expolygons(expolygons_owned),
|
||||
// All surfaces of the object supporting this region.
|
||||
|
@ -20,9 +20,9 @@ BridgeDetector::BridgeDetector(
|
|||
}
|
||||
|
||||
BridgeDetector::BridgeDetector(
|
||||
const ExPolygons &_expolygons,
|
||||
const ExPolygonCollection &_lower_slices,
|
||||
coord_t _spacing) :
|
||||
const ExPolygons &_expolygons,
|
||||
const ExPolygons &_lower_slices,
|
||||
coord_t _spacing) :
|
||||
// The original infill polygon, not inflated.
|
||||
expolygons(_expolygons),
|
||||
// All surfaces of the object supporting this region.
|
||||
|
@ -46,7 +46,11 @@ void BridgeDetector::initialize()
|
|||
// Detect what edges lie on lower slices by turning bridge contour and holes
|
||||
// into polylines and then clipping them with each lower slice's contour.
|
||||
// Currently _edges are only used to set a candidate direction of the bridge (see bridge_direction_candidates()).
|
||||
this->_edges = intersection_pl(to_polylines(grown), this->lower_slices.contours());
|
||||
Polygons contours;
|
||||
contours.reserve(this->lower_slices.size());
|
||||
for (const ExPolygon &expoly : this->lower_slices)
|
||||
contours.push_back(expoly.contour);
|
||||
this->_edges = intersection_pl(to_polylines(grown), contours);
|
||||
|
||||
#ifdef SLIC3R_DEBUG
|
||||
printf(" bridge has " PRINTF_ZU " support(s)\n", this->_edges.size());
|
||||
|
@ -54,7 +58,7 @@ void BridgeDetector::initialize()
|
|||
|
||||
// detect anchors as intersection between our bridge expolygon and the lower slices
|
||||
// safety offset required to avoid Clipper from detecting empty intersection while Boost actually found some edges
|
||||
this->_anchor_regions = intersection_ex(grown, to_polygons(this->lower_slices.expolygons), true);
|
||||
this->_anchor_regions = intersection_ex(grown, to_polygons(this->lower_slices), true);
|
||||
|
||||
/*
|
||||
if (0) {
|
||||
|
@ -271,7 +275,7 @@ BridgeDetector::unsupported_edges(double angle, Polylines* unsupported) const
|
|||
if (angle == -1) angle = this->angle;
|
||||
if (angle == -1) return;
|
||||
|
||||
Polygons grown_lower = offset(this->lower_slices.expolygons, float(this->spacing));
|
||||
Polygons grown_lower = offset(this->lower_slices, float(this->spacing));
|
||||
|
||||
for (ExPolygons::const_iterator it_expoly = this->expolygons.begin(); it_expoly != this->expolygons.end(); ++ it_expoly) {
|
||||
// get unsupported bridge edges (both contour and holes)
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
#include "libslic3r.h"
|
||||
#include "ExPolygon.hpp"
|
||||
#include "ExPolygonCollection.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Slic3r {
|
||||
|
@ -21,7 +20,7 @@ public:
|
|||
// In case the caller gaves us the input polygons by a value, make a copy.
|
||||
ExPolygons expolygons_owned;
|
||||
// Lower slices, all regions.
|
||||
const ExPolygonCollection &lower_slices;
|
||||
const ExPolygons &lower_slices;
|
||||
// Scaled extrusion width of the infill.
|
||||
coord_t spacing;
|
||||
// Angle resolution for the brute force search of the best bridging angle.
|
||||
|
@ -29,8 +28,8 @@ public:
|
|||
// The final optimal angle.
|
||||
double angle;
|
||||
|
||||
BridgeDetector(ExPolygon _expolygon, const ExPolygonCollection &_lower_slices, coord_t _extrusion_width);
|
||||
BridgeDetector(const ExPolygons &_expolygons, const ExPolygonCollection &_lower_slices, coord_t _extrusion_width);
|
||||
BridgeDetector(ExPolygon _expolygon, const ExPolygons &_lower_slices, coord_t _extrusion_width);
|
||||
BridgeDetector(const ExPolygons &_expolygons, const ExPolygons &_lower_slices, coord_t _extrusion_width);
|
||||
// If bridge_direction_override != 0, then the angle is used instead of auto-detect.
|
||||
bool detect_angle(double bridge_direction_override = 0.);
|
||||
Polygons coverage(double angle = -1) const;
|
||||
|
|
|
@ -176,8 +176,13 @@ 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/SLASupportTreeBuilder.hpp
|
||||
SLA/SLASupportTreeBuildsteps.hpp
|
||||
SLA/SLASupportTreeBuildsteps.cpp
|
||||
SLA/SLASupportTreeBuilder.cpp
|
||||
SLA/SLAConcurrency.hpp
|
||||
SLA/SLASupportTree.hpp
|
||||
SLA/SLASupportTree.cpp
|
||||
SLA/SLASupportTreeIGL.cpp
|
||||
|
@ -215,6 +220,7 @@ target_link_libraries(libslic3r
|
|||
qhull
|
||||
semver
|
||||
tbb
|
||||
${CMAKE_DL_LIBS}
|
||||
)
|
||||
|
||||
if(WIN32)
|
||||
|
|
|
@ -194,6 +194,19 @@ ClipperLib::Paths Slic3rMultiPoints_to_ClipperPaths(const Polygons &input)
|
|||
return retval;
|
||||
}
|
||||
|
||||
ClipperLib::Paths Slic3rMultiPoints_to_ClipperPaths(const ExPolygons &input)
|
||||
{
|
||||
ClipperLib::Paths retval;
|
||||
for (auto &ep : input) {
|
||||
retval.emplace_back(Slic3rMultiPoint_to_ClipperPath(ep.contour));
|
||||
|
||||
for (auto &h : ep.holes)
|
||||
retval.emplace_back(Slic3rMultiPoint_to_ClipperPath(h));
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
ClipperLib::Paths Slic3rMultiPoints_to_ClipperPaths(const Polylines &input)
|
||||
{
|
||||
ClipperLib::Paths retval;
|
||||
|
@ -472,14 +485,16 @@ ExPolygons offset2_ex(const ExPolygons &expolygons, const float delta1,
|
|||
return union_ex(polys);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
T
|
||||
_clipper_do(const ClipperLib::ClipType clipType, const Polygons &subject,
|
||||
const Polygons &clip, const ClipperLib::PolyFillType fillType, const bool safety_offset_)
|
||||
template<class T, class TSubj, class TClip>
|
||||
T _clipper_do(const ClipperLib::ClipType clipType,
|
||||
TSubj && subject,
|
||||
TClip && clip,
|
||||
const ClipperLib::PolyFillType fillType,
|
||||
const bool safety_offset_)
|
||||
{
|
||||
// read input
|
||||
ClipperLib::Paths input_subject = Slic3rMultiPoints_to_ClipperPaths(subject);
|
||||
ClipperLib::Paths input_clip = Slic3rMultiPoints_to_ClipperPaths(clip);
|
||||
ClipperLib::Paths input_subject = Slic3rMultiPoints_to_ClipperPaths(std::forward<TSubj>(subject));
|
||||
ClipperLib::Paths input_clip = Slic3rMultiPoints_to_ClipperPaths(std::forward<TClip>(clip));
|
||||
|
||||
// perform safety offset
|
||||
if (safety_offset_) {
|
||||
|
@ -648,12 +663,26 @@ _clipper_ln(ClipperLib::ClipType clipType, const Lines &subject, const Polygons
|
|||
return retval;
|
||||
}
|
||||
|
||||
ClipperLib::PolyTree
|
||||
union_pt(const Polygons &subject, bool safety_offset_)
|
||||
ClipperLib::PolyTree union_pt(const Polygons &subject, bool safety_offset_)
|
||||
{
|
||||
return _clipper_do<ClipperLib::PolyTree>(ClipperLib::ctUnion, subject, Polygons(), ClipperLib::pftEvenOdd, safety_offset_);
|
||||
}
|
||||
|
||||
ClipperLib::PolyTree union_pt(const ExPolygons &subject, bool safety_offset_)
|
||||
{
|
||||
return _clipper_do<ClipperLib::PolyTree>(ClipperLib::ctUnion, subject, Polygons(), ClipperLib::pftEvenOdd, safety_offset_);
|
||||
}
|
||||
|
||||
ClipperLib::PolyTree union_pt(Polygons &&subject, bool safety_offset_)
|
||||
{
|
||||
return _clipper_do<ClipperLib::PolyTree>(ClipperLib::ctUnion, std::move(subject), Polygons(), ClipperLib::pftEvenOdd, safety_offset_);
|
||||
}
|
||||
|
||||
ClipperLib::PolyTree union_pt(ExPolygons &&subject, bool safety_offset_)
|
||||
{
|
||||
return _clipper_do<ClipperLib::PolyTree>(ClipperLib::ctUnion, std::move(subject), Polygons(), ClipperLib::pftEvenOdd, safety_offset_);
|
||||
}
|
||||
|
||||
Polygons
|
||||
union_pt_chained(const Polygons &subject, bool safety_offset_)
|
||||
{
|
||||
|
@ -664,28 +693,123 @@ union_pt_chained(const Polygons &subject, bool safety_offset_)
|
|||
return retval;
|
||||
}
|
||||
|
||||
void traverse_pt(ClipperLib::PolyNodes &nodes, Polygons* retval)
|
||||
static ClipperLib::PolyNodes order_nodes(const ClipperLib::PolyNodes &nodes)
|
||||
{
|
||||
// collect ordering points
|
||||
Points ordering_points;
|
||||
ordering_points.reserve(nodes.size());
|
||||
for (const ClipperLib::PolyNode *node : nodes)
|
||||
ordering_points.emplace_back(Point(node->Contour.front().X, node->Contour.front().Y));
|
||||
|
||||
// perform the ordering
|
||||
ClipperLib::PolyNodes ordered_nodes = chain_clipper_polynodes(ordering_points, nodes);
|
||||
|
||||
return ordered_nodes;
|
||||
}
|
||||
|
||||
enum class e_ordering {
|
||||
ORDER_POLYNODES,
|
||||
DONT_ORDER_POLYNODES
|
||||
};
|
||||
|
||||
template<e_ordering o>
|
||||
void foreach_node(const ClipperLib::PolyNodes &nodes,
|
||||
std::function<void(const ClipperLib::PolyNode *)> fn);
|
||||
|
||||
template<> void foreach_node<e_ordering::DONT_ORDER_POLYNODES>(
|
||||
const ClipperLib::PolyNodes & nodes,
|
||||
std::function<void(const ClipperLib::PolyNode *)> fn)
|
||||
{
|
||||
for (auto &n : nodes) fn(n);
|
||||
}
|
||||
|
||||
template<> void foreach_node<e_ordering::ORDER_POLYNODES>(
|
||||
const ClipperLib::PolyNodes & nodes,
|
||||
std::function<void(const ClipperLib::PolyNode *)> fn)
|
||||
{
|
||||
auto ordered_nodes = order_nodes(nodes);
|
||||
for (auto &n : ordered_nodes) fn(n);
|
||||
}
|
||||
|
||||
template<e_ordering o>
|
||||
void _traverse_pt(const ClipperLib::PolyNodes &nodes, Polygons *retval)
|
||||
{
|
||||
/* use a nearest neighbor search to order these children
|
||||
TODO: supply start_near to chained_path() too? */
|
||||
|
||||
// collect ordering points
|
||||
Points ordering_points;
|
||||
ordering_points.reserve(nodes.size());
|
||||
for (ClipperLib::PolyNode *pn : nodes)
|
||||
ordering_points.emplace_back(Point(pn->Contour.front().X, pn->Contour.front().Y));
|
||||
|
||||
// perform the ordering
|
||||
ClipperLib::PolyNodes ordered_nodes = chain_clipper_polynodes(ordering_points, nodes);
|
||||
|
||||
// push results recursively
|
||||
for (ClipperLib::PolyNode *pn : ordered_nodes) {
|
||||
foreach_node<o>(nodes, [&retval](const ClipperLib::PolyNode *node) {
|
||||
// traverse the next depth
|
||||
traverse_pt(pn->Childs, retval);
|
||||
retval->emplace_back(ClipperPath_to_Slic3rPolygon(pn->Contour));
|
||||
if (pn->IsHole())
|
||||
retval->back().reverse(); // ccw
|
||||
}
|
||||
_traverse_pt<o>(node->Childs, retval);
|
||||
retval->emplace_back(ClipperPath_to_Slic3rPolygon(node->Contour));
|
||||
if (node->IsHole()) retval->back().reverse(); // ccw
|
||||
});
|
||||
}
|
||||
|
||||
template<e_ordering o>
|
||||
void _traverse_pt(const ClipperLib::PolyNode *tree, ExPolygons *retval)
|
||||
{
|
||||
if (!retval || !tree) return;
|
||||
|
||||
ExPolygons &retv = *retval;
|
||||
|
||||
std::function<void(const ClipperLib::PolyNode*, ExPolygon&)> hole_fn;
|
||||
|
||||
auto contour_fn = [&retv, &hole_fn](const ClipperLib::PolyNode *pptr) {
|
||||
ExPolygon poly;
|
||||
poly.contour.points = ClipperPath_to_Slic3rPolygon(pptr->Contour);
|
||||
auto fn = std::bind(hole_fn, std::placeholders::_1, poly);
|
||||
foreach_node<o>(pptr->Childs, fn);
|
||||
retv.push_back(poly);
|
||||
};
|
||||
|
||||
hole_fn = [&contour_fn](const ClipperLib::PolyNode *pptr, ExPolygon& poly)
|
||||
{
|
||||
poly.holes.emplace_back();
|
||||
poly.holes.back().points = ClipperPath_to_Slic3rPolygon(pptr->Contour);
|
||||
foreach_node<o>(pptr->Childs, contour_fn);
|
||||
};
|
||||
|
||||
contour_fn(tree);
|
||||
}
|
||||
|
||||
template<e_ordering o>
|
||||
void _traverse_pt(const ClipperLib::PolyNodes &nodes, ExPolygons *retval)
|
||||
{
|
||||
// Here is the actual traverse
|
||||
foreach_node<o>(nodes, [&retval](const ClipperLib::PolyNode *node) {
|
||||
_traverse_pt<o>(node, retval);
|
||||
});
|
||||
}
|
||||
|
||||
void traverse_pt(const ClipperLib::PolyNode *tree, ExPolygons *retval)
|
||||
{
|
||||
_traverse_pt<e_ordering::ORDER_POLYNODES>(tree, retval);
|
||||
}
|
||||
|
||||
void traverse_pt_unordered(const ClipperLib::PolyNode *tree, ExPolygons *retval)
|
||||
{
|
||||
_traverse_pt<e_ordering::DONT_ORDER_POLYNODES>(tree, retval);
|
||||
}
|
||||
|
||||
void traverse_pt(const ClipperLib::PolyNodes &nodes, Polygons *retval)
|
||||
{
|
||||
_traverse_pt<e_ordering::ORDER_POLYNODES>(nodes, retval);
|
||||
}
|
||||
|
||||
void traverse_pt(const ClipperLib::PolyNodes &nodes, ExPolygons *retval)
|
||||
{
|
||||
_traverse_pt<e_ordering::ORDER_POLYNODES>(nodes, retval);
|
||||
}
|
||||
|
||||
void traverse_pt_unordered(const ClipperLib::PolyNodes &nodes, Polygons *retval)
|
||||
{
|
||||
_traverse_pt<e_ordering::DONT_ORDER_POLYNODES>(nodes, retval);
|
||||
}
|
||||
|
||||
void traverse_pt_unordered(const ClipperLib::PolyNodes &nodes, ExPolygons *retval)
|
||||
{
|
||||
_traverse_pt<e_ordering::DONT_ORDER_POLYNODES>(nodes, retval);
|
||||
}
|
||||
|
||||
Polygons simplify_polygons(const Polygons &subject, bool preserve_collinear)
|
||||
|
|
|
@ -34,6 +34,7 @@ Slic3r::ExPolygons PolyTreeToExPolygons(ClipperLib::PolyTree& polytree);
|
|||
|
||||
ClipperLib::Path Slic3rMultiPoint_to_ClipperPath(const Slic3r::MultiPoint &input);
|
||||
ClipperLib::Paths Slic3rMultiPoints_to_ClipperPaths(const Polygons &input);
|
||||
ClipperLib::Paths Slic3rMultiPoints_to_ClipperPaths(const ExPolygons &input);
|
||||
ClipperLib::Paths Slic3rMultiPoints_to_ClipperPaths(const Polylines &input);
|
||||
Slic3r::Polygon ClipperPath_to_Slic3rPolygon(const ClipperLib::Path &input);
|
||||
Slic3r::Polyline ClipperPath_to_Slic3rPolyline(const ClipperLib::Path &input);
|
||||
|
@ -215,8 +216,19 @@ inline Slic3r::ExPolygons union_ex(const Slic3r::Surfaces &subject, bool safety_
|
|||
|
||||
|
||||
ClipperLib::PolyTree union_pt(const Slic3r::Polygons &subject, bool safety_offset_ = false);
|
||||
ClipperLib::PolyTree union_pt(const Slic3r::ExPolygons &subject, bool safety_offset_ = false);
|
||||
ClipperLib::PolyTree union_pt(Slic3r::Polygons &&subject, bool safety_offset_ = false);
|
||||
ClipperLib::PolyTree union_pt(Slic3r::ExPolygons &&subject, bool safety_offset_ = false);
|
||||
|
||||
Slic3r::Polygons union_pt_chained(const Slic3r::Polygons &subject, bool safety_offset_ = false);
|
||||
void traverse_pt(ClipperLib::PolyNodes &nodes, Slic3r::Polygons* retval);
|
||||
|
||||
void traverse_pt(const ClipperLib::PolyNodes &nodes, Slic3r::Polygons *retval);
|
||||
void traverse_pt(const ClipperLib::PolyNodes &nodes, Slic3r::ExPolygons *retval);
|
||||
void traverse_pt(const ClipperLib::PolyNode *tree, Slic3r::ExPolygons *retval);
|
||||
|
||||
void traverse_pt_unordered(const ClipperLib::PolyNodes &nodes, Slic3r::Polygons *retval);
|
||||
void traverse_pt_unordered(const ClipperLib::PolyNodes &nodes, Slic3r::ExPolygons *retval);
|
||||
void traverse_pt_unordered(const ClipperLib::PolyNode *tree, Slic3r::ExPolygons *retval);
|
||||
|
||||
/* OTHER */
|
||||
Slic3r::Polygons simplify_polygons(const Slic3r::Polygons &subject, bool preserve_collinear = false);
|
||||
|
|
|
@ -271,8 +271,6 @@ ConfigOptionDef* ConfigDef::add_nullable(const t_config_option_key &opt_key, Con
|
|||
return def;
|
||||
}
|
||||
|
||||
std::string ConfigOptionDef::nocli = "~~~noCLI";
|
||||
|
||||
std::ostream& ConfigDef::print_cli_help(std::ostream& out, bool show_defaults, std::function<bool(const ConfigOptionDef &)> filter) const
|
||||
{
|
||||
// prepare a function for wrapping text
|
||||
|
|
|
@ -1444,7 +1444,7 @@ public:
|
|||
std::vector<std::string> cli_args(const std::string &key) const;
|
||||
|
||||
// Assign this key to cli to disable CLI for this option.
|
||||
static std::string nocli;
|
||||
static const constexpr char *nocli = "~~~noCLI";
|
||||
};
|
||||
|
||||
// Map from a config option name to its definition.
|
||||
|
|
|
@ -24,6 +24,9 @@ public:
|
|||
ExPolygon& operator=(const ExPolygon &other) { contour = other.contour; holes = other.holes; return *this; }
|
||||
ExPolygon& operator=(ExPolygon &&other) { contour = std::move(other.contour); holes = std::move(other.holes); return *this; }
|
||||
|
||||
inline explicit ExPolygon(const Polygon &p): contour(p) {}
|
||||
inline explicit ExPolygon(Polygon &&p): contour(std::move(p)) {}
|
||||
|
||||
Polygon contour;
|
||||
Polygons holes;
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ ExPolygonCollection::ExPolygonCollection(const ExPolygon &expolygon)
|
|||
ExPolygonCollection::operator Points() const
|
||||
{
|
||||
Points points;
|
||||
Polygons pp = *this;
|
||||
Polygons pp = (Polygons)*this;
|
||||
for (Polygons::const_iterator poly = pp.begin(); poly != pp.end(); ++poly) {
|
||||
for (Points::const_iterator point = poly->points.begin(); point != poly->points.end(); ++point)
|
||||
points.push_back(*point);
|
||||
|
|
|
@ -13,15 +13,15 @@ typedef std::vector<ExPolygonCollection> ExPolygonCollections;
|
|||
|
||||
class ExPolygonCollection
|
||||
{
|
||||
public:
|
||||
public:
|
||||
ExPolygons expolygons;
|
||||
|
||||
ExPolygonCollection() {};
|
||||
ExPolygonCollection(const ExPolygon &expolygon);
|
||||
ExPolygonCollection(const ExPolygons &expolygons) : expolygons(expolygons) {};
|
||||
operator Points() const;
|
||||
operator Polygons() const;
|
||||
operator ExPolygons&();
|
||||
ExPolygonCollection() {}
|
||||
explicit ExPolygonCollection(const ExPolygon &expolygon);
|
||||
explicit ExPolygonCollection(const ExPolygons &expolygons) : expolygons(expolygons) {}
|
||||
explicit operator Points() const;
|
||||
explicit operator Polygons() const;
|
||||
explicit operator ExPolygons&();
|
||||
void scale(double factor);
|
||||
void translate(double x, double y);
|
||||
void rotate(double angle, const Point ¢er);
|
||||
|
|
|
@ -14,12 +14,12 @@ namespace Slic3r {
|
|||
|
||||
void ExtrusionPath::intersect_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const
|
||||
{
|
||||
this->_inflate_collection(intersection_pl(this->polyline, collection), retval);
|
||||
this->_inflate_collection(intersection_pl(this->polyline, (Polygons)collection), retval);
|
||||
}
|
||||
|
||||
void ExtrusionPath::subtract_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const
|
||||
{
|
||||
this->_inflate_collection(diff_pl(this->polyline, collection), retval);
|
||||
this->_inflate_collection(diff_pl(this->polyline, (Polygons)collection), retval);
|
||||
}
|
||||
|
||||
void ExtrusionPath::clip_end(double distance)
|
||||
|
|
|
@ -14,7 +14,7 @@ class ExtrusionEntityCollection;
|
|||
class Extruder;
|
||||
|
||||
// Each ExtrusionRole value identifies a distinct set of { extruder, speed }
|
||||
enum ExtrusionRole {
|
||||
enum ExtrusionRole : uint8_t {
|
||||
erNone,
|
||||
erPerimeter,
|
||||
erExternalPerimeter,
|
||||
|
@ -117,25 +117,16 @@ public:
|
|||
float width;
|
||||
// Height of the extrusion, used for visualization purposes.
|
||||
float height;
|
||||
// Feedrate of the extrusion, used for visualization purposes.
|
||||
float feedrate;
|
||||
// Id of the extruder, used for visualization purposes.
|
||||
unsigned int extruder_id;
|
||||
// Id of the color, used for visualization purposes in the color printing case.
|
||||
unsigned int cp_color_id;
|
||||
// Fan speed for the extrusion, used for visualization purposes.
|
||||
float fan_speed;
|
||||
|
||||
ExtrusionPath(ExtrusionRole role) : mm3_per_mm(-1), width(-1), height(-1), feedrate(0.0f), extruder_id(0), cp_color_id(0), fan_speed(0.0f), m_role(role) {};
|
||||
ExtrusionPath(ExtrusionRole role, double mm3_per_mm, float width, float height) : mm3_per_mm(mm3_per_mm), width(width), height(height), feedrate(0.0f), extruder_id(0), cp_color_id(0), fan_speed(0.0f), m_role(role) {};
|
||||
ExtrusionPath(const ExtrusionPath& rhs) : polyline(rhs.polyline), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), feedrate(rhs.feedrate), extruder_id(rhs.extruder_id), cp_color_id(rhs.cp_color_id), fan_speed(rhs.fan_speed), m_role(rhs.m_role) {}
|
||||
ExtrusionPath(ExtrusionPath&& rhs) : polyline(std::move(rhs.polyline)), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), feedrate(rhs.feedrate), extruder_id(rhs.extruder_id), cp_color_id(rhs.cp_color_id), fan_speed(rhs.fan_speed), m_role(rhs.m_role) {}
|
||||
ExtrusionPath(const Polyline &polyline, const ExtrusionPath &rhs) : polyline(polyline), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), feedrate(rhs.feedrate), extruder_id(rhs.extruder_id), cp_color_id(rhs.cp_color_id), fan_speed(rhs.fan_speed), m_role(rhs.m_role) {}
|
||||
ExtrusionPath(Polyline &&polyline, const ExtrusionPath &rhs) : polyline(std::move(polyline)), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), feedrate(rhs.feedrate), extruder_id(rhs.extruder_id), cp_color_id(rhs.cp_color_id), fan_speed(rhs.fan_speed), m_role(rhs.m_role) {}
|
||||
// ExtrusionPath(ExtrusionRole role, const Flow &flow) : m_role(role), mm3_per_mm(flow.mm3_per_mm()), width(flow.width), height(flow.height), feedrate(0.0f), extruder_id(0) {};
|
||||
ExtrusionPath(ExtrusionRole role) : mm3_per_mm(-1), width(-1), height(-1), m_role(role) {};
|
||||
ExtrusionPath(ExtrusionRole role, double mm3_per_mm, float width, float height) : mm3_per_mm(mm3_per_mm), width(width), height(height), m_role(role) {};
|
||||
ExtrusionPath(const ExtrusionPath& rhs) : polyline(rhs.polyline), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), m_role(rhs.m_role) {}
|
||||
ExtrusionPath(ExtrusionPath&& rhs) : polyline(std::move(rhs.polyline)), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), m_role(rhs.m_role) {}
|
||||
ExtrusionPath(const Polyline &polyline, const ExtrusionPath &rhs) : polyline(polyline), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), m_role(rhs.m_role) {}
|
||||
ExtrusionPath(Polyline &&polyline, const ExtrusionPath &rhs) : polyline(std::move(polyline)), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), m_role(rhs.m_role) {}
|
||||
|
||||
ExtrusionPath& operator=(const ExtrusionPath& rhs) { m_role = rhs.m_role; this->mm3_per_mm = rhs.mm3_per_mm; this->width = rhs.width; this->height = rhs.height; this->feedrate = rhs.feedrate, this->extruder_id = rhs.extruder_id, this->cp_color_id = rhs.cp_color_id, this->fan_speed = rhs.fan_speed, this->polyline = rhs.polyline; return *this; }
|
||||
ExtrusionPath& operator=(ExtrusionPath&& rhs) { m_role = rhs.m_role; this->mm3_per_mm = rhs.mm3_per_mm; this->width = rhs.width; this->height = rhs.height; this->feedrate = rhs.feedrate, this->extruder_id = rhs.extruder_id, this->cp_color_id = rhs.cp_color_id, this->fan_speed = rhs.fan_speed, this->polyline = std::move(rhs.polyline); return *this; }
|
||||
ExtrusionPath& operator=(const ExtrusionPath& rhs) { m_role = rhs.m_role; this->mm3_per_mm = rhs.mm3_per_mm; this->width = rhs.width; this->height = rhs.height; this->polyline = rhs.polyline; return *this; }
|
||||
ExtrusionPath& operator=(ExtrusionPath&& rhs) { m_role = rhs.m_role; this->mm3_per_mm = rhs.mm3_per_mm; this->width = rhs.width; this->height = rhs.height; this->polyline = std::move(rhs.polyline); return *this; }
|
||||
|
||||
ExtrusionEntity* clone() const override { return new ExtrusionPath(*this); }
|
||||
// Create a new object, initialize it with this object using the move semantics.
|
||||
|
|
|
@ -15,39 +15,41 @@
|
|||
|
||||
namespace Slic3r {
|
||||
|
||||
bool load_obj(const char *path, Model *model, const char *object_name_in)
|
||||
bool load_obj(const char *path, TriangleMesh *meshptr)
|
||||
{
|
||||
if(meshptr == nullptr) return false;
|
||||
|
||||
// Parse the OBJ file.
|
||||
ObjParser::ObjData data;
|
||||
if (! ObjParser::objparse(path, data)) {
|
||||
// die "Failed to parse $file\n" if !-e $path;
|
||||
// die "Failed to parse $file\n" if !-e $path;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Count the faces and verify, that all faces are triangular.
|
||||
size_t num_faces = 0;
|
||||
size_t num_quads = 0;
|
||||
size_t num_quads = 0;
|
||||
for (size_t i = 0; i < data.vertices.size(); ) {
|
||||
size_t j = i;
|
||||
for (; j < data.vertices.size() && data.vertices[j].coordIdx != -1; ++ j) ;
|
||||
if (i == j)
|
||||
continue;
|
||||
size_t face_vertices = j - i;
|
||||
if (face_vertices != 3 && face_vertices != 4) {
|
||||
size_t face_vertices = j - i;
|
||||
if (face_vertices != 3 && face_vertices != 4) {
|
||||
// Non-triangular and non-quad faces are not supported as of now.
|
||||
return false;
|
||||
}
|
||||
if (face_vertices == 4)
|
||||
++ num_quads;
|
||||
++ num_faces;
|
||||
if (face_vertices == 4)
|
||||
++ num_quads;
|
||||
++ num_faces;
|
||||
i = j + 1;
|
||||
}
|
||||
|
||||
|
||||
// Convert ObjData into STL.
|
||||
TriangleMesh mesh;
|
||||
TriangleMesh &mesh = *meshptr;
|
||||
stl_file &stl = mesh.stl;
|
||||
stl.stats.type = inmemory;
|
||||
stl.stats.number_of_facets = int(num_faces + num_quads);
|
||||
stl.stats.number_of_facets = uint32_t(num_faces + num_quads);
|
||||
stl.stats.original_num_facets = int(num_faces + num_quads);
|
||||
// stl_allocate clears all the allocated data to zero, all normals are set to zeros as well.
|
||||
stl_allocate(&stl);
|
||||
|
@ -68,14 +70,14 @@ bool load_obj(const char *path, Model *model, const char *object_name_in)
|
|||
++ num_normals;
|
||||
}
|
||||
}
|
||||
if (data.vertices[i].coordIdx != -1) {
|
||||
// This is a quad. Produce the other triangle.
|
||||
stl_facet &facet2 = stl.facet_start[i_face++];
|
||||
if (data.vertices[i].coordIdx != -1) {
|
||||
// This is a quad. Produce the other triangle.
|
||||
stl_facet &facet2 = stl.facet_start[i_face++];
|
||||
facet2.vertex[0] = facet.vertex[0];
|
||||
facet2.vertex[1] = facet.vertex[2];
|
||||
const ObjParser::ObjVertex &vertex = data.vertices[i++];
|
||||
memcpy(facet2.vertex[2].data(), &data.coordinates[vertex.coordIdx * 4], 3 * sizeof(float));
|
||||
if (vertex.normalIdx != -1) {
|
||||
const ObjParser::ObjVertex &vertex = data.vertices[i++];
|
||||
memcpy(facet2.vertex[2].data(), &data.coordinates[vertex.coordIdx * 4], 3 * sizeof(float));
|
||||
if (vertex.normalIdx != -1) {
|
||||
normal(0) += data.normals[vertex.normalIdx*3];
|
||||
normal(1) += data.normals[vertex.normalIdx*3+1];
|
||||
normal(2) += data.normals[vertex.normalIdx*3+2];
|
||||
|
@ -96,25 +98,37 @@ bool load_obj(const char *path, Model *model, const char *object_name_in)
|
|||
if (len > EPSILON)
|
||||
facet.normal = normal / len;
|
||||
}
|
||||
}
|
||||
}
|
||||
stl_get_size(&stl);
|
||||
mesh.repair();
|
||||
if (mesh.facets_count() == 0) {
|
||||
// die "This STL file couldn't be read because it's empty.\n"
|
||||
// die "This OBJ file couldn't be read because it's empty.\n"
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string object_name;
|
||||
if (object_name_in == nullptr) {
|
||||
const char *last_slash = strrchr(path, DIR_SEPARATOR);
|
||||
object_name.assign((last_slash == nullptr) ? path : last_slash + 1);
|
||||
} else
|
||||
object_name.assign(object_name_in);
|
||||
|
||||
model->add_object(object_name.c_str(), path, std::move(mesh));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool load_obj(const char *path, Model *model, const char *object_name_in)
|
||||
{
|
||||
TriangleMesh mesh;
|
||||
|
||||
bool ret = load_obj(path, &mesh);
|
||||
|
||||
if (ret) {
|
||||
std::string object_name;
|
||||
if (object_name_in == nullptr) {
|
||||
const char *last_slash = strrchr(path, DIR_SEPARATOR);
|
||||
object_name.assign((last_slash == nullptr) ? path : last_slash + 1);
|
||||
} else
|
||||
object_name.assign(object_name_in);
|
||||
|
||||
model->add_object(object_name.c_str(), path, std::move(mesh));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool store_obj(const char *path, TriangleMesh *mesh)
|
||||
{
|
||||
//FIXME returning false even if write failed.
|
||||
|
|
|
@ -5,8 +5,10 @@ namespace Slic3r {
|
|||
|
||||
class TriangleMesh;
|
||||
class Model;
|
||||
class ModelObject;
|
||||
|
||||
// Load an OBJ file into a provided model.
|
||||
extern bool load_obj(const char *path, TriangleMesh *mesh);
|
||||
extern bool load_obj(const char *path, Model *model, const char *object_name = nullptr);
|
||||
|
||||
extern bool store_obj(const char *path, TriangleMesh *mesh);
|
||||
|
|
|
@ -117,11 +117,11 @@ Polygons AvoidCrossingPerimeters::collect_contours_all_layers(const PrintObjectP
|
|||
const Layer* layer1 = object->layers()[i * 2];
|
||||
const Layer* layer2 = object->layers()[i * 2 + 1];
|
||||
Polygons polys;
|
||||
polys.reserve(layer1->slices.expolygons.size() + layer2->slices.expolygons.size());
|
||||
for (const ExPolygon &expoly : layer1->slices.expolygons)
|
||||
polys.reserve(layer1->slices.size() + layer2->slices.size());
|
||||
for (const ExPolygon &expoly : layer1->slices)
|
||||
//FIXME no holes?
|
||||
polys.emplace_back(expoly.contour);
|
||||
for (const ExPolygon &expoly : layer2->slices.expolygons)
|
||||
for (const ExPolygon &expoly : layer2->slices)
|
||||
//FIXME no holes?
|
||||
polys.emplace_back(expoly.contour);
|
||||
polygons_per_layer[i] = union_(polys);
|
||||
|
@ -130,8 +130,8 @@ Polygons AvoidCrossingPerimeters::collect_contours_all_layers(const PrintObjectP
|
|||
if (object->layers().size() & 1) {
|
||||
const Layer *layer = object->layers().back();
|
||||
Polygons polys;
|
||||
polys.reserve(layer->slices.expolygons.size());
|
||||
for (const ExPolygon &expoly : layer->slices.expolygons)
|
||||
polys.reserve(layer->slices.size());
|
||||
for (const ExPolygon &expoly : layer->slices)
|
||||
//FIXME no holes?
|
||||
polys.emplace_back(expoly.contour);
|
||||
polygons_per_layer.back() = union_(polys);
|
||||
|
@ -1802,11 +1802,8 @@ void GCode::process_layer(
|
|||
// - for each island, we extrude perimeters first, unless user set the infill_first
|
||||
// option
|
||||
// (Still, we have to keep track of regions because we need to apply their config)
|
||||
size_t n_slices = layer.slices.expolygons.size();
|
||||
std::vector<BoundingBox> layer_surface_bboxes;
|
||||
layer_surface_bboxes.reserve(n_slices);
|
||||
for (const ExPolygon &expoly : layer.slices.expolygons)
|
||||
layer_surface_bboxes.push_back(get_extents(expoly.contour));
|
||||
size_t n_slices = layer.slices.size();
|
||||
const std::vector<BoundingBox> &layer_surface_bboxes = layer.slices_bboxes;
|
||||
// Traverse the slices in an increasing order of bounding box size, so that the islands inside another islands are tested first,
|
||||
// so we can just test a point inside ExPolygon::contour and we may skip testing the holes.
|
||||
std::vector<size_t> slices_test_order;
|
||||
|
@ -1822,7 +1819,7 @@ void GCode::process_layer(
|
|||
const BoundingBox &bbox = layer_surface_bboxes[i];
|
||||
return point(0) >= bbox.min(0) && point(0) < bbox.max(0) &&
|
||||
point(1) >= bbox.min(1) && point(1) < bbox.max(1) &&
|
||||
layer.slices.expolygons[i].contour.contains(point);
|
||||
layer.slices[i].contour.contains(point);
|
||||
};
|
||||
|
||||
for (size_t region_id = 0; region_id < print.regions().size(); ++ region_id) {
|
||||
|
@ -2418,7 +2415,7 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou
|
|||
static int iRun = 0;
|
||||
SVG svg(debug_out_path("GCode_extrude_loop-%d.svg", iRun ++));
|
||||
if (m_layer->lower_layer != NULL)
|
||||
svg.draw(m_layer->lower_layer->slices.expolygons);
|
||||
svg.draw(m_layer->lower_layer->slices);
|
||||
for (size_t i = 0; i < loop.paths.size(); ++ i)
|
||||
svg.draw(loop.paths[i].as_polyline(), "red");
|
||||
Polylines polylines;
|
||||
|
|
|
@ -866,7 +866,7 @@ void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& previ
|
|||
}
|
||||
|
||||
// if layer not found, create and return it
|
||||
layers.emplace_back(z, ExtrusionPaths());
|
||||
layers.emplace_back(z, GCodePreviewData::Extrusion::Paths());
|
||||
return layers.back();
|
||||
}
|
||||
|
||||
|
@ -875,14 +875,18 @@ void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& previ
|
|||
// if the polyline is valid, create the extrusion path from it and store it
|
||||
if (polyline.is_valid())
|
||||
{
|
||||
ExtrusionPath path(data.extrusion_role, data.mm3_per_mm, data.width, data.height);
|
||||
auto& paths = get_layer_at_z(preview_data.extrusion.layers, z).paths;
|
||||
paths.emplace_back(GCodePreviewData::Extrusion::Path());
|
||||
GCodePreviewData::Extrusion::Path &path = paths.back();
|
||||
path.polyline = polyline;
|
||||
path.extrusion_role = data.extrusion_role;
|
||||
path.mm3_per_mm = data.mm3_per_mm;
|
||||
path.width = data.width;
|
||||
path.height = data.height;
|
||||
path.feedrate = data.feedrate;
|
||||
path.extruder_id = data.extruder_id;
|
||||
path.fan_speed = data.fan_speed;
|
||||
path.cp_color_id = data.cp_color_id;
|
||||
|
||||
get_layer_at_z(preview_data.extrusion.layers, z).paths.push_back(path);
|
||||
path.fan_speed = data.fan_speed;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -23,7 +23,7 @@ std::vector<unsigned char> GCodePreviewData::Color::as_bytes() const
|
|||
return ret;
|
||||
}
|
||||
|
||||
GCodePreviewData::Extrusion::Layer::Layer(float z, const ExtrusionPaths& paths)
|
||||
GCodePreviewData::Extrusion::Layer::Layer(float z, const Paths& paths)
|
||||
: z(z)
|
||||
, paths(paths)
|
||||
{
|
||||
|
@ -171,8 +171,8 @@ size_t GCodePreviewData::Extrusion::memory_used() const
|
|||
size_t out = sizeof(*this);
|
||||
out += SLIC3R_STDVEC_MEMSIZE(this->layers, Layer);
|
||||
for (const Layer &layer : this->layers) {
|
||||
out += SLIC3R_STDVEC_MEMSIZE(layer.paths, ExtrusionPath);
|
||||
for (const ExtrusionPath &path : layer.paths)
|
||||
out += SLIC3R_STDVEC_MEMSIZE(layer.paths, Path);
|
||||
for (const Path &path : layer.paths)
|
||||
out += SLIC3R_STDVEC_MEMSIZE(path.polyline.points, Point);
|
||||
}
|
||||
return out;
|
||||
|
|
|
@ -87,12 +87,34 @@ public:
|
|||
static const std::string Default_Extrusion_Role_Names[erCount];
|
||||
static const EViewType Default_View_Type;
|
||||
|
||||
class Path
|
||||
{
|
||||
public:
|
||||
Polyline polyline;
|
||||
ExtrusionRole extrusion_role;
|
||||
// Volumetric velocity. mm^3 of plastic per mm of linear head motion. Used by the G-code generator.
|
||||
float mm3_per_mm;
|
||||
// Width of the extrusion, used for visualization purposes.
|
||||
float width;
|
||||
// Height of the extrusion, used for visualization purposes.
|
||||
float height;
|
||||
// Feedrate of the extrusion, used for visualization purposes.
|
||||
float feedrate;
|
||||
// Id of the extruder, used for visualization purposes.
|
||||
uint32_t extruder_id;
|
||||
// Id of the color, used for visualization purposes in the color printing case.
|
||||
uint32_t cp_color_id;
|
||||
// Fan speed for the extrusion, used for visualization purposes.
|
||||
float fan_speed;
|
||||
};
|
||||
using Paths = std::vector<Path>;
|
||||
|
||||
struct Layer
|
||||
{
|
||||
float z;
|
||||
ExtrusionPaths paths;
|
||||
Paths paths;
|
||||
|
||||
Layer(float z, const ExtrusionPaths& paths);
|
||||
Layer(float z, const Paths& paths);
|
||||
};
|
||||
|
||||
typedef std::vector<Layer> LayersList;
|
||||
|
|
|
@ -14,6 +14,11 @@
|
|||
using boost::polygon::voronoi_builder;
|
||||
using boost::polygon::voronoi_diagram;
|
||||
|
||||
namespace ClipperLib {
|
||||
class PolyNode;
|
||||
using PolyNodes = std::vector<PolyNode*>;
|
||||
}
|
||||
|
||||
namespace Slic3r { namespace Geometry {
|
||||
|
||||
// Generic result of an orientation predicate.
|
||||
|
|
|
@ -47,8 +47,8 @@ void Layer::make_slices()
|
|||
slices = union_ex(slices_p);
|
||||
}
|
||||
|
||||
this->slices.expolygons.clear();
|
||||
this->slices.expolygons.reserve(slices.size());
|
||||
this->slices.clear();
|
||||
this->slices.reserve(slices.size());
|
||||
|
||||
// prepare ordering points
|
||||
Points ordering_points;
|
||||
|
@ -61,7 +61,7 @@ void Layer::make_slices()
|
|||
|
||||
// populate slices vector
|
||||
for (size_t i : order)
|
||||
this->slices.expolygons.push_back(std::move(slices[i]));
|
||||
this->slices.push_back(std::move(slices[i]));
|
||||
}
|
||||
|
||||
// Merge typed slices into untyped slices. This method is used to revert the effects of detect_surfaces_type() called for posPrepareInfill.
|
||||
|
@ -70,7 +70,7 @@ void Layer::merge_slices()
|
|||
if (m_regions.size() == 1) {
|
||||
// Optimization, also more robust. Don't merge classified pieces of layerm->slices,
|
||||
// but use the non-split islands of a layer. For a single region print, these shall be equal.
|
||||
m_regions.front()->slices.set(this->slices.expolygons, stInternal);
|
||||
m_regions.front()->slices.set(this->slices, stInternal);
|
||||
} else {
|
||||
for (LayerRegion *layerm : m_regions)
|
||||
// without safety offset, artifacts are generated (GH #2494)
|
||||
|
|
|
@ -110,7 +110,8 @@ public:
|
|||
// also known as 'islands' (all regions and surface types are merged here)
|
||||
// The slices are chained by the shortest traverse distance and this traversal
|
||||
// order will be recovered by the G-code generator.
|
||||
ExPolygonCollection slices;
|
||||
ExPolygons slices;
|
||||
std::vector<BoundingBox> slices_bboxes;
|
||||
|
||||
size_t region_count() const { return m_regions.size(); }
|
||||
const LayerRegion* get_region(int idx) const { return m_regions.at(idx); }
|
||||
|
|
|
@ -140,7 +140,7 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
|
|||
// Remove voids from fill_boundaries, that are not supported by the layer below.
|
||||
if (lower_layer_covered == nullptr) {
|
||||
lower_layer_covered = &lower_layer_covered_tmp;
|
||||
lower_layer_covered_tmp = to_polygons(lower_layer->slices.expolygons);
|
||||
lower_layer_covered_tmp = to_polygons(lower_layer->slices);
|
||||
}
|
||||
if (! lower_layer_covered->empty())
|
||||
voids = diff(voids, *lower_layer_covered);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -136,11 +136,11 @@ Polyline MotionPlanner::shortest_path(const Point &from, const Point &to)
|
|||
if (! grown_env.contains(from)) {
|
||||
// delete second point while the line connecting first to third crosses the
|
||||
// boundaries as many times as the current first to second
|
||||
while (polyline.points.size() > 2 && intersection_ln(Line(from, polyline.points[2]), grown_env).size() == 1)
|
||||
while (polyline.points.size() > 2 && intersection_ln(Line(from, polyline.points[2]), (Polygons)grown_env).size() == 1)
|
||||
polyline.points.erase(polyline.points.begin() + 1);
|
||||
}
|
||||
if (! grown_env.contains(to))
|
||||
while (polyline.points.size() > 2 && intersection_ln(Line(*(polyline.points.end() - 3), to), grown_env).size() == 1)
|
||||
while (polyline.points.size() > 2 && intersection_ln(Line(*(polyline.points.end() - 3), to), (Polygons)grown_env).size() == 1)
|
||||
polyline.points.erase(polyline.points.end() - 2);
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
#include "libslic3r.h"
|
||||
#include <vector>
|
||||
#include "ExPolygonCollection.hpp"
|
||||
#include "Flow.hpp"
|
||||
#include "Polygon.hpp"
|
||||
#include "PrintConfig.hpp"
|
||||
|
@ -15,7 +14,7 @@ class PerimeterGenerator {
|
|||
public:
|
||||
// Inputs:
|
||||
const SurfaceCollection *slices;
|
||||
const ExPolygonCollection *lower_slices;
|
||||
const ExPolygons *lower_slices;
|
||||
double layer_height;
|
||||
int layer_id;
|
||||
Flow perimeter_flow;
|
||||
|
@ -45,7 +44,7 @@ public:
|
|||
ExtrusionEntityCollection* gap_fill,
|
||||
// Infills without the gap fills
|
||||
SurfaceCollection* fill_surfaces)
|
||||
: slices(slices), lower_slices(NULL), layer_height(layer_height),
|
||||
: slices(slices), lower_slices(nullptr), layer_height(layer_height),
|
||||
layer_id(-1), perimeter_flow(flow), ext_perimeter_flow(flow),
|
||||
overhang_flow(flow), solid_infill_flow(flow),
|
||||
config(config), object_config(object_config), print_config(print_config),
|
||||
|
|
|
@ -328,17 +328,6 @@ unsigned int Print::num_object_instances() const
|
|||
return instances;
|
||||
}
|
||||
|
||||
void Print::_simplify_slices(double distance)
|
||||
{
|
||||
for (PrintObject *object : m_objects) {
|
||||
for (Layer *layer : object->m_layers) {
|
||||
layer->slices.simplify(distance);
|
||||
for (LayerRegion *layerm : layer->regions())
|
||||
layerm->slices.simplify(distance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double Print::max_allowed_layer_height() const
|
||||
{
|
||||
double nozzle_diameter_max = 0.;
|
||||
|
@ -1114,6 +1103,9 @@ std::string Print::validate() const
|
|||
if (m_objects.empty())
|
||||
return L("All objects are outside of the print volume.");
|
||||
|
||||
if (extruders().empty())
|
||||
return L("The supplied settings will cause an empty print.");
|
||||
|
||||
if (m_config.complete_objects) {
|
||||
// Check horizontal clearance.
|
||||
{
|
||||
|
@ -1271,10 +1263,7 @@ std::string Print::validate() const
|
|||
}
|
||||
|
||||
{
|
||||
// find the smallest nozzle diameter
|
||||
std::vector<unsigned int> extruders = this->extruders();
|
||||
if (extruders.empty())
|
||||
return L("The supplied settings will cause an empty print.");
|
||||
|
||||
// Find the smallest used nozzle diameter and the number of unique nozzle diameters.
|
||||
double min_nozzle_diameter = std::numeric_limits<double>::max();
|
||||
|
@ -1593,7 +1582,7 @@ void Print::_make_skirt()
|
|||
for (const Layer *layer : object->m_layers) {
|
||||
if (layer->print_z > skirt_height_z)
|
||||
break;
|
||||
for (const ExPolygon &expoly : layer->slices.expolygons)
|
||||
for (const ExPolygon &expoly : layer->slices)
|
||||
// Collect the outer contour points only, ignore holes for the calculation of the convex hull.
|
||||
append(object_points, expoly.contour.points);
|
||||
}
|
||||
|
@ -1704,7 +1693,7 @@ void Print::_make_brim()
|
|||
Polygons islands;
|
||||
for (PrintObject *object : m_objects) {
|
||||
Polygons object_islands;
|
||||
for (ExPolygon &expoly : object->m_layers.front()->slices.expolygons)
|
||||
for (ExPolygon &expoly : object->m_layers.front()->slices)
|
||||
object_islands.push_back(expoly.contour);
|
||||
if (! object->support_layers().empty())
|
||||
object->support_layers().front()->support_fills.polygons_covered_by_spacing(object_islands, float(SCALED_EPSILON));
|
||||
|
|
|
@ -180,7 +180,6 @@ private:
|
|||
void _slice(const std::vector<coordf_t> &layer_height_profile);
|
||||
std::string _fix_slicing_errors();
|
||||
void _simplify_slices(double distance);
|
||||
void _make_perimeters();
|
||||
bool has_support_material() const;
|
||||
void detect_surfaces_type();
|
||||
void process_external_surfaces();
|
||||
|
@ -383,7 +382,6 @@ private:
|
|||
void _make_skirt();
|
||||
void _make_brim();
|
||||
void _make_wipe_tower();
|
||||
void _simplify_slices(double distance);
|
||||
|
||||
// Declared here to have access to Model / ModelObject / ModelInstance
|
||||
static void model_volume_list_update_supports(ModelObject &model_object_dst, const ModelObject &model_object_src);
|
||||
|
|
|
@ -2396,6 +2396,7 @@ void PrintConfigDef::init_sla_params()
|
|||
"the threshold in the middle. This behaviour eliminates "
|
||||
"antialiasing without losing holes in polygons.");
|
||||
def->min = 0;
|
||||
def->max = 1;
|
||||
def->mode = comExpert;
|
||||
def->set_default_value(new ConfigOptionFloat(1.0));
|
||||
|
||||
|
@ -2713,6 +2714,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");
|
||||
|
@ -2753,6 +2765,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");
|
||||
|
|
|
@ -1030,6 +1030,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.
|
||||
|
@ -1050,7 +1053,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;
|
||||
|
@ -1090,10 +1095,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);
|
||||
|
|
|
@ -117,6 +117,19 @@ void PrintObject::slice()
|
|||
// Simplify slices if required.
|
||||
if (m_print->config().resolution)
|
||||
this->_simplify_slices(scale_(this->print()->config().resolution));
|
||||
// Update bounding boxes
|
||||
tbb::parallel_for(
|
||||
tbb::blocked_range<size_t>(0, m_layers.size()),
|
||||
[this](const tbb::blocked_range<size_t>& range) {
|
||||
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
|
||||
m_print->throw_if_canceled();
|
||||
Layer &layer = *m_layers[layer_idx];
|
||||
layer.slices_bboxes.clear();
|
||||
layer.slices_bboxes.reserve(layer.slices.size());
|
||||
for (const ExPolygon &expoly : layer.slices)
|
||||
layer.slices_bboxes.emplace_back(get_extents(expoly));
|
||||
}
|
||||
});
|
||||
if (m_layers.empty())
|
||||
throw std::runtime_error("No layers were detected. You might want to repair your STL file(s) or check their size or thickness and retry.\n");
|
||||
this->set_done(posSlice);
|
||||
|
@ -865,7 +878,7 @@ void PrintObject::process_external_surfaces()
|
|||
// Shrink the holes, let the layer above expand slightly inside the unsupported areas.
|
||||
polygons_append(voids, offset(surface.expolygon, unsupported_width));
|
||||
}
|
||||
surfaces_covered[layer_idx] = diff(to_polygons(this->m_layers[layer_idx]->slices.expolygons), voids);
|
||||
surfaces_covered[layer_idx] = diff(to_polygons(this->m_layers[layer_idx]->slices), voids);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
@ -975,8 +988,8 @@ void PrintObject::discover_vertical_shells()
|
|||
polygons_append(cache.holes, offset(offset_ex(layer.slices, 0.3f * perimeter_min_spacing), - perimeter_offset - 0.3f * perimeter_min_spacing));
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
{
|
||||
Slic3r::SVG svg(debug_out_path("discover_vertical_shells-extra-holes-%d.svg", debug_idx), get_extents(layer.slices.expolygons));
|
||||
svg.draw(layer.slices.expolygons, "blue");
|
||||
Slic3r::SVG svg(debug_out_path("discover_vertical_shells-extra-holes-%d.svg", debug_idx), get_extents(layer.slices));
|
||||
svg.draw(layer.slices, "blue");
|
||||
svg.draw(union_ex(cache.holes), "red");
|
||||
svg.draw_outline(union_ex(cache.holes), "black", "blue", scale_(0.05));
|
||||
svg.Close();
|
||||
|
@ -1659,25 +1672,26 @@ void PrintObject::_slice(const std::vector<coordf_t> &layer_height_profile)
|
|||
// Trim volumes in a single layer, one by the other, possibly apply upscaling.
|
||||
{
|
||||
Polygons processed;
|
||||
for (SlicedVolume &sliced_volume : sliced_volumes) {
|
||||
ExPolygons slices = std::move(sliced_volume.expolygons_by_layer[layer_id]);
|
||||
if (upscale)
|
||||
slices = offset_ex(std::move(slices), delta);
|
||||
if (! processed.empty())
|
||||
// Trim by the slices of already processed regions.
|
||||
slices = diff_ex(to_polygons(std::move(slices)), processed);
|
||||
if (size_t(&sliced_volume - &sliced_volumes.front()) + 1 < sliced_volumes.size())
|
||||
// Collect the already processed regions to trim the to be processed regions.
|
||||
polygons_append(processed, slices);
|
||||
sliced_volume.expolygons_by_layer[layer_id] = std::move(slices);
|
||||
}
|
||||
for (SlicedVolume &sliced_volume : sliced_volumes)
|
||||
if (! sliced_volume.expolygons_by_layer.empty()) {
|
||||
ExPolygons slices = std::move(sliced_volume.expolygons_by_layer[layer_id]);
|
||||
if (upscale)
|
||||
slices = offset_ex(std::move(slices), delta);
|
||||
if (! processed.empty())
|
||||
// Trim by the slices of already processed regions.
|
||||
slices = diff_ex(to_polygons(std::move(slices)), processed);
|
||||
if (size_t(&sliced_volume - &sliced_volumes.front()) + 1 < sliced_volumes.size())
|
||||
// Collect the already processed regions to trim the to be processed regions.
|
||||
polygons_append(processed, slices);
|
||||
sliced_volume.expolygons_by_layer[layer_id] = std::move(slices);
|
||||
}
|
||||
}
|
||||
// Collect and union volumes of a single region.
|
||||
for (int region_id = 0; region_id < (int)this->region_volumes.size(); ++ region_id) {
|
||||
ExPolygons expolygons;
|
||||
size_t num_volumes = 0;
|
||||
for (SlicedVolume &sliced_volume : sliced_volumes)
|
||||
if (sliced_volume.region_id == region_id && ! sliced_volume.expolygons_by_layer[layer_id].empty()) {
|
||||
if (sliced_volume.region_id == region_id && ! sliced_volume.expolygons_by_layer.empty() && ! sliced_volume.expolygons_by_layer[layer_id].empty()) {
|
||||
++ num_volumes;
|
||||
append(expolygons, std::move(sliced_volume.expolygons_by_layer[layer_id]));
|
||||
}
|
||||
|
@ -2140,7 +2154,7 @@ std::string PrintObject::_fix_slicing_errors()
|
|||
BOOST_LOG_TRIVIAL(debug) << "Slicing objects - fixing slicing errors in parallel - end";
|
||||
|
||||
// remove empty layers from bottom
|
||||
while (! m_layers.empty() && m_layers.front()->slices.expolygons.empty()) {
|
||||
while (! m_layers.empty() && m_layers.front()->slices.empty()) {
|
||||
delete m_layers.front();
|
||||
m_layers.erase(m_layers.begin());
|
||||
m_layers.front()->lower_layer = nullptr;
|
||||
|
@ -2167,115 +2181,17 @@ void PrintObject::_simplify_slices(double distance)
|
|||
Layer *layer = m_layers[layer_idx];
|
||||
for (size_t region_idx = 0; region_idx < layer->m_regions.size(); ++ region_idx)
|
||||
layer->m_regions[region_idx]->slices.simplify(distance);
|
||||
layer->slices.simplify(distance);
|
||||
{
|
||||
ExPolygons simplified;
|
||||
for (const ExPolygon& expoly : layer->slices)
|
||||
expoly.simplify(distance, &simplified);
|
||||
layer->slices = std::move(simplified);
|
||||
}
|
||||
}
|
||||
});
|
||||
BOOST_LOG_TRIVIAL(debug) << "Slicing objects - siplifying slices in parallel - end";
|
||||
}
|
||||
|
||||
void PrintObject::_make_perimeters()
|
||||
{
|
||||
if (! this->set_started(posPerimeters))
|
||||
return;
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "Generating perimeters..." << log_memory_info();
|
||||
|
||||
// merge slices if they were split into types
|
||||
if (this->typed_slices) {
|
||||
for (Layer *layer : m_layers)
|
||||
layer->merge_slices();
|
||||
this->typed_slices = false;
|
||||
this->invalidate_step(posPrepareInfill);
|
||||
}
|
||||
|
||||
// compare each layer to the one below, and mark those slices needing
|
||||
// one additional inner perimeter, like the top of domed objects-
|
||||
|
||||
// this algorithm makes sure that at least one perimeter is overlapping
|
||||
// but we don't generate any extra perimeter if fill density is zero, as they would be floating
|
||||
// inside the object - infill_only_where_needed should be the method of choice for printing
|
||||
// hollow objects
|
||||
for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) {
|
||||
const PrintRegion ®ion = *m_print->regions()[region_id];
|
||||
if (! region.config().extra_perimeters || region.config().perimeters == 0 || region.config().fill_density == 0 || this->layer_count() < 2)
|
||||
continue;
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "Generating extra perimeters for region " << region_id << " in parallel - start";
|
||||
tbb::parallel_for(
|
||||
tbb::blocked_range<size_t>(0, m_layers.size() - 1),
|
||||
[this, ®ion, region_id](const tbb::blocked_range<size_t>& range) {
|
||||
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
|
||||
LayerRegion &layerm = *m_layers[layer_idx]->regions()[region_id];
|
||||
const LayerRegion &upper_layerm = *m_layers[layer_idx+1]->regions()[region_id];
|
||||
const Polygons upper_layerm_polygons = upper_layerm.slices;
|
||||
// Filter upper layer polygons in intersection_ppl by their bounding boxes?
|
||||
// my $upper_layerm_poly_bboxes= [ map $_->bounding_box, @{$upper_layerm_polygons} ];
|
||||
const double total_loop_length = total_length(upper_layerm_polygons);
|
||||
const coord_t perimeter_spacing = layerm.flow(frPerimeter).scaled_spacing();
|
||||
const Flow ext_perimeter_flow = layerm.flow(frExternalPerimeter);
|
||||
const coord_t ext_perimeter_width = ext_perimeter_flow.scaled_width();
|
||||
const coord_t ext_perimeter_spacing = ext_perimeter_flow.scaled_spacing();
|
||||
|
||||
for (Surface &slice : layerm.slices.surfaces) {
|
||||
for (;;) {
|
||||
// compute the total thickness of perimeters
|
||||
const coord_t perimeters_thickness = ext_perimeter_width/2 + ext_perimeter_spacing/2
|
||||
+ (region.config().perimeters-1 + slice.extra_perimeters) * perimeter_spacing;
|
||||
// define a critical area where we don't want the upper slice to fall into
|
||||
// (it should either lay over our perimeters or outside this area)
|
||||
const coord_t critical_area_depth = coord_t(perimeter_spacing * 1.5);
|
||||
const Polygons critical_area = diff(
|
||||
offset(slice.expolygon, float(- perimeters_thickness)),
|
||||
offset(slice.expolygon, float(- perimeters_thickness - critical_area_depth))
|
||||
);
|
||||
// check whether a portion of the upper slices falls inside the critical area
|
||||
const Polylines intersection = intersection_pl(to_polylines(upper_layerm_polygons), critical_area);
|
||||
// only add an additional loop if at least 30% of the slice loop would benefit from it
|
||||
if (total_length(intersection) <= total_loop_length*0.3)
|
||||
break;
|
||||
/*
|
||||
if (0) {
|
||||
require "Slic3r/SVG.pm";
|
||||
Slic3r::SVG::output(
|
||||
"extra.svg",
|
||||
no_arrows => 1,
|
||||
expolygons => union_ex($critical_area),
|
||||
polylines => [ map $_->split_at_first_point, map $_->p, @{$upper_layerm->slices} ],
|
||||
);
|
||||
}
|
||||
*/
|
||||
++ slice.extra_perimeters;
|
||||
}
|
||||
#ifdef DEBUG
|
||||
if (slice.extra_perimeters > 0)
|
||||
printf(" adding %d more perimeter(s) at layer %zu\n", slice.extra_perimeters, layer_idx);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
});
|
||||
BOOST_LOG_TRIVIAL(debug) << "Generating extra perimeters for region " << region_id << " in parallel - end";
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "Generating perimeters in parallel - start";
|
||||
tbb::parallel_for(
|
||||
tbb::blocked_range<size_t>(0, m_layers.size()),
|
||||
[this](const tbb::blocked_range<size_t>& range) {
|
||||
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx)
|
||||
m_layers[layer_idx]->make_perimeters();
|
||||
}
|
||||
);
|
||||
BOOST_LOG_TRIVIAL(debug) << "Generating perimeters in parallel - end";
|
||||
|
||||
/*
|
||||
simplify slices (both layer and region slices),
|
||||
we only need the max resolution for perimeters
|
||||
### This makes this method not-idempotent, so we keep it disabled for now.
|
||||
###$self->_simplify_slices(&Slic3r::SCALED_RESOLUTION);
|
||||
*/
|
||||
|
||||
this->set_done(posPerimeters);
|
||||
}
|
||||
|
||||
// Only active if config->infill_only_where_needed. This step trims the sparse infill,
|
||||
// so it acts as an internal support. It maintains all other infill types intact.
|
||||
// Here the internal surfaces and perimeters have to be supported by the sparse infill.
|
||||
|
@ -2301,7 +2217,7 @@ void PrintObject::clip_fill_surfaces()
|
|||
// Detect things that we need to support.
|
||||
// Cummulative slices.
|
||||
Polygons slices;
|
||||
polygons_append(slices, layer->slices.expolygons);
|
||||
polygons_append(slices, layer->slices);
|
||||
// Cummulative fill surfaces.
|
||||
Polygons fill_surfaces;
|
||||
// Solid surfaces to be supported.
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
#ifndef SLACOMMON_HPP
|
||||
#define SLACOMMON_HPP
|
||||
|
||||
#include <Eigen/Geometry>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <Eigen/Geometry>
|
||||
|
||||
// #define SLIC3R_SLA_NEEDS_WINDTREE
|
||||
|
||||
|
@ -69,6 +70,8 @@ struct SupportPoint
|
|||
}
|
||||
};
|
||||
|
||||
using SupportPoints = std::vector<SupportPoint>;
|
||||
|
||||
/// An index-triangle structure for libIGL functions. Also serves as an
|
||||
/// alternative (raw) input format for the SLASupportTree
|
||||
class EigenMesh3D {
|
||||
|
@ -175,6 +178,8 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
using PointSet = Eigen::MatrixXd;
|
||||
|
||||
} // namespace sla
|
||||
} // namespace Slic3r
|
||||
|
||||
|
|
56
src/libslic3r/SLA/SLAConcurrency.hpp
Normal file
56
src/libslic3r/SLA/SLAConcurrency.hpp
Normal file
|
@ -0,0 +1,56 @@
|
|||
#ifndef SLACONCURRENCY_H
|
||||
#define SLACONCURRENCY_H
|
||||
|
||||
#include <tbb/spin_mutex.h>
|
||||
#include <tbb/mutex.h>
|
||||
#include <tbb/parallel_for.h>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace sla {
|
||||
|
||||
// Set this to true to enable full parallelism in this module.
|
||||
// Only the well tested parts will be concurrent if this is set to false.
|
||||
const constexpr bool USE_FULL_CONCURRENCY = true;
|
||||
|
||||
template<bool> struct _ccr {};
|
||||
|
||||
template<> struct _ccr<true>
|
||||
{
|
||||
using SpinningMutex = tbb::spin_mutex;
|
||||
using BlockingMutex = tbb::mutex;
|
||||
|
||||
template<class It, class Fn>
|
||||
static inline void enumerate(It from, It to, Fn fn)
|
||||
{
|
||||
auto iN = to - from;
|
||||
size_t N = iN < 0 ? 0 : size_t(iN);
|
||||
|
||||
tbb::parallel_for(size_t(0), N, [from, fn](size_t n) {
|
||||
fn(*(from + decltype(iN)(n)), n);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
template<> struct _ccr<false>
|
||||
{
|
||||
private:
|
||||
struct _Mtx { inline void lock() {} inline void unlock() {} };
|
||||
|
||||
public:
|
||||
using SpinningMutex = _Mtx;
|
||||
using BlockingMutex = _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, size_t(it - from));
|
||||
}
|
||||
};
|
||||
|
||||
using ccr = _ccr<USE_FULL_CONCURRENCY>;
|
||||
using ccr_seq = _ccr<false>;
|
||||
using ccr_par = _ccr<true>;
|
||||
|
||||
}} // namespace Slic3r::sla
|
||||
|
||||
#endif // SLACONCURRENCY_H
|
870
src/libslic3r/SLA/SLAPad.cpp
Normal file
870
src/libslic3r/SLA/SLAPad.cpp
Normal file
|
@ -0,0 +1,870 @@
|
|||
#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);
|
||||
}
|
||||
|
||||
template<class...Args>
|
||||
ExPolygons breakstick_holes(const ExPolygons &input, Args...args)
|
||||
{
|
||||
ExPolygons ret = input;
|
||||
for (ExPolygon &p : ret) {
|
||||
breakstick_holes(p.contour.points, args...);
|
||||
for (auto &h : p.holes) breakstick_holes(h.points, args...);
|
||||
}
|
||||
|
||||
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<float>(1.));
|
||||
|
||||
m_polys.emplace_back(r);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
ConcaveHull(const ExPolygons& polys, double merge_dist, ThrowOnCancel thr)
|
||||
: ConcaveHull{to_polygons(polys), merge_dist, thr} {}
|
||||
|
||||
ConcaveHull(const Polygons& polys, double mergedist, ThrowOnCancel thr)
|
||||
{
|
||||
if(polys.empty()) return;
|
||||
|
||||
m_polys = polys;
|
||||
merge_polygons();
|
||||
|
||||
if(m_polys.size() == 1) return;
|
||||
|
||||
Points centroids = calculate_centroids();
|
||||
|
||||
add_connector_rectangles(centroids, scaled(mergedist), thr);
|
||||
|
||||
merge_polygons();
|
||||
}
|
||||
|
||||
// const Polygons & polygons() const { return m_polys; }
|
||||
|
||||
ExPolygons to_expolygons() const
|
||||
{
|
||||
auto ret = reserve_vector<ExPolygon>(m_polys.size());
|
||||
for (const Polygon &p : m_polys) ret.emplace_back(ExPolygon(p));
|
||||
return ret;
|
||||
}
|
||||
|
||||
void offset_waffle_style(coord_t delta) {
|
||||
ClipperLib::Paths paths = Slic3rMultiPoints_to_ClipperPaths(m_polys);
|
||||
paths = fast_offset(paths, 2 * delta, ClipperLib::jtRound);
|
||||
paths = fast_offset(paths, -delta, ClipperLib::jtRound);
|
||||
m_polys = ClipperPaths_to_Slic3rPolygons(paths);
|
||||
}
|
||||
|
||||
static inline coord_t get_waffle_offset(const PadConfig &c)
|
||||
{
|
||||
return scaled(c.brim_size_mm + c.wing_distance());
|
||||
}
|
||||
|
||||
static inline double get_merge_distance(const PadConfig &c)
|
||||
{
|
||||
return 2. * (1.8 * c.wall_thickness_mm) + c.max_merge_dist_mm;
|
||||
}
|
||||
};
|
||||
|
||||
// Part of the pad configuration that is used for 3D geometry generation
|
||||
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);
|
||||
|
||||
auto model_bp_offs =
|
||||
offset_ex(model_blueprint,
|
||||
scaled<float>(cfg.embed_object.object_gap_mm),
|
||||
ClipperLib::jtMiter, 1);
|
||||
|
||||
ConcaveHull fullcvh =
|
||||
wafflized_concave_hull(support_blueprint, model_bp_offs, cfg, thr);
|
||||
|
||||
auto model_bp_sticks =
|
||||
breakstick_holes(model_bp_offs, cfg.embed_object.object_gap_mm,
|
||||
cfg.embed_object.stick_stride_mm,
|
||||
cfg.embed_object.stick_width_mm,
|
||||
cfg.embed_object.stick_penetration_mm);
|
||||
|
||||
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, float(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
|
||||
{
|
||||
static const double constexpr MIN_BRIM_SIZE_MM = .1;
|
||||
|
||||
if (brim_size_mm < MIN_BRIM_SIZE_MM ||
|
||||
bottom_offset() > brim_size_mm + wing_distance() ||
|
||||
ConcaveHull::get_waffle_offset(*this) <= MIN_BRIM_SIZE_MM)
|
||||
return L("Pad brim size is too small for the current configuration.");
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::sla
|
94
src/libslic3r/SLA/SLAPad.hpp
Normal file
94
src/libslic3r/SLA/SLAPad.hpp
Normal file
|
@ -0,0 +1,94 @@
|
|||
#ifndef SLABASEPOOL_HPP
|
||||
#define SLABASEPOOL_HPP
|
||||
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
|
||||
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
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
#include "SLARaster.hpp"
|
||||
#include "libslic3r/ExPolygon.hpp"
|
||||
#include "libslic3r/MTUtils.hpp"
|
||||
#include <libnest2d/backends/clipper/clipper_polygon.hpp>
|
||||
|
||||
// For rasterizing
|
||||
|
@ -32,25 +33,30 @@ inline const ClipperLib::Paths& holes(const ClipperLib::Polygon& p) { return p.H
|
|||
|
||||
namespace sla {
|
||||
|
||||
const Raster::TMirroring Raster::NoMirror = {false, false};
|
||||
const Raster::TMirroring Raster::MirrorX = {true, false};
|
||||
const Raster::TMirroring Raster::MirrorY = {false, true};
|
||||
const Raster::TMirroring Raster::MirrorXY = {true, true};
|
||||
|
||||
|
||||
using TPixelRenderer = agg::pixfmt_gray8; // agg::pixfmt_rgb24;
|
||||
using TRawRenderer = agg::renderer_base<TPixelRenderer>;
|
||||
using TPixel = TPixelRenderer::color_type;
|
||||
using TRawBuffer = agg::rendering_buffer;
|
||||
using TBuffer = std::vector<TPixelRenderer::pixel_type>;
|
||||
|
||||
using TRendererAA = agg::renderer_scanline_aa_solid<TRawRenderer>;
|
||||
|
||||
class Raster::Impl {
|
||||
public:
|
||||
using TPixelRenderer = agg::pixfmt_gray8; // agg::pixfmt_rgb24;
|
||||
using TRawRenderer = agg::renderer_base<TPixelRenderer>;
|
||||
using TPixel = TPixelRenderer::color_type;
|
||||
using TRawBuffer = agg::rendering_buffer;
|
||||
|
||||
using TBuffer = std::vector<TPixelRenderer::pixel_type>;
|
||||
|
||||
using TRendererAA = agg::renderer_scanline_aa_solid<TRawRenderer>;
|
||||
|
||||
static const TPixel ColorWhite;
|
||||
static const TPixel ColorBlack;
|
||||
|
||||
using Format = Raster::Format;
|
||||
using Format = Raster::RawData;
|
||||
|
||||
private:
|
||||
Raster::Resolution m_resolution;
|
||||
// Raster::PixelDim m_pxdim;
|
||||
Raster::PixelDim m_pxdim_scaled; // used for scaled coordinate polygons
|
||||
TBuffer m_buf;
|
||||
TRawBuffer m_rbuf;
|
||||
|
@ -59,74 +65,49 @@ private:
|
|||
TRendererAA m_renderer;
|
||||
|
||||
std::function<double(double)> m_gammafn;
|
||||
std::array<bool, 2> m_mirror;
|
||||
Format m_fmt = Format::PNG;
|
||||
Trafo m_trafo;
|
||||
|
||||
inline void flipy(agg::path_storage& path) const {
|
||||
path.flip_y(0, m_resolution.height_px);
|
||||
path.flip_y(0, double(m_resolution.height_px));
|
||||
}
|
||||
|
||||
inline void flipx(agg::path_storage& path) const {
|
||||
path.flip_x(0, m_resolution.width_px);
|
||||
path.flip_x(0, double(m_resolution.width_px));
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
inline Impl(const Raster::Resolution& res, const Raster::PixelDim &pd,
|
||||
const std::array<bool, 2>& mirror, double gamma = 1.0):
|
||||
m_resolution(res),
|
||||
// m_pxdim(pd),
|
||||
m_pxdim_scaled(SCALING_FACTOR / pd.w_mm, SCALING_FACTOR / pd.h_mm),
|
||||
m_buf(res.pixels()),
|
||||
m_rbuf(reinterpret_cast<TPixelRenderer::value_type*>(m_buf.data()),
|
||||
res.width_px, res.height_px,
|
||||
int(res.width_px*TPixelRenderer::num_components)),
|
||||
m_pixfmt(m_rbuf),
|
||||
m_raw_renderer(m_pixfmt),
|
||||
m_renderer(m_raw_renderer),
|
||||
m_mirror(mirror)
|
||||
inline Impl(const Raster::Resolution & res,
|
||||
const Raster::PixelDim & pd,
|
||||
const Trafo &trafo)
|
||||
: m_resolution(res)
|
||||
, m_pxdim_scaled(SCALING_FACTOR / pd.w_mm, SCALING_FACTOR / pd.h_mm)
|
||||
, m_buf(res.pixels())
|
||||
, m_rbuf(reinterpret_cast<TPixelRenderer::value_type *>(m_buf.data()),
|
||||
unsigned(res.width_px),
|
||||
unsigned(res.height_px),
|
||||
int(res.width_px * TPixelRenderer::num_components))
|
||||
, m_pixfmt(m_rbuf)
|
||||
, m_raw_renderer(m_pixfmt)
|
||||
, m_renderer(m_raw_renderer)
|
||||
, m_trafo(trafo)
|
||||
{
|
||||
m_renderer.color(ColorWhite);
|
||||
|
||||
if(gamma > 0) m_gammafn = agg::gamma_power(gamma);
|
||||
if (trafo.gamma > 0) m_gammafn = agg::gamma_power(trafo.gamma);
|
||||
else m_gammafn = agg::gamma_threshold(0.5);
|
||||
|
||||
clear();
|
||||
}
|
||||
|
||||
inline Impl(const Raster::Resolution& res,
|
||||
const Raster::PixelDim &pd,
|
||||
Format fmt,
|
||||
double gamma = 1.0):
|
||||
Impl(res, pd, {false, false}, gamma)
|
||||
{
|
||||
switch (fmt) {
|
||||
case Format::PNG: m_mirror = {false, true}; break;
|
||||
case Format::RAW: m_mirror = {false, false}; break;
|
||||
}
|
||||
m_fmt = fmt;
|
||||
}
|
||||
|
||||
template<class P> void draw(const P &poly) {
|
||||
agg::rasterizer_scanline_aa<> ras;
|
||||
agg::scanline_p8 scanlines;
|
||||
|
||||
ras.gamma(m_gammafn);
|
||||
|
||||
auto&& path = to_path(contour(poly));
|
||||
|
||||
if(m_mirror[X]) flipx(path);
|
||||
if(m_mirror[Y]) flipy(path);
|
||||
|
||||
ras.add_path(path);
|
||||
|
||||
for(auto& h : holes(poly)) {
|
||||
auto&& holepath = to_path(h);
|
||||
if(m_mirror[X]) flipx(holepath);
|
||||
if(m_mirror[Y]) flipy(holepath);
|
||||
ras.add_path(holepath);
|
||||
}
|
||||
|
||||
ras.add_path(to_path(contour(poly)));
|
||||
for(auto& h : holes(poly)) ras.add_path(to_path(h));
|
||||
|
||||
agg::render_scanlines(ras, scanlines, m_renderer);
|
||||
}
|
||||
|
||||
|
@ -135,11 +116,16 @@ public:
|
|||
}
|
||||
|
||||
inline TBuffer& buffer() { return m_buf; }
|
||||
inline const TBuffer& buffer() const { return m_buf; }
|
||||
|
||||
inline Format format() const { return m_fmt; }
|
||||
|
||||
inline const Raster::Resolution resolution() { return m_resolution; }
|
||||
|
||||
inline const Raster::PixelDim pixdim()
|
||||
{
|
||||
return {SCALING_FACTOR / m_pxdim_scaled.w_mm,
|
||||
SCALING_FACTOR / m_pxdim_scaled.h_mm};
|
||||
}
|
||||
|
||||
private:
|
||||
inline double getPx(const Point& p) {
|
||||
return p(0) * m_pxdim_scaled.w_mm;
|
||||
|
@ -162,49 +148,67 @@ private:
|
|||
return p.Y * m_pxdim_scaled.h_mm;
|
||||
}
|
||||
|
||||
template<class PointVec> agg::path_storage to_path(const PointVec& poly)
|
||||
template<class PointVec> agg::path_storage _to_path(const PointVec& v)
|
||||
{
|
||||
agg::path_storage path;
|
||||
|
||||
auto it = poly.begin();
|
||||
auto it = v.begin();
|
||||
path.move_to(getPx(*it), getPy(*it));
|
||||
while(++it != v.end()) path.line_to(getPx(*it), getPy(*it));
|
||||
path.line_to(getPx(v.front()), getPy(v.front()));
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
template<class PointVec> agg::path_storage _to_path_flpxy(const PointVec& v)
|
||||
{
|
||||
agg::path_storage path;
|
||||
|
||||
auto it = v.begin();
|
||||
path.move_to(getPy(*it), getPx(*it));
|
||||
while(++it != v.end()) path.line_to(getPy(*it), getPx(*it));
|
||||
path.line_to(getPy(v.front()), getPx(v.front()));
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
template<class PointVec> agg::path_storage to_path(const PointVec &v)
|
||||
{
|
||||
auto path = m_trafo.flipXY ? _to_path_flpxy(v) : _to_path(v);
|
||||
|
||||
path.translate_all_paths(m_trafo.origin_x * m_pxdim_scaled.w_mm,
|
||||
m_trafo.origin_y * m_pxdim_scaled.h_mm);
|
||||
|
||||
if(m_trafo.mirror_x) flipx(path);
|
||||
if(m_trafo.mirror_y) flipy(path);
|
||||
|
||||
while(++it != poly.end())
|
||||
path.line_to(getPx(*it), getPy(*it));
|
||||
|
||||
path.line_to(getPx(poly.front()), getPy(poly.front()));
|
||||
return path;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
const Raster::Impl::TPixel Raster::Impl::ColorWhite = Raster::Impl::TPixel(255);
|
||||
const Raster::Impl::TPixel Raster::Impl::ColorBlack = Raster::Impl::TPixel(0);
|
||||
const TPixel Raster::Impl::ColorWhite = TPixel(255);
|
||||
const TPixel Raster::Impl::ColorBlack = TPixel(0);
|
||||
|
||||
Raster::Raster() { reset(); }
|
||||
|
||||
Raster::Raster(const Raster::Resolution &r,
|
||||
const Raster::PixelDim & pd,
|
||||
const Raster::Trafo & tr)
|
||||
{
|
||||
reset(r, pd, tr);
|
||||
}
|
||||
|
||||
template<> Raster::Raster() { reset(); };
|
||||
Raster::~Raster() = default;
|
||||
|
||||
// Raster::Raster(Raster &&m) = default;
|
||||
// Raster& Raster::operator=(Raster&&) = default;
|
||||
|
||||
// FIXME: remove after migrating to higher version of windows compiler
|
||||
Raster::Raster(Raster &&m): m_impl(std::move(m.m_impl)) {}
|
||||
Raster& Raster::operator=(Raster &&m) {
|
||||
m_impl = std::move(m.m_impl); return *this;
|
||||
}
|
||||
Raster::Raster(Raster &&m) = default;
|
||||
Raster &Raster::operator=(Raster &&) = default;
|
||||
|
||||
void Raster::reset(const Raster::Resolution &r, const Raster::PixelDim &pd,
|
||||
Format fmt, double gamma)
|
||||
const Trafo &trafo)
|
||||
{
|
||||
m_impl.reset();
|
||||
m_impl.reset(new Impl(r, pd, fmt, gamma));
|
||||
}
|
||||
|
||||
void Raster::reset(const Raster::Resolution &r, const Raster::PixelDim &pd,
|
||||
const std::array<bool, 2>& mirror, double gamma)
|
||||
{
|
||||
m_impl.reset();
|
||||
m_impl.reset(new Impl(r, pd, mirror, gamma));
|
||||
m_impl.reset(new Impl(r, pd, trafo));
|
||||
}
|
||||
|
||||
void Raster::reset()
|
||||
|
@ -214,9 +218,16 @@ void Raster::reset()
|
|||
|
||||
Raster::Resolution Raster::resolution() const
|
||||
{
|
||||
if(m_impl) return m_impl->resolution();
|
||||
if (m_impl) return m_impl->resolution();
|
||||
|
||||
return Resolution{0, 0};
|
||||
}
|
||||
|
||||
return Resolution(0, 0);
|
||||
Raster::PixelDim Raster::pixel_dimensions() const
|
||||
{
|
||||
if (m_impl) return m_impl->pixdim();
|
||||
|
||||
return PixelDim{0., 0.};
|
||||
}
|
||||
|
||||
void Raster::clear()
|
||||
|
@ -227,103 +238,83 @@ void Raster::clear()
|
|||
|
||||
void Raster::draw(const ExPolygon &expoly)
|
||||
{
|
||||
assert(m_impl);
|
||||
m_impl->draw(expoly);
|
||||
}
|
||||
|
||||
void Raster::draw(const ClipperLib::Polygon &poly)
|
||||
{
|
||||
assert(m_impl);
|
||||
m_impl->draw(poly);
|
||||
}
|
||||
|
||||
void Raster::save(std::ostream& stream, Format fmt)
|
||||
uint8_t Raster::read_pixel(size_t x, size_t y) const
|
||||
{
|
||||
assert(m_impl);
|
||||
if(!stream.good()) return;
|
||||
|
||||
switch(fmt) {
|
||||
case Format::PNG: {
|
||||
auto& b = m_impl->buffer();
|
||||
size_t out_len = 0;
|
||||
void * rawdata = tdefl_write_image_to_png_file_in_memory(
|
||||
b.data(),
|
||||
int(resolution().width_px),
|
||||
int(resolution().height_px), 1, &out_len);
|
||||
|
||||
if(rawdata == nullptr) break;
|
||||
|
||||
stream.write(static_cast<const char*>(rawdata),
|
||||
std::streamsize(out_len));
|
||||
|
||||
MZ_FREE(rawdata);
|
||||
|
||||
break;
|
||||
}
|
||||
case Format::RAW: {
|
||||
stream << "P5 "
|
||||
<< m_impl->resolution().width_px << " "
|
||||
<< m_impl->resolution().height_px << " "
|
||||
<< "255 ";
|
||||
|
||||
auto sz = m_impl->buffer().size()*sizeof(Impl::TBuffer::value_type);
|
||||
stream.write(reinterpret_cast<const char*>(m_impl->buffer().data()),
|
||||
std::streamsize(sz));
|
||||
}
|
||||
}
|
||||
assert (m_impl);
|
||||
TPixel::value_type px;
|
||||
m_impl->buffer()[y * resolution().width_px + x].get(px);
|
||||
return px;
|
||||
}
|
||||
|
||||
void Raster::save(std::ostream &stream)
|
||||
PNGImage & PNGImage::serialize(const Raster &raster)
|
||||
{
|
||||
save(stream, m_impl->format());
|
||||
size_t s = 0;
|
||||
m_buffer.clear();
|
||||
|
||||
void *rawdata = tdefl_write_image_to_png_file_in_memory(
|
||||
get_internals(raster).buffer().data(),
|
||||
int(raster.resolution().width_px),
|
||||
int(raster.resolution().height_px), 1, &s);
|
||||
|
||||
// On error, data() will return an empty vector. No other info can be
|
||||
// retrieved from miniz anyway...
|
||||
if (rawdata == nullptr) return *this;
|
||||
|
||||
auto ptr = static_cast<std::uint8_t*>(rawdata);
|
||||
|
||||
m_buffer.reserve(s);
|
||||
std::copy(ptr, ptr + s, std::back_inserter(m_buffer));
|
||||
|
||||
MZ_FREE(rawdata);
|
||||
return *this;
|
||||
}
|
||||
|
||||
RawBytes Raster::save(Format fmt)
|
||||
std::ostream &operator<<(std::ostream &stream, const Raster::RawData &bytes)
|
||||
{
|
||||
assert(m_impl);
|
||||
|
||||
std::vector<std::uint8_t> data; size_t s = 0;
|
||||
|
||||
switch(fmt) {
|
||||
case Format::PNG: {
|
||||
void *rawdata = tdefl_write_image_to_png_file_in_memory(
|
||||
m_impl->buffer().data(),
|
||||
int(resolution().width_px),
|
||||
int(resolution().height_px), 1, &s);
|
||||
|
||||
if(rawdata == nullptr) break;
|
||||
auto ptr = static_cast<std::uint8_t*>(rawdata);
|
||||
|
||||
data.reserve(s); std::copy(ptr, ptr + s, std::back_inserter(data));
|
||||
|
||||
MZ_FREE(rawdata);
|
||||
break;
|
||||
}
|
||||
case Format::RAW: {
|
||||
auto header = std::string("P5 ") +
|
||||
std::to_string(m_impl->resolution().width_px) + " " +
|
||||
std::to_string(m_impl->resolution().height_px) + " " + "255 ";
|
||||
|
||||
auto sz = m_impl->buffer().size()*sizeof(Impl::TBuffer::value_type);
|
||||
s = sz + header.size();
|
||||
|
||||
data.reserve(s);
|
||||
|
||||
auto buff = reinterpret_cast<std::uint8_t*>(m_impl->buffer().data());
|
||||
std::copy(header.begin(), header.end(), std::back_inserter(data));
|
||||
std::copy(buff, buff+sz, std::back_inserter(data));
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return {std::move(data)};
|
||||
stream.write(reinterpret_cast<const char *>(bytes.data()),
|
||||
std::streamsize(bytes.size()));
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
RawBytes Raster::save()
|
||||
Raster::RawData::~RawData() = default;
|
||||
|
||||
PPMImage & PPMImage::serialize(const Raster &raster)
|
||||
{
|
||||
return save(m_impl->format());
|
||||
auto header = std::string("P5 ") +
|
||||
std::to_string(raster.resolution().width_px) + " " +
|
||||
std::to_string(raster.resolution().height_px) + " " + "255 ";
|
||||
|
||||
const auto &impl = get_internals(raster);
|
||||
auto sz = impl.buffer().size() * sizeof(TBuffer::value_type);
|
||||
size_t s = sz + header.size();
|
||||
|
||||
m_buffer.clear();
|
||||
m_buffer.reserve(s);
|
||||
|
||||
auto buff = reinterpret_cast<const std::uint8_t*>(impl.buffer().data());
|
||||
std::copy(header.begin(), header.end(), std::back_inserter(m_buffer));
|
||||
std::copy(buff, buff+sz, std::back_inserter(m_buffer));
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
const Raster::Impl &Raster::RawData::get_internals(const Raster &raster)
|
||||
{
|
||||
return *raster.m_impl;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace sla
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // SLARASTER_CPP
|
||||
|
|
|
@ -8,45 +8,13 @@
|
|||
#include <utility>
|
||||
#include <cstdint>
|
||||
|
||||
#include <libslic3r/ExPolygon.hpp>
|
||||
|
||||
namespace ClipperLib { struct Polygon; }
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class ExPolygon;
|
||||
|
||||
namespace Slic3r {
|
||||
namespace sla {
|
||||
|
||||
// Raw byte buffer paired with its size. Suitable for compressed PNG data.
|
||||
class RawBytes {
|
||||
|
||||
std::vector<std::uint8_t> m_buffer;
|
||||
public:
|
||||
|
||||
RawBytes() = default;
|
||||
RawBytes(std::vector<std::uint8_t>&& data): m_buffer(std::move(data)) {}
|
||||
|
||||
size_t size() const { return m_buffer.size(); }
|
||||
const uint8_t * data() { return m_buffer.data(); }
|
||||
|
||||
RawBytes(const RawBytes&) = delete;
|
||||
RawBytes& operator=(const RawBytes&) = delete;
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
// FIXME: the following is needed for MSVC2013 compatibility
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// RawBytes(RawBytes&&) = default;
|
||||
// RawBytes& operator=(RawBytes&&) = default;
|
||||
|
||||
RawBytes(RawBytes&& mv) : m_buffer(std::move(mv.m_buffer)) {}
|
||||
RawBytes& operator=(RawBytes&& mv) {
|
||||
m_buffer = std::move(mv.m_buffer);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Raster captures an anti-aliased monochrome canvas where vectorial
|
||||
* polygons can be rasterized. Fill color is always white and the background is
|
||||
|
@ -60,10 +28,28 @@ class Raster {
|
|||
std::unique_ptr<Impl> m_impl;
|
||||
public:
|
||||
|
||||
/// Supported compression types
|
||||
enum class Format {
|
||||
RAW, //!> Uncompressed pixel data
|
||||
PNG //!> PNG compression
|
||||
// Raw byte buffer paired with its size. Suitable for compressed image data.
|
||||
class RawData
|
||||
{
|
||||
protected:
|
||||
std::vector<std::uint8_t> m_buffer;
|
||||
const Impl& get_internals(const Raster& raster);
|
||||
public:
|
||||
RawData() = default;
|
||||
RawData(std::vector<std::uint8_t>&& data): m_buffer(std::move(data)) {}
|
||||
virtual ~RawData();
|
||||
|
||||
RawData(const RawData &) = delete;
|
||||
RawData &operator=(const RawData &) = delete;
|
||||
|
||||
RawData(RawData &&) = default;
|
||||
RawData &operator=(RawData &&) = default;
|
||||
|
||||
size_t size() const { return m_buffer.size(); }
|
||||
const uint8_t * data() const { return m_buffer.data(); }
|
||||
|
||||
virtual RawData& serialize(const Raster &/*raster*/) { return *this; }
|
||||
virtual std::string get_file_extension() const = 0;
|
||||
};
|
||||
|
||||
/// Type that represents a resolution in pixels.
|
||||
|
@ -86,11 +72,36 @@ public:
|
|||
w_mm(px_width_mm), h_mm(px_height_mm) {}
|
||||
};
|
||||
|
||||
/// Constructor taking the resolution and the pixel dimension.
|
||||
template <class...Args> Raster(Args...args) {
|
||||
reset(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
enum Orientation { roLandscape, roPortrait };
|
||||
|
||||
using TMirroring = std::array<bool, 2>;
|
||||
static const TMirroring NoMirror;
|
||||
static const TMirroring MirrorX;
|
||||
static const TMirroring MirrorY;
|
||||
static const TMirroring MirrorXY;
|
||||
|
||||
struct Trafo {
|
||||
bool mirror_x = false, mirror_y = false, flipXY = false;
|
||||
coord_t origin_x = 0, origin_y = 0;
|
||||
|
||||
// If gamma is zero, thresholding will be performed which disables AA.
|
||||
double gamma = 1.;
|
||||
|
||||
// Portrait orientation will make sure the drawed polygons are rotated
|
||||
// by 90 degrees.
|
||||
Trafo(Orientation o = roLandscape, const TMirroring &mirror = NoMirror)
|
||||
// XY flipping implicitly does an X mirror
|
||||
: mirror_x(o == roPortrait ? !mirror[0] : mirror[0])
|
||||
, mirror_y(!mirror[1]) // Makes raster origin to be top left corner
|
||||
, flipXY(o == roPortrait)
|
||||
{}
|
||||
};
|
||||
|
||||
Raster();
|
||||
Raster(const Resolution &r,
|
||||
const PixelDim & pd,
|
||||
const Trafo & tr = {});
|
||||
|
||||
Raster(const Raster& cpy) = delete;
|
||||
Raster& operator=(const Raster& cpy) = delete;
|
||||
Raster(Raster&& m);
|
||||
|
@ -98,18 +109,10 @@ public:
|
|||
~Raster();
|
||||
|
||||
/// Reallocated everything for the given resolution and pixel dimension.
|
||||
/// The third parameter is either the X, Y mirroring or a supported format
|
||||
/// for which the correct mirroring will be configured.
|
||||
void reset(const Resolution&,
|
||||
const PixelDim&,
|
||||
const std::array<bool, 2>& mirror,
|
||||
double gamma = 1.0);
|
||||
|
||||
void reset(const Resolution& r,
|
||||
const PixelDim& pd,
|
||||
Format o,
|
||||
double gamma = 1.0);
|
||||
|
||||
void reset(const Resolution& r,
|
||||
const PixelDim& pd,
|
||||
const Trafo &tr = {});
|
||||
|
||||
/**
|
||||
* Release the allocated resources. Drawing in this state ends in
|
||||
* unspecified behavior.
|
||||
|
@ -118,6 +121,7 @@ public:
|
|||
|
||||
/// Get the resolution of the raster.
|
||||
Resolution resolution() const;
|
||||
PixelDim pixel_dimensions() const;
|
||||
|
||||
/// Clear the raster with black color.
|
||||
void clear();
|
||||
|
@ -126,24 +130,28 @@ public:
|
|||
void draw(const ExPolygon& poly);
|
||||
void draw(const ClipperLib::Polygon& poly);
|
||||
|
||||
// Saving the raster:
|
||||
// It is possible to override the format given in the constructor but
|
||||
// be aware that the mirroring will not be modified.
|
||||
|
||||
/// Save the raster on the specified stream.
|
||||
void save(std::ostream& stream, Format);
|
||||
void save(std::ostream& stream);
|
||||
uint8_t read_pixel(size_t w, size_t h) const;
|
||||
|
||||
inline bool empty() const { return ! bool(m_impl); }
|
||||
|
||||
/// Save into a continuous byte stream which is returned.
|
||||
RawBytes save(Format fmt);
|
||||
RawBytes save();
|
||||
};
|
||||
|
||||
// This prevents the duplicate default constructor warning on MSVC2013
|
||||
template<> Raster::Raster();
|
||||
class PNGImage: public Raster::RawData {
|
||||
public:
|
||||
PNGImage& serialize(const Raster &raster) override;
|
||||
std::string get_file_extension() const override { return "png"; }
|
||||
};
|
||||
|
||||
class PPMImage: public Raster::RawData {
|
||||
public:
|
||||
PPMImage& serialize(const Raster &raster) override;
|
||||
std::string get_file_extension() const override { return "ppm"; }
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream &stream, const Raster::RawData &bytes);
|
||||
|
||||
} // sla
|
||||
} // Slic3r
|
||||
|
||||
|
||||
#endif // SLARASTER_HPP
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
namespace Slic3r { namespace sla {
|
||||
|
||||
std::string SLARasterWriter::createIniContent(const std::string& projectname) const
|
||||
std::string RasterWriter::createIniContent(const std::string& projectname) const
|
||||
{
|
||||
std::string out("action = print\njobDir = ");
|
||||
out += projectname + "\n";
|
||||
|
@ -21,39 +21,14 @@ std::string SLARasterWriter::createIniContent(const std::string& projectname) co
|
|||
return out;
|
||||
}
|
||||
|
||||
void SLARasterWriter::flpXY(ClipperLib::Polygon &poly)
|
||||
{
|
||||
for(auto& p : poly.Contour) std::swap(p.X, p.Y);
|
||||
std::reverse(poly.Contour.begin(), poly.Contour.end());
|
||||
|
||||
for(auto& h : poly.Holes) {
|
||||
for(auto& p : h) std::swap(p.X, p.Y);
|
||||
std::reverse(h.begin(), h.end());
|
||||
}
|
||||
}
|
||||
RasterWriter::RasterWriter(const Raster::Resolution &res,
|
||||
const Raster::PixelDim & pixdim,
|
||||
const Raster::Trafo & trafo,
|
||||
double gamma)
|
||||
: m_res(res), m_pxdim(pixdim), m_trafo(trafo), m_gamma(gamma)
|
||||
{}
|
||||
|
||||
void SLARasterWriter::flpXY(ExPolygon &poly)
|
||||
{
|
||||
for(auto& p : poly.contour.points) p = Point(p.y(), p.x());
|
||||
std::reverse(poly.contour.points.begin(), poly.contour.points.end());
|
||||
|
||||
for(auto& h : poly.holes) {
|
||||
for(auto& p : h.points) p = Point(p.y(), p.x());
|
||||
std::reverse(h.points.begin(), h.points.end());
|
||||
}
|
||||
}
|
||||
|
||||
SLARasterWriter::SLARasterWriter(const Raster::Resolution &res,
|
||||
const Raster::PixelDim &pixdim,
|
||||
const std::array<bool, 2> &mirror,
|
||||
double gamma)
|
||||
: m_res(res), m_pxdim(pixdim), m_mirror(mirror), m_gamma(gamma)
|
||||
{
|
||||
// PNG raster will implicitly do an Y mirror
|
||||
m_mirror[1] = !m_mirror[1];
|
||||
}
|
||||
|
||||
void SLARasterWriter::save(const std::string &fpath, const std::string &prjname)
|
||||
void RasterWriter::save(const std::string &fpath, const std::string &prjname)
|
||||
{
|
||||
try {
|
||||
Zipper zipper(fpath); // zipper with no compression
|
||||
|
@ -103,7 +78,7 @@ std::string get_cfg_value(const DynamicPrintConfig &cfg, const std::string &key)
|
|||
|
||||
} // namespace
|
||||
|
||||
void SLARasterWriter::set_config(const DynamicPrintConfig &cfg)
|
||||
void RasterWriter::set_config(const DynamicPrintConfig &cfg)
|
||||
{
|
||||
m_config["layerHeight"] = get_cfg_value(cfg, "layer_height");
|
||||
m_config["expTime"] = get_cfg_value(cfg, "exposure_time");
|
||||
|
@ -114,11 +89,11 @@ void SLARasterWriter::set_config(const DynamicPrintConfig &cfg)
|
|||
m_config["printerProfile"] = get_cfg_value(cfg, "printer_settings_id");
|
||||
m_config["printProfile"] = get_cfg_value(cfg, "sla_print_settings_id");
|
||||
|
||||
m_config["fileCreationTimestamp"] = Utils::current_utc_time2str();
|
||||
m_config["fileCreationTimestamp"] = Utils::utc_timestamp();
|
||||
m_config["prusaSlicerVersion"] = SLIC3R_BUILD_ID;
|
||||
}
|
||||
|
||||
void SLARasterWriter::set_statistics(const PrintStatistics &stats)
|
||||
void RasterWriter::set_statistics(const PrintStatistics &stats)
|
||||
{
|
||||
m_config["usedMaterial"] = std::to_string(stats.used_material);
|
||||
m_config["numFade"] = std::to_string(stats.num_fade);
|
||||
|
|
|
@ -15,20 +15,17 @@
|
|||
|
||||
namespace Slic3r { namespace sla {
|
||||
|
||||
// Implementation for PNG raster output
|
||||
// API to write the zipped sla output layers and metadata.
|
||||
// Implementation uses PNG raster output.
|
||||
// Be aware that if a large number of layers are allocated, it can very well
|
||||
// exhaust the available memory especially on 32 bit platform.
|
||||
// This class is designed to be used in parallel mode. Layers have an ID and
|
||||
// each layer can be written and compressed independently (in parallel).
|
||||
// At the end when all layers where written, the save method can be used to
|
||||
// write out the result into a zipped archive.
|
||||
class SLARasterWriter
|
||||
class RasterWriter
|
||||
{
|
||||
public:
|
||||
enum Orientation {
|
||||
roLandscape,
|
||||
roPortrait
|
||||
};
|
||||
|
||||
// Used for addressing parameters of set_statistics()
|
||||
struct PrintStatistics
|
||||
|
@ -45,7 +42,7 @@ private:
|
|||
// A struct to bind the raster image data and its compressed bytes together.
|
||||
struct Layer {
|
||||
Raster raster;
|
||||
RawBytes rawbytes;
|
||||
PNGImage rawbytes;
|
||||
|
||||
Layer() = default;
|
||||
|
||||
|
@ -61,70 +58,55 @@ private:
|
|||
// parallel. Later we can write every layer to the disk sequentially.
|
||||
std::vector<Layer> m_layers_rst;
|
||||
Raster::Resolution m_res;
|
||||
Raster::PixelDim m_pxdim;
|
||||
std::array<bool, 2> m_mirror;
|
||||
double m_gamma;
|
||||
|
||||
Raster::PixelDim m_pxdim;
|
||||
Raster::Trafo m_trafo;
|
||||
double m_gamma;
|
||||
|
||||
std::map<std::string, std::string> m_config;
|
||||
|
||||
std::string createIniContent(const std::string& projectname) const;
|
||||
|
||||
static void flpXY(ClipperLib::Polygon& poly);
|
||||
static void flpXY(ExPolygon& poly);
|
||||
|
||||
public:
|
||||
SLARasterWriter(const Raster::Resolution &res,
|
||||
const Raster::PixelDim &pixdim,
|
||||
const std::array<bool, 2> &mirror,
|
||||
double gamma = 1.);
|
||||
|
||||
// SLARasterWriter is using Raster in custom mirroring mode
|
||||
RasterWriter(const Raster::Resolution &res,
|
||||
const Raster::PixelDim & pixdim,
|
||||
const Raster::Trafo & trafo,
|
||||
double gamma = 1.);
|
||||
|
||||
SLARasterWriter(const SLARasterWriter& ) = delete;
|
||||
SLARasterWriter& operator=(const SLARasterWriter&) = delete;
|
||||
SLARasterWriter(SLARasterWriter&& m) = default;
|
||||
SLARasterWriter& operator=(SLARasterWriter&&) = default;
|
||||
RasterWriter(const RasterWriter& ) = delete;
|
||||
RasterWriter& operator=(const RasterWriter&) = delete;
|
||||
RasterWriter(RasterWriter&& m) = default;
|
||||
RasterWriter& operator=(RasterWriter&&) = default;
|
||||
|
||||
inline void layers(unsigned cnt) { if(cnt > 0) m_layers_rst.resize(cnt); }
|
||||
inline unsigned layers() const { return unsigned(m_layers_rst.size()); }
|
||||
|
||||
template<class Poly> void draw_polygon(const Poly& p, unsigned lyr,
|
||||
Orientation o = roPortrait)
|
||||
template<class Poly> void draw_polygon(const Poly& p, unsigned lyr)
|
||||
{
|
||||
assert(lyr < m_layers_rst.size());
|
||||
|
||||
switch (o) {
|
||||
case roPortrait: {
|
||||
Poly poly(p);
|
||||
flpXY(poly);
|
||||
m_layers_rst[lyr].raster.draw(poly);
|
||||
break;
|
||||
}
|
||||
case roLandscape:
|
||||
m_layers_rst[lyr].raster.draw(p);
|
||||
break;
|
||||
}
|
||||
m_layers_rst[lyr].raster.draw(p);
|
||||
}
|
||||
|
||||
inline void begin_layer(unsigned lyr) {
|
||||
if(m_layers_rst.size() <= lyr) m_layers_rst.resize(lyr+1);
|
||||
m_layers_rst[lyr].raster.reset(m_res, m_pxdim, m_mirror, m_gamma);
|
||||
m_layers_rst[lyr].raster.reset(m_res, m_pxdim, m_trafo);
|
||||
}
|
||||
|
||||
inline void begin_layer() {
|
||||
m_layers_rst.emplace_back();
|
||||
m_layers_rst.front().raster.reset(m_res, m_pxdim, m_mirror, m_gamma);
|
||||
m_layers_rst.front().raster.reset(m_res, m_pxdim, m_trafo);
|
||||
}
|
||||
|
||||
inline void finish_layer(unsigned lyr_id) {
|
||||
assert(lyr_id < m_layers_rst.size());
|
||||
m_layers_rst[lyr_id].rawbytes =
|
||||
m_layers_rst[lyr_id].raster.save(Raster::Format::PNG);
|
||||
m_layers_rst[lyr_id].rawbytes.serialize(m_layers_rst[lyr_id].raster);
|
||||
m_layers_rst[lyr_id].raster.reset();
|
||||
}
|
||||
|
||||
inline void finish_layer() {
|
||||
if(!m_layers_rst.empty()) {
|
||||
m_layers_rst.back().rawbytes =
|
||||
m_layers_rst.back().raster.save(Raster::Format::PNG);
|
||||
m_layers_rst.back().rawbytes.serialize(m_layers_rst.back().raster);
|
||||
m_layers_rst.back().raster.reset();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>;
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -2,24 +2,14 @@
|
|||
#define SLASUPPORTTREE_HPP
|
||||
|
||||
#include <vector>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <Eigen/Geometry>
|
||||
|
||||
#include "SLACommon.hpp"
|
||||
|
||||
#include "SLAPad.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// Needed types from Point.hpp
|
||||
typedef int32_t coord_t;
|
||||
typedef Eigen::Matrix<double, 3, 1, Eigen::DontAlign> Vec3d;
|
||||
typedef Eigen::Matrix<float, 3, 1, Eigen::DontAlign> Vec3f;
|
||||
typedef Eigen::Matrix<coord_t, 3, 1, Eigen::DontAlign> Vec3crd;
|
||||
typedef std::vector<Vec3d> Pointf3s;
|
||||
typedef std::vector<Vec3crd> Points3;
|
||||
|
||||
class TriangleMesh;
|
||||
class Model;
|
||||
class ModelInstance;
|
||||
|
@ -32,13 +22,17 @@ using ExPolygons = std::vector<ExPolygon>;
|
|||
|
||||
namespace sla {
|
||||
|
||||
enum class PillarConnectionMode {
|
||||
enum class PillarConnectionMode
|
||||
{
|
||||
zigzag,
|
||||
cross,
|
||||
dynamic
|
||||
};
|
||||
|
||||
struct SupportConfig {
|
||||
struct SupportConfig
|
||||
{
|
||||
bool enabled = true;
|
||||
|
||||
// Radius in mm of the pointing side of the head.
|
||||
double head_front_radius_mm = 0.2;
|
||||
|
||||
|
@ -85,6 +79,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,101 +103,78 @@ struct SupportConfig {
|
|||
static const unsigned max_bridges_on_pillar;
|
||||
};
|
||||
|
||||
struct PoolConfig;
|
||||
enum class MeshType { Support, Pad };
|
||||
|
||||
/// A Control structure for the support calculation. Consists of the status
|
||||
/// indicator callback and the stop condition predicate.
|
||||
struct Controller {
|
||||
|
||||
struct JobController
|
||||
{
|
||||
using StatusFn = std::function<void(unsigned, const std::string&)>;
|
||||
using StopCond = std::function<bool(void)>;
|
||||
using CancelFn = std::function<void(void)>;
|
||||
|
||||
// This will signal the status of the calculation to the front-end
|
||||
std::function<void(unsigned, const std::string&)> statuscb =
|
||||
[](unsigned, const std::string&){};
|
||||
|
||||
StatusFn statuscb = [](unsigned, const std::string&){};
|
||||
|
||||
// Returns true if the calculation should be aborted.
|
||||
std::function<bool(void)> stopcondition = [](){ return false; };
|
||||
|
||||
StopCond stopcondition = [](){ return false; };
|
||||
|
||||
// Similar to cancel callback. This should check the stop condition and
|
||||
// if true, throw an appropriate exception. (TriangleMeshSlicer needs this)
|
||||
// consider it a hard abort. stopcondition is permits the algorithm to
|
||||
// terminate itself
|
||||
std::function<void(void)> cancelfn = [](){};
|
||||
CancelFn cancelfn = [](){};
|
||||
};
|
||||
|
||||
using PointSet = Eigen::MatrixXd;
|
||||
struct SupportableMesh
|
||||
{
|
||||
EigenMesh3D emesh;
|
||||
SupportPoints pts;
|
||||
SupportConfig cfg;
|
||||
|
||||
//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>&);
|
||||
|
||||
|
||||
/* ************************************************************************** */
|
||||
explicit SupportableMesh(const TriangleMesh & trmsh,
|
||||
const SupportPoints &sp,
|
||||
const SupportConfig &c)
|
||||
: emesh{trmsh}, pts{sp}, cfg{c}
|
||||
{}
|
||||
|
||||
explicit SupportableMesh(const EigenMesh3D &em,
|
||||
const SupportPoints &sp,
|
||||
const SupportConfig &c)
|
||||
: emesh{em}, pts{sp}, cfg{c}
|
||||
{}
|
||||
};
|
||||
|
||||
/// The class containing mesh data for the generated supports.
|
||||
class SLASupportTree {
|
||||
class Impl; // persistent support data
|
||||
std::unique_ptr<Impl> m_impl;
|
||||
|
||||
Impl& get() { return *m_impl; }
|
||||
const Impl& get() const { return *m_impl; }
|
||||
|
||||
friend void add_sla_supports(Model&,
|
||||
const SupportConfig&,
|
||||
const Controller&);
|
||||
|
||||
// The generation algorithm is quite long and will be captured in a separate
|
||||
// class with private data, helper methods, etc... This data is only needed
|
||||
// during the calculation whereas the Impl class contains the persistent
|
||||
// data, mostly the meshes.
|
||||
class Algorithm;
|
||||
|
||||
// Generate the 3D supports for a model intended for SLA print. This
|
||||
// will instantiate the Algorithm class and call its appropriate methods
|
||||
// with status indication.
|
||||
bool generate(const std::vector<SupportPoint>& pts,
|
||||
const EigenMesh3D& mesh,
|
||||
const SupportConfig& cfg = {},
|
||||
const Controller& ctl = {});
|
||||
|
||||
class SupportTree
|
||||
{
|
||||
JobController m_ctl;
|
||||
public:
|
||||
|
||||
SLASupportTree(double ground_level = 0.0);
|
||||
|
||||
SLASupportTree(const std::vector<SupportPoint>& pts,
|
||||
const EigenMesh3D& em,
|
||||
const SupportConfig& cfg = {},
|
||||
const Controller& ctl = {});
|
||||
using UPtr = std::unique_ptr<SupportTree>;
|
||||
|
||||
SLASupportTree(const SLASupportTree&) = delete;
|
||||
SLASupportTree& operator=(const SLASupportTree&) = delete;
|
||||
static UPtr create(const SupportableMesh &input,
|
||||
const JobController &ctl = {});
|
||||
|
||||
~SLASupportTree();
|
||||
virtual ~SupportTree() = default;
|
||||
|
||||
/// Get the whole mesh united into the output TriangleMesh
|
||||
/// WITHOUT THE PAD
|
||||
const TriangleMesh& merged_mesh() const;
|
||||
virtual const TriangleMesh &retrieve_mesh(MeshType meshtype) const = 0;
|
||||
|
||||
void merged_mesh_with_pad(TriangleMesh&) const;
|
||||
|
||||
std::vector<ExPolygons> slice(const std::vector<float> &,
|
||||
float closing_radius) const;
|
||||
|
||||
/// Adding the "pad" (base pool) under the supports
|
||||
/// Adding the "pad" under the supports.
|
||||
/// modelbase will be used according to the embed_object flag in PoolConfig.
|
||||
/// If set, the plate will interpreted as the model's intrinsic pad.
|
||||
/// If set, the plate will be interpreted as the model's intrinsic pad.
|
||||
/// 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;
|
||||
|
||||
/// Get the pad geometry
|
||||
const TriangleMesh& get_pad() const;
|
||||
|
||||
void remove_pad();
|
||||
|
||||
virtual const TriangleMesh &add_pad(const ExPolygons &modelbase,
|
||||
const PadConfig & pcfg) = 0;
|
||||
|
||||
virtual void remove_pad() = 0;
|
||||
|
||||
std::vector<ExPolygons> slice(const std::vector<float> &,
|
||||
float closing_radius) const;
|
||||
|
||||
void retrieve_full_mesh(TriangleMesh &outmesh) const;
|
||||
|
||||
const JobController &ctl() const { return m_ctl; }
|
||||
};
|
||||
|
||||
}
|
||||
|
|
525
src/libslic3r/SLA/SLASupportTreeBuilder.cpp
Normal file
525
src/libslic3r/SLA/SLASupportTreeBuilder.cpp
Normal file
|
@ -0,0 +1,525 @@
|
|||
#include "SLASupportTreeBuilder.hpp"
|
||||
#include "SLASupportTreeBuildsteps.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.indices;
|
||||
|
||||
// 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)
|
||||
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,
|
||||
// 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(Vec3crd(id - 1, id,
|
||||
id + coord_t(ring.size() - 1)));
|
||||
facets.emplace_back(Vec3crd(id - 1, id_ringsize, id));
|
||||
} else {
|
||||
facets.emplace_back(Vec3crd(id_ringsize - 1, id_ringsize, id));
|
||||
facets.emplace_back(Vec3crd(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(Vec3crd(id - 1, id_ringsize, id));
|
||||
} else {
|
||||
auto ci = coord_t(id_ringsize + coord_t(i));
|
||||
facets.emplace_back(Vec3crd(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.indices;
|
||||
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;
|
||||
}
|
||||
|
||||
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)
|
||||
, tr(offset)
|
||||
, r_back_mm(r_big_mm)
|
||||
, r_pin_mm(r_small_mm)
|
||||
, width_mm(length_mm)
|
||||
, penetration_mm(penetration)
|
||||
{
|
||||
assert(width_mm > 0.);
|
||||
assert(r_back_mm > 0.);
|
||||
assert(r_pin_mm > 0.);
|
||||
|
||||
// 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_big_mm + r_small_mm + width_mm;
|
||||
double phi = PI/2 - std::acos( (r_big_mm - r_small_mm) / 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_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) 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.indices.emplace_back(i1s1, i2s1, i2s2);
|
||||
mesh.indices.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.indices.emplace_back(i2s2, i2s1, i1s1);
|
||||
mesh.indices.emplace_back(i1s2, i2s2, i1s1);
|
||||
|
||||
// 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 + r_small_mm - penetration_mm);
|
||||
}
|
||||
|
||||
Pillar::Pillar(const Vec3d &jp, const Vec3d &endp, double radius, size_t st):
|
||||
r(radius), steps(st), endpt(endp), starts_from_head(false)
|
||||
{
|
||||
assert(steps > 0);
|
||||
|
||||
height = jp(Z) - endp(Z);
|
||||
if(height > EPSILON) { // Endpoint is below the starting point
|
||||
|
||||
// We just create a bridge geometry with the pillar parameters and
|
||||
// move the data.
|
||||
Contour3D body = cylinder(radius, height, st, endp);
|
||||
mesh.points.swap(body.points);
|
||||
mesh.indices.swap(body.indices);
|
||||
}
|
||||
}
|
||||
|
||||
Pillar &Pillar::add_base(double baseheight, double radius)
|
||||
{
|
||||
if(baseheight <= 0) return *this;
|
||||
if(baseheight > height) baseheight = height;
|
||||
|
||||
assert(steps >= 0);
|
||||
auto last = int(steps - 1);
|
||||
|
||||
if(radius < r ) radius = r;
|
||||
|
||||
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) + r*std::cos(phi);
|
||||
double y = endpt(Y) + r*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.indices;
|
||||
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 *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;
|
||||
}
|
||||
|
||||
CompactBridge::CompactBridge(const Vec3d &sp,
|
||||
const Vec3d &ep,
|
||||
const Vec3d &n,
|
||||
double r,
|
||||
bool endball,
|
||||
size_t steps)
|
||||
{
|
||||
Vec3d startp = sp + r * n;
|
||||
Vec3d dir = (ep - startp).normalized();
|
||||
Vec3d endp = ep - r * dir;
|
||||
|
||||
Bridge br(startp, endp, r, steps);
|
||||
mesh.merge(br.mesh);
|
||||
|
||||
// now add the pins
|
||||
double fa = 2*PI/steps;
|
||||
auto upperball = sphere(r, Portion{PI / 2 - fa, PI}, fa);
|
||||
for(auto& p : upperball.points) p += startp;
|
||||
|
||||
if(endball) {
|
||||
auto lowerball = sphere(r, Portion{0, PI/2 + 2*fa}, fa);
|
||||
for(auto& p : lowerball.points) p += endp;
|
||||
mesh.merge(lowerball);
|
||||
}
|
||||
|
||||
mesh.merge(upperball);
|
||||
}
|
||||
|
||||
Pad::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())
|
||||
{
|
||||
thr();
|
||||
|
||||
ExPolygons sup_contours;
|
||||
|
||||
float zstart = float(zlevel);
|
||||
float zend = zstart + float(pcfg.full_height() + EPSILON);
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
const TriangleMesh &SupportTreeBuilder::add_pad(const ExPolygons &modelbase,
|
||||
const PadConfig & cfg)
|
||||
{
|
||||
m_pad = Pad{merged_mesh(), modelbase, ground_level, cfg, ctl().cancelfn};
|
||||
return m_pad.tmesh;
|
||||
}
|
||||
|
||||
SupportTreeBuilder::SupportTreeBuilder(SupportTreeBuilder &&o)
|
||||
: m_heads(std::move(o.m_heads))
|
||||
, m_head_indices{std::move(o.m_head_indices)}
|
||||
, m_pillars{std::move(o.m_pillars)}
|
||||
, m_bridges{std::move(o.m_bridges)}
|
||||
, m_crossbridges{std::move(o.m_crossbridges)}
|
||||
, m_compact_bridges{std::move(o.m_compact_bridges)}
|
||||
, m_pad{std::move(o.m_pad)}
|
||||
, m_meshcache{std::move(o.m_meshcache)}
|
||||
, m_meshcache_valid{o.m_meshcache_valid}
|
||||
, m_model_height{o.m_model_height}
|
||||
, ground_level{o.ground_level}
|
||||
{}
|
||||
|
||||
SupportTreeBuilder::SupportTreeBuilder(const SupportTreeBuilder &o)
|
||||
: m_heads(o.m_heads)
|
||||
, m_head_indices{o.m_head_indices}
|
||||
, m_pillars{o.m_pillars}
|
||||
, m_bridges{o.m_bridges}
|
||||
, m_crossbridges{o.m_crossbridges}
|
||||
, m_compact_bridges{o.m_compact_bridges}
|
||||
, m_pad{o.m_pad}
|
||||
, m_meshcache{o.m_meshcache}
|
||||
, m_meshcache_valid{o.m_meshcache_valid}
|
||||
, m_model_height{o.m_model_height}
|
||||
, ground_level{o.ground_level}
|
||||
{}
|
||||
|
||||
SupportTreeBuilder &SupportTreeBuilder::operator=(SupportTreeBuilder &&o)
|
||||
{
|
||||
m_heads = std::move(o.m_heads);
|
||||
m_head_indices = std::move(o.m_head_indices);
|
||||
m_pillars = std::move(o.m_pillars);
|
||||
m_bridges = std::move(o.m_bridges);
|
||||
m_crossbridges = std::move(o.m_crossbridges);
|
||||
m_compact_bridges = std::move(o.m_compact_bridges);
|
||||
m_pad = std::move(o.m_pad);
|
||||
m_meshcache = std::move(o.m_meshcache);
|
||||
m_meshcache_valid = o.m_meshcache_valid;
|
||||
m_model_height = o.m_model_height;
|
||||
ground_level = o.ground_level;
|
||||
return *this;
|
||||
}
|
||||
|
||||
SupportTreeBuilder &SupportTreeBuilder::operator=(const SupportTreeBuilder &o)
|
||||
{
|
||||
m_heads = o.m_heads;
|
||||
m_head_indices = o.m_head_indices;
|
||||
m_pillars = o.m_pillars;
|
||||
m_bridges = o.m_bridges;
|
||||
m_crossbridges = o.m_crossbridges;
|
||||
m_compact_bridges = o.m_compact_bridges;
|
||||
m_pad = o.m_pad;
|
||||
m_meshcache = o.m_meshcache;
|
||||
m_meshcache_valid = o.m_meshcache_valid;
|
||||
m_model_height = o.m_model_height;
|
||||
ground_level = o.ground_level;
|
||||
return *this;
|
||||
}
|
||||
|
||||
const TriangleMesh &SupportTreeBuilder::merged_mesh() const
|
||||
{
|
||||
if (m_meshcache_valid) return m_meshcache;
|
||||
|
||||
Contour3D merged;
|
||||
|
||||
for (auto &head : m_heads) {
|
||||
if (ctl().stopcondition()) break;
|
||||
if (head.is_valid()) merged.merge(head.mesh);
|
||||
}
|
||||
|
||||
for (auto &stick : m_pillars) {
|
||||
if (ctl().stopcondition()) break;
|
||||
merged.merge(stick.mesh);
|
||||
merged.merge(stick.base);
|
||||
}
|
||||
|
||||
for (auto &j : m_junctions) {
|
||||
if (ctl().stopcondition()) break;
|
||||
merged.merge(j.mesh);
|
||||
}
|
||||
|
||||
for (auto &cb : m_compact_bridges) {
|
||||
if (ctl().stopcondition()) break;
|
||||
merged.merge(cb.mesh);
|
||||
}
|
||||
|
||||
for (auto &bs : m_bridges) {
|
||||
if (ctl().stopcondition()) break;
|
||||
merged.merge(bs.mesh);
|
||||
}
|
||||
|
||||
for (auto &bs : m_crossbridges) {
|
||||
if (ctl().stopcondition()) break;
|
||||
merged.merge(bs.mesh);
|
||||
}
|
||||
|
||||
if (ctl().stopcondition()) {
|
||||
// In case of failure we have to return an empty mesh
|
||||
m_meshcache = TriangleMesh();
|
||||
return m_meshcache;
|
||||
}
|
||||
|
||||
m_meshcache = mesh(merged);
|
||||
|
||||
// The mesh will be passed by const-pointer to TriangleMeshSlicer,
|
||||
// which will need this.
|
||||
if (!m_meshcache.empty()) m_meshcache.require_shared_vertices();
|
||||
|
||||
BoundingBoxf3 &&bb = m_meshcache.bounding_box();
|
||||
m_model_height = bb.max(Z) - bb.min(Z);
|
||||
|
||||
m_meshcache_valid = true;
|
||||
return m_meshcache;
|
||||
}
|
||||
|
||||
double SupportTreeBuilder::full_height() const
|
||||
{
|
||||
if (merged_mesh().empty() && !pad().empty())
|
||||
return pad().cfg.full_height();
|
||||
|
||||
double h = mesh_height();
|
||||
if (!pad().empty()) h += pad().cfg.required_elevation();
|
||||
return h;
|
||||
}
|
||||
|
||||
const TriangleMesh &SupportTreeBuilder::merge_and_cleanup()
|
||||
{
|
||||
// in case the mesh is not generated, it should be...
|
||||
auto &ret = merged_mesh();
|
||||
|
||||
// Doing clear() does not garantee to release the memory.
|
||||
m_heads = {};
|
||||
m_head_indices = {};
|
||||
m_pillars = {};
|
||||
m_junctions = {};
|
||||
m_bridges = {};
|
||||
m_compact_bridges = {};
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
const TriangleMesh &SupportTreeBuilder::retrieve_mesh(MeshType meshtype) const
|
||||
{
|
||||
switch(meshtype) {
|
||||
case MeshType::Support: return merged_mesh();
|
||||
case MeshType::Pad: return pad().tmesh;
|
||||
}
|
||||
|
||||
return m_meshcache;
|
||||
}
|
||||
|
||||
bool SupportTreeBuilder::build(const SupportableMesh &sm)
|
||||
{
|
||||
ground_level = sm.emesh.ground_level() - sm.cfg.object_elevation_mm;
|
||||
return SupportTreeBuildsteps::execute(*this, sm);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
496
src/libslic3r/SLA/SLASupportTreeBuilder.hpp
Normal file
496
src/libslic3r/SLA/SLASupportTreeBuilder.hpp
Normal file
|
@ -0,0 +1,496 @@
|
|||
#ifndef SUPPORTTREEBUILDER_HPP
|
||||
#define SUPPORTTREEBUILDER_HPP
|
||||
|
||||
#include "SLAConcurrency.hpp"
|
||||
#include "SLABoilerPlate.hpp"
|
||||
#include "SLASupportTree.hpp"
|
||||
#include "SLAPad.hpp"
|
||||
#include <libslic3r/MTUtils.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace sla {
|
||||
|
||||
/**
|
||||
* Terminology:
|
||||
*
|
||||
* Support point:
|
||||
* The point on the model surface that needs support.
|
||||
*
|
||||
* Pillar:
|
||||
* A thick column that spans from a support point to the ground and has
|
||||
* a thick cone shaped base where it touches the ground.
|
||||
*
|
||||
* Ground facing support point:
|
||||
* A support point that can be directly connected with the ground with a pillar
|
||||
* that does not collide or cut through the model.
|
||||
*
|
||||
* Non ground facing support point:
|
||||
* A support point that cannot be directly connected with the ground (only with
|
||||
* the model surface).
|
||||
*
|
||||
* Head:
|
||||
* The pinhead that connects to the model surface with the sharp end end
|
||||
* to a pillar or bridge stick with the dull end.
|
||||
*
|
||||
* Headless support point:
|
||||
* A support point on the model surface for which there is not enough place for
|
||||
* the head. It is either in a hole or there is some barrier that would collide
|
||||
* with the head geometry. The headless support point can be ground facing and
|
||||
* non ground facing as well.
|
||||
*
|
||||
* Bridge:
|
||||
* A stick that connects two pillars or a head with a pillar.
|
||||
*
|
||||
* Junction:
|
||||
* A small ball in the intersection of two or more sticks (pillar, bridge, ...)
|
||||
*
|
||||
* CompactBridge:
|
||||
* A bridge that connects a headless support point with the model surface or a
|
||||
* 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);
|
||||
}
|
||||
|
||||
template<class Vec> double distance(const Vec& pp1, const Vec& pp2) {
|
||||
auto p = pp2 - pp1;
|
||||
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 ssteps, const Vec3d &sp = {0,0,0});
|
||||
|
||||
const constexpr long ID_UNSET = -1;
|
||||
|
||||
struct Head {
|
||||
Contour3D mesh;
|
||||
|
||||
size_t steps = 45;
|
||||
Vec3d dir = {0, 0, -1};
|
||||
Vec3d tr = {0, 0, 0};
|
||||
|
||||
double r_back_mm = 1;
|
||||
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 = ID_UNSET;
|
||||
|
||||
// If there is a pillar connecting to this head, then the id will be set.
|
||||
long pillar_id = ID_UNSET;
|
||||
|
||||
long bridge_id = ID_UNSET;
|
||||
|
||||
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,
|
||||
const Vec3d &direction = {0, 0, -1}, // direction (normal to the dull end)
|
||||
const Vec3d &offset = {0, 0, 0}, // displacement
|
||||
const size_t circlesteps = 45);
|
||||
|
||||
void transform()
|
||||
{
|
||||
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}, dir);
|
||||
|
||||
for(auto& p : mesh.points) p = quatern * p + tr;
|
||||
}
|
||||
|
||||
inline double fullwidth() const
|
||||
{
|
||||
return 2 * r_pin_mm + width_mm + 2*r_back_mm - penetration_mm;
|
||||
}
|
||||
|
||||
inline Vec3d junction_point() const
|
||||
{
|
||||
return tr + ( 2 * r_pin_mm + width_mm + r_back_mm - penetration_mm)*dir;
|
||||
}
|
||||
|
||||
inline double request_pillar_radius(double radius) const
|
||||
{
|
||||
const double rmax = r_back_mm;
|
||||
return radius > 0 && radius < rmax ? radius : rmax;
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
struct Pillar {
|
||||
Contour3D mesh;
|
||||
Contour3D base;
|
||||
double r = 1;
|
||||
size_t steps = 0;
|
||||
Vec3d endpt;
|
||||
double height = 0;
|
||||
|
||||
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 = ID_UNSET;
|
||||
|
||||
// How many bridges are connected to this pillar
|
||||
unsigned bridges = 0;
|
||||
|
||||
// How many pillars are cascaded with this one
|
||||
unsigned links = 0;
|
||||
|
||||
Pillar(const Vec3d& jp, const Vec3d& endp,
|
||||
double radius = 1, size_t st = 45);
|
||||
|
||||
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)
|
||||
{}
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
// 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);
|
||||
};
|
||||
|
||||
// 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 = ID_UNSET;
|
||||
|
||||
CompactBridge(const Vec3d& sp,
|
||||
const Vec3d& ep,
|
||||
const Vec3d& n,
|
||||
double r,
|
||||
bool endball = true,
|
||||
size_t steps = 45);
|
||||
};
|
||||
|
||||
// A wrapper struct around the pad
|
||||
struct Pad {
|
||||
TriangleMesh tmesh;
|
||||
PadConfig cfg;
|
||||
double zlevel = 0;
|
||||
|
||||
Pad() = default;
|
||||
|
||||
Pad(const TriangleMesh &support_mesh,
|
||||
const ExPolygons & model_contours,
|
||||
double ground_level,
|
||||
const PadConfig & pcfg,
|
||||
ThrowOnCancel thr);
|
||||
|
||||
bool empty() const { return tmesh.facets_count() == 0; }
|
||||
};
|
||||
|
||||
// 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
|
||||
// merged result is cached for fast subsequent delivery of the merged mesh
|
||||
// which can be quite complex. The support tree creation algorithm can use an
|
||||
// instance of this class as a somewhat higher level tool for crafting the 3D
|
||||
// support mesh. Parts can be added with the appropriate methods such as
|
||||
// add_head or add_pillar which forwards the constructor arguments and fills
|
||||
// the IDs of these substructures. The IDs are basically indices into the
|
||||
// arrays of the appropriate type (heads, pillars, etc...). One can later query
|
||||
// e.g. a pillar for a specific head...
|
||||
//
|
||||
// The support pad is considered an auxiliary geometry and is not part of the
|
||||
// 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<Junction> m_junctions;
|
||||
std::vector<Bridge> m_bridges;
|
||||
std::vector<Bridge> m_crossbridges;
|
||||
std::vector<CompactBridge> m_compact_bridges;
|
||||
Pad m_pad;
|
||||
|
||||
using Mutex = ccr::SpinningMutex;
|
||||
|
||||
mutable TriangleMesh m_meshcache;
|
||||
mutable Mutex m_mutex;
|
||||
mutable bool m_meshcache_valid = false;
|
||||
mutable double m_model_height = 0; // the full height of the model
|
||||
|
||||
template<class...Args>
|
||||
const Bridge& _add_bridge(std::vector<Bridge> &br, Args&&... args)
|
||||
{
|
||||
std::lock_guard<Mutex> lk(m_mutex);
|
||||
br.emplace_back(std::forward<Args>(args)...);
|
||||
br.back().id = long(br.size() - 1);
|
||||
m_meshcache_valid = false;
|
||||
return br.back();
|
||||
}
|
||||
|
||||
public:
|
||||
double ground_level = 0;
|
||||
|
||||
SupportTreeBuilder() = default;
|
||||
SupportTreeBuilder(SupportTreeBuilder &&o);
|
||||
SupportTreeBuilder(const SupportTreeBuilder &o);
|
||||
SupportTreeBuilder& operator=(SupportTreeBuilder &&o);
|
||||
SupportTreeBuilder& operator=(const SupportTreeBuilder &o);
|
||||
|
||||
template<class...Args> Head& add_head(unsigned id, Args&&... args)
|
||||
{
|
||||
std::lock_guard<Mutex> lk(m_mutex);
|
||||
m_heads.emplace_back(std::forward<Args>(args)...);
|
||||
m_heads.back().id = id;
|
||||
|
||||
if (id >= m_head_indices.size()) m_head_indices.resize(id + 1);
|
||||
m_head_indices[id] = m_heads.size() - 1;
|
||||
|
||||
m_meshcache_valid = false;
|
||||
return m_heads.back();
|
||||
}
|
||||
|
||||
template<class...Args> long add_pillar(long headid, Args&&... args)
|
||||
{
|
||||
std::lock_guard<Mutex> lk(m_mutex);
|
||||
if (m_pillars.capacity() < m_heads.size())
|
||||
m_pillars.reserve(m_heads.size() * 10);
|
||||
|
||||
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)...);
|
||||
Pillar& pillar = m_pillars.back();
|
||||
pillar.id = long(m_pillars.size() - 1);
|
||||
head.pillar_id = pillar.id;
|
||||
pillar.start_junction_id = head.id;
|
||||
pillar.starts_from_head = true;
|
||||
|
||||
m_meshcache_valid = false;
|
||||
return pillar.id;
|
||||
}
|
||||
|
||||
void add_pillar_base(long pid, double baseheight = 3, double radius = 2)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
void increment_bridges(const Pillar& pillar)
|
||||
{
|
||||
std::lock_guard<Mutex> lk(m_mutex);
|
||||
assert(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size());
|
||||
|
||||
if(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size())
|
||||
m_pillars[size_t(pillar.id)].bridges++;
|
||||
}
|
||||
|
||||
void increment_links(const Pillar& pillar)
|
||||
{
|
||||
std::lock_guard<Mutex> lk(m_mutex);
|
||||
assert(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size());
|
||||
|
||||
if(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size())
|
||||
m_pillars[size_t(pillar.id)].links++;
|
||||
}
|
||||
|
||||
unsigned bridgecount(const Pillar &pillar) const {
|
||||
std::lock_guard<Mutex> lk(m_mutex);
|
||||
assert(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size());
|
||||
return pillar.bridges;
|
||||
}
|
||||
|
||||
template<class...Args> long add_pillar(Args&&...args)
|
||||
{
|
||||
std::lock_guard<Mutex> lk(m_mutex);
|
||||
if (m_pillars.capacity() < m_heads.size())
|
||||
m_pillars.reserve(m_heads.size() * 10);
|
||||
|
||||
m_pillars.emplace_back(std::forward<Args>(args)...);
|
||||
Pillar& pillar = m_pillars.back();
|
||||
pillar.id = long(m_pillars.size() - 1);
|
||||
pillar.starts_from_head = false;
|
||||
m_meshcache_valid = false;
|
||||
return pillar.id;
|
||||
}
|
||||
|
||||
const Pillar& head_pillar(unsigned headid) const
|
||||
{
|
||||
std::lock_guard<Mutex> lk(m_mutex);
|
||||
assert(headid < m_head_indices.size());
|
||||
|
||||
const Head& h = m_heads[m_head_indices[headid]];
|
||||
assert(h.pillar_id >= 0 && h.pillar_id < long(m_pillars.size()));
|
||||
|
||||
return m_pillars[size_t(h.pillar_id)];
|
||||
}
|
||||
|
||||
template<class...Args> const Junction& add_junction(Args&&... args)
|
||||
{
|
||||
std::lock_guard<Mutex> lk(m_mutex);
|
||||
m_junctions.emplace_back(std::forward<Args>(args)...);
|
||||
m_junctions.back().id = long(m_junctions.size() - 1);
|
||||
m_meshcache_valid = false;
|
||||
return m_junctions.back();
|
||||
}
|
||||
|
||||
const Bridge& add_bridge(const Vec3d &s, const Vec3d &e, double r, size_t n = 45)
|
||||
{
|
||||
return _add_bridge(m_bridges, s, e, r, n);
|
||||
}
|
||||
|
||||
const Bridge& add_bridge(long headid, const Vec3d &endp, size_t s = 45)
|
||||
{
|
||||
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.back().id = long(m_bridges.size() - 1);
|
||||
|
||||
h.bridge_id = m_bridges.back().id;
|
||||
m_meshcache_valid = false;
|
||||
return m_bridges.back();
|
||||
}
|
||||
|
||||
template<class...Args> const Bridge& add_crossbridge(Args&&... args)
|
||||
{
|
||||
return _add_bridge(m_crossbridges, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template<class...Args> const CompactBridge& add_compact_bridge(Args&&...args)
|
||||
{
|
||||
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);
|
||||
m_meshcache_valid = false;
|
||||
return m_compact_bridges.back();
|
||||
}
|
||||
|
||||
Head &head(unsigned id)
|
||||
{
|
||||
std::lock_guard<Mutex> lk(m_mutex);
|
||||
assert(id < m_head_indices.size());
|
||||
|
||||
m_meshcache_valid = false;
|
||||
return m_heads[m_head_indices[id]];
|
||||
}
|
||||
|
||||
inline size_t pillarcount() const {
|
||||
std::lock_guard<Mutex> lk(m_mutex);
|
||||
return m_pillars.size();
|
||||
}
|
||||
|
||||
inline const std::vector<Pillar> &pillars() const { return m_pillars; }
|
||||
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; }
|
||||
|
||||
template<class T> inline IntegerOnly<T, const Pillar&> pillar(T id) const
|
||||
{
|
||||
std::lock_guard<Mutex> lk(m_mutex);
|
||||
assert(id >= 0 && size_t(id) < m_pillars.size() &&
|
||||
size_t(id) < std::numeric_limits<size_t>::max());
|
||||
|
||||
return m_pillars[size_t(id)];
|
||||
}
|
||||
|
||||
template<class T> inline IntegerOnly<T, Pillar&> pillar(T id)
|
||||
{
|
||||
std::lock_guard<Mutex> lk(m_mutex);
|
||||
assert(id >= 0 && size_t(id) < m_pillars.size() &&
|
||||
size_t(id) < std::numeric_limits<size_t>::max());
|
||||
|
||||
return m_pillars[size_t(id)];
|
||||
}
|
||||
|
||||
const Pad& pad() const { return m_pad; }
|
||||
|
||||
// WITHOUT THE PAD!!!
|
||||
const TriangleMesh &merged_mesh() const;
|
||||
|
||||
// WITH THE PAD
|
||||
double full_height() const;
|
||||
|
||||
// WITHOUT THE PAD!!!
|
||||
inline double mesh_height() const
|
||||
{
|
||||
if (!m_meshcache_valid) merged_mesh();
|
||||
return m_model_height;
|
||||
}
|
||||
|
||||
// Intended to be called after the generation is fully complete
|
||||
const TriangleMesh & merge_and_cleanup();
|
||||
|
||||
// Implement SupportTree interface:
|
||||
|
||||
const TriangleMesh &add_pad(const ExPolygons &modelbase,
|
||||
const PadConfig & pcfg) override;
|
||||
|
||||
void remove_pad() override { m_pad = Pad(); }
|
||||
|
||||
virtual const TriangleMesh &retrieve_mesh(
|
||||
MeshType meshtype = MeshType::Support) const override;
|
||||
|
||||
bool build(const SupportableMesh &supportable_mesh);
|
||||
};
|
||||
|
||||
}} // namespace Slic3r::sla
|
||||
|
||||
#endif // SUPPORTTREEBUILDER_HPP
|
1387
src/libslic3r/SLA/SLASupportTreeBuildsteps.cpp
Normal file
1387
src/libslic3r/SLA/SLASupportTreeBuildsteps.cpp
Normal file
File diff suppressed because it is too large
Load diff
289
src/libslic3r/SLA/SLASupportTreeBuildsteps.hpp
Normal file
289
src/libslic3r/SLA/SLASupportTreeBuildsteps.hpp
Normal file
|
@ -0,0 +1,289 @@
|
|||
#ifndef SLASUPPORTTREEALGORITHM_H
|
||||
#define SLASUPPORTTREEALGORITHM_H
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "SLASupportTreeBuilder.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace sla {
|
||||
|
||||
// The minimum distance for two support points to remain valid.
|
||||
const double /*constexpr*/ D_SP = 0.1;
|
||||
|
||||
enum { // For indexing Eigen vectors as v(X), v(Y), v(Z) instead of numbers
|
||||
X, Y, Z
|
||||
};
|
||||
|
||||
inline Vec2d to_vec2(const Vec3d& v3) {
|
||||
return {v3(X), v3(Y)};
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
class PillarIndex {
|
||||
PointIndex m_index;
|
||||
using Mutex = ccr::BlockingMutex;
|
||||
mutable Mutex m_mutex;
|
||||
|
||||
public:
|
||||
|
||||
template<class...Args> inline void guarded_insert(Args&&...args)
|
||||
{
|
||||
std::lock_guard<Mutex> 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<Mutex> 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<Mutex> lck(m_mutex);
|
||||
m_index.foreach(fn);
|
||||
}
|
||||
|
||||
PointIndex guarded_clone()
|
||||
{
|
||||
std::lock_guard<Mutex> lck(m_mutex);
|
||||
return m_index;
|
||||
}
|
||||
};
|
||||
|
||||
// Helper function for pillar interconnection where pairs of already connected
|
||||
// pillars should be checked for not to be processed again. This can be done
|
||||
// in constant time with a 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. The hash value has to have twice as many bits as the
|
||||
// arguments need. If the same integral type is used for args and return val,
|
||||
// make sure the arguments use only the half of the type's bit depth.
|
||||
template<class I, class DoubleI = IntegerOnly<I>>
|
||||
IntegerOnly<DoubleI> pairhash(I a, I b)
|
||||
{
|
||||
using std::ceil; using std::log2; using std::max; using std::min;
|
||||
static const auto constexpr Ibits = int(sizeof(I) * CHAR_BIT);
|
||||
static const auto constexpr DoubleIbits = int(sizeof(DoubleI) * CHAR_BIT);
|
||||
static const auto constexpr shift = DoubleIbits / 2 < Ibits ? Ibits / 2 : Ibits;
|
||||
|
||||
I g = min(a, b), l = max(a, b);
|
||||
|
||||
// Assume the hash will fit into the output variable
|
||||
assert((g ? (ceil(log2(g))) : 0) <= shift);
|
||||
assert((l ? (ceil(log2(l))) : 0) <= shift);
|
||||
|
||||
return (DoubleI(g) << shift) + l;
|
||||
}
|
||||
|
||||
class SupportTreeBuildsteps {
|
||||
const SupportConfig& m_cfg;
|
||||
const EigenMesh3D& m_mesh;
|
||||
const std::vector<SupportPoint>& m_support_pts;
|
||||
|
||||
using PtIndices = std::vector<unsigned>;
|
||||
|
||||
PtIndices m_iheads; // support points with pinhead
|
||||
PtIndices m_iheadless; // headless support points
|
||||
|
||||
// supp. pts. connecting to model: point index and the ray hit data
|
||||
std::vector<std::pair<unsigned, EigenMesh3D::hit_result>> m_iheads_onmodel;
|
||||
|
||||
// normals for support points from model faces.
|
||||
PointSet m_support_nmls;
|
||||
|
||||
// Clusters of points which can reach the ground directly and can be
|
||||
// bridged to one central pillar
|
||||
std::vector<PtIndices> m_pillar_clusters;
|
||||
|
||||
// This algorithm uses the SupportTreeBuilder class to fill gradually
|
||||
// the support elements (heads, pillars, bridges, ...)
|
||||
SupportTreeBuilder& m_builder;
|
||||
|
||||
// support points in Eigen/IGL format
|
||||
PointSet m_points;
|
||||
|
||||
// throw if canceled: It will be called many times so a shorthand will
|
||||
// come in handy.
|
||||
ThrowOnCancel m_thr;
|
||||
|
||||
// A spatial index to easily find strong pillars to connect to.
|
||||
PillarIndex m_pillar_index;
|
||||
|
||||
// When bridging heads to pillars... TODO: find a cleaner solution
|
||||
ccr::BlockingMutex m_bridge_mutex;
|
||||
|
||||
inline double ray_mesh_intersect(const Vec3d& s,
|
||||
const Vec3d& dir)
|
||||
{
|
||||
return m_mesh.query_ray_hit(s, dir).distance();
|
||||
}
|
||||
|
||||
// This function will test if a future pinhead would not collide with the
|
||||
// model geometry. It does not take a 'Head' object because those are
|
||||
// created after this test. Parameters: s: The touching point on the model
|
||||
// surface. dir: This is the direction of the head from the pin to the back
|
||||
// r_pin, r_back: the radiuses of the pin and the back sphere width: This
|
||||
// is the full width from the pin center to the back center m: The object
|
||||
// mesh.
|
||||
// The return value is the hit result from the ray casting. If the starting
|
||||
// point was inside the model, an "invalid" hit_result will be returned
|
||||
// with a zero distance value instead of a NAN. This way the result can
|
||||
// be used safely for comparison with other distances.
|
||||
EigenMesh3D::hit_result pinhead_mesh_intersect(
|
||||
const Vec3d& s,
|
||||
const Vec3d& dir,
|
||||
double r_pin,
|
||||
double r_back,
|
||||
double width);
|
||||
|
||||
// Checking bridge (pillar and stick as well) intersection with the model.
|
||||
// If the function is used for headless sticks, the ins_check parameter
|
||||
// have to be true as the beginning of the stick might be inside the model
|
||||
// geometry.
|
||||
// The return value is the hit result from the ray casting. If the starting
|
||||
// point was inside the model, an "invalid" hit_result will be returned
|
||||
// with a zero distance value instead of a NAN. This way the result can
|
||||
// be used safely for comparison with other distances.
|
||||
EigenMesh3D::hit_result bridge_mesh_intersect(
|
||||
const Vec3d& s,
|
||||
const Vec3d& dir,
|
||||
double r,
|
||||
bool ins_check = false);
|
||||
|
||||
// Helper function for interconnecting two pillars with zig-zag bridges.
|
||||
bool interconnect(const Pillar& pillar, const Pillar& nextpillar);
|
||||
|
||||
// For connecting a head to a nearby pillar.
|
||||
bool connect_to_nearpillar(const Head& head, long nearpillar_id);
|
||||
|
||||
bool search_pillar_and_connect(const Head& head);
|
||||
|
||||
// This is a proxy function for pillar creation which will mind the gap
|
||||
// between the pad and the model bottom in zero elevation mode.
|
||||
void create_ground_pillar(const Vec3d &jp,
|
||||
const Vec3d &sourcedir,
|
||||
double radius,
|
||||
long head_id = ID_UNSET);
|
||||
public:
|
||||
SupportTreeBuildsteps(SupportTreeBuilder & builder, const SupportableMesh &sm);
|
||||
|
||||
// Now let's define the individual steps of the support generation algorithm
|
||||
|
||||
// Filtering step: here we will discard inappropriate support points
|
||||
// and decide the future of the appropriate ones. We will check if a
|
||||
// pinhead is applicable and adjust its angle at each support point. We
|
||||
// will also merge the support points that are just too close and can
|
||||
// be considered as one.
|
||||
void filter();
|
||||
|
||||
// Pinhead creation: based on the filtering results, the Head objects
|
||||
// will be constructed (together with their triangle meshes).
|
||||
void add_pinheads();
|
||||
|
||||
// Further classification of the support points with pinheads. If the
|
||||
// ground is directly reachable through a vertical line parallel to the
|
||||
// Z axis we consider a support point as pillar candidate. If touches
|
||||
// the model geometry, it will be marked as non-ground facing and
|
||||
// further steps will process it. Also, the pillars will be grouped
|
||||
// into clusters that can be interconnected with bridges. Elements of
|
||||
// these groups may or may not be interconnected. Here we only run the
|
||||
// clustering algorithm.
|
||||
void classify();
|
||||
|
||||
// Step: Routing the ground connected pinheads, and interconnecting
|
||||
// them with additional (angled) bridges. Not all of these pinheads
|
||||
// will be a full pillar (ground connected). Some will connect to a
|
||||
// nearby pillar using a bridge. The max number of such side-heads for
|
||||
// a central pillar is limited to avoid bad weight distribution.
|
||||
void routing_to_ground();
|
||||
|
||||
// Step: routing the pinheads that would connect to the model surface
|
||||
// along the Z axis downwards. For now these will actually be connected with
|
||||
// the model surface with a flipped pinhead. In the future here we could use
|
||||
// some smart algorithms to search for a safe path to the ground or to a
|
||||
// nearby pillar that can hold the supported weight.
|
||||
void routing_to_model();
|
||||
|
||||
void interconnect_pillars();
|
||||
|
||||
// Step: process the support points where there is not enough space for a
|
||||
// full pinhead. In this case we will use a rounded sphere as a touching
|
||||
// point and use a thinner bridge (let's call it a stick).
|
||||
void routing_headless ();
|
||||
|
||||
inline void merge_result() { m_builder.merged_mesh(); }
|
||||
|
||||
static bool execute(SupportTreeBuilder & builder, const SupportableMesh &sm);
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif // SLASUPPORTTREEALGORITHM_H
|
|
@ -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"
|
||||
|
@ -32,17 +32,19 @@
|
|||
|
||||
namespace Slic3r {
|
||||
|
||||
using SupportTreePtr = std::unique_ptr<sla::SLASupportTree>;
|
||||
|
||||
class SLAPrintObject::SupportData
|
||||
class SLAPrintObject::SupportData : public sla::SupportableMesh
|
||||
{
|
||||
public:
|
||||
sla::EigenMesh3D emesh; // index-triangle representation
|
||||
std::vector<sla::SupportPoint> support_points; // all the support points (manual/auto)
|
||||
SupportTreePtr support_tree_ptr; // the supports
|
||||
sla::SupportTree::UPtr support_tree_ptr; // the supports
|
||||
std::vector<ExPolygons> support_slices; // sliced supports
|
||||
|
||||
inline SupportData(const TriangleMesh &trmesh) : emesh(trmesh) {}
|
||||
|
||||
inline SupportData(const TriangleMesh &t): sla::SupportableMesh{t, {}, {}} {}
|
||||
|
||||
sla::SupportTree::UPtr &create_support_tree(const sla::JobController &ctl)
|
||||
{
|
||||
support_tree_ptr = sla::SupportTree::create(*this, ctl);
|
||||
return support_tree_ptr;
|
||||
}
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
@ -53,7 +55,7 @@ const std::array<unsigned, slaposCount> OBJ_STEP_LEVELS =
|
|||
30, // slaposObjectSlice,
|
||||
20, // slaposSupportPoints,
|
||||
10, // slaposSupportTree,
|
||||
10, // slaposBasePool,
|
||||
10, // slaposPad,
|
||||
30, // slaposSliceSupports,
|
||||
};
|
||||
|
||||
|
@ -64,7 +66,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:;
|
||||
}
|
||||
|
@ -583,7 +585,8 @@ bool is_zero_elevation(const SLAPrintObjectConfig &c) {
|
|||
// Compile the argument for support creation from the static print config.
|
||||
sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c) {
|
||||
sla::SupportConfig scfg;
|
||||
|
||||
|
||||
scfg.enabled = c.supports_enable.getBool();
|
||||
scfg.head_front_radius_mm = 0.5*c.support_head_front_diameter.getFloat();
|
||||
scfg.head_back_radius_mm = 0.5*c.support_pillar_diameter.getFloat();
|
||||
scfg.head_penetration_mm = c.support_head_penetration.getFloat();
|
||||
|
@ -612,12 +615,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 +632,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 +648,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 +672,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 +690,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 +883,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,
|
||||
|
@ -887,10 +893,10 @@ void SLAPrint::process()
|
|||
// Now let's extract the result.
|
||||
const std::vector<sla::SupportPoint>& points = auto_supports.output();
|
||||
this->throw_if_canceled();
|
||||
po.m_supportdata->support_points = points;
|
||||
po.m_supportdata->pts = points;
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "Automatic support points: "
|
||||
<< po.m_supportdata->support_points.size();
|
||||
<< po.m_supportdata->pts.size();
|
||||
|
||||
// Using RELOAD_SLA_SUPPORT_POINTS to tell the Plater to pass
|
||||
// the update status to GLGizmoSlaSupports
|
||||
|
@ -902,29 +908,19 @@ void SLAPrint::process()
|
|||
else {
|
||||
// There are either some points on the front-end, or the user
|
||||
// removed them on purpose. No calculation will be done.
|
||||
po.m_supportdata->support_points = po.transformed_support_points();
|
||||
po.m_supportdata->pts = po.transformed_support_points();
|
||||
}
|
||||
|
||||
// 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->pts,
|
||||
po.m_supportdata->emesh.ground_level(),
|
||||
tolerance);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -933,45 +929,31 @@ 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);
|
||||
|
||||
if(!po.m_config.supports_enable.getBool()) {
|
||||
|
||||
// Generate empty support tree. It can still host a pad
|
||||
po.m_supportdata->support_tree_ptr.reset(
|
||||
new SLASupportTree(po.m_supportdata->emesh.ground_level()));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sla::SupportConfig scfg = make_support_cfg(po.m_config);
|
||||
sla::Controller ctl;
|
||||
|
||||
po.m_supportdata->emesh.ground_level_offset(pcfg.wall_thickness_mm);
|
||||
|
||||
po.m_supportdata->cfg = make_support_cfg(po.m_config);
|
||||
|
||||
// scaling for the sub operations
|
||||
double d = ostepd * OBJ_STEP_LEVELS[slaposSupportTree] / 100.0;
|
||||
double init = m_report_status.status();
|
||||
JobController ctl;
|
||||
|
||||
ctl.statuscb = [this, d, init](unsigned st, const std::string &logmsg)
|
||||
{
|
||||
ctl.statuscb = [this, d, init](unsigned st, const std::string &logmsg) {
|
||||
double current = init + st * d;
|
||||
if(std::round(m_report_status.status()) < std::round(current))
|
||||
if (std::round(m_report_status.status()) < std::round(current))
|
||||
m_report_status(*this, current,
|
||||
OBJ_STEP_LABELS(slaposSupportTree),
|
||||
SlicingStatus::DEFAULT,
|
||||
logmsg);
|
||||
|
||||
SlicingStatus::DEFAULT, logmsg);
|
||||
};
|
||||
|
||||
ctl.stopcondition = [this](){ return canceled(); };
|
||||
ctl.stopcondition = [this]() { return canceled(); };
|
||||
ctl.cancelfn = [this]() { throw_if_canceled(); };
|
||||
|
||||
po.m_supportdata->support_tree_ptr.reset(
|
||||
new SLASupportTree(po.m_supportdata->support_points,
|
||||
po.m_supportdata->emesh, scfg, ctl));
|
||||
|
||||
po.m_supportdata->create_support_tree(ctl);
|
||||
|
||||
if (!po.m_config.supports_enable.getBool()) return;
|
||||
|
||||
throw_if_canceled();
|
||||
|
||||
|
@ -980,10 +962,9 @@ void SLAPrint::process()
|
|||
|
||||
// This is to prevent "Done." being displayed during merged_mesh()
|
||||
m_report_status(*this, -1, L("Visualizing supports"));
|
||||
po.m_supportdata->support_tree_ptr->merged_mesh();
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "Processed support point count "
|
||||
<< po.m_supportdata->support_points.size();
|
||||
<< po.m_supportdata->pts.size();
|
||||
|
||||
// Check the mesh for later troubleshooting.
|
||||
if(po.support_mesh().empty())
|
||||
|
@ -993,7 +974,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 +982,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 +996,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->retrieve_mesh(MeshType::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();
|
||||
}
|
||||
|
@ -1191,9 +1176,8 @@ void SLAPrint::process()
|
|||
{
|
||||
ClipperPolygon poly;
|
||||
|
||||
// We need to reverse if flpXY OR is_lefthanded is true but
|
||||
// not if both are true which is a logical inequality (XOR)
|
||||
bool needreverse = /*flpXY !=*/ is_lefthanded;
|
||||
// We need to reverse if is_lefthanded is true but
|
||||
bool needreverse = is_lefthanded;
|
||||
|
||||
// should be a move
|
||||
poly.Contour.reserve(polygon.contour.size() + 1);
|
||||
|
@ -1396,7 +1380,7 @@ void SLAPrint::process()
|
|||
if(canceled()) return;
|
||||
|
||||
// Set up the printer, allocate space for all the layers
|
||||
sla::SLARasterWriter &printer = init_printer();
|
||||
sla::RasterWriter &printer = init_printer();
|
||||
|
||||
auto lvlcnt = unsigned(m_printer_input.size());
|
||||
printer.layers(lvlcnt);
|
||||
|
@ -1416,11 +1400,9 @@ void SLAPrint::process()
|
|||
|
||||
SpinMutex slck;
|
||||
|
||||
auto orientation = get_printer_orientation();
|
||||
|
||||
// procedure to process one height level. This will run in parallel
|
||||
auto lvlfn =
|
||||
[this, &slck, &printer, increment, &dstatus, &pst, orientation]
|
||||
[this, &slck, &printer, increment, &dstatus, &pst]
|
||||
(unsigned level_id)
|
||||
{
|
||||
if(canceled()) return;
|
||||
|
@ -1431,7 +1413,7 @@ void SLAPrint::process()
|
|||
printer.begin_layer(level_id);
|
||||
|
||||
for(const ClipperLib::Polygon& poly : printlayer.transformed_slices())
|
||||
printer.draw_polygon(poly, level_id, orientation);
|
||||
printer.draw_polygon(poly, level_id);
|
||||
|
||||
// Finish the layer for later saving it.
|
||||
printer.finish_layer(level_id);
|
||||
|
@ -1459,7 +1441,7 @@ void SLAPrint::process()
|
|||
tbb::parallel_for<unsigned, decltype(lvlfn)>(0, lvlcnt, lvlfn);
|
||||
|
||||
// Set statistics values to the printer
|
||||
sla::SLARasterWriter::PrintStatistics stats;
|
||||
sla::RasterWriter::PrintStatistics stats;
|
||||
stats.used_material = (m_print_statistics.objects_used_material +
|
||||
m_print_statistics.support_used_material) /
|
||||
1000;
|
||||
|
@ -1478,12 +1460,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
|
||||
|
@ -1654,12 +1636,11 @@ bool SLAPrint::invalidate_state_by_config_options(const std::vector<t_config_opt
|
|||
return invalidated;
|
||||
}
|
||||
|
||||
sla::SLARasterWriter & SLAPrint::init_printer()
|
||||
sla::RasterWriter & SLAPrint::init_printer()
|
||||
{
|
||||
sla::Raster::Resolution res;
|
||||
sla::Raster::PixelDim pxdim;
|
||||
std::array<bool, 2> mirror;
|
||||
double gamma;
|
||||
|
||||
double w = m_printer_config.display_width.getFloat();
|
||||
double h = m_printer_config.display_height.getFloat();
|
||||
|
@ -1669,20 +1650,18 @@ sla::SLARasterWriter & SLAPrint::init_printer()
|
|||
mirror[X] = m_printer_config.display_mirror_x.getBool();
|
||||
mirror[Y] = m_printer_config.display_mirror_y.getBool();
|
||||
|
||||
if (get_printer_orientation() == sla::SLARasterWriter::roPortrait) {
|
||||
auto orientation = get_printer_orientation();
|
||||
if (orientation == sla::Raster::roPortrait) {
|
||||
std::swap(w, h);
|
||||
std::swap(pw, ph);
|
||||
|
||||
// XY flipping implicitly does an X mirror
|
||||
mirror[X] = !mirror[X];
|
||||
}
|
||||
|
||||
res = sla::Raster::Resolution{pw, ph};
|
||||
pxdim = sla::Raster::PixelDim{w / pw, h / ph};
|
||||
|
||||
gamma = m_printer_config.gamma_correction.getFloat();
|
||||
|
||||
m_printer.reset(new sla::SLARasterWriter(res, pxdim, mirror, gamma));
|
||||
sla::Raster::Trafo tr{orientation, mirror};
|
||||
tr.gamma = m_printer_config.gamma_correction.getFloat();
|
||||
|
||||
m_printer.reset(new sla::RasterWriter(res, pxdim, tr));
|
||||
m_printer->set_config(m_full_print_config);
|
||||
return *m_printer;
|
||||
}
|
||||
|
@ -1730,6 +1709,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 +1734,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 +1743,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 +1763,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 +1794,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 +1806,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;
|
||||
|
@ -1866,7 +1847,7 @@ const SliceRecord SliceRecord::EMPTY(0, std::nanf(""), 0.f);
|
|||
|
||||
const std::vector<sla::SupportPoint>& SLAPrintObject::get_support_points() const
|
||||
{
|
||||
return m_supportdata? m_supportdata->support_points : EMPTY_SUPPORT_POINTS;
|
||||
return m_supportdata? m_supportdata->pts : EMPTY_SUPPORT_POINTS;
|
||||
}
|
||||
|
||||
const std::vector<ExPolygons> &SLAPrintObject::get_support_slices() const
|
||||
|
@ -1896,7 +1877,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 +1889,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();
|
||||
|
@ -1917,18 +1898,20 @@ TriangleMesh SLAPrintObject::get_mesh(SLAPrintObjectStep step) const
|
|||
|
||||
const TriangleMesh& SLAPrintObject::support_mesh() const
|
||||
{
|
||||
if(m_config.supports_enable.getBool() && m_supportdata &&
|
||||
m_supportdata->support_tree_ptr) {
|
||||
return m_supportdata->support_tree_ptr->merged_mesh();
|
||||
}
|
||||
|
||||
sla::SupportTree::UPtr &stree = m_supportdata->support_tree_ptr;
|
||||
|
||||
if(m_config.supports_enable.getBool() && m_supportdata && stree)
|
||||
return stree->retrieve_mesh(sla::MeshType::Support);
|
||||
|
||||
return EMPTY_MESH;
|
||||
}
|
||||
|
||||
const TriangleMesh& SLAPrintObject::pad_mesh() const
|
||||
{
|
||||
if(m_config.pad_enable.getBool() && m_supportdata && m_supportdata->support_tree_ptr)
|
||||
return m_supportdata->support_tree_ptr->get_pad();
|
||||
sla::SupportTree::UPtr &stree = m_supportdata->support_tree_ptr;
|
||||
|
||||
if(m_config.pad_enable.getBool() && m_supportdata && stree)
|
||||
return stree->retrieve_mesh(sla::MeshType::Pad);
|
||||
|
||||
return EMPTY_MESH;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
@ -440,7 +440,7 @@ private:
|
|||
std::vector<PrintLayer> m_printer_input;
|
||||
|
||||
// The printer itself
|
||||
std::unique_ptr<sla::SLARasterWriter> m_printer;
|
||||
std::unique_ptr<sla::RasterWriter> m_printer;
|
||||
|
||||
// Estimated print time, material consumed.
|
||||
SLAPrintStatistics m_print_statistics;
|
||||
|
@ -459,14 +459,13 @@ private:
|
|||
double status() const { return m_st; }
|
||||
} m_report_status;
|
||||
|
||||
sla::SLARasterWriter &init_printer();
|
||||
sla::RasterWriter &init_printer();
|
||||
|
||||
inline sla::SLARasterWriter::Orientation get_printer_orientation() const
|
||||
inline sla::Raster::Orientation get_printer_orientation() const
|
||||
{
|
||||
auto ro = m_printer_config.display_orientation.getInt();
|
||||
return ro == sla::SLARasterWriter::roPortrait ?
|
||||
sla::SLARasterWriter::roPortrait :
|
||||
sla::SLARasterWriter::roLandscape;
|
||||
return ro == sla::Raster::roPortrait ? sla::Raster::roPortrait :
|
||||
sla::Raster::roLandscape;
|
||||
}
|
||||
|
||||
friend SLAPrintObject;
|
||||
|
|
|
@ -445,8 +445,8 @@ Polygons collect_region_slices_by_type(const Layer &layer, SurfaceType surface_t
|
|||
Polygons collect_slices_outer(const Layer &layer)
|
||||
{
|
||||
Polygons out;
|
||||
out.reserve(out.size() + layer.slices.expolygons.size());
|
||||
for (const ExPolygon &expoly : layer.slices.expolygons)
|
||||
out.reserve(out.size() + layer.slices.size());
|
||||
for (const ExPolygon &expoly : layer.slices)
|
||||
out.emplace_back(expoly.contour);
|
||||
return out;
|
||||
}
|
||||
|
@ -907,9 +907,13 @@ namespace SupportMaterialInternal {
|
|||
polyline.extend_start(fw);
|
||||
polyline.extend_end(fw);
|
||||
// Is the straight perimeter segment supported at both sides?
|
||||
if (lower_layer.slices.contains(polyline.first_point()) && lower_layer.slices.contains(polyline.last_point()))
|
||||
// Offset a polyline into a thick line.
|
||||
polygons_append(bridges, offset(polyline, 0.5f * w + 10.f));
|
||||
for (size_t i = 0; i < lower_layer.slices.size(); ++ i)
|
||||
if (lower_layer.slices_bboxes[i].contains(polyline.first_point()) && lower_layer.slices_bboxes[i].contains(polyline.last_point()) &&
|
||||
lower_layer.slices[i].contains(polyline.first_point()) && lower_layer.slices[i].contains(polyline.last_point())) {
|
||||
// Offset a polyline into a thick line.
|
||||
polygons_append(bridges, offset(polyline, 0.5f * w + 10.f));
|
||||
break;
|
||||
}
|
||||
}
|
||||
bridges = union_(bridges);
|
||||
}
|
||||
|
@ -994,7 +998,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
|
|||
// inflate the polygons over and over.
|
||||
Polygons &covered = buildplate_covered[layer_id];
|
||||
covered = buildplate_covered[layer_id - 1];
|
||||
polygons_append(covered, offset(lower_layer.slices.expolygons, scale_(0.01)));
|
||||
polygons_append(covered, offset(lower_layer.slices, scale_(0.01)));
|
||||
covered = union_(covered, false); // don't apply the safety offset.
|
||||
}
|
||||
}
|
||||
|
@ -1023,7 +1027,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
|
|||
Polygons contact_polygons;
|
||||
Polygons slices_margin_cached;
|
||||
float slices_margin_cached_offset = -1.;
|
||||
Polygons lower_layer_polygons = (layer_id == 0) ? Polygons() : to_polygons(object.layers()[layer_id-1]->slices.expolygons);
|
||||
Polygons lower_layer_polygons = (layer_id == 0) ? Polygons() : to_polygons(object.layers()[layer_id-1]->slices);
|
||||
// Offset of the lower layer, to trim the support polygons with to calculate dense supports.
|
||||
float no_interface_offset = 0.f;
|
||||
if (layer_id == 0) {
|
||||
|
@ -1162,7 +1166,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
|
|||
slices_margin_cached_offset = slices_margin_offset;
|
||||
slices_margin_cached = (slices_margin_offset == 0.f) ?
|
||||
lower_layer_polygons :
|
||||
offset2(to_polygons(lower_layer.slices.expolygons), - no_interface_offset * 0.5f, slices_margin_offset + no_interface_offset * 0.5f, SUPPORT_SURFACES_OFFSET_PARAMETERS);
|
||||
offset2(to_polygons(lower_layer.slices), - no_interface_offset * 0.5f, slices_margin_offset + no_interface_offset * 0.5f, SUPPORT_SURFACES_OFFSET_PARAMETERS);
|
||||
if (! buildplate_covered.empty()) {
|
||||
// Trim the inflated contact surfaces by the top surfaces as well.
|
||||
polygons_append(slices_margin_cached, buildplate_covered[layer_id]);
|
||||
|
@ -1468,7 +1472,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta
|
|||
svg.draw(union_ex(top, false), "blue", 0.5f);
|
||||
svg.draw(union_ex(projection_raw, true), "red", 0.5f);
|
||||
svg.draw_outline(union_ex(projection_raw, true), "red", "blue", scale_(0.1f));
|
||||
svg.draw(layer.slices.expolygons, "green", 0.5f);
|
||||
svg.draw(layer.slices, "green", 0.5f);
|
||||
}
|
||||
#endif /* SLIC3R_DEBUG */
|
||||
|
||||
|
@ -1568,8 +1572,8 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta
|
|||
Polygons &layer_support_area = layer_support_areas[layer_id];
|
||||
task_group.run([this, &projection, &projection_raw, &layer, &layer_support_area, layer_id] {
|
||||
// Remove the areas that touched from the projection that will continue on next, lower, top surfaces.
|
||||
// Polygons trimming = union_(to_polygons(layer.slices.expolygons), touching, true);
|
||||
Polygons trimming = offset(layer.slices.expolygons, float(SCALED_EPSILON));
|
||||
// Polygons trimming = union_(to_polygons(layer.slices), touching, true);
|
||||
Polygons trimming = offset(layer.slices, float(SCALED_EPSILON));
|
||||
projection = diff(projection_raw, trimming, false);
|
||||
#ifdef SLIC3R_DEBUG
|
||||
{
|
||||
|
@ -2101,7 +2105,7 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object(
|
|||
const Layer &object_layer = *object.layers()[i];
|
||||
if (object_layer.print_z - object_layer.height > support_layer.print_z + gap_extra_above - EPSILON)
|
||||
break;
|
||||
polygons_append(polygons_trimming, offset(object_layer.slices.expolygons, gap_xy_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS));
|
||||
polygons_append(polygons_trimming, offset(object_layer.slices, gap_xy_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS));
|
||||
}
|
||||
if (! m_slicing_params.soluble_interface) {
|
||||
// Collect all bottom surfaces, which will be extruded with a bridging flow.
|
||||
|
@ -2214,7 +2218,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_raf
|
|||
// Expand the bases of the support columns in the 1st layer.
|
||||
columns_base->polygons = diff(
|
||||
offset(columns_base->polygons, inflate_factor_1st_layer),
|
||||
offset(m_object->layers().front()->slices.expolygons, (float)scale_(m_gap_xy), SUPPORT_SURFACES_OFFSET_PARAMETERS));
|
||||
offset(m_object->layers().front()->slices, (float)scale_(m_gap_xy), SUPPORT_SURFACES_OFFSET_PARAMETERS));
|
||||
if (contacts != nullptr)
|
||||
columns_base->polygons = diff(columns_base->polygons, interface_polygons);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -3,116 +3,232 @@
|
|||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <chrono>
|
||||
#include <cassert>
|
||||
#include <ctime>
|
||||
#include <cstdio>
|
||||
|
||||
//#include <boost/date_time/local_time/local_time.hpp>
|
||||
//#include <boost/chrono.hpp>
|
||||
#ifdef _MSC_VER
|
||||
#include <map>
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#undef WIN32_LEAN_AND_MEAN
|
||||
#endif /* WIN32 */
|
||||
#include "libslic3r/Utils.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace Utils {
|
||||
|
||||
namespace {
|
||||
// "YYYY-MM-DD at HH:MM::SS [UTC]"
|
||||
// If TimeZone::utc is used with the conversion functions, it will append the
|
||||
// UTC letters to the end.
|
||||
static const constexpr char *const SLICER_UTC_TIME_FMT = "%Y-%m-%d at %T";
|
||||
|
||||
// FIXME: after we switch to gcc > 4.9 on the build server, please remove me
|
||||
#if defined(__GNUC__) && __GNUC__ <= 4
|
||||
std::string put_time(const std::tm *tm, const char *fmt)
|
||||
// ISO8601Z representation of time, without time zone info
|
||||
static const constexpr char *const ISO8601Z_TIME_FMT = "%Y%m%dT%H%M%SZ";
|
||||
|
||||
static const char * get_fmtstr(TimeFormat fmt)
|
||||
{
|
||||
static const constexpr int MAX_CHARS = 200;
|
||||
char out[MAX_CHARS];
|
||||
std::strftime(out, MAX_CHARS, fmt, tm);
|
||||
return out;
|
||||
switch (fmt) {
|
||||
case TimeFormat::gcode: return SLICER_UTC_TIME_FMT;
|
||||
case TimeFormat::iso8601Z: return ISO8601Z_TIME_FMT;
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
#else
|
||||
auto put_time(const std::tm *tm, const char *fmt) -> decltype (std::put_time(tm, fmt))
|
||||
|
||||
namespace __get_put_time_emulation {
|
||||
// FIXME: Implementations with the cpp11 put_time and get_time either not
|
||||
// compile or do not pass the tests on the build server. If we switch to newer
|
||||
// compilers, this namespace can be deleted with all its content.
|
||||
|
||||
#ifdef _MSC_VER
|
||||
// VS2019 implementation fails with ISO8601Z_TIME_FMT.
|
||||
// VS2019 does not have std::strptime either. See bug:
|
||||
// https://developercommunity.visualstudio.com/content/problem/140618/c-stdget-time-not-parsing-correctly.html
|
||||
|
||||
static const std::map<std::string, std::string> sscanf_fmt_map = {
|
||||
{SLICER_UTC_TIME_FMT, "%04d-%02d-%02d at %02d:%02d:%02d"},
|
||||
{std::string(SLICER_UTC_TIME_FMT) + " UTC", "%04d-%02d-%02d at %02d:%02d:%02d UTC"},
|
||||
{ISO8601Z_TIME_FMT, "%04d%02d%02dT%02d%02d%02dZ"}
|
||||
};
|
||||
|
||||
static const char * strptime(const char *str, const char *const fmt, std::tm *tms)
|
||||
{
|
||||
return std::put_time(tm, fmt);
|
||||
auto it = sscanf_fmt_map.find(fmt);
|
||||
if (it == sscanf_fmt_map.end()) return nullptr;
|
||||
|
||||
int y, M, d, h, m, s;
|
||||
if (sscanf(str, it->second.c_str(), &y, &M, &d, &h, &m, &s) != 6)
|
||||
return nullptr;
|
||||
|
||||
tms->tm_year = y - 1900; // Year since 1900
|
||||
tms->tm_mon = M - 1; // 0-11
|
||||
tms->tm_mday = d; // 1-31
|
||||
tms->tm_hour = h; // 0-23
|
||||
tms->tm_min = m; // 0-59
|
||||
tms->tm_sec = s; // 0-61 (0-60 in C++11)
|
||||
|
||||
return str; // WARN strptime return val should point after the parsed string
|
||||
}
|
||||
#endif
|
||||
|
||||
template<class Ttm>
|
||||
struct GetPutTimeReturnT {
|
||||
Ttm *tms;
|
||||
const char *fmt;
|
||||
GetPutTimeReturnT(Ttm *_tms, const char *_fmt): tms(_tms), fmt(_fmt) {}
|
||||
};
|
||||
|
||||
using GetTimeReturnT = GetPutTimeReturnT<std::tm>;
|
||||
using PutTimeReturnT = GetPutTimeReturnT<const std::tm>;
|
||||
|
||||
std::ostream &operator<<(std::ostream &stream, PutTimeReturnT &&pt)
|
||||
{
|
||||
static const constexpr int MAX_CHARS = 200;
|
||||
char _out[MAX_CHARS];
|
||||
strftime(_out, MAX_CHARS, pt.fmt, pt.tms);
|
||||
stream << _out;
|
||||
return stream;
|
||||
}
|
||||
|
||||
time_t parse_time_ISO8601Z(const std::string &sdate)
|
||||
inline PutTimeReturnT put_time(const std::tm *tms, const char *fmt)
|
||||
{
|
||||
int y, M, d, h, m, s;
|
||||
if (sscanf(sdate.c_str(), "%04d%02d%02dT%02d%02d%02dZ", &y, &M, &d, &h, &m, &s) != 6)
|
||||
return time_t(-1);
|
||||
struct tm tms;
|
||||
tms.tm_year = y - 1900; // Year since 1900
|
||||
tms.tm_mon = M - 1; // 0-11
|
||||
tms.tm_mday = d; // 1-31
|
||||
tms.tm_hour = h; // 0-23
|
||||
tms.tm_min = m; // 0-59
|
||||
tms.tm_sec = s; // 0-61 (0-60 in C++11)
|
||||
return {tms, fmt};
|
||||
}
|
||||
|
||||
std::istream &operator>>(std::istream &stream, GetTimeReturnT &>)
|
||||
{
|
||||
std::string line;
|
||||
std::getline(stream, line);
|
||||
|
||||
if (strptime(line.c_str(), gt.fmt, gt.tms) == nullptr)
|
||||
stream.setstate(std::ios::failbit);
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
inline GetTimeReturnT get_time(std::tm *tms, const char *fmt)
|
||||
{
|
||||
return {tms, fmt};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// Platform independent versions of gmtime and localtime. Completely thread
|
||||
// safe only on Linux. MSVC gtime_s and localtime_s sets global errno thus not
|
||||
// thread safe.
|
||||
struct std::tm * _gmtime_r(const time_t *timep, struct tm *result)
|
||||
{
|
||||
assert(timep != nullptr && result != nullptr);
|
||||
#ifdef WIN32
|
||||
return _mkgmtime(&tms);
|
||||
time_t t = *timep;
|
||||
gmtime_s(result, &t);
|
||||
return result;
|
||||
#else
|
||||
return gmtime_r(timep, result);
|
||||
#endif
|
||||
}
|
||||
|
||||
struct std::tm * _localtime_r(const time_t *timep, struct tm *result)
|
||||
{
|
||||
assert(timep != nullptr && result != nullptr);
|
||||
#ifdef WIN32
|
||||
// Converts a time_t time value to a tm structure, and corrects for the
|
||||
// local time zone.
|
||||
time_t t = *timep;
|
||||
localtime_s(result, &t);
|
||||
return result;
|
||||
#else
|
||||
return localtime_r(timep, result);
|
||||
#endif
|
||||
}
|
||||
|
||||
time_t _mktime(const struct std::tm *tms)
|
||||
{
|
||||
assert(tms != nullptr);
|
||||
std::tm _tms = *tms;
|
||||
return mktime(&_tms);
|
||||
}
|
||||
|
||||
time_t _timegm(const struct std::tm *tms)
|
||||
{
|
||||
std::tm _tms = *tms;
|
||||
#ifdef WIN32
|
||||
return _mkgmtime(&_tms);
|
||||
#else /* WIN32 */
|
||||
return timegm(&tms);
|
||||
return timegm(&_tms);
|
||||
#endif /* WIN32 */
|
||||
}
|
||||
|
||||
std::string format_time_ISO8601Z(time_t time)
|
||||
std::string process_format(const char *fmt, TimeZone zone)
|
||||
{
|
||||
struct tm tms;
|
||||
#ifdef WIN32
|
||||
gmtime_s(&tms, &time);
|
||||
#else
|
||||
gmtime_r(&time, &tms);
|
||||
#endif
|
||||
char buf[128];
|
||||
sprintf(buf, "%04d%02d%02dT%02d%02d%02dZ",
|
||||
tms.tm_year + 1900,
|
||||
tms.tm_mon + 1,
|
||||
tms.tm_mday,
|
||||
tms.tm_hour,
|
||||
tms.tm_min,
|
||||
tms.tm_sec);
|
||||
return buf;
|
||||
std::string fmtstr(fmt);
|
||||
|
||||
if (fmtstr == SLICER_UTC_TIME_FMT && zone == TimeZone::utc)
|
||||
fmtstr += " UTC";
|
||||
|
||||
return fmtstr;
|
||||
}
|
||||
|
||||
std::string format_local_date_time(time_t time)
|
||||
{
|
||||
struct tm tms;
|
||||
#ifdef WIN32
|
||||
// Converts a time_t time value to a tm structure, and corrects for the local time zone.
|
||||
localtime_s(&tms, &time);
|
||||
#else
|
||||
localtime_r(&time, &tms);
|
||||
#endif
|
||||
char buf[80];
|
||||
strftime(buf, 80, "%x %X", &tms);
|
||||
return buf;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
time_t get_current_time_utc()
|
||||
{
|
||||
{
|
||||
using clk = std::chrono::system_clock;
|
||||
return clk::to_time_t(clk::now());
|
||||
}
|
||||
|
||||
static std::string tm2str(const std::tm *tm, const char *fmt)
|
||||
static std::string tm2str(const std::tm *tms, const char *fmt)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << put_time(tm, fmt);
|
||||
ss.imbue(std::locale("C"));
|
||||
ss << __get_put_time_emulation::put_time(tms, fmt);
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string time2str(const time_t &t, TimeZone zone, const char *fmt)
|
||||
std::string time2str(const time_t &t, TimeZone zone, TimeFormat fmt)
|
||||
{
|
||||
std::string ret;
|
||||
|
||||
std::tm tms = {};
|
||||
tms.tm_isdst = -1;
|
||||
std::string fmtstr = process_format(get_fmtstr(fmt), zone);
|
||||
|
||||
switch (zone) {
|
||||
case TimeZone::local: ret = tm2str(std::localtime(&t), fmt); break;
|
||||
case TimeZone::utc: ret = tm2str(std::gmtime(&t), fmt) + " UTC"; break;
|
||||
case TimeZone::local:
|
||||
ret = tm2str(_localtime_r(&t, &tms), fmtstr.c_str()); break;
|
||||
case TimeZone::utc:
|
||||
ret = tm2str(_gmtime_r(&t, &tms), fmtstr.c_str()); break;
|
||||
}
|
||||
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static time_t str2time(std::istream &stream, TimeZone zone, const char *fmt)
|
||||
{
|
||||
std::tm tms = {};
|
||||
tms.tm_isdst = -1;
|
||||
|
||||
stream >> __get_put_time_emulation::get_time(&tms, fmt);
|
||||
time_t ret = time_t(-1);
|
||||
|
||||
switch (zone) {
|
||||
case TimeZone::local: ret = _mktime(&tms); break;
|
||||
case TimeZone::utc: ret = _timegm(&tms); break;
|
||||
}
|
||||
|
||||
if (stream.fail() || ret < time_t(0)) ret = time_t(-1);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
time_t str2time(const std::string &str, TimeZone zone, TimeFormat fmt)
|
||||
{
|
||||
std::string fmtstr = process_format(get_fmtstr(fmt), zone).c_str();
|
||||
std::stringstream ss(str);
|
||||
|
||||
ss.imbue(std::locale("C"));
|
||||
return str2time(ss, zone, fmtstr.c_str());
|
||||
}
|
||||
|
||||
}; // namespace Utils
|
||||
}; // namespace Slic3r
|
||||
|
|
|
@ -7,41 +7,61 @@
|
|||
namespace Slic3r {
|
||||
namespace Utils {
|
||||
|
||||
// Utilities to convert an UTC time_t to/from an ISO8601 time format,
|
||||
// useful for putting timestamps into file and directory names.
|
||||
// Returns (time_t)-1 on error.
|
||||
time_t parse_time_ISO8601Z(const std::string &s);
|
||||
std::string format_time_ISO8601Z(time_t time);
|
||||
|
||||
// Format the date and time from an UTC time according to the active locales and a local time zone.
|
||||
// TODO: make sure time2str is a suitable replacement
|
||||
std::string format_local_date_time(time_t time);
|
||||
|
||||
// There is no gmtime() on windows.
|
||||
// Should be thread safe.
|
||||
time_t get_current_time_utc();
|
||||
|
||||
const constexpr char *const SLIC3R_TIME_FMT = "%Y-%m-%d at %T";
|
||||
|
||||
enum class TimeZone { local, utc };
|
||||
enum class TimeFormat { gcode, iso8601Z };
|
||||
|
||||
std::string time2str(const time_t &t, TimeZone zone, const char *fmt = SLIC3R_TIME_FMT);
|
||||
// time_t to string functions...
|
||||
|
||||
inline std::string current_time2str(TimeZone zone, const char *fmt = SLIC3R_TIME_FMT)
|
||||
std::string time2str(const time_t &t, TimeZone zone, TimeFormat fmt);
|
||||
|
||||
inline std::string time2str(TimeZone zone, TimeFormat fmt)
|
||||
{
|
||||
return time2str(get_current_time_utc(), zone, fmt);
|
||||
}
|
||||
|
||||
inline std::string current_local_time2str(const char * fmt = SLIC3R_TIME_FMT)
|
||||
inline std::string utc_timestamp(time_t t)
|
||||
{
|
||||
return current_time2str(TimeZone::local, fmt);
|
||||
return time2str(t, TimeZone::utc, TimeFormat::gcode);
|
||||
}
|
||||
|
||||
inline std::string current_utc_time2str(const char * fmt = SLIC3R_TIME_FMT)
|
||||
inline std::string utc_timestamp()
|
||||
{
|
||||
return current_time2str(TimeZone::utc, fmt);
|
||||
return utc_timestamp(get_current_time_utc());
|
||||
}
|
||||
|
||||
}; // namespace Utils
|
||||
}; // namespace Slic3r
|
||||
// String to time_t function. Returns time_t(-1) if fails to parse the input.
|
||||
time_t str2time(const std::string &str, TimeZone zone, TimeFormat fmt);
|
||||
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
// Utilities to convert an UTC time_t to/from an ISO8601 time format,
|
||||
// useful for putting timestamps into file and directory names.
|
||||
// Returns (time_t)-1 on error.
|
||||
|
||||
// Use these functions to convert safely to and from the ISO8601 format on
|
||||
// all platforms
|
||||
|
||||
inline std::string iso_utc_timestamp(time_t t)
|
||||
{
|
||||
return time2str(t, TimeZone::utc, TimeFormat::iso8601Z);
|
||||
}
|
||||
|
||||
inline std::string iso_utc_timestamp()
|
||||
{
|
||||
return iso_utc_timestamp(get_current_time_utc());
|
||||
}
|
||||
|
||||
inline time_t parse_iso_utc_timestamp(const std::string &str)
|
||||
{
|
||||
return str2time(str, TimeZone::utc, TimeFormat::iso8601Z);
|
||||
}
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
} // namespace Utils
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_Utils_Time_hpp_ */
|
||||
|
|
|
@ -236,7 +236,7 @@ bool TriangleMesh::needed_repair() const
|
|||
|| this->stl.stats.backwards_edges > 0;
|
||||
}
|
||||
|
||||
void TriangleMesh::WriteOBJFile(const char* output_file)
|
||||
void TriangleMesh::WriteOBJFile(const char* output_file) const
|
||||
{
|
||||
its_write_obj(this->its, output_file);
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ public:
|
|||
float volume();
|
||||
void check_topology();
|
||||
bool is_manifold() const { return this->stl.stats.connected_facets_3_edge == (int)this->stl.stats.number_of_facets; }
|
||||
void WriteOBJFile(const char* output_file);
|
||||
void WriteOBJFile(const char* output_file) const;
|
||||
void scale(float factor);
|
||||
void scale(const Vec3d &versor);
|
||||
void translate(float x, float y, float z);
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include <utility>
|
||||
#include <functional>
|
||||
#include <type_traits>
|
||||
#include <system_error>
|
||||
|
||||
#include "libslic3r.h"
|
||||
|
||||
|
|
|
@ -543,7 +543,7 @@ std::string string_printf(const char *format, ...)
|
|||
|
||||
std::string header_slic3r_generated()
|
||||
{
|
||||
return std::string("generated by " SLIC3R_APP_NAME " " SLIC3R_VERSION " on " ) + Utils::current_utc_time2str();
|
||||
return std::string("generated by " SLIC3R_APP_NAME " " SLIC3R_VERSION " on " ) + Utils::utc_timestamp();
|
||||
}
|
||||
|
||||
unsigned get_current_pid()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue