Merge remote-tracking branch 'origin/master' into ys_resin_cost

This commit is contained in:
YuSanka 2019-11-08 19:33:18 +01:00
commit 49175c3112
306 changed files with 91525 additions and 9504 deletions

View file

@ -375,7 +375,7 @@ public:
for(unsigned idx = 0; idx < fixeditems.size(); ++idx) {
Item& itm = fixeditems[idx];
itm.markAsFixed();
itm.markAsFixedInBin(itm.binId());
}
m_pck.configure(m_pconf);

View file

@ -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;
};

View file

@ -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)

View file

@ -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;

View file

@ -22,6 +22,8 @@ add_library(libslic3r STATIC
Config.hpp
EdgeGrid.cpp
EdgeGrid.hpp
ElephantFootCompensation.cpp
ElephantFootCompensation.hpp
ExPolygon.cpp
ExPolygon.hpp
ExPolygonCollection.cpp
@ -71,6 +73,8 @@ add_library(libslic3r STATIC
Format/STL.hpp
GCode/Analyzer.cpp
GCode/Analyzer.hpp
GCode/ThumbnailData.cpp
GCode/ThumbnailData.hpp
GCode/CoolingBuffer.cpp
GCode/CoolingBuffer.hpp
GCode/PostProcessor.cpp
@ -100,7 +104,7 @@ add_library(libslic3r STATIC
Geometry.cpp
Geometry.hpp
Int128.hpp
# KdTree.hpp
KDTreeIndirect.hpp
Layer.cpp
Layer.hpp
LayerRegion.cpp
@ -131,8 +135,6 @@ add_library(libslic3r STATIC
PolygonTrimmer.hpp
Polyline.cpp
Polyline.hpp
PolylineCollection.cpp
PolylineCollection.hpp
Print.cpp
Print.hpp
PrintBase.cpp
@ -142,6 +144,8 @@ add_library(libslic3r STATIC
PrintObject.cpp
PrintRegion.cpp
Semver.cpp
ShortestPath.cpp
ShortestPath.hpp
SLAPrint.cpp
SLAPrint.hpp
SLA/SLAAutoSupports.hpp
@ -176,8 +180,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
@ -189,6 +198,8 @@ add_library(libslic3r STATIC
SLA/SLARaster.cpp
SLA/SLARasterWriter.hpp
SLA/SLARasterWriter.cpp
SLA/ConcaveHull.hpp
SLA/ConcaveHull.cpp
)
encoding_check(libslic3r)
@ -197,7 +208,7 @@ if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY)
add_precompiled_header(libslic3r pchheader.hpp FORCEINCLUDE)
endif ()
target_compile_definitions(libslic3r PUBLIC -DUSE_TBB)
target_compile_definitions(libslic3r PUBLIC -DUSE_TBB -DTBB_USE_CAPTURED_EXCEPTION=0)
target_include_directories(libslic3r PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${LIBNEST2D_INCLUDES} PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
target_link_libraries(libslic3r
libnest2d
@ -214,7 +225,9 @@ target_link_libraries(libslic3r
poly2tri
qhull
semver
tbb
TBB::tbb
# OpenVDB::openvdb
${CMAKE_DL_LIBS}
)
if(WIN32)

View file

@ -1,5 +1,6 @@
#include "ClipperUtils.hpp"
#include "Geometry.hpp"
#include "ShortestPath.hpp"
// #define CLIPPER_UTILS_DEBUG
@ -106,8 +107,7 @@ void AddOuterPolyNodeToExPolygons(ClipperLib::PolyNode& polynode, ExPolygons* ex
}
}
ExPolygons
PolyTreeToExPolygons(ClipperLib::PolyTree& polytree)
ExPolygons PolyTreeToExPolygons(ClipperLib::PolyTree& polytree)
{
ExPolygons retval;
for (int i = 0; i < polytree.ChildCount(); ++i)
@ -150,8 +150,7 @@ Slic3r::Polylines ClipperPaths_to_Slic3rPolylines(const ClipperLib::Paths &input
return retval;
}
ExPolygons
ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input)
ExPolygons ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input)
{
// init Clipper
ClipperLib::Clipper clipper;
@ -166,8 +165,7 @@ ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input)
return PolyTreeToExPolygons(polytree);
}
ClipperLib::Path
Slic3rMultiPoint_to_ClipperPath(const MultiPoint &input)
ClipperLib::Path Slic3rMultiPoint_to_ClipperPath(const MultiPoint &input)
{
ClipperLib::Path retval;
for (Points::const_iterator pit = input.points.begin(); pit != input.points.end(); ++pit)
@ -175,8 +173,7 @@ Slic3rMultiPoint_to_ClipperPath(const MultiPoint &input)
return retval;
}
ClipperLib::Path
Slic3rMultiPoint_to_ClipperPath_reversed(const Slic3r::MultiPoint &input)
ClipperLib::Path Slic3rMultiPoint_to_ClipperPath_reversed(const Slic3r::MultiPoint &input)
{
ClipperLib::Path output;
output.reserve(input.points.size());
@ -193,6 +190,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;
@ -471,14 +481,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_) {
@ -505,7 +517,7 @@ _clipper_do(const ClipperLib::ClipType clipType, const Polygons &subject,
// Fix of #117: A large fractal pyramid takes ages to slice
// The Clipper library has difficulties processing overlapping polygons.
// Namely, the function Clipper::JoinCommonEdges() has potentially a terrible time complexity if the output
// Namely, the function ClipperLib::JoinCommonEdges() has potentially a terrible time complexity if the output
// of the operation is of the PolyTree type.
// This function implmenets a following workaround:
// 1) Peform the Clipper operation with the output to Paths. This method handles overlaps in a reasonable time.
@ -647,12 +659,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_)
{
@ -663,30 +689,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::PolyNodes::const_iterator it = nodes.begin(); it != nodes.end(); ++it) {
Point p((*it)->Contour.front().X, (*it)->Contour.front().Y);
ordering_points.emplace_back(p);
}
// perform the ordering
ClipperLib::PolyNodes ordered_nodes;
Slic3r::Geometry::chained_path_items(ordering_points, nodes, ordered_nodes);
// push results recursively
for (ClipperLib::PolyNodes::iterator it = ordered_nodes.begin(); it != ordered_nodes.end(); ++it) {
foreach_node<o>(nodes, [&retval](const ClipperLib::PolyNode *node) {
// traverse the next depth
traverse_pt((*it)->Childs, retval);
retval->emplace_back(ClipperPath_to_Slic3rPolygon((*it)->Contour));
if ((*it)->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)
@ -795,4 +914,330 @@ Polygons top_level_islands(const Slic3r::Polygons &polygons)
return out;
}
// Outer offset shall not split the input contour into multiples. It is expected, that the solution will be non empty and it will contain just a single polygon.
ClipperLib::Paths fix_after_outer_offset(const ClipperLib::Path &input, ClipperLib::PolyFillType filltype, bool reverse_result)
{
ClipperLib::Paths solution;
if (! input.empty()) {
ClipperLib::Clipper clipper;
clipper.AddPath(input, ClipperLib::ptSubject, true);
clipper.ReverseSolution(reverse_result);
clipper.Execute(ClipperLib::ctUnion, solution, filltype, filltype);
}
return solution;
}
// Inner offset may split the source contour into multiple contours, but one shall not be inside the other.
ClipperLib::Paths fix_after_inner_offset(const ClipperLib::Path &input, ClipperLib::PolyFillType filltype, bool reverse_result)
{
ClipperLib::Paths solution;
if (! input.empty()) {
ClipperLib::Clipper clipper;
clipper.AddPath(input, ClipperLib::ptSubject, true);
ClipperLib::IntRect r = clipper.GetBounds();
r.left -= 10; r.top -= 10; r.right += 10; r.bottom += 10;
if (filltype == ClipperLib::pftPositive)
clipper.AddPath({ ClipperLib::IntPoint(r.left, r.bottom), ClipperLib::IntPoint(r.left, r.top), ClipperLib::IntPoint(r.right, r.top), ClipperLib::IntPoint(r.right, r.bottom) }, ClipperLib::ptSubject, true);
else
clipper.AddPath({ ClipperLib::IntPoint(r.left, r.bottom), ClipperLib::IntPoint(r.right, r.bottom), ClipperLib::IntPoint(r.right, r.top), ClipperLib::IntPoint(r.left, r.top) }, ClipperLib::ptSubject, true);
clipper.ReverseSolution(reverse_result);
clipper.Execute(ClipperLib::ctUnion, solution, filltype, filltype);
if (! solution.empty())
solution.erase(solution.begin());
}
return solution;
}
ClipperLib::Path mittered_offset_path_scaled(const Points &contour, const std::vector<float> &deltas, double miter_limit)
{
assert(contour.size() == deltas.size());
#ifndef NDEBUG
// Verify that the deltas are either all positive, or all negative.
bool positive = false;
bool negative = false;
for (float delta : deltas)
if (delta < 0.f)
negative = true;
else if (delta > 0.f)
positive = true;
assert(! (negative && positive));
#endif /* NDEBUG */
ClipperLib::Path out;
if (deltas.size() > 2)
{
out.reserve(contour.size() * 2);
// Clamp miter limit to 2.
miter_limit = (miter_limit > 2.) ? 2. / (miter_limit * miter_limit) : 0.5;
// perpenduclar vector
auto perp = [](const Vec2d &v) -> Vec2d { return Vec2d(v.y(), - v.x()); };
// Add a new point to the output, scale by CLIPPER_OFFSET_SCALE and round to ClipperLib::cInt.
auto add_offset_point = [&out](Vec2d pt) {
pt *= double(CLIPPER_OFFSET_SCALE);
pt += Vec2d(0.5 - (pt.x() < 0), 0.5 - (pt.y() < 0));
out.emplace_back(ClipperLib::cInt(pt.x()), ClipperLib::cInt(pt.y()));
};
// Minimum edge length, squared.
double lmin = *std::max_element(deltas.begin(), deltas.end()) * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR;
double l2min = lmin * lmin;
// Minimum angle to consider two edges to be parallel.
// Vojtech's estimate.
// const double sin_min_parallel = EPSILON + 1. / double(CLIPPER_OFFSET_SCALE);
// Implementation equal to Clipper.
const double sin_min_parallel = 1.;
// Find the last point further from pt by l2min.
Vec2d pt = contour.front().cast<double>();
size_t iprev = contour.size() - 1;
Vec2d ptprev;
for (; iprev > 0; -- iprev) {
ptprev = contour[iprev].cast<double>();
if ((ptprev - pt).squaredNorm() > l2min)
break;
}
if (iprev != 0) {
size_t ilast = iprev;
// Normal to the (pt - ptprev) segment.
Vec2d nprev = perp(pt - ptprev).normalized();
for (size_t i = 0; ; ) {
// Find the next point further from pt by l2min.
size_t j = i + 1;
Vec2d ptnext;
for (; j <= ilast; ++ j) {
ptnext = contour[j].cast<double>();
double l2 = (ptnext - pt).squaredNorm();
if (l2 > l2min)
break;
}
if (j > ilast) {
assert(i <= ilast);
// If the last edge is too short, merge it with the previous edge.
i = ilast;
ptnext = contour.front().cast<double>();
}
// Normal to the (ptnext - pt) segment.
Vec2d nnext = perp(ptnext - pt).normalized();
double delta = deltas[i];
double sin_a = clamp(-1., 1., cross2(nprev, nnext));
double convex = sin_a * delta;
if (convex <= - sin_min_parallel) {
// Concave corner.
add_offset_point(pt + nprev * delta);
add_offset_point(pt);
add_offset_point(pt + nnext * delta);
} else {
double dot = nprev.dot(nnext);
if (convex < sin_min_parallel && dot > 0.) {
// Nearly parallel.
add_offset_point((nprev.dot(nnext) > 0.) ? (pt + nprev * delta) : pt);
} else {
// Convex corner, possibly extremely sharp if convex < sin_min_parallel.
double r = 1. + dot;
if (r >= miter_limit)
add_offset_point(pt + (nprev + nnext) * (delta / r));
else {
double dx = std::tan(std::atan2(sin_a, dot) / 4.);
Vec2d newpt1 = pt + (nprev - perp(nprev) * dx) * delta;
Vec2d newpt2 = pt + (nnext + perp(nnext) * dx) * delta;
#ifndef NDEBUG
Vec2d vedge = 0.5 * (newpt1 + newpt2) - pt;
double dist_norm = vedge.norm();
assert(std::abs(dist_norm - std::abs(delta)) < SCALED_EPSILON);
#endif /* NDEBUG */
add_offset_point(newpt1);
add_offset_point(newpt2);
}
}
}
if (i == ilast)
break;
ptprev = pt;
nprev = nnext;
pt = ptnext;
i = j;
}
}
}
#if 0
{
ClipperLib::Path polytmp(out);
unscaleClipperPolygon(polytmp);
Slic3r::Polygon offsetted = ClipperPath_to_Slic3rPolygon(polytmp);
BoundingBox bbox = get_extents(contour);
bbox.merge(get_extents(offsetted));
static int iRun = 0;
SVG svg(debug_out_path("mittered_offset_path_scaled-%d.svg", iRun ++).c_str(), bbox);
svg.draw_outline(Polygon(contour), "blue", scale_(0.01));
svg.draw_outline(offsetted, "red", scale_(0.01));
svg.draw(contour, "blue", scale_(0.03));
svg.draw((Points)offsetted, "blue", scale_(0.03));
}
#endif
return out;
}
Polygons variable_offset_inner(const ExPolygon &expoly, const std::vector<std::vector<float>> &deltas, double miter_limit)
{
#ifndef NDEBUG
// Verify that the deltas are all non positive.
for (const std::vector<float> &ds : deltas)
for (float delta : ds)
assert(delta <= 0.);
assert(expoly.holes.size() + 1 == deltas.size());
#endif /* NDEBUG */
// 1) Offset the outer contour.
ClipperLib::Paths contours = fix_after_inner_offset(mittered_offset_path_scaled(expoly.contour.points, deltas.front(), miter_limit), ClipperLib::pftNegative, true);
// 2) Offset the holes one by one, collect the results.
ClipperLib::Paths holes;
holes.reserve(expoly.holes.size());
for (const Polygon& hole : expoly.holes)
append(holes, fix_after_outer_offset(mittered_offset_path_scaled(hole, deltas[1 + &hole - expoly.holes.data()], miter_limit), ClipperLib::pftPositive, false));
// 3) Subtract holes from the contours.
ClipperLib::Paths output;
if (holes.empty())
output = std::move(contours);
else {
ClipperLib::Clipper clipper;
clipper.Clear();
clipper.AddPaths(contours, ClipperLib::ptSubject, true);
clipper.AddPaths(holes, ClipperLib::ptClip, true);
clipper.Execute(ClipperLib::ctDifference, output, ClipperLib::pftNonZero, ClipperLib::pftNonZero);
}
// 4) Unscale the output.
unscaleClipperPolygons(output);
return ClipperPaths_to_Slic3rPolygons(output);
}
Polygons variable_offset_outer(const ExPolygon &expoly, const std::vector<std::vector<float>> &deltas, double miter_limit)
{
#ifndef NDEBUG
// Verify that the deltas are all non positive.
for (const std::vector<float>& ds : deltas)
for (float delta : ds)
assert(delta >= 0.);
assert(expoly.holes.size() + 1 == deltas.size());
#endif /* NDEBUG */
// 1) Offset the outer contour.
ClipperLib::Paths contours = fix_after_outer_offset(mittered_offset_path_scaled(expoly.contour.points, deltas.front(), miter_limit), ClipperLib::pftPositive, false);
// 2) Offset the holes one by one, collect the results.
ClipperLib::Paths holes;
holes.reserve(expoly.holes.size());
for (const Polygon& hole : expoly.holes)
append(holes, fix_after_inner_offset(mittered_offset_path_scaled(hole, deltas[1 + &hole - expoly.holes.data()], miter_limit), ClipperLib::pftPositive, true));
// 3) Subtract holes from the contours.
ClipperLib::Paths output;
if (holes.empty())
output = std::move(contours);
else {
ClipperLib::Clipper clipper;
clipper.Clear();
clipper.AddPaths(contours, ClipperLib::ptSubject, true);
clipper.AddPaths(holes, ClipperLib::ptClip, true);
clipper.Execute(ClipperLib::ctDifference, output, ClipperLib::pftNonZero, ClipperLib::pftNonZero);
}
// 4) Unscale the output.
unscaleClipperPolygons(output);
return ClipperPaths_to_Slic3rPolygons(output);
}
ExPolygons variable_offset_outer_ex(const ExPolygon &expoly, const std::vector<std::vector<float>> &deltas, double miter_limit)
{
#ifndef NDEBUG
// Verify that the deltas are all non positive.
for (const std::vector<float>& ds : deltas)
for (float delta : ds)
assert(delta >= 0.);
assert(expoly.holes.size() + 1 == deltas.size());
#endif /* NDEBUG */
// 1) Offset the outer contour.
ClipperLib::Paths contours = fix_after_outer_offset(mittered_offset_path_scaled(expoly.contour.points, deltas.front(), miter_limit), ClipperLib::pftPositive, false);
// 2) Offset the holes one by one, collect the results.
ClipperLib::Paths holes;
holes.reserve(expoly.holes.size());
for (const Polygon& hole : expoly.holes)
append(holes, fix_after_inner_offset(mittered_offset_path_scaled(hole, deltas[1 + &hole - expoly.holes.data()], miter_limit), ClipperLib::pftPositive, true));
// 3) Subtract holes from the contours.
unscaleClipperPolygons(contours);
ExPolygons output;
if (holes.empty()) {
output.reserve(contours.size());
for (ClipperLib::Path &path : contours)
output.emplace_back(ClipperPath_to_Slic3rPolygon(path));
} else {
ClipperLib::Clipper clipper;
unscaleClipperPolygons(holes);
clipper.AddPaths(contours, ClipperLib::ptSubject, true);
clipper.AddPaths(holes, ClipperLib::ptClip, true);
ClipperLib::PolyTree polytree;
clipper.Execute(ClipperLib::ctDifference, polytree, ClipperLib::pftNonZero, ClipperLib::pftNonZero);
output = PolyTreeToExPolygons(polytree);
}
return output;
}
ExPolygons variable_offset_inner_ex(const ExPolygon &expoly, const std::vector<std::vector<float>> &deltas, double miter_limit)
{
#ifndef NDEBUG
// Verify that the deltas are all non positive.
for (const std::vector<float>& ds : deltas)
for (float delta : ds)
assert(delta <= 0.);
assert(expoly.holes.size() + 1 == deltas.size());
#endif /* NDEBUG */
// 1) Offset the outer contour.
ClipperLib::Paths contours = fix_after_inner_offset(mittered_offset_path_scaled(expoly.contour.points, deltas.front(), miter_limit), ClipperLib::pftNegative, false);
// 2) Offset the holes one by one, collect the results.
ClipperLib::Paths holes;
holes.reserve(expoly.holes.size());
for (const Polygon& hole : expoly.holes)
append(holes, fix_after_outer_offset(mittered_offset_path_scaled(hole, deltas[1 + &hole - expoly.holes.data()], miter_limit), ClipperLib::pftNegative, true));
// 3) Subtract holes from the contours.
unscaleClipperPolygons(contours);
ExPolygons output;
if (holes.empty()) {
output.reserve(contours.size());
for (ClipperLib::Path &path : contours)
output.emplace_back(ClipperPath_to_Slic3rPolygon(path));
} else {
ClipperLib::Clipper clipper;
unscaleClipperPolygons(holes);
clipper.AddPaths(contours, ClipperLib::ptSubject, true);
clipper.AddPaths(holes, ClipperLib::ptClip, true);
ClipperLib::PolyTree polytree;
clipper.Execute(ClipperLib::ctDifference, polytree, ClipperLib::pftNonZero, ClipperLib::pftNonZero);
output = PolyTreeToExPolygons(polytree);
}
return output;
}
}

View file

@ -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);
@ -226,6 +238,11 @@ void safety_offset(ClipperLib::Paths* paths);
Polygons top_level_islands(const Slic3r::Polygons &polygons);
Polygons variable_offset_inner(const ExPolygon &expoly, const std::vector<std::vector<float>> &deltas, double miter_limit = 2.);
Polygons variable_offset_outer(const ExPolygon &expoly, const std::vector<std::vector<float>> &deltas, double miter_limit = 2.);
ExPolygons variable_offset_outer_ex(const ExPolygon &expoly, const std::vector<std::vector<float>> &deltas, double miter_limit = 2.);
ExPolygons variable_offset_inner_ex(const ExPolygon &expoly, const std::vector<std::vector<float>> &deltas, double miter_limit = 2.);
}
#endif

View file

@ -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
@ -427,7 +425,30 @@ std::string ConfigBase::opt_serialize(const t_config_option_key &opt_key) const
return opt->serialize();
}
bool ConfigBase::set_deserialize(const t_config_option_key &opt_key_src, const std::string &value_src, bool append)
void ConfigBase::set(const std::string &opt_key, int value, bool create)
{
ConfigOption *opt = this->option_throw(opt_key, create);
switch (opt->type()) {
case coInt: static_cast<ConfigOptionInt*>(opt)->value = value; break;
case coFloat: static_cast<ConfigOptionFloat*>(opt)->value = value; break;
case coFloatOrPercent: static_cast<ConfigOptionFloatOrPercent*>(opt)->value = value; static_cast<ConfigOptionFloatOrPercent*>(opt)->percent = false; break;
case coString: static_cast<ConfigOptionString*>(opt)->value = std::to_string(value); break;
default: throw BadOptionTypeException("Configbase::set() - conversion from int not possible");
}
}
void ConfigBase::set(const std::string &opt_key, double value, bool create)
{
ConfigOption *opt = this->option_throw(opt_key, create);
switch (opt->type()) {
case coFloat: static_cast<ConfigOptionFloat*>(opt)->value = value; break;
case coFloatOrPercent: static_cast<ConfigOptionFloatOrPercent*>(opt)->value = value; static_cast<ConfigOptionFloatOrPercent*>(opt)->percent = false; break;
case coString: static_cast<ConfigOptionString*>(opt)->value = std::to_string(value); break;
default: throw BadOptionTypeException("Configbase::set() - conversion from float not possible");
}
}
bool ConfigBase::set_deserialize_nothrow(const t_config_option_key &opt_key_src, const std::string &value_src, bool append)
{
t_config_option_key opt_key = opt_key_src;
std::string value = value_src;
@ -440,6 +461,18 @@ bool ConfigBase::set_deserialize(const t_config_option_key &opt_key_src, const s
return this->set_deserialize_raw(opt_key, value, append);
}
void ConfigBase::set_deserialize(const t_config_option_key &opt_key_src, const std::string &value_src, bool append)
{
if (! this->set_deserialize_nothrow(opt_key_src, value_src, append))
throw BadOptionTypeException("ConfigBase::set_deserialize() failed");
}
void ConfigBase::set_deserialize(std::initializer_list<SetDeserializeItem> items)
{
for (const SetDeserializeItem &item : items)
this->set_deserialize(item.opt_key, item.opt_value, item.append);
}
bool ConfigBase::set_deserialize_raw(const t_config_option_key &opt_key_src, const std::string &value, bool append)
{
t_config_option_key opt_key = opt_key_src;
@ -670,6 +703,12 @@ void ConfigBase::null_nullables()
}
}
DynamicConfig::DynamicConfig(const ConfigBase& rhs, const t_config_option_keys& keys)
{
for (const t_config_option_key& opt_key : keys)
this->options[opt_key] = std::unique_ptr<ConfigOption>(rhs.option(opt_key)->clone());
}
bool DynamicConfig::operator==(const DynamicConfig &rhs) const
{
auto it1 = this->options.begin();
@ -819,7 +858,7 @@ bool DynamicConfig::read_cli(int argc, char** argv, t_config_option_keys* extra,
static_cast<ConfigOptionString*>(opt_base)->value = value;
} else {
// Any scalar value of a type different from Bool and String.
if (! this->set_deserialize(opt_key, value, false)) {
if (! this->set_deserialize_nothrow(opt_key, value, false)) {
boost::nowide::cerr << "Invalid value supplied for --" << token.c_str() << std::endl;
return false;
}

View file

@ -52,6 +52,16 @@ public:
std::runtime_error(std::string("No definition exception: ") + opt_key) {}
};
/// Indicate that an unsupported accessor was called on a config option.
class BadOptionTypeException : public std::runtime_error
{
public:
BadOptionTypeException() :
std::runtime_error("Bad option type exception") {}
BadOptionTypeException(const char* message) :
std::runtime_error(message) {}
};
// Type of a configuration value.
enum ConfigOptionType {
coVectorType = 0x4000,
@ -117,10 +127,10 @@ public:
virtual ConfigOption* clone() const = 0;
// Set a value from a ConfigOption. The two options should be compatible.
virtual void set(const ConfigOption *option) = 0;
virtual int getInt() const { throw std::runtime_error("Calling ConfigOption::getInt on a non-int ConfigOption"); }
virtual double getFloat() const { throw std::runtime_error("Calling ConfigOption::getFloat on a non-float ConfigOption"); }
virtual bool getBool() const { throw std::runtime_error("Calling ConfigOption::getBool on a non-boolean ConfigOption"); }
virtual void setInt(int /* val */) { throw std::runtime_error("Calling ConfigOption::setInt on a non-int ConfigOption"); }
virtual int getInt() const { throw BadOptionTypeException("Calling ConfigOption::getInt on a non-int ConfigOption"); }
virtual double getFloat() const { throw BadOptionTypeException("Calling ConfigOption::getFloat on a non-float ConfigOption"); }
virtual bool getBool() const { throw BadOptionTypeException("Calling ConfigOption::getBool on a non-boolean ConfigOption"); }
virtual void setInt(int /* val */) { throw BadOptionTypeException("Calling ConfigOption::setInt on a non-int ConfigOption"); }
virtual bool operator==(const ConfigOption &rhs) const = 0;
bool operator!=(const ConfigOption &rhs) const { return ! (*this == rhs); }
bool is_scalar() const { return (int(this->type()) & int(coVectorType)) == 0; }
@ -1444,7 +1454,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.
@ -1513,32 +1523,48 @@ protected:
public:
// Non-virtual methods:
bool has(const t_config_option_key &opt_key) const { return this->option(opt_key) != nullptr; }
const ConfigOption* option(const t_config_option_key &opt_key) const
{ return const_cast<ConfigBase*>(this)->option(opt_key, false); }
ConfigOption* option(const t_config_option_key &opt_key, bool create = false)
{ return this->optptr(opt_key, create); }
template<typename TYPE>
TYPE* option(const t_config_option_key &opt_key, bool create = false)
{
ConfigOption *opt = this->optptr(opt_key, create);
return (opt == nullptr || opt->type() != TYPE::static_type()) ? nullptr : static_cast<TYPE*>(opt);
}
template<typename TYPE>
const TYPE* option(const t_config_option_key &opt_key) const
{ return const_cast<ConfigBase*>(this)->option<TYPE>(opt_key, false); }
template<typename TYPE>
TYPE* option_throw(const t_config_option_key &opt_key, bool create = false)
ConfigOption* option_throw(const t_config_option_key &opt_key, bool create = false)
{
ConfigOption *opt = this->optptr(opt_key, create);
if (opt == nullptr)
throw UnknownOptionException(opt_key);
return opt;
}
const ConfigOption* option_throw(const t_config_option_key &opt_key) const
{ return const_cast<ConfigBase*>(this)->option_throw(opt_key, false); }
template<typename TYPE>
TYPE* option_throw(const t_config_option_key &opt_key, bool create = false)
{
ConfigOption *opt = this->option_throw(opt_key, create);
if (opt->type() != TYPE::static_type())
throw std::runtime_error("Conversion to a wrong type");
throw BadOptionTypeException("Conversion to a wrong type");
return static_cast<TYPE*>(opt);
}
template<typename TYPE>
const TYPE* option_throw(const t_config_option_key &opt_key) const
{ return const_cast<ConfigBase*>(this)->option_throw<TYPE>(opt_key, false); }
// Apply all keys of other ConfigBase defined by this->def() to this ConfigBase.
// An UnknownOptionException is thrown in case some option keys of other are not defined by this->def(),
// or this ConfigBase is of a StaticConfig type and it does not support some of the keys, and ignore_nonexistent is not set.
@ -1551,9 +1577,40 @@ public:
t_config_option_keys diff(const ConfigBase &other) const;
t_config_option_keys equal(const ConfigBase &other) const;
std::string opt_serialize(const t_config_option_key &opt_key) const;
// Set a value. Convert numeric types using a C style implicit conversion / promotion model.
// Throw if option is not avaiable and create is not enabled,
// or if the conversion is not possible.
// Conversion to string is always possible.
void set(const std::string &opt_key, bool value, bool create = false)
{ this->option_throw<ConfigOptionBool>(opt_key, create)->value = value; }
void set(const std::string &opt_key, int value, bool create = false);
void set(const std::string &opt_key, double value, bool create = false);
void set(const std::string &opt_key, const char *value, bool create = false)
{ this->option_throw<ConfigOptionString>(opt_key, create)->value = value; }
void set(const std::string &opt_key, const std::string &value, bool create = false)
{ this->option_throw<ConfigOptionString>(opt_key, create)->value = value; }
// Set a configuration value from a string, it will call an overridable handle_legacy()
// to resolve renamed and removed configuration keys.
bool set_deserialize(const t_config_option_key &opt_key, const std::string &str, bool append = false);
bool set_deserialize_nothrow(const t_config_option_key &opt_key_src, const std::string &value_src, bool append = false);
// May throw BadOptionTypeException() if the operation fails.
void set_deserialize(const t_config_option_key &opt_key, const std::string &str, bool append = false);
struct SetDeserializeItem {
SetDeserializeItem(const char *opt_key, const char *opt_value, bool append = false) : opt_key(opt_key), opt_value(opt_value), append(append) {}
SetDeserializeItem(const std::string &opt_key, const std::string &opt_value, bool append = false) : opt_key(opt_key), opt_value(opt_value), append(append) {}
SetDeserializeItem(const char *opt_key, const bool value, bool append = false) : opt_key(opt_key), opt_value(value ? "1" : "0"), append(append) {}
SetDeserializeItem(const std::string &opt_key, const bool value, bool append = false) : opt_key(opt_key), opt_value(value ? "1" : "0"), append(append) {}
SetDeserializeItem(const char *opt_key, const int value, bool append = false) : opt_key(opt_key), opt_value(std::to_string(value)), append(append) {}
SetDeserializeItem(const std::string &opt_key, const int value, bool append = false) : opt_key(opt_key), opt_value(std::to_string(value)), append(append) {}
SetDeserializeItem(const char *opt_key, const float value, bool append = false) : opt_key(opt_key), opt_value(std::to_string(value)), append(append) {}
SetDeserializeItem(const std::string &opt_key, const float value, bool append = false) : opt_key(opt_key), opt_value(std::to_string(value)), append(append) {}
SetDeserializeItem(const char *opt_key, const double value, bool append = false) : opt_key(opt_key), opt_value(std::to_string(value)), append(append) {}
SetDeserializeItem(const std::string &opt_key, const double value, bool append = false) : opt_key(opt_key), opt_value(std::to_string(value)), append(append) {}
std::string opt_key; std::string opt_value; bool append = false;
};
// May throw BadOptionTypeException() if the operation fails.
void set_deserialize(std::initializer_list<SetDeserializeItem> items);
double get_abs_value(const t_config_option_key &opt_key) const;
double get_abs_value(const t_config_option_key &opt_key, double ratio_over) const;
@ -1580,9 +1637,11 @@ class DynamicConfig : public virtual ConfigBase
{
public:
DynamicConfig() {}
DynamicConfig(const DynamicConfig& other) { *this = other; }
DynamicConfig(DynamicConfig&& other) : options(std::move(other.options)) { other.options.clear(); }
virtual ~DynamicConfig() override { clear(); }
DynamicConfig(const DynamicConfig &rhs) { *this = rhs; }
DynamicConfig(DynamicConfig &&rhs) : options(std::move(rhs.options)) { rhs.options.clear(); }
explicit DynamicConfig(const ConfigBase &rhs, const t_config_option_keys &keys);
explicit DynamicConfig(const ConfigBase& rhs) : DynamicConfig(rhs, rhs.keys()) {}
virtual ~DynamicConfig() override { clear(); }
// Copy a content of one DynamicConfig to another DynamicConfig.
// If rhs.def() is not null, then it has to be equal to this->def().

View file

@ -46,11 +46,29 @@ void EdgeGrid::Grid::create(const Polygons &polygons, coord_t resolution)
++ ncontours;
// Collect the contours.
m_contours.assign(ncontours, NULL);
m_contours.assign(ncontours, nullptr);
ncontours = 0;
for (size_t j = 0; j < polygons.size(); ++ j)
if (! polygons[j].points.empty())
m_contours[ncontours++] = &polygons[j].points;
m_contours[ncontours ++] = &polygons[j].points;
create_from_m_contours(resolution);
}
void EdgeGrid::Grid::create(const std::vector<Points> &polygons, coord_t resolution)
{
// Count the contours.
size_t ncontours = 0;
for (size_t j = 0; j < polygons.size(); ++ j)
if (! polygons[j].empty())
++ ncontours;
// Collect the contours.
m_contours.assign(ncontours, nullptr);
ncontours = 0;
for (size_t j = 0; j < polygons.size(); ++ j)
if (! polygons[j].empty())
m_contours[ncontours ++] = &polygons[j];
create_from_m_contours(resolution);
}
@ -66,7 +84,7 @@ void EdgeGrid::Grid::create(const ExPolygon &expoly, coord_t resolution)
++ ncontours;
// Collect the contours.
m_contours.assign(ncontours, NULL);
m_contours.assign(ncontours, nullptr);
ncontours = 0;
if (! expoly.contour.points.empty())
m_contours[ncontours++] = &expoly.contour.points;
@ -91,7 +109,7 @@ void EdgeGrid::Grid::create(const ExPolygons &expolygons, coord_t resolution)
}
// Collect the contours.
m_contours.assign(ncontours, NULL);
m_contours.assign(ncontours, nullptr);
ncontours = 0;
for (size_t i = 0; i < expolygons.size(); ++ i) {
const ExPolygon &expoly = expolygons[i];
@ -113,6 +131,7 @@ void EdgeGrid::Grid::create(const ExPolygonCollection &expolygons, coord_t resol
// m_contours has been initialized. Now fill in the edge grid.
void EdgeGrid::Grid::create_from_m_contours(coord_t resolution)
{
assert(resolution > 0);
// 1) Measure the bounding box.
for (size_t i = 0; i < m_contours.size(); ++ i) {
const Slic3r::Points &pts = *m_contours[i];
@ -281,7 +300,11 @@ void EdgeGrid::Grid::create_from_m_contours(coord_t resolution)
Visitor(std::vector<std::pair<size_t, size_t>> &cell_data, std::vector<Cell> &cells, size_t cols) :
cell_data(cell_data), cells(cells), cols(cols), i(0), j(0) {}
void operator()(coord_t iy, coord_t ix) { cell_data[cells[iy*cols + ix].end++] = std::pair<size_t, size_t>(i, j); }
inline bool operator()(coord_t iy, coord_t ix) {
cell_data[cells[iy*cols + ix].end++] = std::pair<size_t, size_t>(i, j);
// Continue traversing the grid along the edge.
return true;
}
std::vector<std::pair<size_t, size_t>> &cell_data;
std::vector<Cell> &cells;
@ -1017,8 +1040,139 @@ float EdgeGrid::Grid::signed_distance_bilinear(const Point &pt) const
return f;
}
bool EdgeGrid::Grid::signed_distance_edges(const Point &pt, coord_t search_radius, coordf_t &result_min_dist, bool *pon_segment) const {
EdgeGrid::Grid::ClosestPointResult EdgeGrid::Grid::closest_point(const Point &pt, coord_t search_radius) const
{
BoundingBox bbox;
bbox.min = bbox.max = Point(pt(0) - m_bbox.min(0), pt(1) - m_bbox.min(1));
bbox.defined = true;
// Upper boundary, round to grid and test validity.
bbox.max(0) += search_radius;
bbox.max(1) += search_radius;
ClosestPointResult result;
if (bbox.max(0) < 0 || bbox.max(1) < 0)
return result;
bbox.max(0) /= m_resolution;
bbox.max(1) /= m_resolution;
if ((size_t)bbox.max(0) >= m_cols)
bbox.max(0) = m_cols - 1;
if ((size_t)bbox.max(1) >= m_rows)
bbox.max(1) = m_rows - 1;
// Lower boundary, round to grid and test validity.
bbox.min(0) -= search_radius;
bbox.min(1) -= search_radius;
if (bbox.min(0) < 0)
bbox.min(0) = 0;
if (bbox.min(1) < 0)
bbox.min(1) = 0;
bbox.min(0) /= m_resolution;
bbox.min(1) /= m_resolution;
// Is the interval empty?
if (bbox.min(0) > bbox.max(0) ||
bbox.min(1) > bbox.max(1))
return result;
// Traverse all cells in the bounding box.
double d_min = double(search_radius);
// Signum of the distance field at pt.
int sign_min = 0;
double l2_seg_min = 1.;
for (int r = bbox.min(1); r <= bbox.max(1); ++ r) {
for (int c = bbox.min(0); c <= bbox.max(0); ++ c) {
const Cell &cell = m_cells[r * m_cols + c];
for (size_t i = cell.begin; i < cell.end; ++ i) {
const size_t contour_idx = m_cell_data[i].first;
const Slic3r::Points &pts = *m_contours[contour_idx];
size_t ipt = m_cell_data[i].second;
// End points of the line segment.
const Slic3r::Point &p1 = pts[ipt];
const Slic3r::Point &p2 = pts[(ipt + 1 == pts.size()) ? 0 : ipt + 1];
const Slic3r::Point v_seg = p2 - p1;
const Slic3r::Point v_pt = pt - p1;
// dot(p2-p1, pt-p1)
int64_t t_pt = int64_t(v_seg(0)) * int64_t(v_pt(0)) + int64_t(v_seg(1)) * int64_t(v_pt(1));
// l2 of seg
int64_t l2_seg = int64_t(v_seg(0)) * int64_t(v_seg(0)) + int64_t(v_seg(1)) * int64_t(v_seg(1));
if (t_pt < 0) {
// Closest to p1.
double dabs = sqrt(int64_t(v_pt(0)) * int64_t(v_pt(0)) + int64_t(v_pt(1)) * int64_t(v_pt(1)));
if (dabs < d_min) {
// Previous point.
const Slic3r::Point &p0 = pts[(ipt == 0) ? (pts.size() - 1) : ipt - 1];
Slic3r::Point v_seg_prev = p1 - p0;
int64_t t2_pt = int64_t(v_seg_prev(0)) * int64_t(v_pt(0)) + int64_t(v_seg_prev(1)) * int64_t(v_pt(1));
if (t2_pt > 0) {
// Inside the wedge between the previous and the next segment.
d_min = dabs;
// Set the signum depending on whether the vertex is convex or reflex.
int64_t det = int64_t(v_seg_prev(0)) * int64_t(v_seg(1)) - int64_t(v_seg_prev(1)) * int64_t(v_seg(0));
assert(det != 0);
sign_min = (det > 0) ? 1 : -1;
result.contour_idx = contour_idx;
result.start_point_idx = ipt;
result.t = 0.;
#ifndef NDEBUG
Vec2d vfoot = (p1 - pt).cast<double>();
double dist_foot = vfoot.norm();
double dist_foot_err = dist_foot - d_min;
assert(std::abs(dist_foot_err) < 1e-7 * d_min);
#endif /* NDEBUG */
}
}
}
else if (t_pt > l2_seg) {
// Closest to p2. Then p2 is the starting point of another segment, which shall be discovered in the same cell.
continue;
} else {
// Closest to the segment.
assert(t_pt >= 0 && t_pt <= l2_seg);
int64_t d_seg = int64_t(v_seg(1)) * int64_t(v_pt(0)) - int64_t(v_seg(0)) * int64_t(v_pt(1));
double d = double(d_seg) / sqrt(double(l2_seg));
double dabs = std::abs(d);
if (dabs < d_min) {
d_min = dabs;
sign_min = (d_seg < 0) ? -1 : ((d_seg == 0) ? 0 : 1);
l2_seg_min = l2_seg;
result.contour_idx = contour_idx;
result.start_point_idx = ipt;
result.t = t_pt;
#ifndef NDEBUG
Vec2d foot = p1.cast<double>() * (1. - result.t / l2_seg_min) + p2.cast<double>() * (result.t / l2_seg_min);
Vec2d vfoot = foot - pt.cast<double>();
double dist_foot = vfoot.norm();
double dist_foot_err = dist_foot - d_min;
assert(std::abs(dist_foot_err) < 1e-7 || std::abs(dist_foot_err) < 1e-7 * d_min);
#endif /* NDEBUG */
}
}
}
}
}
if (result.contour_idx != -1 && d_min <= double(search_radius)) {
result.distance = d_min * sign_min;
result.t /= l2_seg_min;
assert(result.t >= 0. && result.t < 1.);
#ifndef NDEBUG
{
const Slic3r::Points &pts = *m_contours[result.contour_idx];
const Slic3r::Point &p1 = pts[result.start_point_idx];
const Slic3r::Point &p2 = pts[(result.start_point_idx + 1 == pts.size()) ? 0 : result.start_point_idx + 1];
Vec2d vfoot;
if (result.t == 0)
vfoot = p1.cast<double>() - pt.cast<double>();
else
vfoot = p1.cast<double>() * (1. - result.t) + p2.cast<double>() * result.t - pt.cast<double>();
double dist_foot = vfoot.norm();
double dist_foot_err = dist_foot - std::abs(result.distance);
assert(std::abs(dist_foot_err) < 1e-7 || std::abs(dist_foot_err) < 1e-7 * std::abs(result.distance));
}
#endif /* NDEBUG */
} else
result = ClosestPointResult();
return result;
}
bool EdgeGrid::Grid::signed_distance_edges(const Point &pt, coord_t search_radius, coordf_t &result_min_dist, bool *pon_segment) const
{
BoundingBox bbox;
bbox.min = bbox.max = Point(pt(0) - m_bbox.min(0), pt(1) - m_bbox.min(1));
bbox.defined = true;
@ -1047,7 +1201,7 @@ bool EdgeGrid::Grid::signed_distance_edges(const Point &pt, coord_t search_radiu
bbox.min(1) > bbox.max(1))
return false;
// Traverse all cells in the bounding box.
float d_min = search_radius;
double d_min = double(search_radius);
// Signum of the distance field at pt.
int sign_min = 0;
bool on_segment = false;

View file

@ -21,10 +21,13 @@ public:
void set_bbox(const BoundingBox &bbox) { m_bbox = bbox; }
void create(const Polygons &polygons, coord_t resolution);
void create(const std::vector<Points> &polygons, coord_t resolution);
void create(const ExPolygon &expoly, coord_t resolution);
void create(const ExPolygons &expolygons, coord_t resolution);
void create(const ExPolygonCollection &expolygons, coord_t resolution);
const std::vector<const Slic3r::Points*>& contours() const { return m_contours; }
#if 0
// Test, whether the edges inside the grid intersect with the polygons provided.
bool intersect(const MultiPoint &polyline, bool closed);
@ -46,7 +49,19 @@ public:
float signed_distance_bilinear(const Point &pt) const;
// Calculate a signed distance to the contours in search_radius from the point.
bool signed_distance_edges(const Point &pt, coord_t search_radius, coordf_t &result_min_dist, bool *pon_segment = NULL) const;
struct ClosestPointResult {
size_t contour_idx = size_t(-1);
size_t start_point_idx = size_t(-1);
// Signed distance to the closest point.
double distance = std::numeric_limits<double>::max();
// Parameter of the closest point on edge starting with start_point_idx <0, 1)
double t = 0.;
bool valid() const { return contour_idx != size_t(-1); }
};
ClosestPointResult closest_point(const Point &pt, coord_t search_radius) const;
bool signed_distance_edges(const Point &pt, coord_t search_radius, coordf_t &result_min_dist, bool *pon_segment = nullptr) const;
// Calculate a signed distance to the contours in search_radius from the point. If no edge is found in search_radius,
// return an interpolated value from m_signed_distance_field, if it exists.
@ -65,7 +80,7 @@ public:
std::vector<std::pair<ContourEdge, ContourEdge>> intersecting_edges() const;
bool has_intersecting_edges() const;
template<typename FUNCTION> void visit_cells_intersecting_line(Slic3r::Point p1, Slic3r::Point p2, FUNCTION func) const
template<typename VISITOR> void visit_cells_intersecting_line(Slic3r::Point p1, Slic3r::Point p2, VISITOR &visitor) const
{
// End points of the line segment.
p1(0) -= m_bbox.min(0);
@ -82,8 +97,7 @@ public:
assert(ixb >= 0 && size_t(ixb) < m_cols);
assert(iyb >= 0 && size_t(iyb) < m_rows);
// Account for the end points.
func(iy, ix);
if (ix == ixb && iy == iyb)
if (! visitor(iy, ix) || (ix == ixb && iy == iyb))
// Both ends fall into the same cell.
return;
// Raster the centeral part of the line.
@ -113,7 +127,8 @@ public:
ey = int64_t(dx) * m_resolution;
iy += 1;
}
func(iy, ix);
if (! visitor(iy, ix))
return;
} while (ix != ixb || iy != iyb);
}
else {
@ -131,7 +146,8 @@ public:
ey = int64_t(dx) * m_resolution;
iy -= 1;
}
func(iy, ix);
if (! visitor(iy, ix))
return;
} while (ix != ixb || iy != iyb);
}
}
@ -153,7 +169,8 @@ public:
ey = int64_t(dx) * m_resolution;
iy += 1;
}
func(iy, ix);
if (! visitor(iy, ix))
return;
} while (ix != ixb || iy != iyb);
}
else {
@ -185,7 +202,8 @@ public:
ey = int64_t(dx) * m_resolution;
iy -= 1;
}
func(iy, ix);
if (! visitor(iy, ix))
return;
} while (ix != ixb || iy != iyb);
}
}

View file

@ -0,0 +1,416 @@
#include "clipper/clipper_z.hpp"
#include "libslic3r.h"
#include "ClipperUtils.hpp"
#include "EdgeGrid.hpp"
#include "ExPolygon.hpp"
#include "ElephantFootCompensation.hpp"
#include "Flow.hpp"
#include "Geometry.hpp"
#include "SVG.hpp"
#include <cmath>
#include <cassert>
// #define CONTOUR_DISTANCE_DEBUG_SVG
namespace Slic3r {
struct ResampledPoint {
ResampledPoint(size_t idx_src, bool interpolated, double curve_parameter) : idx_src(idx_src), interpolated(interpolated), curve_parameter(curve_parameter) {}
size_t idx_src;
// Is this point interpolated or initial?
bool interpolated;
// Euclidean distance along the curve from the 0th point.
double curve_parameter;
};
std::vector<float> contour_distance(const EdgeGrid::Grid &grid, const size_t idx_contour, const Slic3r::Points &contour, const std::vector<ResampledPoint> &resampled_point_parameters, double search_radius)
{
assert(! contour.empty());
assert(contour.size() >= 2);
std::vector<float> out;
if (contour.size() > 2)
{
#ifdef CONTOUR_DISTANCE_DEBUG_SVG
static int iRun = 0;
++ iRun;
BoundingBox bbox = get_extents(contour);
bbox.merge(grid.bbox());
ExPolygon expoly_grid;
expoly_grid.contour = Polygon(*grid.contours().front());
for (size_t i = 1; i < grid.contours().size(); ++ i)
expoly_grid.holes.emplace_back(Polygon(*grid.contours()[i]));
#endif
struct Visitor {
Visitor(const EdgeGrid::Grid &grid, const size_t idx_contour, const std::vector<ResampledPoint> &resampled_point_parameters, double dist_same_contour_reject) :
grid(grid), idx_contour(idx_contour), resampled_point_parameters(resampled_point_parameters), dist_same_contour_reject(dist_same_contour_reject) {}
void init(const size_t aidx_point_start, const Point &apt_start, Vec2d dir, const double radius) {
this->idx_point_start = aidx_point_start;
this->pt = apt_start.cast<double>() + SCALED_EPSILON * dir;
dir *= radius;
this->pt_start = this->pt.cast<coord_t>();
// Trim the vector by the grid's bounding box.
const BoundingBox &bbox = this->grid.bbox();
double t = 1.;
for (size_t axis = 0; axis < 2; ++ axis) {
double dx = std::abs(dir(axis));
if (dx >= EPSILON) {
double tedge = (dir(axis) > 0) ? (double(bbox.max(axis)) - EPSILON - this->pt(axis)) : (this->pt(axis) - double(bbox.min(axis)) - EPSILON);
if (tedge < dx)
t = tedge / dx;
}
}
this->dir = dir;
if (t < 1.)
dir *= t;
this->pt_end = (this->pt + dir).cast<coord_t>();
this->t_min = 1.;
}
bool operator()(coord_t iy, coord_t ix) {
// Called with a row and colum of the grid cell, which is intersected by a line.
auto cell_data_range = this->grid.cell_data_range(iy, ix);
bool valid = true;
for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++ it_contour_and_segment) {
// End points of the line segment and their vector.
auto segment = this->grid.segment(*it_contour_and_segment);
if (Geometry::segments_intersect(segment.first, segment.second, this->pt_start, this->pt_end)) {
// The two segments intersect. Calculate the intersection.
Vec2d pt2 = segment.first.cast<double>();
Vec2d dir2 = segment.second.cast<double>() - pt2;
Vec2d vptpt2 = pt - pt2;
double denom = dir(0) * dir2(1) - dir2(0) * dir(1);
if (std::abs(denom) >= EPSILON) {
double t = cross2(dir2, vptpt2) / denom;
assert(t > - EPSILON && t < 1. + EPSILON);
bool this_valid = true;
if (it_contour_and_segment->first == idx_contour) {
// The intersected segment originates from the same contour as the starting point.
// Reject the intersection if it is close to the starting point.
// Find the start and end points of this segment
double param_lo = resampled_point_parameters[idx_point_start].curve_parameter;
double param_hi;
double param_end = resampled_point_parameters.back().curve_parameter;
{
const Slic3r::Points &ipts = *grid.contours()[it_contour_and_segment->first];
size_t ipt = it_contour_and_segment->second;
ResampledPoint key(ipt, false, 0.);
auto lower = [](const ResampledPoint& l, const ResampledPoint r) { return l.idx_src < r.idx_src || (l.idx_src == r.idx_src && int(l.interpolated) > int(r.interpolated)); };
auto it = std::lower_bound(resampled_point_parameters.begin(), resampled_point_parameters.end(), key, lower);
assert(it != resampled_point_parameters.end() && it->idx_src == ipt && ! it->interpolated);
double t2 = cross2(dir, vptpt2) / denom;
assert(t2 > - EPSILON && t2 < 1. + EPSILON);
if (++ ipt == ipts.size())
param_hi = t2 * dir2.norm();
else
param_hi = it->curve_parameter + t2 * dir2.norm();
}
if (param_lo > param_hi)
std::swap(param_lo, param_hi);
assert(param_lo >= 0. && param_lo <= param_end);
assert(param_hi >= 0. && param_hi <= param_end);
this_valid = param_hi > param_lo + dist_same_contour_reject && param_hi - param_end < param_lo - dist_same_contour_reject;
}
if (t < this->t_min) {
this->t_min = t;
valid = this_valid;
}
}
}
if (! valid)
this->t_min = 1.;
}
// Continue traversing the grid along the edge.
return true;
}
const EdgeGrid::Grid &grid;
const size_t idx_contour;
const std::vector<ResampledPoint> &resampled_point_parameters;
const double dist_same_contour_reject;
size_t idx_point_start;
Point pt_start;
Point pt_end;
Vec2d pt;
Vec2d dir;
// Minium parameter along the vector (pt_end - pt_start).
double t_min;
} visitor(grid, idx_contour, resampled_point_parameters, search_radius);
const Point *pt_this = &contour.back();
size_t idx_pt_this = contour.size() - 1;
const Point *pt_prev = pt_this - 1;
// perpenduclar vector
auto perp = [](const Vec2d& v) -> Vec2d { return Vec2d(v.y(), -v.x()); };
Vec2d vprev = (*pt_this - *pt_prev).cast<double>().normalized();
out.reserve(contour.size() + 1);
for (const Point &pt_next : contour) {
Vec2d vnext = (pt_next - *pt_this).cast<double>().normalized();
Vec2d dir = - (perp(vprev) + perp(vnext)).normalized();
Vec2d dir_perp = perp(dir);
double cross = cross2(vprev, vnext);
double dot = vprev.dot(vnext);
double a = (cross < 0 || dot > 0.5) ? (M_PI / 3.) : (0.48 * acos(std::min(1., - dot)));
// Throw rays, collect distances.
std::vector<double> distances;
int num_rays = 15;
#ifdef CONTOUR_DISTANCE_DEBUG_SVG
SVG svg(debug_out_path("contour_distance_raycasted-%d-%d.svg", iRun, &pt_next - contour.data()).c_str(), bbox);
svg.draw(expoly_grid);
svg.draw_outline(Polygon(contour), "blue", scale_(0.01));
svg.draw(*pt_this, "red", scale_(0.1));
#endif /* CONTOUR_DISTANCE_DEBUG_SVG */
for (int i = - num_rays + 1; i < num_rays; ++ i) {
double angle = a * i / (int)num_rays;
double c = cos(angle);
double s = sin(angle);
Vec2d v = c * dir + s * dir_perp;
visitor.init(idx_pt_this, *pt_this, v, search_radius);
grid.visit_cells_intersecting_line(visitor.pt_start, visitor.pt_end, visitor);
distances.emplace_back(visitor.t_min);
#ifdef CONTOUR_DISTANCE_DEBUG_SVG
svg.draw(Line(visitor.pt_start, visitor.pt_end), "yellow", scale_(0.01));
if (visitor.t_min < 1.) {
Vec2d pt = visitor.pt + visitor.dir * visitor.t_min;
svg.draw(Point(pt), "red", scale_(0.1));
}
#endif /* CONTOUR_DISTANCE_DEBUG_SVG */
}
#ifdef CONTOUR_DISTANCE_DEBUG_SVG
svg.Close();
#endif /* CONTOUR_DISTANCE_DEBUG_SVG */
std::sort(distances.begin(), distances.end());
#if 0
double median = distances[distances.size() / 2];
double standard_deviation = 0;
for (double d : distances)
standard_deviation += (d - median) * (d - median);
standard_deviation = sqrt(standard_deviation / (distances.size() - 1));
double avg = 0;
size_t cnt = 0;
for (double d : distances)
if (d > median - standard_deviation - EPSILON && d < median + standard_deviation + EPSILON) {
avg += d;
++ cnt;
}
avg /= double(cnt);
out.emplace_back(float(avg * search_radius));
#else
out.emplace_back(float(distances.front() * search_radius));
#endif
#ifdef CONTOUR_DISTANCE_DEBUG_SVG
printf("contour_distance_raycasted-%d-%d.svg - distance %lf\n", iRun, &pt_next - contour.data(), unscale<double>(out.back()));
#endif /* CONTOUR_DISTANCE_DEBUG_SVG */
pt_this = &pt_next;
idx_pt_this = &pt_next - contour.data();
vprev = vnext;
}
// Rotate the vector by one item.
out.emplace_back(out.front());
out.erase(out.begin());
}
return out;
}
Points resample_polygon(const Points &contour, double dist, std::vector<ResampledPoint> &resampled_point_parameters)
{
Points out;
out.reserve(contour.size());
resampled_point_parameters.reserve(contour.size());
if (contour.size() > 2) {
Vec2d pt_prev = contour.back().cast<double>();
for (const Point &pt : contour) {
size_t idx_this = &pt - contour.data();
const Vec2d pt_this = pt.cast<double>();
const Vec2d v = pt_this - pt_prev;
const double l = v.norm();
const size_t n = size_t(ceil(l / dist));
const double l_step = l / n;
for (size_t i = 1; i < n; ++ i) {
double interpolation_parameter = double(i) / n;
Vec2d new_pt = pt_prev + v * interpolation_parameter;
out.emplace_back(new_pt.cast<coord_t>());
resampled_point_parameters.emplace_back(idx_this, true, l_step);
}
out.emplace_back(pt);
resampled_point_parameters.emplace_back(idx_this, false, l_step);
pt_prev = pt_this;
}
for (size_t i = 1; i < resampled_point_parameters.size(); ++i)
resampled_point_parameters[i].curve_parameter += resampled_point_parameters[i - 1].curve_parameter;
}
return out;
}
static inline void smooth_compensation(std::vector<float> &compensation, float strength, size_t num_iterations)
{
std::vector<float> out(compensation);
for (size_t iter = 0; iter < num_iterations; ++ iter) {
for (size_t i = 0; i < compensation.size(); ++ i) {
float prev = (i == 0) ? compensation.back() : compensation[i - 1];
float next = (i + 1 == compensation.size()) ? compensation.front() : compensation[i + 1];
float laplacian = compensation[i] * (1.f - strength) + 0.5f * strength * (prev + next);
// Compensations are negative. Only apply the laplacian if it leads to lower compensation.
out[i] = std::max(laplacian, compensation[i]);
}
out.swap(compensation);
}
}
template<typename INDEX_TYPE, typename CONTAINER>
static inline INDEX_TYPE prev_idx_cyclic(INDEX_TYPE idx, const CONTAINER &container)
{
if (idx == 0)
idx = INDEX_TYPE(container.size());
return -- idx;
}
template<typename INDEX_TYPE, typename CONTAINER>
static inline INDEX_TYPE next_idx_cyclic(INDEX_TYPE idx, const CONTAINER &container)
{
if (++ idx == INDEX_TYPE(container.size()))
idx = 0;
return idx;
}
template<class T, class U = T>
static inline T exchange(T& obj, U&& new_value)
{
T old_value = std::move(obj);
obj = std::forward<U>(new_value);
return old_value;
}
static inline void smooth_compensation_banded(const Points &contour, float band, std::vector<float> &compensation, float strength, size_t num_iterations)
{
assert(contour.size() == compensation.size());
assert(contour.size() > 2);
std::vector<float> out(compensation);
float dist_min2 = band * band;
static constexpr bool use_min = false;
for (size_t iter = 0; iter < num_iterations; ++ iter) {
for (int i = 0; i < int(compensation.size()); ++ i) {
const Vec2f pthis = contour[i].cast<float>();
int j = prev_idx_cyclic(i, contour);
Vec2f pprev = contour[j].cast<float>();
float prev = compensation[j];
float l2 = (pthis - pprev).squaredNorm();
if (l2 < dist_min2) {
float l = sqrt(l2);
int jprev = exchange(j, prev_idx_cyclic(j, contour));
while (j != i) {
const Vec2f pp = contour[j].cast<float>();
const float lthis = (pp - pprev).norm();
const float lnext = l + lthis;
if (lnext > band) {
// Interpolate the compensation value.
prev = use_min ?
std::min(prev, lerp(compensation[jprev], compensation[j], (band - l) / lthis)) :
lerp(compensation[jprev], compensation[j], (band - l) / lthis);
break;
}
prev = use_min ? std::min(prev, compensation[j]) : compensation[j];
pprev = pp;
l = lnext;
jprev = exchange(j, prev_idx_cyclic(j, contour));
}
}
j = next_idx_cyclic(i, contour);
pprev = contour[j].cast<float>();
float next = compensation[j];
l2 = (pprev - pthis).squaredNorm();
if (l2 < dist_min2) {
float l = sqrt(l2);
int jprev = exchange(j, next_idx_cyclic(j, contour));
while (j != i) {
const Vec2f pp = contour[j].cast<float>();
const float lthis = (pp - pprev).norm();
const float lnext = l + lthis;
if (lnext > band) {
// Interpolate the compensation value.
next = use_min ?
std::min(next, lerp(compensation[jprev], compensation[j], (band - l) / lthis)) :
lerp(compensation[jprev], compensation[j], (band - l) / lthis);
break;
}
next = use_min ? std::min(next, compensation[j]) : compensation[j];
pprev = pp;
l = lnext;
jprev = exchange(j, next_idx_cyclic(j, contour));
}
}
float laplacian = compensation[i] * (1.f - strength) + 0.5f * strength * (prev + next);
// Compensations are negative. Only apply the laplacian if it leads to lower compensation.
out[i] = std::max(laplacian, compensation[i]);
}
out.swap(compensation);
}
}
ExPolygon elephant_foot_compensation(const ExPolygon &input_expoly, const Flow &external_perimeter_flow, const double compensation)
{
// The contour shall be wide enough to apply the external perimeter plus compensation on both sides.
double min_contour_width = double(external_perimeter_flow.scaled_width() + external_perimeter_flow.scaled_spacing());
double scaled_compensation = scale_(compensation);
double min_contour_width_compensated = min_contour_width + 2. * scaled_compensation;
// Make the search radius a bit larger for the averaging in contour_distance over a fan of rays to work.
double search_radius = min_contour_width_compensated + min_contour_width * 0.5;
EdgeGrid::Grid grid;
ExPolygon simplified = input_expoly.simplify(SCALED_EPSILON).front();
BoundingBox bbox = get_extents(simplified.contour);
bbox.offset(SCALED_EPSILON);
grid.set_bbox(bbox);
grid.create(simplified, coord_t(0.7 * search_radius));
std::vector<std::vector<float>> deltas;
deltas.reserve(simplified.holes.size() + 1);
ExPolygon resampled(simplified);
double resample_interval = scale_(0.5);
for (size_t idx_contour = 0; idx_contour <= simplified.holes.size(); ++ idx_contour) {
Polygon &poly = (idx_contour == 0) ? resampled.contour : resampled.holes[idx_contour - 1];
std::vector<ResampledPoint> resampled_point_parameters;
poly.points = resample_polygon(poly.points, resample_interval, resampled_point_parameters);
std::vector<float> dists = contour_distance(grid, idx_contour, poly.points, resampled_point_parameters, search_radius);
for (float &d : dists) {
// printf("Point %d, Distance: %lf\n", int(&d - dists.data()), unscale<double>(d));
// Convert contour width to available compensation distance.
if (d < min_contour_width)
d = 0.f;
else if (d > min_contour_width_compensated)
d = - float(scaled_compensation);
else
d = - (d - float(min_contour_width)) / 2.f;
assert(d >= - float(scaled_compensation) && d <= 0.f);
}
// smooth_compensation(dists, 0.4f, 10);
smooth_compensation_banded(poly.points, float(0.8 * resample_interval), dists, 0.3f, 3);
deltas.emplace_back(dists);
}
ExPolygons out = variable_offset_inner_ex(resampled, deltas, 2.);
return out.front();
}
ExPolygons elephant_foot_compensation(const ExPolygons &input, const Flow &external_perimeter_flow, const double compensation)
{
ExPolygons out;
out.reserve(input.size());
for (const ExPolygon &expoly : input)
out.emplace_back(elephant_foot_compensation(expoly, external_perimeter_flow, compensation));
return out;
}
} // namespace Slic3r

View file

@ -0,0 +1,16 @@
#ifndef slic3r_ElephantFootCompensation_hpp_
#define slic3r_ElephantFootCompensation_hpp_
#include "libslic3r.h"
#include <vector>
namespace Slic3r {
class Flow;
ExPolygon elephant_foot_compensation(const ExPolygon &input, const Flow &external_perimeter_flow, const double compensation);
ExPolygons elephant_foot_compensation(const ExPolygons &input, const Flow &external_perimeter_flow, const double compensation);
} // Slic3r
#endif /* slic3r_ElephantFootCompensation_hpp_ */

View file

@ -18,8 +18,18 @@ class ExPolygon
{
public:
ExPolygon() {}
ExPolygon(const ExPolygon &other) : contour(other.contour), holes(other.holes) {}
ExPolygon(const ExPolygon &other) : contour(other.contour), holes(other.holes) {}
ExPolygon(ExPolygon &&other) : contour(std::move(other.contour)), holes(std::move(other.holes)) {}
explicit ExPolygon(const Polygon &contour) : contour(contour) {}
explicit ExPolygon(Polygon &&contour) : contour(std::move(contour)) {}
explicit ExPolygon(const Points &contour) : contour(contour) {}
explicit ExPolygon(Points &&contour) : contour(std::move(contour)) {}
explicit ExPolygon(const Polygon &contour, const Polygon &hole) : contour(contour) { holes.emplace_back(hole); }
explicit ExPolygon(Polygon &&contour, Polygon &&hole) : contour(std::move(contour)) { holes.emplace_back(std::move(hole)); }
explicit ExPolygon(const Points &contour, const Points &hole) : contour(contour) { holes.emplace_back(hole); }
explicit ExPolygon(Points &&contour, Polygon &&hole) : contour(std::move(contour)) { holes.emplace_back(std::move(hole)); }
ExPolygon(std::initializer_list<Point> contour) : contour(contour) {}
ExPolygon(std::initializer_list<Point> contour, std::initializer_list<Point> hole) : contour(contour), holes({ hole }) {}
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; }
@ -67,8 +77,16 @@ public:
void triangulate_pp(Points *triangles) const;
void triangulate_p2t(Polygons* polygons) const;
Lines lines() const;
// Number of contours (outer contour with holes).
size_t num_contours() const { return this->holes.size() + 1; }
Polygon& contour_or_hole(size_t idx) { return (idx == 0) ? this->contour : this->holes[idx - 1]; }
const Polygon& contour_or_hole(size_t idx) const { return (idx == 0) ? this->contour : this->holes[idx - 1]; }
};
inline bool operator==(const ExPolygon &lhs, const ExPolygon &rhs) { return lhs.contour == rhs.contour && lhs.holes == rhs.holes; }
inline bool operator!=(const ExPolygon &lhs, const ExPolygon &rhs) { return lhs.contour != rhs.contour || lhs.holes != rhs.holes; }
// Count a nuber of polygons stored inside the vector of expolygons.
// Useful for allocating space for polygons when converting expolygons to polygons.
inline size_t number_polygons(const ExPolygons &expolys)
@ -293,6 +311,15 @@ inline bool expolygons_contain(ExPolygons &expolys, const Point &pt)
return false;
}
inline ExPolygons expolygons_simplify(const ExPolygons &expolys, double tolerance)
{
ExPolygons out;
out.reserve(expolys.size());
for (const ExPolygon &exp : expolys)
exp.simplify(tolerance, &out);
return out;
}
extern BoundingBox get_extents(const ExPolygon &expolygon);
extern BoundingBox get_extents(const ExPolygons &expolygons);
extern BoundingBox get_extents_rotated(const ExPolygon &poly, double angle);

View file

@ -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);

View file

@ -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 &center);

View file

@ -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)

View file

@ -5,6 +5,8 @@
#include "Polygon.hpp"
#include "Polyline.hpp"
#include <assert.h>
namespace Slic3r {
class ExPolygonCollection;
@ -12,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,
@ -79,8 +81,8 @@ public:
virtual ExtrusionEntity* clone_move() = 0;
virtual ~ExtrusionEntity() {}
virtual void reverse() = 0;
virtual Point first_point() const = 0;
virtual Point last_point() const = 0;
virtual const Point& first_point() const = 0;
virtual const Point& last_point() const = 0;
// Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width.
// Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps.
virtual void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const = 0;
@ -115,30 +117,23 @@ 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;
ExtrusionPath(ExtrusionRole role) : mm3_per_mm(-1), width(-1), height(-1), feedrate(0.0f), extruder_id(0), cp_color_id(0), 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), 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), 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), 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), 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), 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->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->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.
ExtrusionEntity* clone_move() override { return new ExtrusionPath(std::move(*this)); }
void reverse() override { this->polyline.reverse(); }
Point first_point() const override { return this->polyline.points.front(); }
Point last_point() const override { return this->polyline.points.back(); }
const Point& first_point() const override { return this->polyline.points.front(); }
const Point& last_point() const override { return this->polyline.points.back(); }
size_t size() const { return this->polyline.size(); }
bool empty() const { return this->polyline.empty(); }
bool is_closed() const { return ! this->empty() && this->polyline.points.front() == this->polyline.points.back(); }
@ -198,8 +193,8 @@ public:
// Create a new object, initialize it with this object using the move semantics.
ExtrusionEntity* clone_move() override { return new ExtrusionMultiPath(std::move(*this)); }
void reverse() override;
Point first_point() const override { return this->paths.front().polyline.points.front(); }
Point last_point() const override { return this->paths.back().polyline.points.back(); }
const Point& first_point() const override { return this->paths.front().polyline.points.front(); }
const Point& last_point() const override { return this->paths.back().polyline.points.back(); }
double length() const override;
ExtrusionRole role() const override { return this->paths.empty() ? erNone : this->paths.front().role(); }
// Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width.
@ -241,8 +236,8 @@ public:
bool make_clockwise();
bool make_counter_clockwise();
void reverse() override;
Point first_point() const override { return this->paths.front().polyline.points.front(); }
Point last_point() const override { assert(first_point() == this->paths.back().polyline.points.back()); return first_point(); }
const Point& first_point() const override { return this->paths.front().polyline.points.front(); }
const Point& last_point() const override { assert(this->first_point() == this->paths.back().polyline.points.back()); return this->first_point(); }
Polygon polygon() const;
double length() const override;
bool split_at_vertex(const Point &point);

View file

@ -1,4 +1,5 @@
#include "ExtrusionEntityCollection.hpp"
#include "ShortestPath.hpp"
#include <algorithm>
#include <cmath>
#include <map>
@ -16,7 +17,6 @@ ExtrusionEntityCollection& ExtrusionEntityCollection::operator=(const ExtrusionE
this->entities = other.entities;
for (size_t i = 0; i < this->entities.size(); ++i)
this->entities[i] = this->entities[i]->clone();
this->orig_indices = other.orig_indices;
this->no_sort = other.no_sort;
return *this;
}
@ -24,7 +24,6 @@ ExtrusionEntityCollection& ExtrusionEntityCollection::operator=(const ExtrusionE
void ExtrusionEntityCollection::swap(ExtrusionEntityCollection &c)
{
std::swap(this->entities, c.entities);
std::swap(this->orig_indices, c.orig_indices);
std::swap(this->no_sort, c.no_sort);
}
@ -75,79 +74,31 @@ void ExtrusionEntityCollection::remove(size_t i)
this->entities.erase(this->entities.begin() + i);
}
ExtrusionEntityCollection ExtrusionEntityCollection::chained_path(bool no_reverse, ExtrusionRole role) const
ExtrusionEntityCollection ExtrusionEntityCollection::chained_path_from(const Point &start_near, ExtrusionRole role) const
{
ExtrusionEntityCollection coll;
this->chained_path(&coll, no_reverse, role);
return coll;
}
void ExtrusionEntityCollection::chained_path(ExtrusionEntityCollection* retval, bool no_reverse, ExtrusionRole role, std::vector<size_t>* orig_indices) const
{
if (this->entities.empty()) return;
this->chained_path_from(this->entities.front()->first_point(), retval, no_reverse, role, orig_indices);
}
ExtrusionEntityCollection ExtrusionEntityCollection::chained_path_from(Point start_near, bool no_reverse, ExtrusionRole role) const
{
ExtrusionEntityCollection coll;
this->chained_path_from(start_near, &coll, no_reverse, role);
return coll;
}
void ExtrusionEntityCollection::chained_path_from(Point start_near, ExtrusionEntityCollection* retval, bool no_reverse, ExtrusionRole role, std::vector<size_t>* orig_indices) const
{
if (this->no_sort) {
*retval = *this;
return;
}
retval->entities.reserve(this->entities.size());
retval->orig_indices.reserve(this->entities.size());
// if we're asked to return the original indices, build a map
std::map<ExtrusionEntity*,size_t> indices_map;
ExtrusionEntitiesPtr my_paths;
for (ExtrusionEntity * const &entity_src : this->entities) {
if (role != erMixed) {
// The caller wants only paths with a specific extrusion role.
auto role2 = entity_src->role();
if (role != role2) {
// This extrusion entity does not match the role asked.
assert(role2 != erMixed);
continue;
}
}
ExtrusionEntity *entity = entity_src->clone();
my_paths.push_back(entity);
if (orig_indices != nullptr)
indices_map[entity] = &entity_src - &this->entities.front();
}
Points endpoints;
for (const ExtrusionEntity *entity : my_paths) {
endpoints.push_back(entity->first_point());
endpoints.push_back((no_reverse || ! entity->can_reverse()) ?
entity->first_point() : entity->last_point());
}
while (! my_paths.empty()) {
// find nearest point
int start_index = start_near.nearest_point_index(endpoints);
int path_index = start_index/2;
ExtrusionEntity* entity = my_paths.at(path_index);
// never reverse loops, since it's pointless for chained path and callers might depend on orientation
if (start_index % 2 && !no_reverse && entity->can_reverse())
entity->reverse();
retval->entities.push_back(my_paths.at(path_index));
if (orig_indices != nullptr)
orig_indices->push_back(indices_map[entity]);
my_paths.erase(my_paths.begin() + path_index);
endpoints.erase(endpoints.begin() + 2*path_index, endpoints.begin() + 2*path_index + 2);
start_near = retval->entities.back()->last_point();
}
ExtrusionEntityCollection out;
if (this->no_sort) {
out = *this;
} else {
if (role == erMixed)
out = *this;
else {
for (const ExtrusionEntity *ee : this->entities) {
if (role != erMixed) {
// The caller wants only paths with a specific extrusion role.
auto role2 = ee->role();
if (role != role2) {
// This extrusion entity does not match the role asked.
assert(role2 != erMixed);
continue;
}
}
out.entities.emplace_back(ee->clone());
}
}
chain_and_reorder_extrusion_entities(out.entities, &start_near);
}
return out;
}
void ExtrusionEntityCollection::polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const
@ -175,18 +126,26 @@ size_t ExtrusionEntityCollection::items_count() const
}
// Returns a single vector of pointers to all non-collection items contained in this one.
ExtrusionEntityCollection ExtrusionEntityCollection::flatten() const
ExtrusionEntityCollection ExtrusionEntityCollection::flatten(bool preserve_ordering) const
{
struct Flatten {
Flatten(bool preserve_ordering) : preserve_ordering(preserve_ordering) {}
ExtrusionEntityCollection out;
bool preserve_ordering;
void recursive_do(const ExtrusionEntityCollection &collection) {
for (const ExtrusionEntity* entity : collection.entities)
if (entity->is_collection())
this->recursive_do(*static_cast<const ExtrusionEntityCollection*>(entity));
else
out.append(*entity);
if (collection.no_sort && preserve_ordering) {
// Don't flatten whatever happens below this level.
out.append(collection);
} else {
for (const ExtrusionEntity *entity : collection.entities)
if (entity->is_collection())
this->recursive_do(*static_cast<const ExtrusionEntityCollection*>(entity));
else
out.append(*entity);
}
}
} flatten;
} flatten(preserve_ordering);
flatten.recursive_do(*this);
return flatten.out;
}

View file

@ -14,19 +14,18 @@ public:
ExtrusionEntity* clone_move() override { return new ExtrusionEntityCollection(std::move(*this)); }
ExtrusionEntitiesPtr entities; // we own these entities
std::vector<size_t> orig_indices; // handy for XS
bool no_sort;
ExtrusionEntityCollection(): no_sort(false) {};
ExtrusionEntityCollection(const ExtrusionEntityCollection &other) : orig_indices(other.orig_indices), no_sort(other.no_sort) { this->append(other.entities); }
ExtrusionEntityCollection(ExtrusionEntityCollection &&other) : entities(std::move(other.entities)), orig_indices(std::move(other.orig_indices)), no_sort(other.no_sort) {}
ExtrusionEntityCollection(): no_sort(false) {}
ExtrusionEntityCollection(const ExtrusionEntityCollection &other) : no_sort(other.no_sort) { this->append(other.entities); }
ExtrusionEntityCollection(ExtrusionEntityCollection &&other) : entities(std::move(other.entities)), no_sort(other.no_sort) {}
explicit ExtrusionEntityCollection(const ExtrusionPaths &paths);
ExtrusionEntityCollection& operator=(const ExtrusionEntityCollection &other);
ExtrusionEntityCollection& operator=(ExtrusionEntityCollection &&other)
{ this->entities = std::move(other.entities); this->orig_indices = std::move(other.orig_indices); this->no_sort = other.no_sort; return *this; }
ExtrusionEntityCollection& operator=(ExtrusionEntityCollection &&other)
{ this->entities = std::move(other.entities); this->no_sort = other.no_sort; return *this; }
~ExtrusionEntityCollection() { clear(); }
explicit operator ExtrusionPaths() const;
bool is_collection() const { return true; };
bool is_collection() const { return true; }
ExtrusionRole role() const override {
ExtrusionRole out = erNone;
for (const ExtrusionEntity *ee : entities) {
@ -35,8 +34,8 @@ public:
}
return out;
}
bool can_reverse() const { return !this->no_sort; };
bool empty() const { return this->entities.empty(); };
bool can_reverse() const { return !this->no_sort; }
bool empty() const { return this->entities.empty(); }
void clear();
void swap (ExtrusionEntityCollection &c);
void append(const ExtrusionEntity &entity) { this->entities.emplace_back(entity.clone()); }
@ -66,13 +65,10 @@ public:
}
void replace(size_t i, const ExtrusionEntity &entity);
void remove(size_t i);
ExtrusionEntityCollection chained_path(bool no_reverse = false, ExtrusionRole role = erMixed) const;
void chained_path(ExtrusionEntityCollection* retval, bool no_reverse = false, ExtrusionRole role = erMixed, std::vector<size_t>* orig_indices = nullptr) const;
ExtrusionEntityCollection chained_path_from(Point start_near, bool no_reverse = false, ExtrusionRole role = erMixed) const;
void chained_path_from(Point start_near, ExtrusionEntityCollection* retval, bool no_reverse = false, ExtrusionRole role = erMixed, std::vector<size_t>* orig_indices = nullptr) const;
ExtrusionEntityCollection chained_path_from(const Point &start_near, ExtrusionRole role = erMixed) const;
void reverse();
Point first_point() const { return this->entities.front()->first_point(); }
Point last_point() const { return this->entities.back()->last_point(); }
const Point& first_point() const { return this->entities.front()->first_point(); }
const Point& last_point() const { return this->entities.back()->last_point(); }
// Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width.
// Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps.
void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const override;
@ -85,7 +81,10 @@ public:
Polygons polygons_covered_by_spacing(const float scaled_epsilon = 0.f) const
{ Polygons out; this->polygons_covered_by_spacing(out, scaled_epsilon); return out; }
size_t items_count() const;
ExtrusionEntityCollection flatten() const;
/// Returns a flattened copy of this ExtrusionEntityCollection. That is, all of the items in its entities vector are not collections.
/// You should be iterating over flatten().entities if you are interested in the underlying ExtrusionEntities (and don't care about hierarchy).
/// \param preserve_ordering Flag to method that will flatten if and only if the underlying collection is sortable when True (default: False).
ExtrusionEntityCollection flatten(bool preserve_ordering = false) const;
double min_mm3_per_mm() const;
double total_volume() const override { double volume=0.; for (const auto& ent : entities) volume+=ent->total_volume(); return volume; }

View file

@ -29,8 +29,6 @@ public:
FillParams params;
};
void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out);
} // namespace Slic3r
#endif // slic3r_Fill_hpp_

View file

@ -1,5 +1,5 @@
#include "../ClipperUtils.hpp"
#include "../PolylineCollection.hpp"
#include "../ShortestPath.hpp"
#include "../Surface.hpp"
#include "Fill3DHoneycomb.hpp"
@ -158,46 +158,18 @@ void Fill3DHoneycomb::_fill_surface_single(
((this->layer_id/thickness_layers) % 2) + 1);
// move pattern in place
for (Polylines::iterator it = polylines.begin(); it != polylines.end(); ++ it)
it->translate(bb.min(0), bb.min(1));
for (Polyline &pl : polylines)
pl.translate(bb.min);
// clip pattern to boundaries
polylines = intersection_pl(polylines, (Polygons)expolygon);
// clip pattern to boundaries, chain the clipped polylines
Polylines polylines_chained = chain_polylines(intersection_pl(polylines, to_polygons(expolygon)));
// connect lines
if (! params.dont_connect && ! polylines.empty()) { // prevent calling leftmost_point() on empty collections
ExPolygon expolygon_off;
{
ExPolygons expolygons_off = offset_ex(expolygon, SCALED_EPSILON);
if (! expolygons_off.empty()) {
// When expanding a polygon, the number of islands could only shrink. Therefore the offset_ex shall generate exactly one expanded island for one input island.
assert(expolygons_off.size() == 1);
std::swap(expolygon_off, expolygons_off.front());
}
}
Polylines chained = PolylineCollection::chained_path_from(
std::move(polylines),
PolylineCollection::leftmost_point(polylines), false); // reverse allowed
bool first = true;
for (Polylines::iterator it_polyline = chained.begin(); it_polyline != chained.end(); ++ it_polyline) {
if (! first) {
// Try to connect the lines.
Points &pts_end = polylines_out.back().points;
const Point &first_point = it_polyline->points.front();
const Point &last_point = pts_end.back();
// TODO: we should also check that both points are on a fill_boundary to avoid
// connecting paths on the boundaries of internal regions
if ((last_point - first_point).cast<double>().norm() <= 1.5 * distance &&
expolygon_off.contains(Line(last_point, first_point))) {
// Append the polyline.
pts_end.insert(pts_end.end(), it_polyline->points.begin(), it_polyline->points.end());
continue;
}
}
// The lines cannot be connected.
polylines_out.emplace_back(std::move(*it_polyline));
first = false;
}
// connect lines if needed
if (! polylines_chained.empty()) {
if (params.dont_connect)
append(polylines_out, std::move(polylines_chained));
else
this->connect_infill(std::move(polylines_chained), expolygon, polylines_out, params);
}
}

View file

@ -1,8 +1,10 @@
#include <stdio.h>
#include "../ClipperUtils.hpp"
#include "../EdgeGrid.hpp"
#include "../Surface.hpp"
#include "../PrintConfig.hpp"
#include "../libslic3r.h"
#include "FillBase.hpp"
#include "FillConcentric.hpp"
@ -148,4 +150,814 @@ std::pair<float, Point> Fill::_infill_direction(const Surface *surface) const
return std::pair<float, Point>(out_angle, out_shift);
}
#if 0
// From pull request "Gyroid improvements" #2730 by @supermerill
/// cut poly between poly.point[idx_1] & poly.point[idx_1+1]
/// add p1+-width to one part and p2+-width to the other one.
/// add the "new" polyline to polylines (to part cut from poly)
/// p1 & p2 have to be between poly.point[idx_1] & poly.point[idx_1+1]
/// if idx_1 is ==0 or == size-1, then we don't need to create a new polyline.
static void cut_polyline(Polyline &poly, Polylines &polylines, size_t idx_1, Point p1, Point p2) {
//reorder points
if (p1.distance_to_square(poly.points[idx_1]) > p2.distance_to_square(poly.points[idx_1])) {
Point temp = p2;
p2 = p1;
p1 = temp;
}
if (idx_1 == poly.points.size() - 1) {
//shouldn't be possible.
poly.points.erase(poly.points.end() - 1);
} else {
// create new polyline
Polyline new_poly;
//put points in new_poly
new_poly.points.push_back(p2);
new_poly.points.insert(new_poly.points.end(), poly.points.begin() + idx_1 + 1, poly.points.end());
//erase&put points in poly
poly.points.erase(poly.points.begin() + idx_1 + 1, poly.points.end());
poly.points.push_back(p1);
//safe test
if (poly.length() == 0)
poly.points = new_poly.points;
else
polylines.emplace_back(new_poly);
}
}
/// the poly is like a polygon but with first_point != last_point (already removed)
static void cut_polygon(Polyline &poly, size_t idx_1, Point p1, Point p2) {
//reorder points
if (p1.distance_to_square(poly.points[idx_1]) > p2.distance_to_square(poly.points[idx_1])) {
Point temp = p2;
p2 = p1;
p1 = temp;
}
//check if we need to rotate before cutting
if (idx_1 != poly.size() - 1) {
//put points in new_poly
poly.points.insert(poly.points.end(), poly.points.begin(), poly.points.begin() + idx_1 + 1);
poly.points.erase(poly.points.begin(), poly.points.begin() + idx_1 + 1);
}
//put points in poly
poly.points.push_back(p1);
poly.points.insert(poly.points.begin(), p2);
}
/// check if the polyline from pts_to_check may be at 'width' distance of a point in polylines_blocker
/// it use equally_spaced_points with width/2 precision, so don't worry with pts_to_check number of points.
/// it use the given polylines_blocker points, be sure to put enough of them to be reliable.
/// complexity : N(pts_to_check.equally_spaced_points(width / 2)) x N(polylines_blocker.points)
static bool collision(const Points &pts_to_check, const Polylines &polylines_blocker, const coordf_t width) {
//check if it's not too close to a polyline
coordf_t min_dist_square = width * width * 0.9 - SCALED_EPSILON;
Polyline better_polylines(pts_to_check);
Points better_pts = better_polylines.equally_spaced_points(width / 2);
for (const Point &p : better_pts) {
for (const Polyline &poly2 : polylines_blocker) {
for (const Point &p2 : poly2.points) {
if (p.distance_to_square(p2) < min_dist_square) {
return true;
}
}
}
}
return false;
}
/// Try to find a path inside polylines that allow to go from p1 to p2.
/// width if the width of the extrusion
/// polylines_blockers are the array of polylines to check if the path isn't blocked by something.
/// complexity: N(polylines.points) + a collision check after that if we finded a path: N(2(p2-p1)/width) x N(polylines_blocker.points)
static Points get_frontier(Polylines &polylines, const Point& p1, const Point& p2, const coord_t width, const Polylines &polylines_blockers, coord_t max_size = -1) {
for (size_t idx_poly = 0; idx_poly < polylines.size(); ++idx_poly) {
Polyline &poly = polylines[idx_poly];
if (poly.size() <= 1) continue;
//loop?
if (poly.first_point() == poly.last_point()) {
//polygon : try to find a line for p1 & p2.
size_t idx_11, idx_12, idx_21, idx_22;
idx_11 = poly.closest_point_index(p1);
idx_12 = idx_11;
if (Line(poly.points[idx_11], poly.points[(idx_11 + 1) % (poly.points.size() - 1)]).distance_to(p1) < SCALED_EPSILON) {
idx_12 = (idx_11 + 1) % (poly.points.size() - 1);
} else if (Line(poly.points[(idx_11 > 0) ? (idx_11 - 1) : (poly.points.size() - 2)], poly.points[idx_11]).distance_to(p1) < SCALED_EPSILON) {
idx_11 = (idx_11 > 0) ? (idx_11 - 1) : (poly.points.size() - 2);
} else {
continue;
}
idx_21 = poly.closest_point_index(p2);
idx_22 = idx_21;
if (Line(poly.points[idx_21], poly.points[(idx_21 + 1) % (poly.points.size() - 1)]).distance_to(p2) < SCALED_EPSILON) {
idx_22 = (idx_21 + 1) % (poly.points.size() - 1);
} else if (Line(poly.points[(idx_21 > 0) ? (idx_21 - 1) : (poly.points.size() - 2)], poly.points[idx_21]).distance_to(p2) < SCALED_EPSILON) {
idx_21 = (idx_21 > 0) ? (idx_21 - 1) : (poly.points.size() - 2);
} else {
continue;
}
//edge case: on the same line
if (idx_11 == idx_21 && idx_12 == idx_22) {
if (collision(Points() = { p1, p2 }, polylines_blockers, width)) return Points();
//break loop
poly.points.erase(poly.points.end() - 1);
cut_polygon(poly, idx_11, p1, p2);
return Points() = { Line(p1, p2).midpoint() };
}
//compute distance & array for the ++ path
Points ret_1_to_2;
double dist_1_to_2 = p1.distance_to(poly.points[idx_12]);
ret_1_to_2.push_back(poly.points[idx_12]);
size_t max = idx_12 <= idx_21 ? idx_21+1 : poly.points.size();
for (size_t i = idx_12 + 1; i < max; i++) {
dist_1_to_2 += poly.points[i - 1].distance_to(poly.points[i]);
ret_1_to_2.push_back(poly.points[i]);
}
if (idx_12 > idx_21) {
dist_1_to_2 += poly.points.back().distance_to(poly.points.front());
ret_1_to_2.push_back(poly.points[0]);
for (size_t i = 1; i <= idx_21; i++) {
dist_1_to_2 += poly.points[i - 1].distance_to(poly.points[i]);
ret_1_to_2.push_back(poly.points[i]);
}
}
dist_1_to_2 += p2.distance_to(poly.points[idx_21]);
//compute distance & array for the -- path
Points ret_2_to_1;
double dist_2_to_1 = p1.distance_to(poly.points[idx_11]);
ret_2_to_1.push_back(poly.points[idx_11]);
size_t min = idx_22 <= idx_11 ? idx_22 : 0;
for (size_t i = idx_11; i > min; i--) {
dist_2_to_1 += poly.points[i - 1].distance_to(poly.points[i]);
ret_2_to_1.push_back(poly.points[i - 1]);
}
if (idx_22 > idx_11) {
dist_2_to_1 += poly.points.back().distance_to(poly.points.front());
ret_2_to_1.push_back(poly.points[poly.points.size() - 1]);
for (size_t i = poly.points.size() - 1; i > idx_22; i--) {
dist_2_to_1 += poly.points[i - 1].distance_to(poly.points[i]);
ret_2_to_1.push_back(poly.points[i - 1]);
}
}
dist_2_to_1 += p2.distance_to(poly.points[idx_22]);
if (max_size < dist_2_to_1 && max_size < dist_1_to_2) {
return Points();
}
//choose between the two direction (keep the short one)
if (dist_1_to_2 < dist_2_to_1) {
if (collision(ret_1_to_2, polylines_blockers, width)) return Points();
//break loop
poly.points.erase(poly.points.end() - 1);
//remove points
if (idx_12 <= idx_21) {
poly.points.erase(poly.points.begin() + idx_12, poly.points.begin() + idx_21 + 1);
if (idx_12 != 0) {
cut_polygon(poly, idx_11, p1, p2);
} //else : already cut at the good place
} else {
poly.points.erase(poly.points.begin() + idx_12, poly.points.end());
poly.points.erase(poly.points.begin(), poly.points.begin() + idx_21);
cut_polygon(poly, poly.points.size() - 1, p1, p2);
}
return ret_1_to_2;
} else {
if (collision(ret_2_to_1, polylines_blockers, width)) return Points();
//break loop
poly.points.erase(poly.points.end() - 1);
//remove points
if (idx_22 <= idx_11) {
poly.points.erase(poly.points.begin() + idx_22, poly.points.begin() + idx_11 + 1);
if (idx_22 != 0) {
cut_polygon(poly, idx_21, p1, p2);
} //else : already cut at the good place
} else {
poly.points.erase(poly.points.begin() + idx_22, poly.points.end());
poly.points.erase(poly.points.begin(), poly.points.begin() + idx_11);
cut_polygon(poly, poly.points.size() - 1, p1, p2);
}
return ret_2_to_1;
}
} else {
//polyline : try to find a line for p1 & p2.
size_t idx_1, idx_2;
idx_1 = poly.closest_point_index(p1);
if (idx_1 < poly.points.size() - 1 && Line(poly.points[idx_1], poly.points[idx_1 + 1]).distance_to(p1) < SCALED_EPSILON) {
} else if (idx_1 > 0 && Line(poly.points[idx_1 - 1], poly.points[idx_1]).distance_to(p1) < SCALED_EPSILON) {
idx_1 = idx_1 - 1;
} else {
continue;
}
idx_2 = poly.closest_point_index(p2);
if (idx_2 < poly.points.size() - 1 && Line(poly.points[idx_2], poly.points[idx_2 + 1]).distance_to(p2) < SCALED_EPSILON) {
} else if (idx_2 > 0 && Line(poly.points[idx_2 - 1], poly.points[idx_2]).distance_to(p2) < SCALED_EPSILON) {
idx_2 = idx_2 - 1;
} else {
continue;
}
//edge case: on the same line
if (idx_1 == idx_2) {
if (collision(Points() = { p1, p2 }, polylines_blockers, width)) return Points();
cut_polyline(poly, polylines, idx_1, p1, p2);
return Points() = { Line(p1, p2).midpoint() };
}
//create ret array
size_t first_idx = idx_1;
size_t last_idx = idx_2 + 1;
if (idx_1 > idx_2) {
first_idx = idx_2;
last_idx = idx_1 + 1;
}
Points p_ret;
p_ret.insert(p_ret.end(), poly.points.begin() + first_idx + 1, poly.points.begin() + last_idx);
coordf_t length = 0;
for (size_t i = 1; i < p_ret.size(); i++) length += p_ret[i - 1].distance_to(p_ret[i]);
if (max_size < length) {
return Points();
}
if (collision(p_ret, polylines_blockers, width)) return Points();
//cut polyline
poly.points.erase(poly.points.begin() + first_idx + 1, poly.points.begin() + last_idx);
cut_polyline(poly, polylines, first_idx, p1, p2);
//order the returned array to be p1->p2
if (idx_1 > idx_2) {
std::reverse(p_ret.begin(), p_ret.end());
}
return p_ret;
}
}
return Points();
}
/// Connect the infill_ordered polylines, in this order, from the back point to the next front point.
/// It uses only the boundary polygons to do so, and can't pass two times at the same place.
/// It avoid passing over the infill_ordered's polylines (preventing local over-extrusion).
/// return the connected polylines in polylines_out. Can output polygons (stored as polylines with first_point = last_point).
/// complexity: worst: N(infill_ordered.points) x N(boundary.points)
/// typical: N(infill_ordered) x ( N(boundary.points) + N(infill_ordered.points) )
void Fill::connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary, Polylines &polylines_out, const FillParams &params) {
//TODO: fallback to the quick & dirty old algorithm when n(points) is too high.
Polylines polylines_frontier = to_polylines(((Polygons)boundary));
Polylines polylines_blocker;
coord_t clip_size = scale_(this->spacing) * 2;
for (const Polyline &polyline : infill_ordered) {
if (polyline.length() > 2.01 * clip_size) {
polylines_blocker.push_back(polyline);
polylines_blocker.back().clip_end(clip_size);
polylines_blocker.back().clip_start(clip_size);
}
}
//length between two lines
coordf_t ideal_length = (1 / params.density) * this->spacing;
Polylines polylines_connected_first;
bool first = true;
for (const Polyline &polyline : infill_ordered) {
if (!first) {
// Try to connect the lines.
Points &pts_end = polylines_connected_first.back().points;
const Point &last_point = pts_end.back();
const Point &first_point = polyline.points.front();
if (last_point.distance_to(first_point) < scale_(this->spacing) * 10) {
Points pts_frontier = get_frontier(polylines_frontier, last_point, first_point, scale_(this->spacing), polylines_blocker, (coord_t)scale_(ideal_length) * 2);
if (!pts_frontier.empty()) {
// The lines can be connected.
pts_end.insert(pts_end.end(), pts_frontier.begin(), pts_frontier.end());
pts_end.insert(pts_end.end(), polyline.points.begin(), polyline.points.end());
continue;
}
}
}
// The lines cannot be connected.
polylines_connected_first.emplace_back(std::move(polyline));
first = false;
}
Polylines polylines_connected;
first = true;
for (const Polyline &polyline : polylines_connected_first) {
if (!first) {
// Try to connect the lines.
Points &pts_end = polylines_connected.back().points;
const Point &last_point = pts_end.back();
const Point &first_point = polyline.points.front();
Polylines before = polylines_frontier;
Points pts_frontier = get_frontier(polylines_frontier, last_point, first_point, scale_(this->spacing), polylines_blocker);
if (!pts_frontier.empty()) {
// The lines can be connected.
pts_end.insert(pts_end.end(), pts_frontier.begin(), pts_frontier.end());
pts_end.insert(pts_end.end(), polyline.points.begin(), polyline.points.end());
continue;
}
}
// The lines cannot be connected.
polylines_connected.emplace_back(std::move(polyline));
first = false;
}
//try to link to nearest point if possible
for (size_t idx1 = 0; idx1 < polylines_connected.size(); idx1++) {
size_t min_idx = 0;
coordf_t min_length = 0;
bool switch_id1 = false;
bool switch_id2 = false;
for (size_t idx2 = idx1 + 1; idx2 < polylines_connected.size(); idx2++) {
double last_first = polylines_connected[idx1].last_point().distance_to_square(polylines_connected[idx2].first_point());
double first_first = polylines_connected[idx1].first_point().distance_to_square(polylines_connected[idx2].first_point());
double first_last = polylines_connected[idx1].first_point().distance_to_square(polylines_connected[idx2].last_point());
double last_last = polylines_connected[idx1].last_point().distance_to_square(polylines_connected[idx2].last_point());
double min = std::min(std::min(last_first, last_last), std::min(first_first, first_last));
if (min < min_length || min_length == 0) {
min_idx = idx2;
switch_id1 = (std::min(last_first, last_last) > std::min(first_first, first_last));
switch_id2 = (std::min(last_first, first_first) > std::min(last_last, first_last));
min_length = min;
}
}
if (min_idx > idx1 && min_idx < polylines_connected.size()){
Points pts_frontier = get_frontier(polylines_frontier,
switch_id1 ? polylines_connected[idx1].first_point() : polylines_connected[idx1].last_point(),
switch_id2 ? polylines_connected[min_idx].last_point() : polylines_connected[min_idx].first_point(),
scale_(this->spacing), polylines_blocker);
if (!pts_frontier.empty()) {
if (switch_id1) polylines_connected[idx1].reverse();
if (switch_id2) polylines_connected[min_idx].reverse();
Points &pts_end = polylines_connected[idx1].points;
pts_end.insert(pts_end.end(), pts_frontier.begin(), pts_frontier.end());
pts_end.insert(pts_end.end(), polylines_connected[min_idx].points.begin(), polylines_connected[min_idx].points.end());
polylines_connected.erase(polylines_connected.begin() + min_idx);
}
}
}
//try to create some loops if possible
for (Polyline &polyline : polylines_connected) {
Points pts_frontier = get_frontier(polylines_frontier, polyline.last_point(), polyline.first_point(), scale_(this->spacing), polylines_blocker);
if (!pts_frontier.empty()) {
polyline.points.insert(polyline.points.end(), pts_frontier.begin(), pts_frontier.end());
polyline.points.insert(polyline.points.begin(), polyline.points.back());
}
polylines_out.emplace_back(polyline);
}
}
#else
struct ContourPointData {
ContourPointData(float param) : param(param) {}
// Eucleidean position of the contour point along the contour.
float param = 0.f;
// Was the segment starting with this contour point extruded?
bool segment_consumed = false;
// Was this point extruded over?
bool point_consumed = false;
};
// Verify whether the contour from point idx_start to point idx_end could be taken (whether all segments along the contour were not yet extruded).
static bool could_take(const std::vector<ContourPointData> &contour_data, size_t idx_start, size_t idx_end)
{
for (size_t i = idx_start; i < idx_end; ) {
if (contour_data[i].segment_consumed || contour_data[i].point_consumed)
return false;
if (++ i == contour_data.size())
i = 0;
}
return ! contour_data[idx_end].point_consumed;
}
// Connect end of pl1 to the start of pl2 using the perimeter contour.
// The idx_start and idx_end are ordered so that the connecting polyline points will be taken with increasing indices.
static void take(Polyline &pl1, Polyline &&pl2, const Points &contour, std::vector<ContourPointData> &contour_data, size_t idx_start, size_t idx_end, bool reversed)
{
#ifndef NDEBUG
size_t num_points_initial = pl1.points.size();
assert(idx_start != idx_end);
#endif /* NDEBUG */
{
// Reserve memory at pl1 for the connecting contour and pl2.
int new_points = int(idx_end) - int(idx_start) - 1;
if (new_points < 0)
new_points += int(contour.size());
pl1.points.reserve(pl1.points.size() + size_t(new_points) + pl2.points.size());
}
contour_data[idx_start].point_consumed = true;
contour_data[idx_start].segment_consumed = true;
contour_data[idx_end ].point_consumed = true;
if (reversed) {
size_t i = (idx_end == 0) ? contour_data.size() - 1 : idx_end - 1;
while (i != idx_start) {
contour_data[i].point_consumed = true;
contour_data[i].segment_consumed = true;
pl1.points.emplace_back(contour[i]);
if (i == 0)
i = contour_data.size();
-- i;
}
} else {
size_t i = idx_start;
if (++ i == contour_data.size())
i = 0;
while (i != idx_end) {
contour_data[i].point_consumed = true;
contour_data[i].segment_consumed = true;
pl1.points.emplace_back(contour[i]);
if (++ i == contour_data.size())
i = 0;
}
}
append(pl1.points, std::move(pl2.points));
}
// Return an index of start of a segment and a point of the clipping point at distance from the end of polyline.
struct SegmentPoint {
// Segment index, defining a line <idx_segment, idx_segment + 1).
size_t idx_segment = std::numeric_limits<size_t>::max();
// Parameter of point in <0, 1) along the line <idx_segment, idx_segment + 1)
double t;
Vec2d point;
bool valid() const { return idx_segment != std::numeric_limits<size_t>::max(); }
};
static inline SegmentPoint clip_start_segment_and_point(const Points &polyline, double distance)
{
assert(polyline.size() >= 2);
assert(distance > 0.);
// Initialized to "invalid".
SegmentPoint out;
if (polyline.size() >= 2) {
const double d2 = distance * distance;
Vec2d pt_prev = polyline.front().cast<double>();
for (int i = 1; i < polyline.size(); ++ i) {
Vec2d pt = polyline[i].cast<double>();
Vec2d v = pt - pt_prev;
double l2 = v.squaredNorm();
if (l2 > d2) {
out.idx_segment = i;
out.t = distance / sqrt(l2);
out.point = pt + out.t * v;
break;
}
distance -= sqrt(l2);
pt_prev = pt;
}
}
return out;
}
static inline SegmentPoint clip_end_segment_and_point(const Points &polyline, double distance)
{
assert(polyline.size() >= 2);
assert(distance > 0.);
// Initialized to "invalid".
SegmentPoint out;
if (polyline.size() >= 2) {
const double d2 = distance * distance;
Vec2d pt_next = polyline.back().cast<double>();
for (int i = int(polyline.size()) - 2; i >= 0; -- i) {
Vec2d pt = polyline[i].cast<double>();
Vec2d v = pt - pt_next;
double l2 = v.squaredNorm();
if (l2 > d2) {
out.idx_segment = i;
out.t = distance / sqrt(l2);
out.point = pt + out.t * v;
break;
}
distance -= sqrt(l2);
pt_next = pt;
}
}
return out;
}
static inline double segment_point_distance_squared(const Vec2d &p1a, const Vec2d &p1b, const Vec2d &p2)
{
const Vec2d v = p1b - p1a;
const Vec2d va = p2 - p1a;
const double l2 = v.squaredNorm();
if (l2 < EPSILON)
// p1a == p1b
return va.squaredNorm();
// Project p2 onto the (p1a, p1b) segment.
const double t = va.dot(v);
if (t < 0.)
return va.squaredNorm();
else if (t > l2)
return (p2 - p1b).squaredNorm();
return ((t / l2) * v - va).squaredNorm();
}
// Distance to the closest point of line.
static inline double min_distance_of_segments(const Vec2d &p1a, const Vec2d &p1b, const Vec2d &p2a, const Vec2d &p2b)
{
Vec2d v1 = p1b - p1a;
double l1_2 = v1.squaredNorm();
if (l1_2 < EPSILON)
// p1a == p1b: Return distance of p1a from the (p2a, p2b) segment.
return segment_point_distance_squared(p2a, p2b, p1a);
Vec2d v2 = p2b - p2a;
double l2_2 = v2.squaredNorm();
if (l2_2 < EPSILON)
// p2a == p2b: Return distance of p2a from the (p1a, p1b) segment.
return segment_point_distance_squared(p1a, p1b, p2a);
// Project p2a, p2b onto the (p1a, p1b) segment.
auto project_p2a_p2b_onto_seg_p1a_p1b = [](const Vec2d& p1a, const Vec2d& p1b, const Vec2d& p2a, const Vec2d& p2b, const Vec2d& v1, const double l1_2) {
Vec2d v1a2a = p2a - p1a;
Vec2d v1a2b = p2b - p1a;
double t1 = v1a2a.dot(v1);
double t2 = v1a2b.dot(v1);
if (t1 <= 0.) {
if (t2 <= 0.)
// Both p2a and p2b are left of v1.
return (((t1 < t2) ? p2b : p2a) - p1a).squaredNorm();
else if (t2 < l1_2)
// Project p2b onto the (p1a, p1b) segment.
return ((t2 / l1_2) * v1 - v1a2b).squaredNorm();
}
else if (t1 >= l1_2) {
if (t2 >= l1_2)
// Both p2a and p2b are right of v1.
return (((t1 < t2) ? p2a : p2b) - p1b).squaredNorm();
else if (t2 < l1_2)
// Project p2b onto the (p1a, p1b) segment.
return ((t2 / l1_2) * v1 - v1a2b).squaredNorm();
}
else {
// Project p1b onto the (p1a, p1b) segment.
double dist_min = ((t2 / l1_2) * v1 - v1a2a).squaredNorm();
if (t2 > 0. && t2 < l1_2)
dist_min = std::min(dist_min, ((t2 / l1_2) * v1 - v1a2b).squaredNorm());
return dist_min;
}
return std::numeric_limits<double>::max();
};
return std::min(
project_p2a_p2b_onto_seg_p1a_p1b(p1a, p1b, p2a, p2b, v1, l1_2),
project_p2a_p2b_onto_seg_p1a_p1b(p2a, p2b, p1a, p1b, v2, l2_2));
}
// Mark the segments of split boundary as consumed if they are very close to some of the infill line.
void mark_boundary_segments_touching_infill(
const std::vector<Points> &boundary,
std::vector<std::vector<ContourPointData>> &boundary_data,
const BoundingBox &boundary_bbox,
const Polylines &infill,
const double clip_distance,
const double distance_colliding)
{
EdgeGrid::Grid grid;
grid.set_bbox(boundary_bbox);
// Inflate the bounding box by a thick line width.
grid.create(boundary, clip_distance + scale_(10.));
struct Visitor {
Visitor(const EdgeGrid::Grid &grid, const std::vector<Points> &boundary, std::vector<std::vector<ContourPointData>> &boundary_data, const double dist2_max) :
grid(grid), boundary(boundary), boundary_data(boundary_data), dist2_max(dist2_max) {}
void init(const Vec2d &pt1, const Vec2d &pt2) {
this->pt1 = &pt1;
this->pt2 = &pt2;
}
bool operator()(coord_t iy, coord_t ix) {
// Called with a row and colum of the grid cell, which is intersected by a line.
auto cell_data_range = this->grid.cell_data_range(iy, ix);
for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++ it_contour_and_segment) {
// End points of the line segment and their vector.
auto segment = this->grid.segment(*it_contour_and_segment);
const Vec2d seg_pt1 = segment.first.cast<double>();
const Vec2d seg_pt2 = segment.second.cast<double>();
if (min_distance_of_segments(seg_pt1, seg_pt2, *this->pt1, *this->pt2) < this->dist2_max) {
// Mark this boundary segment as touching the infill line.
ContourPointData&bdp = boundary_data[it_contour_and_segment->first][it_contour_and_segment->second];
bdp.segment_consumed = true;
// There is no need for checking seg_pt2 as it will be checked the next time.
if (segment_point_distance_squared(*this->pt1, *this->pt2, seg_pt1) < this->dist2_max)
bdp.point_consumed = true;
}
}
// Continue traversing the grid along the edge.
return true;
}
const EdgeGrid::Grid &grid;
const std::vector<Points> &boundary;
std::vector<std::vector<ContourPointData>> &boundary_data;
// Maximum distance between the boundary and the infill line allowed to consider the boundary not touching the infill line.
const double dist2_max;
const Vec2d *pt1;
const Vec2d *pt2;
} visitor(grid, boundary, boundary_data, distance_colliding * distance_colliding);
for (const Polyline &polyline : infill) {
// Clip the infill polyline by the Eucledian distance along the polyline.
SegmentPoint start_point = clip_start_segment_and_point(polyline.points, clip_distance);
SegmentPoint end_point = clip_end_segment_and_point(polyline.points, clip_distance);
if (start_point.valid() && end_point.valid() &&
(start_point.idx_segment < end_point.idx_segment || (start_point.idx_segment == end_point.idx_segment && start_point.t < end_point.t))) {
// The clipped polyline is non-empty.
for (size_t point_idx = start_point.idx_segment; point_idx <= end_point.idx_segment; ++ point_idx) {
//FIXME extend the EdgeGrid to suport tracing a thick line.
#if 0
Point pt1, pt2;
Vec2d pt1d, pt2d;
if (point_idx == start_point.idx_segment) {
pt1d = start_point.point;
pt1 = pt1d.cast<coord_t>();
} else {
pt1 = polyline.points[point_idx];
pt1d = pt1.cast<double>();
}
if (point_idx == start_point.idx_segment) {
pt2d = end_point.point;
pt2 = pt1d.cast<coord_t>();
} else {
pt2 = polyline.points[point_idx];
pt2d = pt2.cast<double>();
}
visitor.init(pt1d, pt2d);
grid.visit_cells_intersecting_thick_line(pt1, pt2, distance_colliding, visitor);
#else
Vec2d pt1 = (point_idx == start_point.idx_segment) ? start_point.point : polyline.points[point_idx].cast<double>();
Vec2d pt2 = (point_idx == end_point .idx_segment) ? end_point .point : polyline.points[point_idx].cast<double>();
visitor.init(pt1, pt2);
// Simulate tracing of a thick line. This only works reliably if distance_colliding <= grid cell size.
Vec2d v = (pt2 - pt1).normalized() * distance_colliding;
Vec2d vperp(-v.y(), v.x());
Vec2d a = pt1 - v - vperp;
Vec2d b = pt1 + v - vperp;
grid.visit_cells_intersecting_line(a.cast<coord_t>(), b.cast<coord_t>(), visitor);
a = pt1 - v + vperp;
b = pt1 + v + vperp;
grid.visit_cells_intersecting_line(a.cast<coord_t>(), b.cast<coord_t>(), visitor);
#endif
}
}
}
}
void Fill::connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary_src, Polylines &polylines_out, const FillParams &params)
{
assert(! infill_ordered.empty());
assert(! boundary_src.contour.points.empty());
BoundingBox bbox = get_extents(boundary_src.contour);
bbox.offset(SCALED_EPSILON);
// 1) Add the end points of infill_ordered to boundary_src.
std::vector<Points> boundary;
std::vector<std::vector<ContourPointData>> boundary_data;
boundary.assign(boundary_src.holes.size() + 1, Points());
boundary_data.assign(boundary_src.holes.size() + 1, std::vector<ContourPointData>());
// Mapping the infill_ordered end point to a (contour, point) of boundary.
std::vector<std::pair<size_t, size_t>> map_infill_end_point_to_boundary;
map_infill_end_point_to_boundary.assign(infill_ordered.size() * 2, std::pair<size_t, size_t>(std::numeric_limits<size_t>::max(), std::numeric_limits<size_t>::max()));
{
// Project the infill_ordered end points onto boundary_src.
std::vector<std::pair<EdgeGrid::Grid::ClosestPointResult, size_t>> intersection_points;
{
EdgeGrid::Grid grid;
grid.set_bbox(bbox);
grid.create(boundary_src, scale_(10.));
intersection_points.reserve(infill_ordered.size() * 2);
for (const Polyline &pl : infill_ordered)
for (const Point *pt : { &pl.points.front(), &pl.points.back() }) {
EdgeGrid::Grid::ClosestPointResult cp = grid.closest_point(*pt, SCALED_EPSILON);
if (cp.valid()) {
// The infill end point shall lie on the contour.
assert(cp.distance < 2.);
intersection_points.emplace_back(cp, (&pl - infill_ordered.data()) * 2 + (pt == &pl.points.front() ? 0 : 1));
}
}
std::sort(intersection_points.begin(), intersection_points.end(), [](const std::pair<EdgeGrid::Grid::ClosestPointResult, size_t> &cp1, const std::pair<EdgeGrid::Grid::ClosestPointResult, size_t> &cp2) {
return cp1.first.contour_idx < cp2.first.contour_idx ||
(cp1.first.contour_idx == cp2.first.contour_idx &&
(cp1.first.start_point_idx < cp2.first.start_point_idx ||
(cp1.first.start_point_idx == cp2.first.start_point_idx && cp1.first.t < cp2.first.t)));
});
}
auto it = intersection_points.begin();
auto it_end = intersection_points.end();
for (size_t idx_contour = 0; idx_contour <= boundary_src.holes.size(); ++ idx_contour) {
const Polygon &contour_src = (idx_contour == 0) ? boundary_src.contour : boundary_src.holes[idx_contour - 1];
Points &contour_dst = boundary[idx_contour];
for (size_t idx_point = 0; idx_point < contour_src.points.size(); ++ idx_point) {
contour_dst.emplace_back(contour_src.points[idx_point]);
for (; it != it_end && it->first.contour_idx == idx_contour && it->first.start_point_idx == idx_point; ++ it) {
// Add these points to the destination contour.
const Vec2d pt1 = contour_src[idx_point].cast<double>();
const Vec2d pt2 = (idx_point + 1 == contour_src.size() ? contour_src.points.front() : contour_src.points[idx_point + 1]).cast<double>();
const Vec2d pt = lerp(pt1, pt2, it->first.t);
map_infill_end_point_to_boundary[it->second] = std::make_pair(idx_contour, contour_dst.size());
contour_dst.emplace_back(pt.cast<coord_t>());
}
}
// Parametrize the curve.
std::vector<ContourPointData> &contour_data = boundary_data[idx_contour];
contour_data.reserve(contour_dst.size());
contour_data.emplace_back(ContourPointData(0.f));
for (size_t i = 1; i < contour_dst.size(); ++ i)
contour_data.emplace_back(contour_data.back().param + (contour_dst[i].cast<float>() - contour_dst[i - 1].cast<float>()).norm());
contour_data.front().param = contour_data.back().param + (contour_dst.back().cast<float>() - contour_dst.front().cast<float>()).norm();
}
#ifndef NDEBUG
assert(boundary.size() == boundary_src.num_contours());
assert(std::all_of(map_infill_end_point_to_boundary.begin(), map_infill_end_point_to_boundary.end(),
[&boundary](const std::pair<size_t, size_t> &contour_point) {
return contour_point.first < boundary.size() && contour_point.second < boundary[contour_point.first].size();
}));
#endif /* NDEBUG */
}
// Mark the points and segments of split boundary as consumed if they are very close to some of the infill line.
{
const double clip_distance = scale_(this->spacing);
const double distance_colliding = scale_(this->spacing);
mark_boundary_segments_touching_infill(boundary, boundary_data, bbox, infill_ordered, clip_distance, distance_colliding);
}
// Chain infill_ordered.
//FIXME run the following loop through a heap sorted by the shortest perimeter edge that could be taken.
//length between two lines
//const float length_max = scale_(this->spacing);
const float length_max = scale_((2. / params.density) * this->spacing);
size_t idx_chain_last = 0;
for (size_t idx_chain = 1; idx_chain < infill_ordered.size(); ++ idx_chain) {
Polyline &pl1 = infill_ordered[idx_chain_last];
Polyline &pl2 = infill_ordered[idx_chain];
const std::pair<size_t, size_t> *cp1 = &map_infill_end_point_to_boundary[(idx_chain - 1) * 2 + 1];
const std::pair<size_t, size_t> *cp2 = &map_infill_end_point_to_boundary[idx_chain * 2];
const Points &contour = boundary[cp1->first];
std::vector<ContourPointData> &contour_data = boundary_data[cp1->first];
bool valid = false;
bool reversed = false;
if (cp1->first == cp2->first) {
// End points on the same contour. Try to connect them.
float param_lo = (cp1->second == 0) ? 0.f : contour_data[cp1->second].param;
float param_hi = (cp2->second == 0) ? 0.f : contour_data[cp2->second].param;
float param_end = contour_data.front().param;
if (param_lo > param_hi) {
std::swap(param_lo, param_hi);
std::swap(cp1, cp2);
reversed = true;
}
assert(param_lo >= 0.f && param_lo <= param_end);
assert(param_hi >= 0.f && param_hi <= param_end);
float dist1 = param_hi - param_lo;
float dist2 = param_lo + param_end - param_hi;
if (dist1 > dist2) {
std::swap(dist1, dist2);
std::swap(cp1, cp2);
reversed = ! reversed;
}
if (dist1 < length_max) {
// Try to connect the shorter path.
valid = could_take(contour_data, cp1->second, cp2->second);
// Try to connect the longer path.
if (! valid && dist2 < length_max) {
std::swap(cp1, cp2);
reversed = ! reversed;
valid = could_take(contour_data, cp1->second, cp2->second);
}
}
}
if (valid)
take(pl1, std::move(pl2), contour, contour_data, cp1->second, cp2->second, reversed);
else if (++ idx_chain_last < idx_chain)
infill_ordered[idx_chain_last] = std::move(pl2);
}
infill_ordered.erase(infill_ordered.begin() + idx_chain_last + 1, infill_ordered.end());
append(polylines_out, std::move(infill_ordered));
}
#endif
} // namespace Slic3r

View file

@ -15,6 +15,7 @@
namespace Slic3r {
class ExPolygon;
class Surface;
struct FillParams
@ -110,6 +111,8 @@ protected:
virtual std::pair<float, Point> _infill_direction(const Surface *surface) const;
void connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary, Polylines &polylines_out, const FillParams &params);
public:
static coord_t _adjust_solid_spacing(const coord_t width, const coord_t distance);

View file

@ -1,5 +1,5 @@
#include "../ClipperUtils.hpp"
#include "../PolylineCollection.hpp"
#include "../ShortestPath.hpp"
#include "../Surface.hpp"
#include <cmath>
#include <algorithm>
@ -31,19 +31,26 @@ static inline double f(double x, double z_sin, double z_cos, bool vertical, bool
static inline Polyline make_wave(
const std::vector<Vec2d>& one_period, double width, double height, double offset, double scaleFactor,
double z_cos, double z_sin, bool vertical)
double z_cos, double z_sin, bool vertical, bool flip)
{
std::vector<Vec2d> points = one_period;
double period = points.back()(0);
points.pop_back();
int n = points.size();
do {
points.emplace_back(Vec2d(points[points.size()-n](0) + period, points[points.size()-n](1)));
} while (points.back()(0) < width);
points.back()(0) = width;
if (width != period) // do not extend if already truncated
{
points.reserve(one_period.size() * floor(width / period));
points.pop_back();
int n = points.size();
do {
points.emplace_back(Vec2d(points[points.size()-n](0) + period, points[points.size()-n](1)));
} while (points.back()(0) < width - EPSILON);
points.emplace_back(Vec2d(width, f(width, z_sin, z_cos, vertical, flip)));
}
// and construct the final polyline to return:
Polyline polyline;
polyline.points.reserve(points.size());
for (auto& point : points) {
point(1) += offset;
point(1) = clamp(0., height, double(point(1)));
@ -55,45 +62,56 @@ static inline Polyline make_wave(
return polyline;
}
static std::vector<Vec2d> make_one_period(double width, double scaleFactor, double z_cos, double z_sin, bool vertical, bool flip)
static std::vector<Vec2d> make_one_period(double width, double scaleFactor, double z_cos, double z_sin, bool vertical, bool flip, double tolerance)
{
std::vector<Vec2d> points;
double dx = M_PI_4; // very coarse spacing to begin with
double dx = M_PI_2; // exact coordinates on main inflexion lobes
double limit = std::min(2*M_PI, width);
for (double x = 0.; x < limit + EPSILON; x += dx) { // so the last point is there too
x = std::min(x, limit);
points.emplace_back(Vec2d(x,f(x, z_sin,z_cos, vertical, flip)));
}
points.reserve(ceil(limit / tolerance / 3));
// now we will check all internal points and in case some are too far from the line connecting its neighbours,
// we will add one more point on each side:
const double tolerance = .1;
for (unsigned int i=1;i<points.size()-1;++i) {
auto& lp = points[i-1]; // left point
auto& tp = points[i]; // this point
Vec2d lrv = tp - lp;
auto& rp = points[i+1]; // right point
// calculate distance of the point to the line:
double dist_mm = unscale<double>(scaleFactor) * std::abs(cross2(rp, lp) - cross2(rp - lp, tp)) / lrv.norm();
if (dist_mm > tolerance) { // if the difference from straight line is more than this
double x = 0.5f * (points[i-1](0) + points[i](0));
points.emplace_back(Vec2d(x, f(x, z_sin, z_cos, vertical, flip)));
x = 0.5f * (points[i+1](0) + points[i](0));
points.emplace_back(Vec2d(x, f(x, z_sin, z_cos, vertical, flip)));
// we added the points to the end, but need them all in order
std::sort(points.begin(), points.end(), [](const Vec2d &lhs, const Vec2d &rhs){ return lhs < rhs; });
// decrement i so we also check the first newly added point
--i;
for (double x = 0.; x < limit - EPSILON; x += dx) {
points.emplace_back(Vec2d(x, f(x, z_sin, z_cos, vertical, flip)));
}
points.emplace_back(Vec2d(limit, f(limit, z_sin, z_cos, vertical, flip)));
// piecewise increase in resolution up to requested tolerance
for(;;)
{
size_t size = points.size();
for (unsigned int i = 1;i < size; ++i) {
auto& lp = points[i-1]; // left point
auto& rp = points[i]; // right point
double x = lp(0) + (rp(0) - lp(0)) / 2;
double y = f(x, z_sin, z_cos, vertical, flip);
Vec2d ip = {x, y};
if (std::abs(cross2(Vec2d(ip - lp), Vec2d(ip - rp))) > sqr(tolerance)) {
points.emplace_back(std::move(ip));
}
}
if (size == points.size())
break;
else
{
// insert new points in order
std::sort(points.begin(), points.end(),
[](const Vec2d &lhs, const Vec2d &rhs) { return lhs(0) < rhs(0); });
}
}
return points;
}
static Polylines make_gyroid_waves(double gridZ, double density_adjusted, double line_spacing, double width, double height)
{
const double scaleFactor = scale_(line_spacing) / density_adjusted;
//scale factor for 5% : 8 712 388
// 1z = 10^-6 mm ?
// tolerance in scaled units. clamp the maximum tolerance as there's
// no processing-speed benefit to do so beyond a certain point
const double tolerance = std::min(line_spacing / 2, FillGyroid::PatternTolerance) / unscale<double>(scaleFactor);
//scale factor for 5% : 8 712 388
// 1z = 10^-6 mm ?
const double z = gridZ / scaleFactor;
const double z_sin = sin(z);
const double z_cos = cos(z);
@ -109,16 +127,20 @@ static Polylines make_gyroid_waves(double gridZ, double density_adjusted, double
std::swap(width,height);
}
std::vector<Vec2d> one_period = make_one_period(width, scaleFactor, z_cos, z_sin, vertical, flip); // creates one period of the waves, so it doesn't have to be recalculated all the time
std::vector<Vec2d> one_period_odd = make_one_period(width, scaleFactor, z_cos, z_sin, vertical, flip, tolerance); // creates one period of the waves, so it doesn't have to be recalculated all the time
flip = !flip; // even polylines are a bit shifted
std::vector<Vec2d> one_period_even = make_one_period(width, scaleFactor, z_cos, z_sin, vertical, flip, tolerance);
Polylines result;
for (double y0 = lower_bound; y0 < upper_bound+EPSILON; y0 += 2*M_PI) // creates odd polylines
result.emplace_back(make_wave(one_period, width, height, y0, scaleFactor, z_cos, z_sin, vertical));
flip = !flip; // even polylines are a bit shifted
one_period = make_one_period(width, scaleFactor, z_cos, z_sin, vertical, flip); // updates the one period sample
for (double y0 = lower_bound + M_PI; y0 < upper_bound+EPSILON; y0 += 2*M_PI) // creates even polylines
result.emplace_back(make_wave(one_period, width, height, y0, scaleFactor, z_cos, z_sin, vertical));
for (double y0 = lower_bound; y0 < upper_bound + EPSILON; y0 += M_PI) {
// creates odd polylines
result.emplace_back(make_wave(one_period_odd, width, height, y0, scaleFactor, z_cos, z_sin, vertical, flip));
// creates even polylines
y0 += M_PI;
if (y0 < upper_bound + EPSILON) {
result.emplace_back(make_wave(one_period_even, width, height, y0, scaleFactor, z_cos, z_sin, vertical, flip));
}
}
return result;
}
@ -130,66 +152,49 @@ void FillGyroid::_fill_surface_single(
ExPolygon &expolygon,
Polylines &polylines_out)
{
// no rotation is supported for this infill pattern (yet)
float infill_angle = this->angle + (CorrectionAngle * 2*M_PI) / 360.;
if(abs(infill_angle) >= EPSILON)
expolygon.rotate(-infill_angle);
BoundingBox bb = expolygon.contour.bounding_box();
// Density adjusted to have a good %of weight.
double density_adjusted = std::max(0., params.density * 2.44);
double density_adjusted = std::max(0., params.density * DensityAdjust);
// Distance between the gyroid waves in scaled coordinates.
coord_t distance = coord_t(scale_(this->spacing) / density_adjusted);
// align bounding box to a multiple of our grid module
bb.merge(_align_to_grid(bb.min, Point(2.*M_PI*distance, 2.*M_PI*distance)));
bb.merge(_align_to_grid(bb.min, Point(2*M_PI*distance, 2*M_PI*distance)));
// generate pattern
Polylines polylines = make_gyroid_waves(
Polylines polylines_square = make_gyroid_waves(
scale_(this->z),
density_adjusted,
this->spacing,
ceil(bb.size()(0) / distance) + 1.,
ceil(bb.size()(1) / distance) + 1.);
// move pattern in place
for (Polyline &polyline : polylines)
polyline.translate(bb.min(0), bb.min(1));
// clip pattern to boundaries
polylines = intersection_pl(polylines, (Polygons)expolygon);
// shift the polyline to the grid origin
for (Polyline &pl : polylines_square)
pl.translate(bb.min);
// connect lines
if (! params.dont_connect && ! polylines.empty()) { // prevent calling leftmost_point() on empty collections
ExPolygon expolygon_off;
{
ExPolygons expolygons_off = offset_ex(expolygon, (float)SCALED_EPSILON);
if (! expolygons_off.empty()) {
// When expanding a polygon, the number of islands could only shrink. Therefore the offset_ex shall generate exactly one expanded island for one input island.
assert(expolygons_off.size() == 1);
std::swap(expolygon_off, expolygons_off.front());
}
}
Polylines chained = PolylineCollection::chained_path_from(
std::move(polylines),
PolylineCollection::leftmost_point(polylines), false); // reverse allowed
bool first = true;
for (Polyline &polyline : chained) {
if (! first) {
// Try to connect the lines.
Points &pts_end = polylines_out.back().points;
const Point &first_point = polyline.points.front();
const Point &last_point = pts_end.back();
// TODO: we should also check that both points are on a fill_boundary to avoid
// connecting paths on the boundaries of internal regions
// TODO: avoid crossing current infill path
if ((last_point - first_point).cast<double>().norm() <= 5 * distance &&
expolygon_off.contains(Line(last_point, first_point))) {
// Append the polyline.
pts_end.insert(pts_end.end(), polyline.points.begin(), polyline.points.end());
continue;
}
}
// The lines cannot be connected.
polylines_out.emplace_back(std::move(polyline));
first = false;
}
Polylines polylines_chained = chain_polylines(intersection_pl(polylines_square, to_polygons(expolygon)));
size_t polylines_out_first_idx = polylines_out.size();
if (! polylines_chained.empty()) {
// connect lines
if (params.dont_connect)
append(polylines_out, std::move(polylines_chained));
else
this->connect_infill(std::move(polylines_chained), expolygon, polylines_out, params);
// remove too small bits (larger than longer)
polylines_out.erase(
std::remove_if(polylines_out.begin() + polylines_out_first_idx, polylines_out.end(), [this](const Polyline &pl){ return pl.length() < scale_(this->spacing * 3); }),
polylines_out.end());
// new paths must be rotated back
if (abs(infill_angle) >= EPSILON) {
for (auto it = polylines_out.begin() + polylines_out_first_idx; it != polylines_out.end(); ++ it)
it->rotate(infill_angle);
}
}
}

View file

@ -16,6 +16,17 @@ public:
// require bridge flow since most of this pattern hangs in air
virtual bool use_bridge_flow() const { return false; }
// Correction applied to regular infill angle to maximize printing
// speed in default configuration (degrees)
static constexpr float CorrectionAngle = -45.;
// Density adjustment to have a good %of weight.
static constexpr double DensityAdjust = 2.44;
// Gyroid upper resolution tolerance (mm^-2)
static constexpr double PatternTolerance = 0.2;
protected:
virtual void _fill_surface_single(
const FillParams &params,

View file

@ -1,5 +1,5 @@
#include "../ClipperUtils.hpp"
#include "../PolylineCollection.hpp"
#include "../ShortestPath.hpp"
#include "../Surface.hpp"
#include "FillHoneycomb.hpp"
@ -93,22 +93,20 @@ void FillHoneycomb::_fill_surface_single(
// connect paths
if (! paths.empty()) { // prevent calling leftmost_point() on empty collections
Polylines chained = PolylineCollection::chained_path_from(
std::move(paths),
PolylineCollection::leftmost_point(paths), false);
Polylines chained = chain_polylines(std::move(paths));
assert(paths.empty());
paths.clear();
for (Polylines::iterator it_path = chained.begin(); it_path != chained.end(); ++ it_path) {
for (Polyline &path : chained) {
if (! paths.empty()) {
// distance between first point of this path and last point of last path
double distance = (it_path->first_point() - paths.back().last_point()).cast<double>().norm();
double distance = (path.first_point() - paths.back().last_point()).cast<double>().norm();
if (distance <= m.hex_width) {
paths.back().points.insert(paths.back().points.end(), it_path->points.begin(), it_path->points.end());
paths.back().points.insert(paths.back().points.end(), path.points.begin(), path.points.end());
continue;
}
}
// Don't connect the paths.
paths.push_back(*it_path);
paths.push_back(std::move(path));
}
}

View file

@ -1,5 +1,4 @@
#include "../ClipperUtils.hpp"
#include "../PolylineCollection.hpp"
#include "../Surface.hpp"
#include "FillPlanePath.hpp"

View file

@ -1,6 +1,6 @@
#include "../ClipperUtils.hpp"
#include "../ExPolygon.hpp"
#include "../PolylineCollection.hpp"
#include "../ShortestPath.hpp"
#include "../Surface.hpp"
#include "FillRectilinear.hpp"
@ -92,15 +92,12 @@ void FillRectilinear::_fill_surface_single(
std::swap(expolygon_off, expolygons_off.front());
}
}
Polylines chained = PolylineCollection::chained_path_from(
std::move(polylines),
PolylineCollection::leftmost_point(polylines), false); // reverse allowed
bool first = true;
for (Polylines::iterator it_polyline = chained.begin(); it_polyline != chained.end(); ++ it_polyline) {
for (Polyline &polyline : chain_polylines(std::move(polylines))) {
if (! first) {
// Try to connect the lines.
Points &pts_end = polylines_out.back().points;
const Point &first_point = it_polyline->points.front();
const Point &first_point = polyline.points.front();
const Point &last_point = pts_end.back();
// Distance in X, Y.
const Vector distance = last_point - first_point;
@ -109,12 +106,12 @@ void FillRectilinear::_fill_surface_single(
if (this->_can_connect(std::abs(distance(0)), std::abs(distance(1))) &&
expolygon_off.contains(Line(last_point, first_point))) {
// Append the polyline.
pts_end.insert(pts_end.end(), it_polyline->points.begin(), it_polyline->points.end());
pts_end.insert(pts_end.end(), polyline.points.begin(), polyline.points.end());
continue;
}
}
// The lines cannot be connected.
polylines_out.emplace_back(std::move(*it_polyline));
polylines_out.emplace_back(std::move(polyline));
first = false;
}
}

View file

@ -3,6 +3,9 @@
#include "../Utils.hpp"
#include "../GCode.hpp"
#include "../Geometry.hpp"
#if ENABLE_THUMBNAIL_GENERATOR
#include "../GCode/ThumbnailData.hpp"
#endif // ENABLE_THUMBNAIL_GENERATOR
#include "../I18N.hpp"
@ -31,7 +34,8 @@ namespace pt = boost::property_tree;
// VERSION NUMBERS
// 0 : .3mf, files saved by older slic3r or other applications. No version definition in them.
// 1 : Introduction of 3mf versioning. No other change in data saved into 3mf files.
const unsigned int VERSION_3MF = 1;
// 2 : Meshes saved in their local system; Volumes' matrices and source data added to Metadata/Slic3r_PE_model.config file.
const unsigned int VERSION_3MF = 2;
const char* SLIC3RPE_3MF_VERSION = "slic3rpe:Version3mf"; // definition of the metadata name saved into .model file
const std::string MODEL_FOLDER = "3D/";
@ -39,6 +43,9 @@ const std::string MODEL_EXTENSION = ".model";
const std::string MODEL_FILE = "3D/3dmodel.model"; // << this is the only format of the string which works with CURA
const std::string CONTENT_TYPES_FILE = "[Content_Types].xml";
const std::string RELATIONSHIPS_FILE = "_rels/.rels";
#if ENABLE_THUMBNAIL_GENERATOR
const std::string THUMBNAIL_FILE = "Metadata/thumbnail.png";
#endif // ENABLE_THUMBNAIL_GENERATOR
const std::string PRINT_CONFIG_FILE = "Metadata/Slic3r_PE.config";
const std::string MODEL_CONFIG_FILE = "Metadata/Slic3r_PE_model.config";
const std::string LAYER_HEIGHTS_PROFILE_FILE = "Metadata/Slic3r_PE_layer_heights_profile.txt";
@ -87,6 +94,13 @@ const char* VOLUME_TYPE = "volume";
const char* NAME_KEY = "name";
const char* MODIFIER_KEY = "modifier";
const char* VOLUME_TYPE_KEY = "volume_type";
const char* MATRIX_KEY = "matrix";
const char* SOURCE_FILE_KEY = "source_file";
const char* SOURCE_OBJECT_ID_KEY = "source_object_id";
const char* SOURCE_VOLUME_ID_KEY = "source_volume_id";
const char* SOURCE_OFFSET_X_KEY = "source_offset_x";
const char* SOURCE_OFFSET_Y_KEY = "source_offset_y";
const char* SOURCE_OFFSET_Z_KEY = "source_offset_z";
const unsigned int VALID_OBJECT_TYPES_COUNT = 1;
const char* VALID_OBJECT_TYPES[] =
@ -148,11 +162,15 @@ bool get_attribute_value_bool(const char** attributes, unsigned int attributes_s
return (text != nullptr) ? (bool)::atoi(text) : true;
}
Slic3r::Transform3d get_transform_from_string(const std::string& mat_str)
Slic3r::Transform3d get_transform_from_3mf_specs_string(const std::string& mat_str)
{
// check: https://3mf.io/3d-manufacturing-format/ or https://github.com/3MFConsortium/spec_core/blob/master/3MF%20Core%20Specification.md
// to see how matrices are stored inside 3mf according to specifications
Slic3r::Transform3d ret = Slic3r::Transform3d::Identity();
if (mat_str.empty())
// empty string means default identity matrix
return Slic3r::Transform3d::Identity();
return ret;
std::vector<std::string> mat_elements_str;
boost::split(mat_elements_str, mat_str, boost::is_any_of(" "), boost::token_compress_on);
@ -160,9 +178,8 @@ Slic3r::Transform3d get_transform_from_string(const std::string& mat_str)
unsigned int size = (unsigned int)mat_elements_str.size();
if (size != 12)
// invalid data, return identity matrix
return Slic3r::Transform3d::Identity();
return ret;
Slic3r::Transform3d ret = Slic3r::Transform3d::Identity();
unsigned int i = 0;
// matrices are stored into 3mf files as 4x3
// we need to transpose them
@ -1375,7 +1392,7 @@ namespace Slic3r {
bool _3MF_Importer::_handle_start_component(const char** attributes, unsigned int num_attributes)
{
int object_id = get_attribute_value_int(attributes, num_attributes, OBJECTID_ATTR);
Transform3d transform = get_transform_from_string(get_attribute_value_string(attributes, num_attributes, TRANSFORM_ATTR));
Transform3d transform = get_transform_from_3mf_specs_string(get_attribute_value_string(attributes, num_attributes, TRANSFORM_ATTR));
IdToModelObjectMap::iterator object_item = m_objects.find(object_id);
if (object_item == m_objects.end())
@ -1421,7 +1438,7 @@ namespace Slic3r {
// see specifications
int object_id = get_attribute_value_int(attributes, num_attributes, OBJECTID_ATTR);
Transform3d transform = get_transform_from_string(get_attribute_value_string(attributes, num_attributes, TRANSFORM_ATTR));
Transform3d transform = get_transform_from_3mf_specs_string(get_attribute_value_string(attributes, num_attributes, TRANSFORM_ATTR));
int printable = get_attribute_value_bool(attributes, num_attributes, PRINTABLE_ATTR);
return _create_object_instance(object_id, transform, printable, 1);
@ -1634,6 +1651,21 @@ namespace Slic3r {
return false;
}
Slic3r::Geometry::Transformation transform;
if (m_version > 1)
{
// extract the volume transformation from the volume's metadata, if present
for (const Metadata& metadata : volume_data.metadata)
{
if (metadata.key == MATRIX_KEY)
{
transform.set_from_string(metadata.value);
break;
}
}
}
Transform3d inv_matrix = transform.get_matrix().inverse();
// splits volume out of imported geometry
TriangleMesh triangle_mesh;
stl_file &stl = triangle_mesh.stl;
@ -1651,7 +1683,12 @@ namespace Slic3r {
stl_facet& facet = stl.facet_start[i];
for (unsigned int v = 0; v < 3; ++v)
{
::memcpy(facet.vertex[v].data(), (const void*)&geometry.vertices[geometry.triangles[src_start_id + ii + v] * 3], 3 * sizeof(float));
unsigned int tri_id = geometry.triangles[src_start_id + ii + v] * 3;
Vec3f vertex(geometry.vertices[tri_id + 0], geometry.vertices[tri_id + 1], geometry.vertices[tri_id + 2]);
if (m_version > 1)
// revert the vertices to the original mesh reference system
vertex = (inv_matrix * vertex.cast<double>()).cast<float>();
::memcpy(facet.vertex[v].data(), (const void*)vertex.data(), 3 * sizeof(float));
}
}
@ -1659,10 +1696,12 @@ namespace Slic3r {
triangle_mesh.repair();
ModelVolume* volume = object.add_volume(std::move(triangle_mesh));
volume->center_geometry_after_creation();
// apply the volume matrix taken from the metadata, if present
if (m_version > 1)
volume->set_transformation(transform);
volume->calculate_convex_hull();
// apply volume's name and config data
// apply the remaining volume's metadata
for (const Metadata& metadata : volume_data.metadata)
{
if (metadata.key == NAME_KEY)
@ -1671,6 +1710,18 @@ namespace Slic3r {
volume->set_type(ModelVolumeType::PARAMETER_MODIFIER);
else if (metadata.key == VOLUME_TYPE_KEY)
volume->set_type(ModelVolume::type_from_string(metadata.value));
else if (metadata.key == SOURCE_FILE_KEY)
volume->source.input_file = metadata.value;
else if (metadata.key == SOURCE_OBJECT_ID_KEY)
volume->source.object_idx = ::atoi(metadata.value.c_str());
else if (metadata.key == SOURCE_VOLUME_ID_KEY)
volume->source.volume_idx = ::atoi(metadata.value.c_str());
else if (metadata.key == SOURCE_OFFSET_X_KEY)
volume->source.mesh_offset(0) = ::atof(metadata.value.c_str());
else if (metadata.key == SOURCE_OFFSET_Y_KEY)
volume->source.mesh_offset(1) = ::atof(metadata.value.c_str());
else if (metadata.key == SOURCE_OFFSET_Z_KEY)
volume->source.mesh_offset(2) = ::atof(metadata.value.c_str());
else
volume->config.set_deserialize(metadata.key, metadata.value);
}
@ -1761,11 +1812,22 @@ namespace Slic3r {
typedef std::map<int, ObjectData> IdToObjectDataMap;
public:
#if ENABLE_THUMBNAIL_GENERATOR
bool save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, const ThumbnailData* thumbnail_data = nullptr);
#else
bool save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config);
#endif // ENABLE_THUMBNAIL_GENERATOR
private:
#if ENABLE_THUMBNAIL_GENERATOR
bool _save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, const ThumbnailData* thumbnail_data);
#else
bool _save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config);
#endif // ENABLE_THUMBNAIL_GENERATOR
bool _add_content_types_file_to_archive(mz_zip_archive& archive);
#if ENABLE_THUMBNAIL_GENERATOR
bool _add_thumbnail_file_to_archive(mz_zip_archive& archive, const ThumbnailData& thumbnail_data);
#endif // ENABLE_THUMBNAIL_GENERATOR
bool _add_relationships_file_to_archive(mz_zip_archive& archive);
bool _add_model_file_to_archive(mz_zip_archive& archive, const Model& model, IdToObjectDataMap &objects_data);
bool _add_object_to_model_stream(std::stringstream& stream, unsigned int& object_id, ModelObject& object, BuildItemsList& build_items, VolumeToOffsetsMap& volumes_offsets);
@ -1778,13 +1840,25 @@ namespace Slic3r {
bool _add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model, const IdToObjectDataMap &objects_data);
};
#if ENABLE_THUMBNAIL_GENERATOR
bool _3MF_Exporter::save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, const ThumbnailData* thumbnail_data)
{
clear_errors();
return _save_model_to_file(filename, model, config, thumbnail_data);
}
#else
bool _3MF_Exporter::save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config)
{
clear_errors();
return _save_model_to_file(filename, model, config);
}
#endif // ENABLE_THUMBNAIL_GENERATOR
#if ENABLE_THUMBNAIL_GENERATOR
bool _3MF_Exporter::_save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, const ThumbnailData* thumbnail_data)
#else
bool _3MF_Exporter::_save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config)
#endif // ENABLE_THUMBNAIL_GENERATOR
{
mz_zip_archive archive;
mz_zip_zero_struct(&archive);
@ -1803,6 +1877,19 @@ namespace Slic3r {
return false;
}
#if ENABLE_THUMBNAIL_GENERATOR
if ((thumbnail_data != nullptr) && thumbnail_data->is_valid())
{
// Adds the file Metadata/thumbnail.png.
if (!_add_thumbnail_file_to_archive(archive, *thumbnail_data))
{
close_zip_writer(&archive);
boost::filesystem::remove(filename);
return false;
}
}
#endif // ENABLE_THUMBNAIL_GENERATOR
// Adds relationships file ("_rels/.rels").
// The content of this file is the same for each PrusaSlicer 3mf.
// The relationshis file contains a reference to the geometry file "3D/3dmodel.model", the name was chosen to be compatible with CURA.
@ -1896,6 +1983,9 @@ namespace Slic3r {
stream << "<Types xmlns=\"http://schemas.openxmlformats.org/package/2006/content-types\">\n";
stream << " <Default Extension=\"rels\" ContentType=\"application/vnd.openxmlformats-package.relationships+xml\" />\n";
stream << " <Default Extension=\"model\" ContentType=\"application/vnd.ms-package.3dmanufacturing-3dmodel+xml\" />\n";
#if ENABLE_THUMBNAIL_GENERATOR
stream << " <Default Extension=\"png\" ContentType=\"image/png\" />\n";
#endif // ENABLE_THUMBNAIL_GENERATOR
stream << "</Types>";
std::string out = stream.str();
@ -1909,12 +1999,35 @@ namespace Slic3r {
return true;
}
#if ENABLE_THUMBNAIL_GENERATOR
bool _3MF_Exporter::_add_thumbnail_file_to_archive(mz_zip_archive& archive, const ThumbnailData& thumbnail_data)
{
bool res = false;
size_t png_size = 0;
void* png_data = tdefl_write_image_to_png_file_in_memory_ex((const void*)thumbnail_data.pixels.data(), thumbnail_data.width, thumbnail_data.height, 4, &png_size, MZ_DEFAULT_LEVEL, 1);
if (png_data != nullptr)
{
res = mz_zip_writer_add_mem(&archive, THUMBNAIL_FILE.c_str(), (const void*)png_data, png_size, MZ_DEFAULT_COMPRESSION);
mz_free(png_data);
}
if (!res)
add_error("Unable to add thumbnail file to archive");
return res;
}
#endif // ENABLE_THUMBNAIL_GENERATOR
bool _3MF_Exporter::_add_relationships_file_to_archive(mz_zip_archive& archive)
{
std::stringstream stream;
stream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
stream << "<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">\n";
stream << " <Relationship Target=\"/" << MODEL_FILE << "\" Id=\"rel-1\" Type=\"http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel\" />\n";
#if ENABLE_THUMBNAIL_GENERATOR
stream << " <Relationship Target=\"/" << THUMBNAIL_FILE << "\" Id=\"rel-2\" Type=\"http://schemas.openxmlformats.org/package/2006/relationships/metadata/thumbnail\" />\n";
#endif // ENABLE_THUMBNAIL_GENERATOR
stream << "</Relationships>";
std::string out = stream.str();
@ -2116,7 +2229,7 @@ namespace Slic3r {
for (const BuildItem& item : build_items)
{
stream << " <" << ITEM_TAG << " objectid=\"" << item.id << "\" transform =\"";
stream << " <" << ITEM_TAG << " " << OBJECTID_ATTR << "=\"" << item.id << "\" " << TRANSFORM_ATTR << "=\"";
for (unsigned c = 0; c < 4; ++c)
{
for (unsigned r = 0; r < 3; ++r)
@ -2126,7 +2239,7 @@ namespace Slic3r {
stream << " ";
}
}
stream << "\" printable =\"" << item.printable << "\" />\n";
stream << "\" " << PRINTABLE_ATTR << "=\"" << item.printable << "\" />\n";
}
stream << " </" << BUILD_TAG << ">\n";
@ -2344,6 +2457,31 @@ namespace Slic3r {
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << VOLUME_TYPE_KEY << "\" " <<
VALUE_ATTR << "=\"" << ModelVolume::type_to_string(volume->type()) << "\"/>\n";
// stores volume's local matrix
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << MATRIX_KEY << "\" " << VALUE_ATTR << "=\"";
const Transform3d& matrix = volume->get_matrix();
for (int r = 0; r < 4; ++r)
{
for (int c = 0; c < 4; ++c)
{
stream << matrix(r, c);
if ((r != 3) || (c != 3))
stream << " ";
}
}
stream << "\"/>\n";
// stores volume's source data
if (!volume->source.input_file.empty())
{
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << SOURCE_FILE_KEY << "\" " << VALUE_ATTR << "=\"" << xml_escape(volume->source.input_file) << "\"/>\n";
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << SOURCE_OBJECT_ID_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.object_idx << "\"/>\n";
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << SOURCE_VOLUME_ID_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.volume_idx << "\"/>\n";
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << SOURCE_OFFSET_X_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(0) << "\"/>\n";
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << SOURCE_OFFSET_Y_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(1) << "\"/>\n";
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << SOURCE_OFFSET_Z_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(2) << "\"/>\n";
}
// stores volume's config data
for (const std::string& key : volume->config.keys())
{
@ -2383,13 +2521,21 @@ namespace Slic3r {
return res;
}
#if ENABLE_THUMBNAIL_GENERATOR
bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, const ThumbnailData* thumbnail_data)
#else
bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config)
#endif // ENABLE_THUMBNAIL_GENERATOR
{
if ((path == nullptr) || (model == nullptr))
return false;
_3MF_Exporter exporter;
#if ENABLE_THUMBNAIL_GENERATOR
bool res = exporter.save_model_to_file(path, *model, config, thumbnail_data);
#else
bool res = exporter.save_model_to_file(path, *model, config);
#endif // ENABLE_THUMBNAIL_GENERATOR
if (!res)
exporter.log_errors();

View file

@ -22,13 +22,20 @@ namespace Slic3r {
class Model;
class DynamicPrintConfig;
#if ENABLE_THUMBNAIL_GENERATOR
struct ThumbnailData;
#endif // ENABLE_THUMBNAIL_GENERATOR
// Load the content of a 3mf file into the given model and preset bundle.
extern bool load_3mf(const char* path, DynamicPrintConfig* config, Model* model, bool check_version);
// Save the given model and the config data contained in the given Print into a 3mf file.
// The model could be modified during the export process if meshes are not repaired or have no shared vertices
#if ENABLE_THUMBNAIL_GENERATOR
extern bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, const ThumbnailData* thumbnail_data = nullptr);
#else
extern bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config);
#endif // ENABLE_THUMBNAIL_GENERATOR
}; // namespace Slic3r

View file

@ -12,6 +12,7 @@
#include "../PrintConfig.hpp"
#include "../Utils.hpp"
#include "../I18N.hpp"
#include "../Geometry.hpp"
#include "AMF.hpp"
@ -36,7 +37,8 @@
// Added x and y components of rotation
// Added x, y and z components of scale
// Added x, y and z components of mirror
const unsigned int VERSION_AMF = 2;
// 3 : Meshes saved in their local system; Added volumes' matrices and source data
const unsigned int VERSION_AMF = 3;
const char* SLIC3RPE_AMF_VERSION = "slic3rpe_amf_version";
const char* SLIC3R_CONFIG_TYPE = "slic3rpe_config";
@ -560,15 +562,38 @@ void AMFParserContext::endElement(const char * /* name */)
stl.stats.number_of_facets = int(m_volume_facets.size() / 3);
stl.stats.original_num_facets = stl.stats.number_of_facets;
stl_allocate(&stl);
Slic3r::Geometry::Transformation transform;
if (m_version > 2)
transform = m_volume->get_transformation();
Transform3d inv_matrix = transform.get_matrix().inverse();
for (size_t i = 0; i < m_volume_facets.size();) {
stl_facet &facet = stl.facet_start[i/3];
for (unsigned int v = 0; v < 3; ++ v)
memcpy(facet.vertex[v].data(), &m_object_vertices[m_volume_facets[i ++] * 3], 3 * sizeof(float));
for (unsigned int v = 0; v < 3; ++v)
{
unsigned int tri_id = m_volume_facets[i++] * 3;
Vec3f vertex(m_object_vertices[tri_id + 0], m_object_vertices[tri_id + 1], m_object_vertices[tri_id + 2]);
if (m_version > 2)
// revert the vertices to the original mesh reference system
vertex = (inv_matrix * vertex.cast<double>()).cast<float>();
::memcpy((void*)facet.vertex[v].data(), (const void*)vertex.data(), 3 * sizeof(float));
}
}
stl_get_size(&stl);
mesh.repair();
m_volume->set_mesh(std::move(mesh));
m_volume->center_geometry_after_creation();
if (m_volume->source.input_file.empty() && (m_volume->type() == ModelVolumeType::MODEL_PART))
{
m_volume->source.object_idx = (int)m_model.objects.size() - 1;
m_volume->source.volume_idx = (int)m_model.objects.back()->volumes.size() - 1;
m_volume->center_geometry_after_creation();
}
else
// pass false if the mesh offset has been already taken from the data
m_volume->center_geometry_after_creation(m_volume->source.input_file.empty());
m_volume->calculate_convex_hull();
m_volume_facets.clear();
m_volume = nullptr;
@ -664,6 +689,29 @@ void AMFParserContext::endElement(const char * /* name */)
} else if (strcmp(opt_key, "volume_type") == 0) {
m_volume->set_type(ModelVolume::type_from_string(m_value[1]));
}
else if (strcmp(opt_key, "matrix") == 0) {
Geometry::Transformation transform;
transform.set_from_string(m_value[1]);
m_volume->set_transformation(transform);
}
else if (strcmp(opt_key, "source_file") == 0) {
m_volume->source.input_file = m_value[1];
}
else if (strcmp(opt_key, "source_object_id") == 0) {
m_volume->source.object_idx = ::atoi(m_value[1].c_str());
}
else if (strcmp(opt_key, "source_volume_id") == 0) {
m_volume->source.volume_idx = ::atoi(m_value[1].c_str());
}
else if (strcmp(opt_key, "source_offset_x") == 0) {
m_volume->source.mesh_offset(0) = ::atof(m_value[1].c_str());
}
else if (strcmp(opt_key, "source_offset_y") == 0) {
m_volume->source.mesh_offset(1) = ::atof(m_value[1].c_str());
}
else if (strcmp(opt_key, "source_offset_z") == 0) {
m_volume->source.mesh_offset(2) = ::atof(m_value[1].c_str());
}
}
} else if (m_path.size() == 3) {
if (m_path[1] == NODE_TYPE_MATERIAL) {
@ -759,6 +807,15 @@ bool load_amf_file(const char *path, DynamicPrintConfig *config, Model *model)
if (result)
ctx.endDocument();
for (ModelObject* o : model->objects)
{
for (ModelVolume* v : o->volumes)
{
if (v->source.input_file.empty() && (v->type() == ModelVolumeType::MODEL_PART))
v->source.input_file = path;
}
}
return result;
}
@ -1057,7 +1114,28 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config)
if (volume->is_modifier())
stream << " <metadata type=\"slic3r.modifier\">1</metadata>\n";
stream << " <metadata type=\"slic3r.volume_type\">" << ModelVolume::type_to_string(volume->type()) << "</metadata>\n";
const indexed_triangle_set &its = volume->mesh().its;
stream << " <metadata type=\"slic3r.matrix\">";
const Transform3d& matrix = volume->get_matrix();
for (int r = 0; r < 4; ++r)
{
for (int c = 0; c < 4; ++c)
{
stream << matrix(r, c);
if ((r != 3) || (c != 3))
stream << " ";
}
}
stream << "</metadata>\n";
if (!volume->source.input_file.empty())
{
stream << " <metadata type=\"slic3r.source_file\">" << xml_escape(volume->source.input_file) << "</metadata>\n";
stream << " <metadata type=\"slic3r.source_object_id\">" << volume->source.object_idx << "</metadata>\n";
stream << " <metadata type=\"slic3r.source_volume_id\">" << volume->source.volume_idx << "</metadata>\n";
stream << " <metadata type=\"slic3r.source_offset_x\">" << volume->source.mesh_offset(0) << "</metadata>\n";
stream << " <metadata type=\"slic3r.source_offset_y\">" << volume->source.mesh_offset(1) << "</metadata>\n";
stream << " <metadata type=\"slic3r.source_offset_z\">" << volume->source.mesh_offset(2) << "</metadata>\n";
}
const indexed_triangle_set &its = volume->mesh().its;
for (size_t i = 0; i < its.indices.size(); ++i) {
stream << " <triangle>\n";
for (int j = 0; j < 3; ++j)

View file

@ -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.

View file

@ -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);

View file

@ -6,6 +6,10 @@
#include "Geometry.hpp"
#include "GCode/PrintExtents.hpp"
#include "GCode/WipeTower.hpp"
#if ENABLE_THUMBNAIL_GENERATOR
#include "GCode/ThumbnailData.hpp"
#endif // ENABLE_THUMBNAIL_GENERATOR
#include "ShortestPath.hpp"
#include "Utils.hpp"
#include <algorithm>
@ -17,6 +21,9 @@
#include <boost/foreach.hpp>
#include <boost/filesystem.hpp>
#include <boost/log/trivial.hpp>
#if ENABLE_THUMBNAIL_GENERATOR
#include <boost/beast/core/detail/base64.hpp>
#endif // ENABLE_THUMBNAIL_GENERATOR
#include <boost/nowide/iostream.hpp>
#include <boost/nowide/cstdio.hpp>
@ -28,6 +35,10 @@
#include <Shiny/Shiny.h>
#if ENABLE_THUMBNAIL_GENERATOR_PNG_TO_GCODE
#include "miniz_extension.hpp"
#endif // ENABLE_THUMBNAIL_GENERATOR_PNG_TO_GCODE
#if 0
// Enable debugging and asserts, even in the release build.
#define DEBUG
@ -116,11 +127,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);
@ -129,8 +140,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);
@ -506,7 +517,7 @@ std::string WipeTowerIntegration::prime(GCode &gcodegen)
std::string WipeTowerIntegration::tool_change(GCode &gcodegen, int extruder_id, bool finish_layer)
{
std::string gcode;
assert(m_layer_idx >= 0 && size_t(m_layer_idx) <= m_tool_changes.size());
assert(m_layer_idx >= 0);
if (! m_brim_done || gcodegen.writer().need_toolchange(extruder_id) || finish_layer) {
if (m_layer_idx < (int)m_tool_changes.size()) {
if (! (size_t(m_tool_change_idx) < m_tool_changes[m_layer_idx].size()))
@ -542,7 +553,7 @@ std::vector<GCode::LayerToPrint> GCode::collect_layers_to_print(const PrintObjec
//FIXME should we use the printing extruders instead?
double gap_over_supports = object.config().support_material_contact_distance;
// FIXME should we test object.config().support_material_synchronize_layers ? Currently the support layers are synchronized with object layers iff soluble supports.
assert(gap_over_supports != 0. || object.config().support_material_synchronize_layers);
assert(! object.config().support_material || gap_over_supports != 0. || object.config().support_material_synchronize_layers);
if (gap_over_supports != 0.) {
gap_over_supports = std::max(0., gap_over_supports);
// Not a soluble support,
@ -651,7 +662,11 @@ std::vector<std::pair<coordf_t, std::vector<GCode::LayerToPrint>>> GCode::collec
return layers_to_print;
}
#if ENABLE_THUMBNAIL_GENERATOR
void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_data, const std::vector<ThumbnailData>* thumbnail_data)
#else
void GCode::do_export(Print *print, const char *path, GCodePreviewData *preview_data)
#endif // ENABLE_THUMBNAIL_GENERATOR
{
PROFILE_CLEAR();
@ -677,7 +692,11 @@ void GCode::do_export(Print *print, const char *path, GCodePreviewData *preview_
try {
m_placeholder_parser_failed_templates.clear();
#if ENABLE_THUMBNAIL_GENERATOR
this->_do_export(*print, file, thumbnail_data);
#else
this->_do_export(*print, file);
#endif // ENABLE_THUMBNAIL_GENERATOR
fflush(file);
if (ferror(file)) {
fclose(file);
@ -741,7 +760,11 @@ void GCode::do_export(Print *print, const char *path, GCodePreviewData *preview_
PROFILE_OUTPUT(debug_out_path("gcode-export-profile.txt").c_str());
}
#if ENABLE_THUMBNAIL_GENERATOR
void GCode::_do_export(Print& print, FILE* file, const std::vector<ThumbnailData>* thumbnail_data)
#else
void GCode::_do_export(Print &print, FILE *file)
#endif // ENABLE_THUMBNAIL_GENERATOR
{
PROFILE_FUNC();
@ -777,22 +800,26 @@ void GCode::_do_export(Print &print, FILE *file)
{
m_silent_time_estimator.reset();
m_silent_time_estimator.set_dialect(print.config().gcode_flavor);
m_silent_time_estimator.set_max_acceleration((float)print.config().machine_max_acceleration_extruding.values[1]);
m_silent_time_estimator.set_retract_acceleration((float)print.config().machine_max_acceleration_retracting.values[1]);
m_silent_time_estimator.set_minimum_feedrate((float)print.config().machine_min_extruding_rate.values[1]);
m_silent_time_estimator.set_minimum_travel_feedrate((float)print.config().machine_min_travel_rate.values[1]);
m_silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::X, (float)print.config().machine_max_acceleration_x.values[1]);
m_silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Y, (float)print.config().machine_max_acceleration_y.values[1]);
m_silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Z, (float)print.config().machine_max_acceleration_z.values[1]);
m_silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::E, (float)print.config().machine_max_acceleration_e.values[1]);
m_silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::X, (float)print.config().machine_max_feedrate_x.values[1]);
m_silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Y, (float)print.config().machine_max_feedrate_y.values[1]);
m_silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Z, (float)print.config().machine_max_feedrate_z.values[1]);
m_silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::E, (float)print.config().machine_max_feedrate_e.values[1]);
m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::X, (float)print.config().machine_max_jerk_x.values[1]);
m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Y, (float)print.config().machine_max_jerk_y.values[1]);
m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Z, (float)print.config().machine_max_jerk_z.values[1]);
m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::E, (float)print.config().machine_max_jerk_e.values[1]);
/* "Stealth mode" values can be just a copy of "normal mode" values
* (when they aren't input for a printer preset).
* Thus, use back value from values, instead of second one, which could be absent
*/
m_silent_time_estimator.set_max_acceleration((float)print.config().machine_max_acceleration_extruding.values.back());
m_silent_time_estimator.set_retract_acceleration((float)print.config().machine_max_acceleration_retracting.values.back());
m_silent_time_estimator.set_minimum_feedrate((float)print.config().machine_min_extruding_rate.values.back());
m_silent_time_estimator.set_minimum_travel_feedrate((float)print.config().machine_min_travel_rate.values.back());
m_silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::X, (float)print.config().machine_max_acceleration_x.values.back());
m_silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Y, (float)print.config().machine_max_acceleration_y.values.back());
m_silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Z, (float)print.config().machine_max_acceleration_z.values.back());
m_silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::E, (float)print.config().machine_max_acceleration_e.values.back());
m_silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::X, (float)print.config().machine_max_feedrate_x.values.back());
m_silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Y, (float)print.config().machine_max_feedrate_y.values.back());
m_silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Z, (float)print.config().machine_max_feedrate_z.values.back());
m_silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::E, (float)print.config().machine_max_feedrate_e.values.back());
m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::X, (float)print.config().machine_max_jerk_x.values.back());
m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Y, (float)print.config().machine_max_jerk_y.values.back());
m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Z, (float)print.config().machine_max_jerk_z.values.back());
m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::E, (float)print.config().machine_max_jerk_e.values.back());
if (print.config().single_extruder_multi_material) {
// As of now the fields are shown at the UI dialog in the same combo box as the ramming values, so they
// are considered to be active for the single extruder multi-material printers only.
@ -929,6 +956,77 @@ void GCode::_do_export(Print &print, FILE *file)
// Write information on the generator.
_write_format(file, "; %s\n\n", Slic3r::header_slic3r_generated().c_str());
#if ENABLE_THUMBNAIL_GENERATOR
// Write thumbnails using base64 encoding
if (thumbnail_data != nullptr)
{
const unsigned int max_row_length = 78;
for (const ThumbnailData& data : *thumbnail_data)
{
if (data.is_valid())
{
#if ENABLE_THUMBNAIL_GENERATOR_PNG_TO_GCODE
size_t png_size = 0;
void* png_data = tdefl_write_image_to_png_file_in_memory_ex((const void*)data.pixels.data(), data.width, data.height, 4, &png_size, MZ_DEFAULT_LEVEL, 1);
if (png_data != nullptr)
{
_write_format(file, "\n;\n; thumbnail begin %dx%d\n", data.width, data.height);
std::string encoded = boost::beast::detail::base64_encode((const std::uint8_t*)png_data, png_size);
unsigned int row_count = 0;
while (encoded.length() > max_row_length)
{
_write_format(file, "; %s\n", encoded.substr(0, max_row_length).c_str());
encoded = encoded.substr(max_row_length);
++row_count;
}
if (encoded.length() > 0)
_write_format(file, "; %s\n", encoded.c_str());
_write(file, "; thumbnail end\n;\n");
mz_free(png_data);
}
#else
_write_format(file, "\n;\n; thumbnail begin %dx%d\n", data.width, data.height);
size_t row_size = 4 * data.width;
for (int r = (int)data.height - 1; r >= 0; --r)
{
std::string encoded = boost::beast::detail::base64_encode((const std::uint8_t*)(data.pixels.data() + r * row_size), row_size);
unsigned int row_count = 0;
while (encoded.length() > max_row_length)
{
if (row_count == 0)
_write_format(file, "; %s\n", encoded.substr(0, max_row_length).c_str());
else
_write_format(file, ";>%s\n", encoded.substr(0, max_row_length).c_str());
encoded = encoded.substr(max_row_length);
++row_count;
}
if (encoded.length() > 0)
{
if (row_count == 0)
_write_format(file, "; %s\n", encoded.c_str());
else
_write_format(file, ";>%s\n", encoded.c_str());
}
}
_write(file, "; thumbnail end\n;\n");
#endif // ENABLE_THUMBNAIL_GENERATOR_PNG_TO_GCODE
}
print.throw_if_canceled();
}
}
#endif // ENABLE_THUMBNAIL_GENERATOR
// Write notes (content of the Print Settings tab -> Notes)
{
std::list<std::string> lines;
@ -970,6 +1068,9 @@ void GCode::_do_export(Print &print, FILE *file)
_writeln(file, GCodeTimeEstimator::Silent_First_M73_Output_Placeholder_Tag);
}
// Hold total number of print toolchanges. Check for negative toolchanges (single extruder mode) and set to 0 (no tool change).
int total_toolchanges = std::max(0, print.wipe_tower_data().number_of_toolchanges);
// Prepare the helper object for replacing placeholders in custom G-code and output filename.
m_placeholder_parser = print.placeholder_parser();
m_placeholder_parser.update_timestamp();
@ -1032,6 +1133,7 @@ void GCode::_do_export(Print &print, FILE *file)
// For the start / end G-code to do the priming and final filament pull in case there is no wipe tower provided.
m_placeholder_parser.set("has_wipe_tower", has_wipe_tower);
m_placeholder_parser.set("has_single_extruder_multi_material_priming", has_wipe_tower && print.config().single_extruder_multi_material_priming);
m_placeholder_parser.set("total_toolchanges", total_toolchanges);
std::string start_gcode = this->placeholder_parser_process("start_gcode", print.config().start_gcode.value, initial_extruder_id);
// Set bed temperature if the start G-code does not contain any bed temp control G-codes.
this->_print_first_layer_bed_temperature(file, print, start_gcode, initial_extruder_id, true);
@ -1160,7 +1262,7 @@ void GCode::_do_export(Print &print, FILE *file)
for (const LayerToPrint &ltp : layers_to_print) {
std::vector<LayerToPrint> lrs;
lrs.emplace_back(std::move(ltp));
this->process_layer(file, print, lrs, tool_ordering.tools_for_layer(ltp.print_z()), &copy - object.copies().data());
this->process_layer(file, print, lrs, tool_ordering.tools_for_layer(ltp.print_z()), nullptr, &copy - object.copies().data());
print.throw_if_canceled();
}
#ifdef HAS_PRESSURE_EQUALIZER
@ -1174,12 +1276,8 @@ void GCode::_do_export(Print &print, FILE *file)
}
}
} else {
// Order objects using a nearest neighbor search.
std::vector<size_t> object_indices;
Points object_reference_points;
for (PrintObject *object : print.objects())
object_reference_points.push_back(object->copies().front());
Slic3r::Geometry::chained_path(object_reference_points, object_indices);
// Order object instances using a nearest neighbor search.
std::vector<std::pair<size_t, size_t>> print_object_instances_ordering = chain_print_object_instances(print);
// Sort layers by Z.
// All extrusion moves with the same top layer height are extruded uninterrupted.
std::vector<std::pair<coordf_t, std::vector<LayerToPrint>>> layers_to_print = collect_layers_to_print(print);
@ -1218,7 +1316,7 @@ void GCode::_do_export(Print &print, FILE *file)
const LayerTools &layer_tools = tool_ordering.tools_for_layer(layer.first);
if (m_wipe_tower && layer_tools.has_wipe_tower)
m_wipe_tower->next_layer();
this->process_layer(file, print, layer.second, layer_tools, size_t(-1));
this->process_layer(file, print, layer.second, layer_tools, &print_object_instances_ordering, size_t(-1));
print.throw_if_canceled();
}
#ifdef HAS_PRESSURE_EQUALIZER
@ -1286,7 +1384,7 @@ void GCode::_do_export(Print &print, FILE *file)
print.m_print_statistics.estimated_normal_color_print_times = m_normal_time_estimator.get_color_times_dhms(true);
if (m_silent_time_estimator_enabled)
print.m_print_statistics.estimated_silent_color_print_times = m_silent_time_estimator.get_color_times_dhms(true);
print.m_print_statistics.total_toolchanges = total_toolchanges;
std::vector<Extruder> extruders = m_writer.extruders();
if (! extruders.empty()) {
std::pair<std::string, unsigned int> out_filament_used_mm ("; filament used [mm] = ", 0);
@ -1336,6 +1434,8 @@ void GCode::_do_export(Print &print, FILE *file)
}
_write_format(file, "; total filament used [g] = %.1lf\n", print.m_print_statistics.total_weight);
_write_format(file, "; total filament cost = %.1lf\n", print.m_print_statistics.total_cost);
if (print.m_print_statistics.total_toolchanges > 0)
_write_format(file, "; total toolchanges = %i\n", print.m_print_statistics.total_toolchanges);
_write_format(file, "; estimated printing time (normal mode) = %s\n", m_normal_time_estimator.get_time_dhms().c_str());
if (m_silent_time_estimator_enabled)
_write_format(file, "; estimated printing time (silent mode) = %s\n", m_silent_time_estimator.get_time_dhms().c_str());
@ -1529,8 +1629,54 @@ inline std::vector<GCode::ObjectByExtruder::Island>& object_islands_by_extruder(
return islands;
}
std::vector<GCode::InstanceToPrint> GCode::sort_print_object_instances(
std::vector<GCode::ObjectByExtruder> &objects_by_extruder,
const std::vector<LayerToPrint> &layers,
// Ordering must be defined for normal (non-sequential print).
const std::vector<std::pair<size_t, size_t>> *ordering,
// For sequential print, the instance of the object to be printing has to be defined.
const size_t single_object_instance_idx)
{
std::vector<InstanceToPrint> out;
if (ordering == nullptr) {
// Sequential print, single object is being printed.
for (ObjectByExtruder &object_by_extruder : objects_by_extruder) {
const size_t layer_id = &object_by_extruder - objects_by_extruder.data();
const PrintObject *print_object = layers[layer_id].object();
if (print_object)
out.emplace_back(object_by_extruder, layer_id, *print_object, single_object_instance_idx);
}
} else {
// Create mapping from PrintObject* to ObjectByExtruder*.
std::vector<std::pair<const PrintObject*, ObjectByExtruder*>> sorted;
sorted.reserve(objects_by_extruder.size());
for (ObjectByExtruder &object_by_extruder : objects_by_extruder) {
const size_t layer_id = &object_by_extruder - objects_by_extruder.data();
const PrintObject *print_object = layers[layer_id].object();
if (print_object)
sorted.emplace_back(print_object, &object_by_extruder);
}
std::sort(sorted.begin(), sorted.end());
if (! sorted.empty()) {
const Print &print = *sorted.front().first->print();
out.reserve(sorted.size());
for (const std::pair<size_t, size_t> &instance_id : *ordering) {
const PrintObject &print_object = *print.objects()[instance_id.first];
std::pair<const PrintObject*, ObjectByExtruder*> key(&print_object, nullptr);
auto it = std::lower_bound(sorted.begin(), sorted.end(), key);
if (it != sorted.end() && it->first == &print_object)
// ObjectByExtruder for this PrintObject was found.
out.emplace_back(*it->second, it->second - objects_by_extruder.data(), print_object, instance_id.second);
}
}
}
return out;
}
// In sequential mode, process_layer is called once per each object and its copy,
// therefore layers will contain a single entry and single_object_idx will point to the copy of the object.
// therefore layers will contain a single entry and single_object_instance_idx will point to the copy of the object.
// In non-sequential mode, process_layer is called per each print_z height with all object and support layers accumulated.
// For multi-material prints, this routine minimizes extruder switches by gathering extruder specific extrusion paths
// and performing the extruder specific extrusions together.
@ -1541,14 +1687,16 @@ void GCode::process_layer(
// Set of object & print layers of the same PrintObject and with the same print_z.
const std::vector<LayerToPrint> &layers,
const LayerTools &layer_tools,
// Pairs of PrintObject index and its instance index.
const std::vector<std::pair<size_t, size_t>> *ordering,
// If set to size_t(-1), then print all copies of all objects.
// Otherwise print a single copy of a single object.
const size_t single_object_idx)
const size_t single_object_instance_idx)
{
assert(! layers.empty());
// assert(! layer_tools.extruders.empty());
// Either printing all copies of all objects, or just a single copy of a single object.
assert(single_object_idx == size_t(-1) || layers.size() == 1);
assert(single_object_instance_idx == size_t(-1) || layers.size() == 1);
if (layer_tools.extruders.empty())
// Nothing to extrude.
@ -1757,16 +1905,24 @@ 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;
slices_test_order.reserve(n_slices);
for (size_t i = 0; i < n_slices; ++ i)
slices_test_order.emplace_back(i);
std::sort(slices_test_order.begin(), slices_test_order.end(), [&layer_surface_bboxes](int i, int j) {
const Vec2d s1 = layer_surface_bboxes[i].size().cast<double>();
const Vec2d s2 = layer_surface_bboxes[j].size().cast<double>();
return s1.x() * s1.y() < s2.x() * s2.y();
});
auto point_inside_surface = [&layer, &layer_surface_bboxes](const size_t i, const Point &point) {
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) {
@ -1809,16 +1965,19 @@ void GCode::process_layer(
extruder,
&layer_to_print - layers.data(),
layers.size(), n_slices+1);
for (size_t i = 0; i <= n_slices; ++i)
for (size_t i = 0; i <= n_slices; ++ i) {
bool last = i == n_slices;
size_t island_idx = last ? n_slices : slices_test_order[i];
if (// fill->first_point does not fit inside any slice
i == n_slices ||
last ||
// fill->first_point fits inside ith slice
point_inside_surface(i, fill->first_point())) {
if (islands[i].by_region.empty())
islands[i].by_region.assign(print.regions().size(), ObjectByExtruder::Island::Region());
islands[i].by_region[region_id].append(entity_type, fill, entity_overrides, layer_to_print.object()->copies().size());
point_inside_surface(island_idx, fill->first_point())) {
if (islands[island_idx].by_region.empty())
islands[island_idx].by_region.assign(print.regions().size(), ObjectByExtruder::Island::Region());
islands[island_idx].by_region[region_id].append(entity_type, fill, entity_overrides, layer_to_print.object()->copies().size());
break;
}
}
}
}
}
@ -1883,62 +2042,49 @@ void GCode::process_layer(
if (objects_by_extruder_it == by_extruder.end())
continue;
std::vector<InstanceToPrint> instances_to_print = sort_print_object_instances(objects_by_extruder_it->second, layers, ordering, single_object_instance_idx);
// We are almost ready to print. However, we must go through all the objects twice to print the the overridden extrusions first (infill/perimeter wiping feature):
bool is_anything_overridden = const_cast<LayerTools&>(layer_tools).wiping_extrusions().is_anything_overridden();
for (int print_wipe_extrusions = is_anything_overridden; print_wipe_extrusions>=0; --print_wipe_extrusions) {
if (is_anything_overridden && print_wipe_extrusions == 0)
gcode+="; PURGING FINISHED\n";
for (ObjectByExtruder &object_by_extruder : objects_by_extruder_it->second) {
const size_t layer_id = &object_by_extruder - objects_by_extruder_it->second.data();
const PrintObject *print_object = layers[layer_id].object();
if (print_object == nullptr)
// This layer is empty for this particular object, it has neither object extrusions nor support extrusions at this print_z.
continue;
m_config.apply(print_object->config(), true);
m_layer = layers[layer_id].layer();
for (InstanceToPrint &instance_to_print : instances_to_print) {
m_config.apply(instance_to_print.print_object.config(), true);
m_layer = layers[instance_to_print.layer_id].layer();
if (m_config.avoid_crossing_perimeters)
m_avoid_crossing_perimeters.init_layer_mp(union_ex(m_layer->slices, true));
Points copies;
if (single_object_idx == size_t(-1))
copies = print_object->copies();
else
copies.push_back(print_object->copies()[single_object_idx]);
// Sort the copies by the closest point starting with the current print position.
unsigned int copy_id = 0;
for (const Point &copy : copies) {
if (this->config().gcode_label_objects)
gcode += std::string("; printing object ") + print_object->model_object()->name + " id:" + std::to_string(layer_id) + " copy " + std::to_string(copy_id) + "\n";
// When starting a new object, use the external motion planner for the first travel move.
std::pair<const PrintObject*, Point> this_object_copy(print_object, copy);
if (m_last_obj_copy != this_object_copy)
m_avoid_crossing_perimeters.use_external_mp_once = true;
m_last_obj_copy = this_object_copy;
this->set_origin(unscale(copy));
if (object_by_extruder.support != nullptr && !print_wipe_extrusions) {
m_layer = layers[layer_id].support_layer;
gcode += this->extrude_support(
// support_extrusion_role is erSupportMaterial, erSupportMaterialInterface or erMixed for all extrusion paths.
object_by_extruder.support->chained_path_from(m_last_pos, false, object_by_extruder.support_extrusion_role));
m_layer = layers[layer_id].layer();
}
for (ObjectByExtruder::Island &island : object_by_extruder.islands) {
const auto& by_region_specific = is_anything_overridden ? island.by_region_per_copy(copy_id, extruder_id, print_wipe_extrusions) : island.by_region;
if (print.config().infill_first) {
gcode += this->extrude_infill(print, by_region_specific);
gcode += this->extrude_perimeters(print, by_region_specific, lower_layer_edge_grids[layer_id]);
} else {
gcode += this->extrude_perimeters(print, by_region_specific, lower_layer_edge_grids[layer_id]);
gcode += this->extrude_infill(print,by_region_specific);
}
}
if (this->config().gcode_label_objects)
gcode += std::string("; stop printing object ") + print_object->model_object()->name + " id:" + std::to_string(layer_id) + " copy " + std::to_string(copy_id) + "\n";
++ copy_id;
if (this->config().gcode_label_objects)
gcode += std::string("; printing object ") + instance_to_print.print_object.model_object()->name + " id:" + std::to_string(instance_to_print.layer_id) + " copy " + std::to_string(instance_to_print.instance_id) + "\n";
// When starting a new object, use the external motion planner for the first travel move.
const Point &offset = instance_to_print.print_object.copies()[instance_to_print.instance_id];
std::pair<const PrintObject*, Point> this_object_copy(&instance_to_print.print_object, offset);
if (m_last_obj_copy != this_object_copy)
m_avoid_crossing_perimeters.use_external_mp_once = true;
m_last_obj_copy = this_object_copy;
this->set_origin(unscale(offset));
if (instance_to_print.object_by_extruder.support != nullptr && !print_wipe_extrusions) {
m_layer = layers[instance_to_print.layer_id].support_layer;
gcode += this->extrude_support(
// support_extrusion_role is erSupportMaterial, erSupportMaterialInterface or erMixed for all extrusion paths.
instance_to_print.object_by_extruder.support->chained_path_from(m_last_pos, instance_to_print.object_by_extruder.support_extrusion_role));
m_layer = layers[instance_to_print.layer_id].layer();
}
for (ObjectByExtruder::Island &island : instance_to_print.object_by_extruder.islands) {
const auto& by_region_specific = is_anything_overridden ? island.by_region_per_copy(instance_to_print.instance_id, extruder_id, print_wipe_extrusions) : island.by_region;
if (print.config().infill_first) {
gcode += this->extrude_infill(print, by_region_specific);
gcode += this->extrude_perimeters(print, by_region_specific, lower_layer_edge_grids[instance_to_print.layer_id]);
} else {
gcode += this->extrude_perimeters(print, by_region_specific, lower_layer_edge_grids[instance_to_print.layer_id]);
gcode += this->extrude_infill(print,by_region_specific);
}
}
if (this->config().gcode_label_objects)
gcode += std::string("; stop printing object ") + instance_to_print.print_object.model_object()->name + " id:" + std::to_string(instance_to_print.layer_id) + " copy " + std::to_string(instance_to_print.instance_id) + "\n";
}
}
}
@ -2372,7 +2518,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;
@ -2542,12 +2688,10 @@ std::string GCode::extrude_infill(const Print &print, const std::vector<ObjectBy
std::string gcode;
for (const ObjectByExtruder::Island::Region &region : by_region) {
m_config.apply(print.regions()[&region - &by_region.front()]->config());
ExtrusionEntityCollection chained = region.infills.chained_path_from(m_last_pos, false);
for (ExtrusionEntity *fill : chained.entities) {
for (ExtrusionEntity *fill : region.infills.chained_path_from(m_last_pos).entities) {
auto *eec = dynamic_cast<ExtrusionEntityCollection*>(fill);
if (eec) {
ExtrusionEntityCollection chained2 = eec->chained_path_from(m_last_pos, false);
for (ExtrusionEntity *ee : chained2.entities)
for (ExtrusionEntity *ee : eec->chained_path_from(m_last_pos).entities)
gcode += this->extrude_entity(*ee, "infill");
} else
gcode += this->extrude_entity(*fill, "infill");

View file

@ -30,6 +30,9 @@ namespace Slic3r {
// Forward declarations.
class GCode;
class GCodePreviewData;
#if ENABLE_THUMBNAIL_GENERATOR
struct ThumbnailData;
#endif // ENABLE_THUMBNAIL_GENERATOR
class AvoidCrossingPerimeters {
public:
@ -162,7 +165,11 @@ public:
// throws std::runtime_exception on error,
// throws CanceledException through print->throw_if_canceled().
#if ENABLE_THUMBNAIL_GENERATOR
void do_export(Print* print, const char* path, GCodePreviewData* preview_data = nullptr, const std::vector<ThumbnailData>* thumbnail_data = nullptr);
#else
void do_export(Print *print, const char *path, GCodePreviewData *preview_data = nullptr);
#endif // ENABLE_THUMBNAIL_GENERATOR
// Exported for the helper classes (OozePrevention, Wipe) and for the Perl binding for unit tests.
const Vec2d& origin() const { return m_origin; }
@ -190,7 +197,11 @@ public:
static void append_full_config(const Print& print, std::string& str);
protected:
#if ENABLE_THUMBNAIL_GENERATOR
void _do_export(Print& print, FILE* file, const std::vector<ThumbnailData>* thumbnail_data);
#else
void _do_export(Print &print, FILE *file);
#endif //ENABLE_THUMBNAIL_GENERATOR
// Object and support extrusions of the same PrintObject at the same print_z.
struct LayerToPrint
@ -202,7 +213,7 @@ protected:
const PrintObject* object() const { return (this->layer() != nullptr) ? this->layer()->object() : nullptr; }
coordf_t print_z() const { return (object_layer != nullptr && support_layer != nullptr) ? 0.5 * (object_layer->print_z + support_layer->print_z) : this->layer()->print_z; }
};
static std::vector<GCode::LayerToPrint> collect_layers_to_print(const PrintObject &object);
static std::vector<LayerToPrint> collect_layers_to_print(const PrintObject &object);
static std::vector<std::pair<coordf_t, std::vector<LayerToPrint>>> collect_layers_to_print(const Print &print);
void process_layer(
// Write into the output file.
@ -210,7 +221,9 @@ protected:
const Print &print,
// Set of object & print layers of the same PrintObject and with the same print_z.
const std::vector<LayerToPrint> &layers,
const LayerTools &layer_tools,
const LayerTools &layer_tools,
// Pairs of PrintObject index and its instance index.
const std::vector<std::pair<size_t, size_t>> *ordering,
// If set to size_t(-1), then print all copies of all objects.
// Otherwise print a single copy of a single object.
const size_t single_object_idx = size_t(-1));
@ -258,6 +271,25 @@ protected:
std::vector<Island> islands;
};
struct InstanceToPrint
{
InstanceToPrint(ObjectByExtruder &object_by_extruder, size_t layer_id, const PrintObject &print_object, size_t instance_id) :
object_by_extruder(object_by_extruder), layer_id(layer_id), print_object(print_object), instance_id(instance_id) {}
ObjectByExtruder &object_by_extruder;
const size_t layer_id;
const PrintObject &print_object;
// Instance idx of the copy of a print object.
const size_t instance_id;
};
std::vector<InstanceToPrint> sort_print_object_instances(
std::vector<ObjectByExtruder> &objects_by_extruder,
const std::vector<LayerToPrint> &layers,
// Ordering must be defined for normal (non-sequential print).
const std::vector<std::pair<size_t, size_t>> *ordering,
// For sequential print, the instance of the object to be printing has to be defined.
const size_t single_object_instance_idx);
std::string extrude_perimeters(const Print &print, const std::vector<ObjectByExtruder::Island::Region> &by_region, std::unique_ptr<EdgeGrid::Grid> &lower_layer_edge_grid);
std::string extrude_infill(const Print &print, const std::vector<ObjectByExtruder::Island::Region> &by_region);

View file

@ -20,6 +20,7 @@ static const unsigned int DEFAULT_EXTRUDER_ID = 0;
static const unsigned int DEFAULT_COLOR_PRINT_ID = 0;
static const Slic3r::Vec3d DEFAULT_START_POSITION = Slic3r::Vec3d(0.0f, 0.0f, 0.0f);
static const float DEFAULT_START_EXTRUSION = 0.0f;
static const float DEFAULT_FAN_SPEED = 0.0f;
namespace Slic3r {
@ -36,21 +37,23 @@ const float GCodeAnalyzer::Default_Height = 0.0f;
GCodeAnalyzer::Metadata::Metadata()
: extrusion_role(erNone)
, extruder_id(DEFAULT_EXTRUDER_ID)
, cp_color_id(DEFAULT_COLOR_PRINT_ID)
, mm3_per_mm(GCodeAnalyzer::Default_mm3_per_mm)
, width(GCodeAnalyzer::Default_Width)
, height(GCodeAnalyzer::Default_Height)
, feedrate(DEFAULT_FEEDRATE)
, fan_speed(DEFAULT_FAN_SPEED)
, cp_color_id(DEFAULT_COLOR_PRINT_ID)
{
}
GCodeAnalyzer::Metadata::Metadata(ExtrusionRole extrusion_role, unsigned int extruder_id, double mm3_per_mm, float width, float height, float feedrate, unsigned int cp_color_id/* = 0*/)
GCodeAnalyzer::Metadata::Metadata(ExtrusionRole extrusion_role, unsigned int extruder_id, double mm3_per_mm, float width, float height, float feedrate, float fan_speed, unsigned int cp_color_id/* = 0*/)
: extrusion_role(extrusion_role)
, extruder_id(extruder_id)
, mm3_per_mm(mm3_per_mm)
, width(width)
, height(height)
, feedrate(feedrate)
, fan_speed(fan_speed)
, cp_color_id(cp_color_id)
{
}
@ -75,15 +78,18 @@ bool GCodeAnalyzer::Metadata::operator != (const GCodeAnalyzer::Metadata& other)
if (feedrate != other.feedrate)
return true;
if (fan_speed != other.fan_speed)
return true;
if (cp_color_id != other.cp_color_id)
return true;
return false;
}
GCodeAnalyzer::GCodeMove::GCodeMove(GCodeMove::EType type, ExtrusionRole extrusion_role, unsigned int extruder_id, double mm3_per_mm, float width, float height, float feedrate, const Vec3d& start_position, const Vec3d& end_position, float delta_extruder, unsigned int cp_color_id/* = 0*/)
GCodeAnalyzer::GCodeMove::GCodeMove(GCodeMove::EType type, ExtrusionRole extrusion_role, unsigned int extruder_id, double mm3_per_mm, float width, float height, float feedrate, const Vec3d& start_position, const Vec3d& end_position, float delta_extruder, float fan_speed, unsigned int cp_color_id/* = 0*/)
: type(type)
, data(extrusion_role, extruder_id, mm3_per_mm, width, height, feedrate, cp_color_id)
, data(extrusion_role, extruder_id, mm3_per_mm, width, height, feedrate, fan_speed, cp_color_id)
, start_position(start_position)
, end_position(end_position)
, delta_extruder(delta_extruder)
@ -133,7 +139,9 @@ void GCodeAnalyzer::reset()
_set_feedrate(DEFAULT_FEEDRATE);
_set_start_position(DEFAULT_START_POSITION);
_set_start_extrusion(DEFAULT_START_EXTRUSION);
_set_fan_speed(DEFAULT_FAN_SPEED);
_reset_axes_position();
_reset_axes_origin();
_reset_cached_position();
m_moves_map.clear();
@ -259,6 +267,16 @@ void GCodeAnalyzer::_process_gcode_line(GCodeReader&, const GCodeReader::GCodeLi
_processM83(line);
break;
}
case 106: // Set fan speed
{
_processM106(line);
break;
}
case 107: // Disable fan
{
_processM107(line);
break;
}
case 108:
case 135:
{
@ -267,6 +285,11 @@ void GCodeAnalyzer::_process_gcode_line(GCodeReader&, const GCodeReader::GCodeLi
_processM108orM135(line);
break;
}
case 132: // Recall stored home offsets
{
_processM132(line);
break;
}
case 401: // Repetier: Store x, y and z position
{
_processM401(line);
@ -293,31 +316,32 @@ void GCodeAnalyzer::_process_gcode_line(GCodeReader&, const GCodeReader::GCodeLi
m_process_output += line.raw() + "\n";
}
// Returns the new absolute position on the given axis in dependence of the given parameters
float axis_absolute_position_from_G1_line(GCodeAnalyzer::EAxis axis, const GCodeReader::GCodeLine& lineG1, GCodeAnalyzer::EUnits units, bool is_relative, float current_absolute_position)
{
float lengthsScaleFactor = (units == GCodeAnalyzer::Inches) ? INCHES_TO_MM : 1.0f;
if (lineG1.has(Slic3r::Axis(axis)))
{
float ret = lineG1.value(Slic3r::Axis(axis)) * lengthsScaleFactor;
return is_relative ? current_absolute_position + ret : ret;
}
else
return current_absolute_position;
}
void GCodeAnalyzer::_processG1(const GCodeReader::GCodeLine& line)
{
auto axis_absolute_position = [this](GCodeAnalyzer::EAxis axis, const GCodeReader::GCodeLine& lineG1) -> float
{
float current_absolute_position = _get_axis_position(axis);
float current_origin = _get_axis_origin(axis);
float lengthsScaleFactor = (_get_units() == GCodeAnalyzer::Inches) ? INCHES_TO_MM : 1.0f;
bool is_relative = (_get_global_positioning_type() == Relative);
if (axis == E)
is_relative |= (_get_e_local_positioning_type() == Relative);
if (lineG1.has(Slic3r::Axis(axis)))
{
float ret = lineG1.value(Slic3r::Axis(axis)) * lengthsScaleFactor;
return is_relative ? current_absolute_position + ret : ret + current_origin;
}
else
return current_absolute_position;
};
// updates axes positions from line
EUnits units = _get_units();
float new_pos[Num_Axis];
for (unsigned char a = X; a < Num_Axis; ++a)
{
bool is_relative = (_get_global_positioning_type() == Relative);
if (a == E)
is_relative |= (_get_e_local_positioning_type() == Relative);
new_pos[a] = axis_absolute_position_from_G1_line((EAxis)a, line, units, is_relative, _get_axis_position((EAxis)a));
new_pos[a] = axis_absolute_position((EAxis)a, line);
}
// updates feedrate from line, if present
@ -407,25 +431,25 @@ void GCodeAnalyzer::_processG92(const GCodeReader::GCodeLine& line)
if (line.has_x())
{
_set_axis_position(X, line.x() * lengthsScaleFactor);
_set_axis_origin(X, _get_axis_position(X) - line.x() * lengthsScaleFactor);
anyFound = true;
}
if (line.has_y())
{
_set_axis_position(Y, line.y() * lengthsScaleFactor);
_set_axis_origin(Y, _get_axis_position(Y) - line.y() * lengthsScaleFactor);
anyFound = true;
}
if (line.has_z())
{
_set_axis_position(Z, line.z() * lengthsScaleFactor);
_set_axis_origin(Z, _get_axis_position(Z) - line.z() * lengthsScaleFactor);
anyFound = true;
}
if (line.has_e())
{
_set_axis_position(E, line.e() * lengthsScaleFactor);
_set_axis_origin(E, _get_axis_position(E) - line.e() * lengthsScaleFactor);
anyFound = true;
}
@ -433,7 +457,7 @@ void GCodeAnalyzer::_processG92(const GCodeReader::GCodeLine& line)
{
for (unsigned char a = X; a < Num_Axis; ++a)
{
_set_axis_position((EAxis)a, 0.0f);
_set_axis_origin((EAxis)a, _get_axis_position((EAxis)a));
}
}
}
@ -448,6 +472,24 @@ void GCodeAnalyzer::_processM83(const GCodeReader::GCodeLine& line)
_set_e_local_positioning_type(Relative);
}
void GCodeAnalyzer::_processM106(const GCodeReader::GCodeLine& line)
{
if (!line.has('P'))
{
// The absence of P means the print cooling fan, so ignore anything else.
float new_fan_speed;
if (line.has_value('S', new_fan_speed))
_set_fan_speed((100.0f / 256.0f) * new_fan_speed);
else
_set_fan_speed(100.0f);
}
}
void GCodeAnalyzer::_processM107(const GCodeReader::GCodeLine& line)
{
_set_fan_speed(0.0f);
}
void GCodeAnalyzer::_processM108orM135(const GCodeReader::GCodeLine& line)
{
// These M-codes are used by MakerWare and Sailfish to change active tool.
@ -467,6 +509,25 @@ void GCodeAnalyzer::_processM108orM135(const GCodeReader::GCodeLine& line)
}
}
void GCodeAnalyzer::_processM132(const GCodeReader::GCodeLine& line)
{
// This command is used by Makerbot to load the current home position from EEPROM
// see: https://github.com/makerbot/s3g/blob/master/doc/GCodeProtocol.md
// Using this command to reset the axis origin to zero helps in fixing: https://github.com/prusa3d/PrusaSlicer/issues/3082
if (line.has_x())
_set_axis_origin(X, 0.0f);
if (line.has_y())
_set_axis_origin(Y, 0.0f);
if (line.has_z())
_set_axis_origin(Z, 0.0f);
if (line.has_e())
_set_axis_origin(E, 0.0f);
}
void GCodeAnalyzer::_processM401(const GCodeReader::GCodeLine& line)
{
if (m_gcode_flavor != gcfRepetier)
@ -726,6 +787,16 @@ float GCodeAnalyzer::_get_feedrate() const
return m_state.data.feedrate;
}
void GCodeAnalyzer::_set_fan_speed(float fan_speed_percentage)
{
m_state.data.fan_speed = fan_speed_percentage;
}
float GCodeAnalyzer::_get_fan_speed() const
{
return m_state.data.fan_speed;
}
void GCodeAnalyzer::_set_axis_position(EAxis axis, float position)
{
m_state.position[axis] = position;
@ -736,11 +807,26 @@ float GCodeAnalyzer::_get_axis_position(EAxis axis) const
return m_state.position[axis];
}
void GCodeAnalyzer::_set_axis_origin(EAxis axis, float position)
{
m_state.origin[axis] = position;
}
float GCodeAnalyzer::_get_axis_origin(EAxis axis) const
{
return m_state.origin[axis];
}
void GCodeAnalyzer::_reset_axes_position()
{
::memset((void*)m_state.position, 0, Num_Axis * sizeof(float));
}
void GCodeAnalyzer::_reset_axes_origin()
{
::memset((void*)m_state.origin, 0, Num_Axis * sizeof(float));
}
void GCodeAnalyzer::_set_start_position(const Vec3d& position)
{
m_state.start_position = position;
@ -798,7 +884,7 @@ void GCodeAnalyzer::_store_move(GCodeAnalyzer::GCodeMove::EType type)
Vec3d start_position = _get_start_position() + extruder_offset;
Vec3d end_position = _get_end_position() + extruder_offset;
it->second.emplace_back(type, _get_extrusion_role(), extruder_id, _get_mm3_per_mm(), _get_width(), _get_height(), _get_feedrate(), start_position, end_position, _get_delta_extrusion(), _get_cp_color_id());
it->second.emplace_back(type, _get_extrusion_role(), extruder_id, _get_mm3_per_mm(), _get_width(), _get_height(), _get_feedrate(), start_position, end_position, _get_delta_extrusion(), _get_fan_speed(), _get_cp_color_id());
}
bool GCodeAnalyzer::_is_valid_extrusion_role(int value) const
@ -821,7 +907,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();
}
@ -830,13 +916,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.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;
}
}
};
@ -854,6 +945,7 @@ void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& previ
GCodePreviewData::Range width_range;
GCodePreviewData::Range feedrate_range;
GCodePreviewData::Range volumetric_rate_range;
GCodePreviewData::Range fan_speed_range;
// to avoid to call the callback too often
unsigned int cancel_callback_threshold = (unsigned int)std::max((int)extrude_moves->second.size() / 25, 1);
@ -888,6 +980,7 @@ void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& previ
width_range.update_from(move.data.width);
feedrate_range.update_from(move.data.feedrate);
volumetric_rate_range.update_from(volumetric_rate);
fan_speed_range.update_from(move.data.fan_speed);
}
else
// append end vertex of the move to current polyline
@ -906,6 +999,7 @@ void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& previ
preview_data.ranges.width.update_from(width_range);
preview_data.ranges.feedrate.update_from(feedrate_range);
preview_data.ranges.volumetric_rate.update_from(volumetric_rate_range);
preview_data.ranges.fan_speed.update_from(fan_speed_range);
// we need to sort the layers by their z as they can be shuffled in case of sequential prints
std::sort(preview_data.extrusion.layers.begin(), preview_data.extrusion.layers.end(), [](const GCodePreviewData::Extrusion::Layer& l1, const GCodePreviewData::Extrusion::Layer& l2)->bool { return l1.z < l2.z; });

View file

@ -54,10 +54,11 @@ public:
float width; // mm
float height; // mm
float feedrate; // mm/s
float fan_speed; // percentage
unsigned int cp_color_id;
Metadata();
Metadata(ExtrusionRole extrusion_role, unsigned int extruder_id, double mm3_per_mm, float width, float height, float feedrate, unsigned int cp_color_id = 0);
Metadata(ExtrusionRole extrusion_role, unsigned int extruder_id, double mm3_per_mm, float width, float height, float feedrate, float fan_speed, unsigned int cp_color_id = 0);
bool operator != (const Metadata& other) const;
};
@ -81,7 +82,7 @@ public:
Vec3d end_position;
float delta_extruder;
GCodeMove(EType type, ExtrusionRole extrusion_role, unsigned int extruder_id, double mm3_per_mm, float width, float height, float feedrate, const Vec3d& start_position, const Vec3d& end_position, float delta_extruder, unsigned int cp_color_id = 0);
GCodeMove(EType type, ExtrusionRole extrusion_role, unsigned int extruder_id, double mm3_per_mm, float width, float height, float feedrate, const Vec3d& start_position, const Vec3d& end_position, float delta_extruder, float fan_speed, unsigned int cp_color_id = 0);
GCodeMove(EType type, const Metadata& data, const Vec3d& start_position, const Vec3d& end_position, float delta_extruder);
};
@ -100,6 +101,7 @@ private:
float cached_position[5];
float start_extrusion;
float position[Num_Axis];
float origin[Num_Axis];
unsigned int cur_cp_color_id = 0;
};
@ -171,9 +173,18 @@ private:
// Set extruder to relative mode
void _processM83(const GCodeReader::GCodeLine& line);
// Set fan speed
void _processM106(const GCodeReader::GCodeLine& line);
// Disable fan
void _processM107(const GCodeReader::GCodeLine& line);
// Set tool (MakerWare and Sailfish flavor)
void _processM108orM135(const GCodeReader::GCodeLine& line);
// Recall stored home offsets
void _processM132(const GCodeReader::GCodeLine& line);
// Repetier: Store x, y and z position
void _processM401(const GCodeReader::GCodeLine& line);
@ -233,11 +244,19 @@ private:
void _set_feedrate(float feedrate_mm_sec);
float _get_feedrate() const;
void _set_fan_speed(float fan_speed_percentage);
float _get_fan_speed() const;
void _set_axis_position(EAxis axis, float position);
float _get_axis_position(EAxis axis) const;
void _set_axis_origin(EAxis axis, float position);
float _get_axis_origin(EAxis axis) const;
// Sets axes position to zero
void _reset_axes_position();
// Sets origin position to zero
void _reset_axes_origin();
void _set_start_position(const Vec3d& position);
const Vec3d& _get_start_position() const;

View file

@ -1,7 +1,7 @@
#ifndef slic3r_CoolingBuffer_hpp_
#define slic3r_CoolingBuffer_hpp_
#include "libslic3r.h"
#include "../libslic3r.h"
#include <map>
#include <string>

View file

@ -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;
@ -241,6 +241,7 @@ void GCodePreviewData::set_default()
::memcpy((void*)ranges.height.colors, (const void*)Range::Default_Colors, Range::Colors_Count * sizeof(Color));
::memcpy((void*)ranges.width.colors, (const void*)Range::Default_Colors, Range::Colors_Count * sizeof(Color));
::memcpy((void*)ranges.feedrate.colors, (const void*)Range::Default_Colors, Range::Colors_Count * sizeof(Color));
::memcpy((void*)ranges.fan_speed.colors, (const void*)Range::Default_Colors, Range::Colors_Count * sizeof(Color));
::memcpy((void*)ranges.volumetric_rate.colors, (const void*)Range::Default_Colors, Range::Colors_Count * sizeof(Color));
extrusion.set_default();
@ -287,6 +288,11 @@ GCodePreviewData::Color GCodePreviewData::get_feedrate_color(float feedrate) con
return ranges.feedrate.get_color_at(feedrate);
}
GCodePreviewData::Color GCodePreviewData::get_fan_speed_color(float fan_speed) const
{
return ranges.fan_speed.get_color_at(fan_speed);
}
GCodePreviewData::Color GCodePreviewData::get_volumetric_rate_color(float rate) const
{
return ranges.volumetric_rate.get_color_at(rate);
@ -358,6 +364,8 @@ std::string GCodePreviewData::get_legend_title() const
return L("Width (mm)");
case Extrusion::Feedrate:
return L("Speed (mm/s)");
case Extrusion::FanSpeed:
return L("Fan Speed (%)");
case Extrusion::VolumetricRate:
return L("Volumetric flow rate (mm³/s)");
case Extrusion::Tool:
@ -421,6 +429,11 @@ GCodePreviewData::LegendItemsList GCodePreviewData::get_legend_items(const std::
Helper::FillListFromRange(items, ranges.feedrate, 1, 1.0f);
break;
}
case Extrusion::FanSpeed:
{
Helper::FillListFromRange(items, ranges.fan_speed, 0, 1.0f);
break;
}
case Extrusion::VolumetricRate:
{
Helper::FillListFromRange(items, ranges.volumetric_rate, 3, 1.0f);

View file

@ -52,6 +52,8 @@ public:
Range width;
// Color mapping by feedrate.
Range feedrate;
// Color mapping by fan speed.
Range fan_speed;
// Color mapping by volumetric extrusion rate.
Range volumetric_rate;
};
@ -74,6 +76,7 @@ public:
Height,
Width,
Feedrate,
FanSpeed,
VolumetricRate,
Tool,
ColorPrint,
@ -84,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;
@ -205,6 +230,7 @@ public:
Color get_height_color(float height) const;
Color get_width_color(float width) const;
Color get_feedrate_color(float feedrate) const;
Color get_fan_speed_color(float fan_speed) const;
Color get_volumetric_rate_color(float rate) const;
void set_extrusion_role_color(const std::string& role_name, float red, float green, float blue, float alpha);

View file

@ -138,7 +138,7 @@ BoundingBoxf get_wipe_tower_extrusions_extents(const Print &print, const coordf_
// We need to get position and angle of the wipe tower to transform them to actual position.
Transform2d trafo =
Eigen::Translation2d(print.config().wipe_tower_x.value, print.config().wipe_tower_y.value) *
Eigen::Rotation2Dd(print.config().wipe_tower_rotation_angle.value);
Eigen::Rotation2Dd(Geometry::deg2rad(print.config().wipe_tower_rotation_angle.value));
BoundingBoxf bbox;
for (const std::vector<WipeTower::ToolChangeResult> &tool_changes : print.wipe_tower_data().tool_changes) {

View file

@ -1,8 +1,8 @@
#ifndef slic3r_SpiralVase_hpp_
#define slic3r_SpiralVase_hpp_
#include "libslic3r.h"
#include "GCodeReader.hpp"
#include "../libslic3r.h"
#include "../GCodeReader.hpp"
namespace Slic3r {

View file

@ -0,0 +1,36 @@
#include "libslic3r/libslic3r.h"
#include "ThumbnailData.hpp"
#if ENABLE_THUMBNAIL_GENERATOR
namespace Slic3r {
void ThumbnailData::set(unsigned int w, unsigned int h)
{
if ((w == 0) || (h == 0))
return;
if ((width != w) || (height != h))
{
width = w;
height = h;
// defaults to white texture
pixels = std::vector<unsigned char>(width * height * 4, 255);
}
}
void ThumbnailData::reset()
{
width = 0;
height = 0;
pixels.clear();
}
bool ThumbnailData::is_valid() const
{
return (width != 0) && (height != 0) && ((unsigned int)pixels.size() == 4 * width * height);
}
} // namespace Slic3r
#endif // ENABLE_THUMBNAIL_GENERATOR

View file

@ -0,0 +1,27 @@
#ifndef slic3r_ThumbnailData_hpp_
#define slic3r_ThumbnailData_hpp_
#if ENABLE_THUMBNAIL_GENERATOR
#include <vector>
namespace Slic3r {
struct ThumbnailData
{
unsigned int width;
unsigned int height;
std::vector<unsigned char> pixels;
ThumbnailData() { reset(); }
void set(unsigned int w, unsigned int h);
void reset();
bool is_valid() const;
};
} // namespace Slic3r
#endif // ENABLE_THUMBNAIL_GENERATOR
#endif // slic3r_ThumbnailData_hpp_

View file

@ -331,15 +331,18 @@ public:
// Let the firmware back up the active speed override value.
WipeTowerWriter& speed_override_backup()
{
m_gcode += "M220 B\n";
{
// This is only supported by Prusa at this point (https://github.com/prusa3d/PrusaSlicer/issues/3114)
if (m_gcode_flavor == gcfMarlin)
m_gcode += "M220 B\n";
return *this;
}
// Let the firmware restore the active speed override value.
WipeTowerWriter& speed_override_restore()
{
m_gcode += "M220 R\n";
if (m_gcode_flavor == gcfMarlin)
m_gcode += "M220 R\n";
return *this;
}
@ -787,8 +790,10 @@ WipeTower::ToolChangeResult WipeTower::toolchange_Brim(bool sideOnly, float y_of
// The tool is supposed to be active and primed at the time when the wipe tower brim is extruded.
// Extrude 4 rounds of a brim around the future wipe tower.
box_coordinates box(wipeTower_box);
// the brim shall have 'normal' spacing with no extra void space
float spacing = m_perimeter_width - m_layer_height*float(1.-M_PI_4);
for (size_t i = 0; i < 4; ++ i) {
box.expand(m_perimeter_width - m_layer_height*float(1.-M_PI_4)); // the brim shall have 'normal' spacing with no extra void space
box.expand(spacing);
writer.travel (box.ld, 7000)
.extrude(box.lu, 2100).extrude(box.ru)
.extrude(box.rd ).extrude(box.ld);
@ -800,6 +805,10 @@ WipeTower::ToolChangeResult WipeTower::toolchange_Brim(bool sideOnly, float y_of
writer.append("; CP WIPE TOWER FIRST LAYER BRIM END\n"
";-----------------------------------\n");
// Save actual brim width to be later passed to the Print object, which will use it
// for skirt calculation and pass it to GLCanvas for precise preview box
m_wipe_tower_brim_width = wipeTower_box.ld.x() - box.ld.x() + spacing/2.f;
m_print_brim = false; // Mark the brim as extruded
// Ask our writer about how much material was consumed:

View file

@ -92,6 +92,7 @@ public:
void generate(std::vector<std::vector<ToolChangeResult>> &result);
float get_depth() const { return m_wipe_tower_depth; }
float get_brim_width() const { return m_wipe_tower_brim_width; }
@ -203,6 +204,7 @@ private:
Vec2f m_wipe_tower_pos; // Left front corner of the wipe tower in mm.
float m_wipe_tower_width; // Width of the wipe tower.
float m_wipe_tower_depth = 0.f; // Depth of the wipe tower
float m_wipe_tower_brim_width = 0.f; // Width of brim (mm)
float m_wipe_tower_rotation_angle = 0.f; // Wipe tower rotation angle in degrees (with respect to x axis)
float m_internal_rotation = 0.f;
float m_y_shift = 0.f; // y shift passed to writer

View file

@ -29,6 +29,8 @@ public:
float value(Axis axis) const { return m_axis[axis]; }
bool has(char axis) const;
bool has_value(char axis, float &value) const;
float new_X(const GCodeReader &reader) const { return this->has(X) ? this->x() : reader.x(); }
float new_Y(const GCodeReader &reader) const { return this->has(Y) ? this->y() : reader.y(); }
float new_Z(const GCodeReader &reader) const { return this->has(Z) ? this->z() : reader.z(); }
float new_E(const GCodeReader &reader) const { return this->has(E) ? this->e() : reader.e(); }
float new_F(const GCodeReader &reader) const { return this->has(F) ? this->f() : reader.f(); }

View file

@ -318,12 +318,15 @@ namespace Slic3r {
assert((g1_line_id >= (int)data->g1_line_ids.size()) || (data->g1_line_ids[g1_line_id].first >= g1_lines_count));
const Block* block = nullptr;
const G1LineIdToBlockId& map_item = data->g1_line_ids[g1_line_id];
if ((g1_line_id < (int)data->g1_line_ids.size()) && (map_item.first == g1_lines_count))
if (g1_line_id < (int)data->g1_line_ids.size())
{
if (line.has_e() && (map_item.second < (unsigned int)data->blocks.size()))
block = &data->blocks[map_item.second];
++g1_line_id;
const G1LineIdToBlockId& map_item = data->g1_line_ids[g1_line_id];
if (map_item.first == g1_lines_count)
{
if (line.has_e() && (map_item.second < (unsigned int)data->blocks.size()))
block = &data->blocks[map_item.second];
++g1_line_id;
}
}
if ((block != nullptr) && (block->elapsed_time != -1.0f))
@ -412,6 +415,11 @@ namespace Slic3r {
m_state.axis[axis].position = position;
}
void GCodeTimeEstimator::set_axis_origin(EAxis axis, float position)
{
m_state.axis[axis].origin = position;
}
void GCodeTimeEstimator::set_axis_max_feedrate(EAxis axis, float feedrate_mm_sec)
{
m_state.axis[axis].max_feedrate = feedrate_mm_sec;
@ -432,6 +440,11 @@ namespace Slic3r {
return m_state.axis[axis].position;
}
float GCodeTimeEstimator::get_axis_origin(EAxis axis) const
{
return m_state.axis[axis].origin;
}
float GCodeTimeEstimator::get_axis_max_feedrate(EAxis axis) const
{
return m_state.axis[axis].max_feedrate;
@ -758,6 +771,10 @@ namespace Slic3r {
set_axis_position(X, 0.0f);
set_axis_position(Y, 0.0f);
set_axis_position(Z, 0.0f);
set_axis_origin(X, 0.0f);
set_axis_origin(Y, 0.0f);
set_axis_origin(Z, 0.0f);
if (get_e_local_positioning_type() == Absolute)
set_axis_position(E, 0.0f);
@ -954,34 +971,35 @@ namespace Slic3r {
}
}
// Returns the new absolute position on the given axis in dependence of the given parameters
float axis_absolute_position_from_G1_line(GCodeTimeEstimator::EAxis axis, const GCodeReader::GCodeLine& lineG1, GCodeTimeEstimator::EUnits units, bool is_relative, float current_absolute_position)
{
float lengthsScaleFactor = (units == GCodeTimeEstimator::Inches) ? INCHES_TO_MM : 1.0f;
if (lineG1.has(Slic3r::Axis(axis)))
{
float ret = lineG1.value(Slic3r::Axis(axis)) * lengthsScaleFactor;
return is_relative ? current_absolute_position + ret : ret;
}
else
return current_absolute_position;
}
void GCodeTimeEstimator::_processG1(const GCodeReader::GCodeLine& line)
{
auto axis_absolute_position = [this](GCodeTimeEstimator::EAxis axis, const GCodeReader::GCodeLine& lineG1) -> float
{
float current_absolute_position = get_axis_position(axis);
float current_origin = get_axis_origin(axis);
float lengthsScaleFactor = (get_units() == GCodeTimeEstimator::Inches) ? INCHES_TO_MM : 1.0f;
bool is_relative = (get_global_positioning_type() == Relative);
if (axis == E)
is_relative |= (get_e_local_positioning_type() == Relative);
if (lineG1.has(Slic3r::Axis(axis)))
{
float ret = lineG1.value(Slic3r::Axis(axis)) * lengthsScaleFactor;
return is_relative ? current_absolute_position + ret : ret + current_origin;
}
else
return current_absolute_position;
};
PROFILE_FUNC();
increment_g1_line_id();
// updates axes positions from line
EUnits units = get_units();
float new_pos[Num_Axis];
for (unsigned char a = X; a < Num_Axis; ++a)
{
bool is_relative = (get_global_positioning_type() == Relative);
if (a == E)
is_relative |= (get_e_local_positioning_type() == Relative);
new_pos[a] = axis_absolute_position_from_G1_line((EAxis)a, line, units, is_relative, get_axis_position((EAxis)a));
new_pos[a] = axis_absolute_position((EAxis)a, line);
}
// updates feedrate from line, if present
@ -1225,25 +1243,25 @@ namespace Slic3r {
if (line.has_x())
{
set_axis_position(X, line.x() * lengthsScaleFactor);
set_axis_origin(X, get_axis_position(X) - line.x() * lengthsScaleFactor);
anyFound = true;
}
if (line.has_y())
{
set_axis_position(Y, line.y() * lengthsScaleFactor);
set_axis_origin(Y, get_axis_position(Y) - line.y() * lengthsScaleFactor);
anyFound = true;
}
if (line.has_z())
{
set_axis_position(Z, line.z() * lengthsScaleFactor);
set_axis_origin(Z, get_axis_position(Z) - line.z() * lengthsScaleFactor);
anyFound = true;
}
if (line.has_e())
{
set_axis_position(E, line.e() * lengthsScaleFactor);
set_axis_origin(E, get_axis_position(E) - line.e() * lengthsScaleFactor);
anyFound = true;
}
else
@ -1253,7 +1271,7 @@ namespace Slic3r {
{
for (unsigned char a = X; a < Num_Axis; ++a)
{
set_axis_position((EAxis)a, 0.0f);
set_axis_origin((EAxis)a, get_axis_position((EAxis)a));
}
}
}

View file

@ -55,6 +55,7 @@ namespace Slic3r {
struct Axis
{
float position; // mm
float origin; // mm
float max_feedrate; // mm/s
float max_acceleration; // mm/s^2
float max_jerk; // mm/s
@ -282,6 +283,8 @@ namespace Slic3r {
// Set current position on the given axis with the given value
void set_axis_position(EAxis axis, float position);
// Set current origin on the given axis with the given value
void set_axis_origin(EAxis axis, float position);
void set_axis_max_feedrate(EAxis axis, float feedrate_mm_sec);
void set_axis_max_acceleration(EAxis axis, float acceleration);
@ -289,6 +292,8 @@ namespace Slic3r {
// Returns current position on the given axis
float get_axis_position(EAxis axis) const;
// Returns current origin on the given axis
float get_axis_origin(EAxis axis) const;
float get_axis_max_feedrate(EAxis axis) const;
float get_axis_max_acceleration(EAxis axis) const;

View file

@ -269,7 +269,7 @@ std::string GCodeWriter::set_speed(double F, const std::string &comment, const s
assert(F > 0.);
assert(F < 100000.);
std::ostringstream gcode;
gcode << "G1 F" << F;
gcode << "G1 F" << XYZF_NUM(F);
COMMENT(comment);
gcode << cooling_marker;
gcode << "\n";

View file

@ -3,18 +3,22 @@
#include "ClipperUtils.hpp"
#include "ExPolygon.hpp"
#include "Line.hpp"
#include "PolylineCollection.hpp"
#include "clipper.hpp"
#include <algorithm>
#include <cassert>
#include <cmath>
#include <list>
#include <map>
#include <numeric>
#include <set>
#include <utility>
#include <stack>
#include <vector>
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/log/trivial.hpp>
#ifdef SLIC3R_DEBUG
#include "SVG.hpp"
#endif
@ -309,49 +313,7 @@ convex_hull(const Polygons &polygons)
return convex_hull(std::move(pp));
}
/* accepts an arrayref of points and returns a list of indices
according to a nearest-neighbor walk */
void
chained_path(const Points &points, std::vector<Points::size_type> &retval, Point start_near)
{
PointConstPtrs my_points;
std::map<const Point*,Points::size_type> indices;
my_points.reserve(points.size());
for (Points::const_iterator it = points.begin(); it != points.end(); ++it) {
my_points.push_back(&*it);
indices[&*it] = it - points.begin();
}
retval.reserve(points.size());
while (!my_points.empty()) {
Points::size_type idx = start_near.nearest_point_index(my_points);
start_near = *my_points[idx];
retval.push_back(indices[ my_points[idx] ]);
my_points.erase(my_points.begin() + idx);
}
}
void
chained_path(const Points &points, std::vector<Points::size_type> &retval)
{
if (points.empty()) return; // can't call front() on empty vector
chained_path(points, retval, points.front());
}
/* retval and items must be different containers */
template<class T>
void
chained_path_items(Points &points, T &items, T &retval)
{
std::vector<Points::size_type> indices;
chained_path(points, indices);
for (std::vector<Points::size_type>::const_iterator it = indices.begin(); it != indices.end(); ++it)
retval.push_back(items[*it]);
}
template void chained_path_items(Points &points, ClipperLib::PolyNodes &items, ClipperLib::PolyNodes &retval);
bool
directions_parallel(double angle1, double angle2, double max_diff)
bool directions_parallel(double angle1, double angle2, double max_diff)
{
double diff = fabs(angle1 - angle2);
max_diff += EPSILON;
@ -359,8 +321,7 @@ directions_parallel(double angle1, double angle2, double max_diff)
}
template<class T>
bool
contains(const std::vector<T> &vector, const Point &point)
bool contains(const std::vector<T> &vector, const Point &point)
{
for (typename std::vector<T>::const_iterator it = vector.begin(); it != vector.end(); ++it) {
if (it->contains(point)) return true;
@ -369,16 +330,101 @@ contains(const std::vector<T> &vector, const Point &point)
}
template bool contains(const ExPolygons &vector, const Point &point);
double
rad2deg_dir(double angle)
double rad2deg_dir(double angle)
{
angle = (angle < PI) ? (-angle + PI/2.0) : (angle + PI/2.0);
if (angle < 0) angle += PI;
return rad2deg(angle);
}
void
simplify_polygons(const Polygons &polygons, double tolerance, Polygons* retval)
Point circle_taubin_newton(const Points::const_iterator& input_begin, const Points::const_iterator& input_end, size_t cycles)
{
Vec2ds tmp;
tmp.reserve(std::distance(input_begin, input_end));
std::transform(input_begin, input_end, std::back_inserter(tmp), [] (const Point& in) { return unscale(in); } );
Vec2d center = circle_taubin_newton(tmp.cbegin(), tmp.end(), cycles);
return Point::new_scale(center.x(), center.y());
}
/// Adapted from work in "Circular and Linear Regression: Fitting circles and lines by least squares", pg 126
/// Returns a point corresponding to the center of a circle for which all of the points from input_begin to input_end
/// lie on.
Vec2d circle_taubin_newton(const Vec2ds::const_iterator& input_begin, const Vec2ds::const_iterator& input_end, size_t cycles)
{
// calculate the centroid of the data set
const Vec2d sum = std::accumulate(input_begin, input_end, Vec2d(0,0));
const size_t n = std::distance(input_begin, input_end);
const double n_flt = static_cast<double>(n);
const Vec2d centroid { sum / n_flt };
// Compute the normalized moments of the data set.
double Mxx = 0, Myy = 0, Mxy = 0, Mxz = 0, Myz = 0, Mzz = 0;
for (auto it = input_begin; it < input_end; ++it) {
// center/normalize the data.
double Xi {it->x() - centroid.x()};
double Yi {it->y() - centroid.y()};
double Zi {Xi*Xi + Yi*Yi};
Mxy += (Xi*Yi);
Mxx += (Xi*Xi);
Myy += (Yi*Yi);
Mxz += (Xi*Zi);
Myz += (Yi*Zi);
Mzz += (Zi*Zi);
}
// divide by number of points to get the moments
Mxx /= n_flt;
Myy /= n_flt;
Mxy /= n_flt;
Mxz /= n_flt;
Myz /= n_flt;
Mzz /= n_flt;
// Compute the coefficients of the characteristic polynomial for the circle
// eq 5.60
const double Mz {Mxx + Myy}; // xx + yy = z
const double Cov_xy {Mxx*Myy - Mxy*Mxy}; // this shows up a couple times so cache it here.
const double C3 {4.0*Mz};
const double C2 {-3.0*(Mz*Mz) - Mzz};
const double C1 {Mz*(Mzz - (Mz*Mz)) + 4.0*Mz*Cov_xy - (Mxz*Mxz) - (Myz*Myz)};
const double C0 {(Mxz*Mxz)*Myy + (Myz*Myz)*Mxx - 2.0*Mxz*Myz*Mxy - Cov_xy*(Mzz - (Mz*Mz))};
const double C22 = {C2 + C2};
const double C33 = {C3 + C3 + C3};
// solve the characteristic polynomial with Newton's method.
double xnew = 0.0;
double ynew = 1e20;
for (size_t i = 0; i < cycles; ++i) {
const double yold {ynew};
ynew = C0 + xnew * (C1 + xnew*(C2 + xnew * C3));
if (std::abs(ynew) > std::abs(yold)) {
BOOST_LOG_TRIVIAL(error) << "Geometry: Fit is going in the wrong direction.\n";
return Vec2d(std::nan(""), std::nan(""));
}
const double Dy {C1 + xnew*(C22 + xnew*C33)};
const double xold {xnew};
xnew = xold - (ynew / Dy);
if (std::abs((xnew-xold) / xnew) < 1e-12) i = cycles; // converged, we're done here
if (xnew < 0) {
// reset, we went negative
xnew = 0.0;
}
}
// compute the determinant and the circle's parameters now that we've solved.
double DET = xnew*xnew - xnew*Mz + Cov_xy;
Vec2d center(Mxz * (Myy - xnew) - Myz * Mxy, Myz * (Mxx - xnew) - Mxz*Mxy);
center /= (DET * 2.);
return center + centroid;
}
void simplify_polygons(const Polygons &polygons, double tolerance, Polygons* retval)
{
Polygons pp;
for (Polygons::const_iterator it = polygons.begin(); it != polygons.end(); ++it) {
@ -391,8 +437,7 @@ simplify_polygons(const Polygons &polygons, double tolerance, Polygons* retval)
*retval = Slic3r::simplify_polygons(pp);
}
double
linint(double value, double oldmin, double oldmax, double newmin, double newmax)
double linint(double value, double oldmin, double oldmax, double newmin, double newmax)
{
return (value - oldmin) * (newmax - newmin) / (oldmax - oldmin) + newmin;
}
@ -618,7 +663,6 @@ namespace Voronoi { namespace Internal {
typedef boost::polygon::point_data<coordinate_type> point_type;
typedef boost::polygon::segment_data<coordinate_type> segment_type;
typedef boost::polygon::rectangle_data<coordinate_type> rect_type;
// typedef voronoi_builder<int> VB;
typedef boost::polygon::voronoi_diagram<coordinate_type> VD;
typedef VD::cell_type cell_type;
typedef VD::cell_type::source_index_type source_index_type;
@ -665,15 +709,15 @@ namespace Voronoi { namespace Internal {
if (cell1.contains_point() && cell2.contains_point()) {
point_type p1 = retrieve_point(segments, cell1);
point_type p2 = retrieve_point(segments, cell2);
origin.x((p1(0) + p2(0)) * 0.5);
origin.y((p1(1) + p2(1)) * 0.5);
direction.x(p1(1) - p2(1));
direction.y(p2(0) - p1(0));
origin.x((p1.x() + p2.x()) * 0.5);
origin.y((p1.y() + p2.y()) * 0.5);
direction.x(p1.y() - p2.y());
direction.y(p2.x() - p1.x());
} else {
origin = cell1.contains_segment() ? retrieve_point(segments, cell2) : retrieve_point(segments, cell1);
segment_type segment = cell1.contains_segment() ? segments[cell1.source_index()] : segments[cell2.source_index()];
coordinate_type dx = high(segment)(0) - low(segment)(0);
coordinate_type dy = high(segment)(1) - low(segment)(1);
coordinate_type dx = high(segment).x() - low(segment).x();
coordinate_type dy = high(segment).y() - low(segment).y();
if ((low(segment) == origin) ^ cell1.contains_point()) {
direction.x(dy);
direction.y(-dx);
@ -682,19 +726,19 @@ namespace Voronoi { namespace Internal {
direction.y(dx);
}
}
coordinate_type koef = bbox_max_size / (std::max)(fabs(direction(0)), fabs(direction(1)));
coordinate_type koef = bbox_max_size / (std::max)(fabs(direction.x()), fabs(direction.y()));
if (edge.vertex0() == NULL) {
clipped_edge->push_back(point_type(
origin(0) - direction(0) * koef,
origin(1) - direction(1) * koef));
origin.x() - direction.x() * koef,
origin.y() - direction.y() * koef));
} else {
clipped_edge->push_back(
point_type(edge.vertex0()->x(), edge.vertex0()->y()));
}
if (edge.vertex1() == NULL) {
clipped_edge->push_back(point_type(
origin(0) + direction(0) * koef,
origin(1) + direction(1) * koef));
origin.x() + direction.x() * koef,
origin.y() + direction.y() * koef));
} else {
clipped_edge->push_back(
point_type(edge.vertex1()->x(), edge.vertex1()->y()));
@ -714,7 +758,7 @@ namespace Voronoi { namespace Internal {
} /* namespace Internal */ } // namespace Voronoi
static inline void dump_voronoi_to_svg(const Lines &lines, /* const */ voronoi_diagram<double> &vd, const ThickPolylines *polylines, const char *path)
static inline void dump_voronoi_to_svg(const Lines &lines, /* const */ boost::polygon::voronoi_diagram<double> &vd, const ThickPolylines *polylines, const char *path)
{
const double scale = 0.2;
const std::string inputSegmentPointColor = "lightseagreen";
@ -758,7 +802,7 @@ static inline void dump_voronoi_to_svg(const Lines &lines, /* const */ voronoi_d
Voronoi::Internal::point_type(double(it->b(0)), double(it->b(1)))));
// Color exterior edges.
for (voronoi_diagram<double>::const_edge_iterator it = vd.edges().begin(); it != vd.edges().end(); ++it)
for (boost::polygon::voronoi_diagram<double>::const_edge_iterator it = vd.edges().begin(); it != vd.edges().end(); ++it)
if (!it->is_finite())
Voronoi::Internal::color_exterior(&(*it));
@ -773,11 +817,11 @@ static inline void dump_voronoi_to_svg(const Lines &lines, /* const */ voronoi_d
#if 1
// Draw voronoi vertices.
for (voronoi_diagram<double>::const_vertex_iterator it = vd.vertices().begin(); it != vd.vertices().end(); ++it)
for (boost::polygon::voronoi_diagram<double>::const_vertex_iterator it = vd.vertices().begin(); it != vd.vertices().end(); ++it)
if (! internalEdgesOnly || it->color() != Voronoi::Internal::EXTERNAL_COLOR)
svg.draw(Point(coord_t((*it)(0)), coord_t((*it)(1))), voronoiPointColor, voronoiPointRadius);
svg.draw(Point(coord_t(it->x()), coord_t(it->y())), voronoiPointColor, voronoiPointRadius);
for (voronoi_diagram<double>::const_edge_iterator it = vd.edges().begin(); it != vd.edges().end(); ++it) {
for (boost::polygon::voronoi_diagram<double>::const_edge_iterator it = vd.edges().begin(); it != vd.edges().end(); ++it) {
if (primaryEdgesOnly && !it->is_primary())
continue;
if (internalEdgesOnly && (it->color() == Voronoi::Internal::EXTERNAL_COLOR))
@ -800,7 +844,7 @@ static inline void dump_voronoi_to_svg(const Lines &lines, /* const */ voronoi_d
color = voronoiLineColorSecondary;
}
for (std::size_t i = 0; i + 1 < samples.size(); ++i)
svg.draw(Line(Point(coord_t(samples[i](0)), coord_t(samples[i](1))), Point(coord_t(samples[i+1](0)), coord_t(samples[i+1](1)))), color, voronoiLineWidth);
svg.draw(Line(Point(coord_t(samples[i].x()), coord_t(samples[i].y())), Point(coord_t(samples[i+1].x()), coord_t(samples[i+1].y()))), color, voronoiLineWidth);
}
#endif
@ -1376,6 +1420,32 @@ void Transformation::set_from_transform(const Transform3d& transform)
// std::cout << "something went wrong in extracting data from matrix" << std::endl;
}
void Transformation::set_from_string(const std::string& transform_str)
{
Transform3d transform = Transform3d::Identity();
if (!transform_str.empty())
{
std::vector<std::string> mat_elements_str;
boost::split(mat_elements_str, transform_str, boost::is_any_of(" "), boost::token_compress_on);
unsigned int size = (unsigned int)mat_elements_str.size();
if (size == 16)
{
unsigned int i = 0;
for (unsigned int r = 0; r < 4; ++r)
{
for (unsigned int c = 0; c < 4; ++c)
{
transform(r, c) = ::atof(mat_elements_str[i++].c_str());
}
}
}
}
set_from_transform(transform);
}
void Transformation::reset()
{
m_offset = Vec3d::Zero();

View file

@ -11,8 +11,11 @@
#include <cereal/access.hpp>
#include "boost/polygon/voronoi.hpp"
using boost::polygon::voronoi_builder;
using boost::polygon::voronoi_diagram;
namespace ClipperLib {
class PolyNode;
using PolyNodes = std::vector<PolyNode*>;
}
namespace Slic3r { namespace Geometry {
@ -138,9 +141,6 @@ Pointf3s convex_hull(Pointf3s points);
Polygon convex_hull(Points points);
Polygon convex_hull(const Polygons &polygons);
void chained_path(const Points &points, std::vector<Points::size_type> &retval, Point start_near);
void chained_path(const Points &points, std::vector<Points::size_type> &retval);
template<class T> void chained_path_items(Points &points, T &items, T &retval);
bool directions_parallel(double angle1, double angle2, double max_diff = 0);
template<class T> bool contains(const std::vector<T> &vector, const Point &point);
template<typename T> T rad2deg(T angle) { return T(180.0) * angle / T(PI); }
@ -160,6 +160,15 @@ template<typename T> T angle_to_0_2PI(T angle)
return angle;
}
/// Find the center of the circle corresponding to the vector of Points as an arc.
Point circle_taubin_newton(const Points::const_iterator& input_start, const Points::const_iterator& input_end, size_t cycles = 20);
inline Point circle_taubin_newton(const Points& input, size_t cycles = 20) { return circle_taubin_newton(input.cbegin(), input.cend(), cycles); }
/// Find the center of the circle corresponding to the vector of Pointfs as an arc.
Vec2d circle_taubin_newton(const Vec2ds::const_iterator& input_start, const Vec2ds::const_iterator& input_end, size_t cycles = 20);
inline Vec2d circle_taubin_newton(const Vec2ds& input, size_t cycles = 20) { return circle_taubin_newton(input.cbegin(), input.cend(), cycles); }
void simplify_polygons(const Polygons &polygons, double tolerance, Polygons* retval);
double linint(double value, double oldmin, double oldmax, double newmin, double newmax);
@ -181,7 +190,7 @@ class MedialAxis {
void build(Polylines* polylines);
private:
class VD : public voronoi_diagram<double> {
class VD : public boost::polygon::voronoi_diagram<double> {
public:
typedef double coord_type;
typedef boost::polygon::point_data<coordinate_type> point_type;
@ -278,6 +287,7 @@ public:
void set_mirror(Axis axis, double mirror);
void set_from_transform(const Transform3d& transform);
void set_from_string(const std::string& transform_str);
void reset();

View file

@ -0,0 +1,233 @@
// KD tree built upon external data set, referencing the external data by integer indices.
#ifndef slic3r_KDTreeIndirect_hpp_
#define slic3r_KDTreeIndirect_hpp_
#include <algorithm>
#include <limits>
#include <vector>
#include "Utils.hpp" // for next_highest_power_of_2()
namespace Slic3r {
// KD tree for N-dimensional closest point search.
template<size_t ANumDimensions, typename ACoordType, typename ACoordinateFn>
class KDTreeIndirect
{
public:
static constexpr size_t NumDimensions = ANumDimensions;
using CoordinateFn = ACoordinateFn;
using CoordType = ACoordType;
// Following could be static constexpr size_t, but that would not link in C++11
enum : size_t {
npos = size_t(-1)
};
KDTreeIndirect(CoordinateFn coordinate) : coordinate(coordinate) {}
KDTreeIndirect(CoordinateFn coordinate, std::vector<size_t> indices) : coordinate(coordinate) { this->build(std::move(indices)); }
KDTreeIndirect(CoordinateFn coordinate, std::vector<size_t> &&indices) : coordinate(coordinate) { this->build(std::move(indices)); }
KDTreeIndirect(CoordinateFn coordinate, size_t num_indices) : coordinate(coordinate) { this->build(num_indices); }
KDTreeIndirect(KDTreeIndirect &&rhs) : m_nodes(std::move(rhs.m_nodes)), coordinate(std::move(rhs.coordinate)) {}
KDTreeIndirect& operator=(KDTreeIndirect &&rhs) { m_nodes = std::move(rhs.m_nodes); coordinate = std::move(rhs.coordinate); return *this; }
void clear() { m_nodes.clear(); }
void build(size_t num_indices)
{
std::vector<size_t> indices;
indices.reserve(num_indices);
for (size_t i = 0; i < num_indices; ++ i)
indices.emplace_back(i);
this->build(std::move(indices));
}
void build(std::vector<size_t> &&indices)
{
if (indices.empty())
clear();
else {
// Allocate a next highest power of 2 nodes, because the incomplete binary tree will not have the leaves filled strictly from the left.
m_nodes.assign(next_highest_power_of_2(indices.size() + 1), npos);
build_recursive(indices, 0, 0, 0, (int)(indices.size() - 1));
}
indices.clear();
}
enum class VisitorReturnMask : unsigned int
{
CONTINUE_LEFT = 1,
CONTINUE_RIGHT = 2,
STOP = 4,
};
template<typename CoordType>
unsigned int descent_mask(const CoordType &point_coord, const CoordType &search_radius, size_t idx, size_t dimension) const
{
CoordType dist = point_coord - this->coordinate(idx, dimension);
return (dist * dist < search_radius + CoordType(EPSILON)) ?
// The plane intersects a hypersphere centered at point_coord of search_radius.
((unsigned int)(VisitorReturnMask::CONTINUE_LEFT) | (unsigned int)(VisitorReturnMask::CONTINUE_RIGHT)) :
// The plane does not intersect the hypersphere.
(dist > CoordType(0)) ? (unsigned int)(VisitorReturnMask::CONTINUE_RIGHT) : (unsigned int)(VisitorReturnMask::CONTINUE_LEFT);
}
// Visitor is supposed to return a bit mask of VisitorReturnMask.
template<typename Visitor>
void visit(Visitor &visitor) const
{
visit_recursive(0, 0, visitor);
}
CoordinateFn coordinate;
private:
// Build a balanced tree by splitting the input sequence by an axis aligned plane at a dimension.
void build_recursive(std::vector<size_t> &input, size_t node, int dimension, int left, int right)
{
if (left > right)
return;
assert(node < m_nodes.size());
if (left == right) {
// Insert a node into the balanced tree.
m_nodes[node] = input[left];
return;
}
// Partition the input sequence to two equal halves.
int center = (left + right) >> 1;
partition_input(input, dimension, left, right, center);
// Insert a node into the tree.
m_nodes[node] = input[center];
// Partition the left and right subtrees.
size_t next_dimension = (++ dimension == NumDimensions) ? 0 : dimension;
build_recursive(input, (node << 1) + 1, next_dimension, left, center - 1);
build_recursive(input, (node << 1) + 2, next_dimension, center + 1, right);
}
// Partition the input m_nodes <left, right> at k using QuickSelect method.
// https://en.wikipedia.org/wiki/Quickselect
void partition_input(std::vector<size_t> &input, int dimension, int left, int right, int k) const
{
while (left < right) {
// Guess the k'th element.
// Pick the pivot as a median of first, center and last value.
// Sort first, center and last values.
int center = (left + right) >> 1;
auto left_value = this->coordinate(input[left], dimension);
auto center_value = this->coordinate(input[center], dimension);
auto right_value = this->coordinate(input[right], dimension);
if (center_value < left_value) {
std::swap(input[left], input[center]);
std::swap(left_value, center_value);
}
if (right_value < left_value) {
std::swap(input[left], input[right]);
std::swap(left_value, right_value);
}
if (right_value < center_value) {
std::swap(input[center], input[right]);
// No need to do that, result is not used.
// std::swap(center_value, right_value);
}
// Only two or three values are left and those are sorted already.
if (left + 3 > right)
break;
// left and right items are already at their correct positions.
// input[left].point[dimension] <= input[center].point[dimension] <= input[right].point[dimension]
// Move the pivot to the (right - 1) position.
std::swap(input[center], input[right - 1]);
// Pivot value.
double pivot = this->coordinate(input[right - 1], dimension);
// Partition the set based on the pivot.
int i = left;
int j = right - 1;
for (;;) {
// Skip left points that are already at correct positions.
// Search will certainly stop at position (right - 1), which stores the pivot.
while (this->coordinate(input[++ i], dimension) < pivot) ;
// Skip right points that are already at correct positions.
while (this->coordinate(input[-- j], dimension) > pivot && i < j) ;
if (i >= j)
break;
std::swap(input[i], input[j]);
}
// Restore pivot to the center of the sequence.
std::swap(input[i], input[right]);
// Which side the kth element is in?
if (k < i)
right = i - 1;
else if (k == i)
// Sequence is partitioned, kth element is at its place.
break;
else
left = i + 1;
}
}
template<typename Visitor>
void visit_recursive(size_t node, size_t dimension, Visitor &visitor) const
{
assert(! m_nodes.empty());
if (node >= m_nodes.size() || m_nodes[node] == npos)
return;
// Left / right child node index.
size_t left = (node << 1) + 1;
size_t right = left + 1;
unsigned int mask = visitor(m_nodes[node], dimension);
if ((mask & (unsigned int)VisitorReturnMask::STOP) == 0) {
size_t next_dimension = (++ dimension == NumDimensions) ? 0 : dimension;
if (mask & (unsigned int)VisitorReturnMask::CONTINUE_LEFT)
visit_recursive(left, next_dimension, visitor);
if (mask & (unsigned int)VisitorReturnMask::CONTINUE_RIGHT)
visit_recursive(right, next_dimension, visitor);
}
}
std::vector<size_t> m_nodes;
};
// Find a closest point using Euclidian metrics.
// Returns npos if not found.
template<typename KDTreeIndirectType, typename PointType, typename FilterFn>
size_t find_closest_point(const KDTreeIndirectType &kdtree, const PointType &point, FilterFn filter)
{
struct Visitor {
using CoordType = typename KDTreeIndirectType::CoordType;
const KDTreeIndirectType &kdtree;
const PointType &point;
const FilterFn filter;
size_t min_idx = KDTreeIndirectType::npos;
CoordType min_dist = std::numeric_limits<CoordType>::max();
Visitor(const KDTreeIndirectType &kdtree, const PointType &point, FilterFn filter) : kdtree(kdtree), point(point), filter(filter) {}
unsigned int operator()(size_t idx, size_t dimension) {
if (this->filter(idx)) {
auto dist = CoordType(0);
for (size_t i = 0; i < KDTreeIndirectType::NumDimensions; ++ i) {
CoordType d = point[i] - kdtree.coordinate(idx, i);
dist += d * d;
}
if (dist < min_dist) {
min_dist = dist;
min_idx = idx;
}
}
return kdtree.descent_mask(point[dimension], min_dist, idx, dimension);
}
} visitor(kdtree, point, filter);
kdtree.visit(visitor);
return visitor.min_idx;
}
template<typename KDTreeIndirectType, typename PointType>
size_t find_closest_point(const KDTreeIndirectType& kdtree, const PointType& point)
{
return find_closest_point(kdtree, point, [](size_t) { return true; });
}
} // namespace Slic3r
#endif /* slic3r_KDTreeIndirect_hpp_ */

View file

@ -1,8 +1,8 @@
#include "Layer.hpp"
#include "ClipperUtils.hpp"
#include "Geometry.hpp"
#include "Print.hpp"
#include "Fill/Fill.hpp"
#include "ShortestPath.hpp"
#include "SVG.hpp"
#include <boost/log/trivial.hpp>
@ -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;
@ -57,12 +57,11 @@ void Layer::make_slices()
ordering_points.push_back(ex.contour.first_point());
// sort slices
std::vector<Points::size_type> order;
Slic3r::Geometry::chained_path(ordering_points, order);
std::vector<Points::size_type> order = chain_points(ordering_points);
// 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.
@ -71,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)
@ -89,8 +88,12 @@ ExPolygons Layer::merged(float offset_scaled) const
offset_scaled2 = float(- EPSILON);
}
Polygons polygons;
for (LayerRegion *layerm : m_regions)
append(polygons, offset(to_expolygons(layerm->slices.surfaces), offset_scaled));
for (LayerRegion *layerm : m_regions) {
const PrintRegionConfig &config = layerm->region()->config();
// Our users learned to bend Slic3r to produce empty volumes to act as subtracters. Only add the region if it is non-empty.
if (config.bottom_solid_layers > 0 || config.top_solid_layers > 0 || config.fill_density > 0. || config.perimeters > 0)
append(polygons, offset(to_expolygons(layerm->slices.surfaces), offset_scaled));
}
ExPolygons out = union_ex(polygons);
if (offset_scaled2 != 0.f)
out = offset_ex(out, offset_scaled2);

View file

@ -6,8 +6,6 @@
#include "SurfaceCollection.hpp"
#include "ExtrusionEntityCollection.hpp"
#include "ExPolygonCollection.hpp"
#include "PolylineCollection.hpp"
namespace Slic3r {
@ -48,7 +46,7 @@ public:
Polygons bridged;
// collection of polylines representing the unsupported bridge edges
PolylineCollection unsupported_bridge_edges;
Polylines unsupported_bridge_edges;
// ordered collection of extrusion paths/loops to build all perimeters
// (this collection contains only ExtrusionEntityCollection objects)
@ -112,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); }

View file

@ -70,7 +70,7 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollec
fill_surfaces
);
if (this->layer()->lower_layer != NULL)
if (this->layer()->lower_layer != nullptr)
// Cummulative sum of polygons over all the regions.
g.lower_slices = &this->layer()->lower_layer->slices;
@ -88,7 +88,6 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollec
void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Polygons *lower_layer_covered)
{
const Surfaces &surfaces = this->fill_surfaces.surfaces;
const bool has_infill = this->region()->config().fill_density.value > 0.;
const float margin = float(scale_(EXTERNAL_INFILL_MARGIN));
@ -130,7 +129,7 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
bridges.emplace_back(surface);
}
if (surface.is_internal()) {
assert(surface.surface_type == stInternal);
assert(surface.surface_type == stInternal || surface.surface_type == stInternalSolid);
if (! has_infill && lower_layer != nullptr)
polygons_append(voids, surface.expolygon);
internal.emplace_back(std::move(surface));
@ -140,7 +139,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);
@ -272,7 +271,7 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
bridges[idx_last].bridge_angle = bd.angle;
if (this->layer()->object()->config().support_material) {
polygons_append(this->bridged, bd.coverage());
this->unsupported_bridge_edges.append(bd.unsupported_edges());
append(this->unsupported_bridge_edges, bd.unsupported_edges());
}
} else if (custom_angle > 0) {
// Bridge was not detected (likely it is only supported at one side). Still it is a surface filled in

View file

@ -86,10 +86,7 @@ bool Line::intersection(const Line &l2, Point *intersection) const
const Line &l1 = *this;
const Vec2d v1 = (l1.b - l1.a).cast<double>();
const Vec2d v2 = (l2.b - l2.a).cast<double>();
const Vec2d v12 = (l1.a - l2.a).cast<double>();
double denom = cross2(v1, v2);
double nume_a = cross2(v2, v12);
double nume_b = cross2(v1, v12);
if (fabs(denom) < EPSILON)
#if 0
// Lines are collinear. Return true if they are coincident (overlappign).
@ -97,6 +94,9 @@ bool Line::intersection(const Line &l2, Point *intersection) const
#else
return false;
#endif
const Vec2d v12 = (l1.a - l2.a).cast<double>();
double nume_a = cross2(v2, v12);
double nume_b = cross2(v1, v12);
double t1 = nume_a / denom;
double t2 = nume_b / denom;
if (t1 >= 0 && t1 <= 1.0f && t2 >= 0 && t2 <= 1.0f) {

View file

@ -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;
}

View file

@ -141,12 +141,12 @@ Model Model::read_from_archive(const std::string& input_file, DynamicPrintConfig
for (ModelObject *o : model.objects)
{
if (boost::algorithm::iends_with(input_file, ".zip.amf"))
{
// we remove the .zip part of the extension to avoid it be added to filenames when exporting
o->input_file = boost::ireplace_last_copy(input_file, ".zip.", ".");
}
else
// if (boost::algorithm::iends_with(input_file, ".zip.amf"))
// {
// // we remove the .zip part of the extension to avoid it be added to filenames when exporting
// o->input_file = boost::ireplace_last_copy(input_file, ".zip.", ".");
// }
// else
o->input_file = input_file;
}
@ -170,6 +170,9 @@ ModelObject* Model::add_object(const char *name, const char *path, const Triangl
new_object->input_file = path;
ModelVolume *new_volume = new_object->add_volume(mesh);
new_volume->name = name;
new_volume->source.input_file = path;
new_volume->source.object_idx = (int)this->objects.size() - 1;
new_volume->source.volume_idx = (int)new_object->volumes.size() - 1;
new_object->invalidate_bounding_box();
return new_object;
}
@ -182,6 +185,9 @@ ModelObject* Model::add_object(const char *name, const char *path, TriangleMesh
new_object->input_file = path;
ModelVolume *new_volume = new_object->add_volume(std::move(mesh));
new_volume->name = name;
new_volume->source.input_file = path;
new_volume->source.object_idx = (int)this->objects.size() - 1;
new_volume->source.volume_idx = (int)new_object->volumes.size() - 1;
new_object->invalidate_bounding_box();
return new_object;
}
@ -1543,7 +1549,7 @@ bool ModelVolume::is_splittable() const
return m_is_splittable == 1;
}
void ModelVolume::center_geometry_after_creation()
void ModelVolume::center_geometry_after_creation(bool update_source_offset)
{
Vec3d shift = this->mesh().bounding_box().center();
if (!shift.isApprox(Vec3d::Zero()))
@ -1554,6 +1560,9 @@ void ModelVolume::center_geometry_after_creation()
const_cast<TriangleMesh*>(m_convex_hull.get())->translate(-(float)shift(0), -(float)shift(1), -(float)shift(2));
translate(shift);
}
if (update_source_offset)
source.mesh_offset = shift;
}
void ModelVolume::calculate_convex_hull()

View file

@ -392,6 +392,18 @@ class ModelVolume final : public ObjectBase
{
public:
std::string name;
// struct used by reload from disk command to recover data from disk
struct Source
{
std::string input_file;
int object_idx{ -1 };
int volume_idx{ -1 };
Vec3d mesh_offset{ Vec3d::Zero() };
template<class Archive> void serialize(Archive& ar) { ar(input_file, object_idx, volume_idx, mesh_offset); }
};
Source source;
// The triangular model.
const TriangleMesh& mesh() const { return *m_mesh.get(); }
void set_mesh(const TriangleMesh &mesh) { m_mesh = std::make_shared<const TriangleMesh>(mesh); }
@ -440,7 +452,7 @@ public:
// Translates the mesh and the convex hull so that the origin of their vertices is in the center of this volume's bounding box.
// Attention! This method may only be called just after ModelVolume creation! It must not be called once the TriangleMesh of this ModelVolume is shared!
void center_geometry_after_creation();
void center_geometry_after_creation(bool update_source_offset = true);
void calculate_convex_hull();
const TriangleMesh& get_convex_hull() const;
@ -529,7 +541,7 @@ private:
// Copying an existing volume, therefore this volume will get a copy of the ID assigned.
ModelVolume(ModelObject *object, const ModelVolume &other) :
ObjectBase(other),
name(other.name), m_mesh(other.m_mesh), m_convex_hull(other.m_convex_hull), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation)
name(other.name), source(other.source), m_mesh(other.m_mesh), m_convex_hull(other.m_convex_hull), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation)
{
assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id());
assert(this->id() == other.id() && this->config.id() == other.config.id());
@ -537,7 +549,7 @@ private:
}
// Providing a new mesh, therefore this volume will get a new unique ID assigned.
ModelVolume(ModelObject *object, const ModelVolume &other, const TriangleMesh &&mesh) :
name(other.name), m_mesh(new TriangleMesh(std::move(mesh))), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation)
name(other.name), source(other.source), m_mesh(new TriangleMesh(std::move(mesh))), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation)
{
assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id());
assert(this->id() != other.id() && this->config.id() == other.config.id());
@ -558,8 +570,8 @@ private:
}
template<class Archive> void load(Archive &ar) {
bool has_convex_hull;
ar(name, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull);
cereal::load_by_value(ar, config);
ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull);
cereal::load_by_value(ar, config);
assert(m_mesh);
if (has_convex_hull) {
cereal::load_optional(ar, m_convex_hull);
@ -571,8 +583,8 @@ private:
}
template<class Archive> void save(Archive &ar) const {
bool has_convex_hull = m_convex_hull.get() != nullptr;
ar(name, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull);
cereal::save_by_value(ar, config);
ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull);
cereal::save_by_value(ar, config);
if (has_convex_hull)
cereal::save_optional(ar, m_convex_hull);
}

View file

@ -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);
}

View file

@ -3,11 +3,6 @@
namespace Slic3r {
MultiPoint::operator Points() const
{
return this->points;
}
void MultiPoint::scale(double factor)
{
for (Point &pt : points)
@ -57,18 +52,7 @@ void MultiPoint::rotate(double angle, const Point &center)
}
}
void MultiPoint::reverse()
{
std::reverse(this->points.begin(), this->points.end());
}
Point MultiPoint::first_point() const
{
return this->points.front();
}
double
MultiPoint::length() const
double MultiPoint::length() const
{
Lines lines = this->lines();
double len = 0;
@ -78,8 +62,7 @@ MultiPoint::length() const
return len;
}
int
MultiPoint::find_point(const Point &point) const
int MultiPoint::find_point(const Point &point) const
{
for (const Point &pt : this->points)
if (pt == point)
@ -87,21 +70,18 @@ MultiPoint::find_point(const Point &point) const
return -1; // not found
}
bool
MultiPoint::has_boundary_point(const Point &point) const
bool MultiPoint::has_boundary_point(const Point &point) const
{
double dist = (point.projection_onto(*this) - point).cast<double>().norm();
return dist < SCALED_EPSILON;
}
BoundingBox
MultiPoint::bounding_box() const
BoundingBox MultiPoint::bounding_box() const
{
return BoundingBox(this->points);
}
bool
MultiPoint::has_duplicate_points() const
bool MultiPoint::has_duplicate_points() const
{
for (size_t i = 1; i < points.size(); ++i)
if (points[i-1] == points[i])
@ -109,8 +89,7 @@ MultiPoint::has_duplicate_points() const
return false;
}
bool
MultiPoint::remove_duplicate_points()
bool MultiPoint::remove_duplicate_points()
{
size_t j = 0;
for (size_t i = 1; i < points.size(); ++i) {
@ -129,8 +108,7 @@ MultiPoint::remove_duplicate_points()
return false;
}
bool
MultiPoint::intersection(const Line& line, Point* intersection) const
bool MultiPoint::intersection(const Line& line, Point* intersection) const
{
Lines lines = this->lines();
for (Lines::const_iterator it = lines.begin(); it != lines.end(); ++it) {

View file

@ -17,7 +17,8 @@ class MultiPoint
public:
Points points;
operator Points() const;
operator Points() const { return this->points; }
MultiPoint() {}
MultiPoint(const MultiPoint &other) : points(other.points) {}
MultiPoint(MultiPoint &&other) : points(std::move(other.points)) {}
@ -32,9 +33,10 @@ public:
void rotate(double angle) { this->rotate(cos(angle), sin(angle)); }
void rotate(double cos_angle, double sin_angle);
void rotate(double angle, const Point &center);
void reverse();
Point first_point() const;
virtual Point last_point() const = 0;
void reverse() { std::reverse(this->points.begin(), this->points.end()); }
const Point& first_point() const { return this->points.front(); }
virtual const Point& last_point() const = 0;
virtual Lines lines() const = 0;
size_t size() const { return points.size(); }
bool empty() const { return points.empty(); }

View file

@ -13,21 +13,28 @@ public:
{}
~MutablePriorityQueue() { clear(); }
inline void clear() { m_heap.clear(); }
inline void reserve(size_t cnt) { m_heap.reserve(cnt); }
inline void push(const T &item);
inline void push(T &&item);
inline void pop();
inline T& top() { return m_heap.front(); }
inline void remove(size_t idx);
inline void update(size_t idx) { T item = m_heap[idx]; remove(idx); push(item); }
void clear();
void reserve(size_t cnt) { m_heap.reserve(cnt); }
void push(const T &item);
void push(T &&item);
void pop();
T& top() { return m_heap.front(); }
void remove(size_t idx);
void update(size_t idx) { T item = m_heap[idx]; remove(idx); push(item); }
inline size_t size() const { return m_heap.size(); }
inline bool empty() const { return m_heap.empty(); }
size_t size() const { return m_heap.size(); }
bool empty() const { return m_heap.empty(); }
using iterator = typename std::vector<T>::iterator;
using const_iterator = typename std::vector<T>::const_iterator;
iterator begin() { return m_heap.begin(); }
iterator end() { return m_heap.end(); }
const_iterator cbegin() const { return m_heap.cbegin(); }
const_iterator cend() const { return m_heap.cend(); }
protected:
inline void update_heap_up(size_t top, size_t bottom);
inline void update_heap_down(size_t top, size_t bottom);
void update_heap_up(size_t top, size_t bottom);
void update_heap_down(size_t top, size_t bottom);
private:
std::vector<T> m_heap;
@ -42,6 +49,17 @@ MutablePriorityQueue<T, IndexSetter, LessPredicate> make_mutable_priority_queue(
std::forward<IndexSetter>(index_setter), std::forward<LessPredicate>(less_predicate));
}
template<class T, class LessPredicate, class IndexSetter>
inline void MutablePriorityQueue<T, LessPredicate, IndexSetter>::clear()
{
#ifndef NDEBUG
for (size_t idx = 0; idx < m_heap.size(); ++ idx)
// Mark as removed from the queue.
m_index_setter(m_heap[idx], std::numeric_limits<size_t>::max());
#endif /* NDEBUG */
m_heap.clear();
}
template<class T, class LessPredicate, class IndexSetter>
inline void MutablePriorityQueue<T, LessPredicate, IndexSetter>::push(const T &item)
{
@ -64,6 +82,10 @@ template<class T, class LessPredicate, class IndexSetter>
inline void MutablePriorityQueue<T, LessPredicate, IndexSetter>::pop()
{
assert(! m_heap.empty());
#ifndef NDEBUG
// Mark as removed from the queue.
m_index_setter(m_heap.front(), std::numeric_limits<size_t>::max());
#endif /* NDEBUG */
if (m_heap.size() > 1) {
m_heap.front() = m_heap.back();
m_heap.pop_back();
@ -77,6 +99,10 @@ template<class T, class LessPredicate, class IndexSetter>
inline void MutablePriorityQueue<T, LessPredicate, IndexSetter>::remove(size_t idx)
{
assert(idx < m_heap.size());
#ifndef NDEBUG
// Mark as removed from the queue.
m_index_setter(m_heap[idx], std::numeric_limits<size_t>::max());
#endif /* NDEBUG */
if (idx + 1 == m_heap.size()) {
m_heap.pop_back();
return;

View file

@ -1,6 +1,8 @@
#include "PerimeterGenerator.hpp"
#include "ClipperUtils.hpp"
#include "ExtrusionEntityCollection.hpp"
#include "ShortestPath.hpp"
#include <cmath>
#include <cassert>
@ -86,24 +88,24 @@ static ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyline &thi
return paths;
}
static ExtrusionEntityCollection variable_width(const ThickPolylines& polylines, ExtrusionRole role, Flow flow)
static void variable_width(const ThickPolylines& polylines, ExtrusionRole role, Flow flow, std::vector<ExtrusionEntity*> &out)
{
// This value determines granularity of adaptive width, as G-code does not allow
// variable extrusion within a single move; this value shall only affect the amount
// of segments, and any pruning shall be performed before we apply this tolerance.
ExtrusionEntityCollection coll;
const float tolerance = float(scale_(0.05));
for (const ThickPolyline &p : polylines) {
ExtrusionPaths paths = thick_polyline_to_extrusion_paths(p, role, flow, tolerance);
// Append paths to collection.
if (! paths.empty()) {
if (paths.front().first_point() == paths.back().last_point())
coll.append(ExtrusionLoop(std::move(paths)));
else
coll.append(std::move(paths));
out.emplace_back(new ExtrusionLoop(std::move(paths)));
else {
for (ExtrusionPath &path : paths)
out.emplace_back(new ExtrusionPath(std::move(path)));
}
}
}
return coll;
}
// Hierarchy of perimeters.
@ -173,10 +175,9 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime
perimeter_generator.overhang_flow.width,
perimeter_generator.overhang_flow.height);
// reapply the nearest point search for starting point
// We allow polyline reversal because Clipper may have randomly
// reversed polylines during clipping.
paths = (ExtrusionPaths)ExtrusionEntityCollection(paths).chained_path();
// Reapply the nearest point search for starting point.
// We allow polyline reversal because Clipper may have randomly reversed polylines during clipping.
chain_and_reorder_extrusion_paths(paths, &paths.front().first_point());
} else {
ExtrusionPath path(role);
path.polyline = loop.polygon.split_at_first_point();
@ -186,43 +187,47 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime
paths.push_back(path);
}
coll.append(ExtrusionLoop(paths, loop_role));
coll.append(ExtrusionLoop(std::move(paths), loop_role));
}
// Append thin walls to the nearest-neighbor search (only for first iteration)
if (! thin_walls.empty()) {
ExtrusionEntityCollection tw = variable_width(thin_walls, erExternalPerimeter, perimeter_generator.ext_perimeter_flow);
coll.append(tw.entities);
variable_width(thin_walls, erExternalPerimeter, perimeter_generator.ext_perimeter_flow, coll.entities);
thin_walls.clear();
}
// Sort entities into a new collection using a nearest-neighbor search,
// preserving the original indices which are useful for detecting thin walls.
ExtrusionEntityCollection sorted_coll;
coll.chained_path(&sorted_coll, false, erMixed, &sorted_coll.orig_indices);
// traverse children and build the final collection
ExtrusionEntityCollection entities;
for (const size_t &idx : sorted_coll.orig_indices) {
if (idx >= loops.size()) {
// This is a thin wall. Let's get it from the sorted collection as it might have been reversed.
entities.append(std::move(*sorted_coll.entities[&idx - &sorted_coll.orig_indices.front()]));
// Traverse children and build the final collection.
Point zero_point(0, 0);
std::vector<std::pair<size_t, bool>> chain = chain_extrusion_entities(coll.entities, &zero_point);
ExtrusionEntityCollection out;
for (const std::pair<size_t, bool> &idx : chain) {
assert(coll.entities[idx.first] != nullptr);
if (idx.first >= loops.size()) {
// This is a thin wall.
out.entities.reserve(out.entities.size() + 1);
out.entities.emplace_back(coll.entities[idx.first]);
coll.entities[idx.first] = nullptr;
if (idx.second)
out.entities.back()->reverse();
} else {
const PerimeterGeneratorLoop &loop = loops[idx];
ExtrusionLoop eloop = *dynamic_cast<ExtrusionLoop*>(coll.entities[idx]);
const PerimeterGeneratorLoop &loop = loops[idx.first];
assert(thin_walls.empty());
ExtrusionEntityCollection children = traverse_loops(perimeter_generator, loop.children, thin_walls);
out.entities.reserve(out.entities.size() + children.entities.size() + 1);
ExtrusionLoop *eloop = static_cast<ExtrusionLoop*>(coll.entities[idx.first]);
coll.entities[idx.first] = nullptr;
if (loop.is_contour) {
eloop.make_counter_clockwise();
entities.append(std::move(children.entities));
entities.append(std::move(eloop));
eloop->make_counter_clockwise();
out.append(std::move(children.entities));
out.entities.emplace_back(eloop);
} else {
eloop.make_clockwise();
entities.append(std::move(eloop));
entities.append(std::move(children.entities));
eloop->make_clockwise();
out.entities.emplace_back(eloop);
out.append(std::move(children.entities));
}
}
}
return entities;
return out;
}
void PerimeterGenerator::process()
@ -445,8 +450,8 @@ void PerimeterGenerator::process()
for (const ExPolygon &ex : gaps_ex)
ex.medial_axis(max, min, &polylines);
if (! polylines.empty()) {
ExtrusionEntityCollection gap_fill = variable_width(polylines, erGapFill, this->solid_infill_flow);
this->gap_fill->append(gap_fill.entities);
ExtrusionEntityCollection gap_fill;
variable_width(polylines, erGapFill, this->solid_infill_flow, gap_fill.entities);
/* Make sure we don't infill narrow parts that are already gap-filled
(we only consider this surface's gaps to reduce the diff() complexity).
Growing actual extrusions ensures that gaps not filled by medial axis
@ -456,7 +461,8 @@ void PerimeterGenerator::process()
//FIXME Vojtech: This grows by a rounded extrusion width, not by line spacing,
// therefore it may cover the area, but no the volume.
last = diff_ex(to_polygons(last), gap_fill.polygons_covered_by_width(10.f));
}
this->gap_fill->append(std::move(gap_fill.entities));
}
}
// create one more offset to be used as boundary for fill

View file

@ -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),

View file

@ -38,6 +38,7 @@ typedef std::vector<Point*> PointPtrs;
typedef std::vector<const Point*> PointConstPtrs;
typedef std::vector<Vec3crd> Points3;
typedef std::vector<Vec2d> Pointfs;
typedef std::vector<Vec2d> Vec2ds;
typedef std::vector<Vec3d> Pointf3s;
typedef Eigen::Matrix<float, 2, 2, Eigen::DontAlign> Matrix2f;
@ -87,12 +88,13 @@ class Point : public Vec2crd
public:
typedef coord_t coord_type;
Point() : Vec2crd() { (*this)(0) = 0; (*this)(1) = 0; }
Point(coord_t x, coord_t y) { (*this)(0) = x; (*this)(1) = y; }
Point(int64_t x, int64_t y) { (*this)(0) = coord_t(x); (*this)(1) = coord_t(y); } // for Clipper
Point(double x, double y) { (*this)(0) = coord_t(lrint(x)); (*this)(1) = coord_t(lrint(y)); }
Point() : Vec2crd(0, 0) {}
Point(coord_t x, coord_t y) : Vec2crd(x, y) {}
Point(int64_t x, int64_t y) : Vec2crd(coord_t(x), coord_t(y)) {} // for Clipper
Point(double x, double y) : Vec2crd(coord_t(lrint(x)), coord_t(lrint(y))) {}
Point(const Point &rhs) { *this = rhs; }
// This constructor allows you to construct Point from Eigen expressions
explicit Point(const Vec2d& rhs) : Vec2crd(coord_t(lrint(rhs.x())), coord_t(lrint(rhs.y()))) {}
// This constructor allows you to construct Point from Eigen expressions
template<typename OtherDerived>
Point(const Eigen::MatrixBase<OtherDerived> &other) : Vec2crd(other) {}
static Point new_scale(coordf_t x, coordf_t y) { return Point(coord_t(scale_(x)), coord_t(scale_(y))); }
@ -126,6 +128,36 @@ public:
Point projection_onto(const Line &line) const;
};
inline bool is_approx(const Point &p1, const Point &p2, coord_t epsilon = coord_t(SCALED_EPSILON))
{
Point d = (p2 - p1).cwiseAbs();
return d.x() < epsilon && d.y() < epsilon;
}
inline bool is_approx(const Vec2f &p1, const Vec2f &p2, float epsilon = float(EPSILON))
{
Vec2f d = (p2 - p1).cwiseAbs();
return d.x() < epsilon && d.y() < epsilon;
}
inline bool is_approx(const Vec2d &p1, const Vec2d &p2, double epsilon = EPSILON)
{
Vec2d d = (p2 - p1).cwiseAbs();
return d.x() < epsilon && d.y() < epsilon;
}
inline bool is_approx(const Vec3f &p1, const Vec3f &p2, float epsilon = float(EPSILON))
{
Vec3f d = (p2 - p1).cwiseAbs();
return d.x() < epsilon && d.y() < epsilon && d.z() < epsilon;
}
inline bool is_approx(const Vec3d &p1, const Vec3d &p2, double epsilon = EPSILON)
{
Vec3d d = (p2 - p1).cwiseAbs();
return d.x() < epsilon && d.y() < epsilon && d.z() < epsilon;
}
namespace int128 {
// Exact orientation predicate,
// returns +1: CCW, 0: collinear, -1: CW.

View file

@ -5,55 +5,23 @@
namespace Slic3r {
Polygon::operator Polygons() const
{
Polygons pp;
pp.push_back(*this);
return pp;
}
Polygon::operator Polyline() const
{
return this->split_at_first_point();
}
Point&
Polygon::operator[](Points::size_type idx)
{
return this->points[idx];
}
const Point&
Polygon::operator[](Points::size_type idx) const
{
return this->points[idx];
}
Point
Polygon::last_point() const
{
return this->points.front(); // last point == first point for polygons
}
Lines Polygon::lines() const
{
return to_lines(*this);
}
Polyline
Polygon::split_at_vertex(const Point &point) const
Polyline Polygon::split_at_vertex(const Point &point) const
{
// find index of point
for (const Point &pt : this->points)
if (pt == point)
return this->split_at_index(&pt - &this->points.front());
return this->split_at_index(int(&pt - &this->points.front()));
throw std::invalid_argument("Point not found");
return Polyline();
}
// Split a closed polygon into an open polyline, with the split point duplicated at both ends.
Polyline
Polygon::split_at_index(int index) const
Polyline Polygon::split_at_index(int index) const
{
Polyline polyline;
polyline.points.reserve(this->points.size() + 1);
@ -64,19 +32,6 @@ Polygon::split_at_index(int index) const
return polyline;
}
// Split a closed polygon into an open polyline, with the split point duplicated at both ends.
Polyline
Polygon::split_at_first_point() const
{
return this->split_at_index(0);
}
Points
Polygon::equally_spaced_points(double distance) const
{
return this->split_at_first_point().equally_spaced_points(distance);
}
/*
int64_t Polygon::area2x() const
{
@ -107,20 +62,17 @@ double Polygon::area() const
return 0.5 * a;
}
bool
Polygon::is_counter_clockwise() const
bool Polygon::is_counter_clockwise() const
{
return ClipperLib::Orientation(Slic3rMultiPoint_to_ClipperPath(*this));
}
bool
Polygon::is_clockwise() const
bool Polygon::is_clockwise() const
{
return !this->is_counter_clockwise();
}
bool
Polygon::make_counter_clockwise()
bool Polygon::make_counter_clockwise()
{
if (!this->is_counter_clockwise()) {
this->reverse();
@ -129,8 +81,7 @@ Polygon::make_counter_clockwise()
return false;
}
bool
Polygon::make_clockwise()
bool Polygon::make_clockwise()
{
if (this->is_counter_clockwise()) {
this->reverse();
@ -139,16 +90,9 @@ Polygon::make_clockwise()
return false;
}
bool
Polygon::is_valid() const
{
return this->points.size() >= 3;
}
// Does an unoriented polygon contain a point?
// Tested by counting intersections along a horizontal line.
bool
Polygon::contains(const Point &point) const
bool Polygon::contains(const Point &point) const
{
// http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
bool result = false;
@ -174,8 +118,7 @@ Polygon::contains(const Point &point) const
}
// this only works on CCW polygons as CW will be ripped out by Clipper's simplify_polygons()
Polygons
Polygon::simplify(double tolerance) const
Polygons Polygon::simplify(double tolerance) const
{
// repeat first point at the end in order to apply Douglas-Peucker
// on the whole polygon
@ -189,8 +132,7 @@ Polygon::simplify(double tolerance) const
return simplify_polygons(pp);
}
void
Polygon::simplify(double tolerance, Polygons &polygons) const
void Polygon::simplify(double tolerance, Polygons &polygons) const
{
Polygons pp = this->simplify(tolerance);
polygons.reserve(polygons.size() + pp.size());
@ -198,8 +140,7 @@ Polygon::simplify(double tolerance, Polygons &polygons) const
}
// Only call this on convex polygons or it will return invalid results
void
Polygon::triangulate_convex(Polygons* polygons) const
void Polygon::triangulate_convex(Polygons* polygons) const
{
for (Points::const_iterator it = this->points.begin() + 2; it != this->points.end(); ++it) {
Polygon p;
@ -214,8 +155,7 @@ Polygon::triangulate_convex(Polygons* polygons) const
}
// center of mass
Point
Polygon::centroid() const
Point Polygon::centroid() const
{
double area_temp = this->area();
double x_temp = 0;
@ -232,20 +172,19 @@ Polygon::centroid() const
// find all concave vertices (i.e. having an internal angle greater than the supplied angle)
// (external = right side, thus we consider ccw orientation)
Points
Polygon::concave_points(double angle) const
Points Polygon::concave_points(double angle) const
{
Points points;
angle = 2*PI - angle;
angle = 2. * PI - angle + EPSILON;
// check whether first point forms a concave angle
if (this->points.front().ccw_angle(this->points.back(), *(this->points.begin()+1)) <= angle)
points.push_back(this->points.front());
// check whether points 1..(n-1) form concave angles
for (Points::const_iterator p = this->points.begin()+1; p != this->points.end()-1; ++p) {
if (p->ccw_angle(*(p-1), *(p+1)) <= angle) points.push_back(*p);
}
for (Points::const_iterator p = this->points.begin()+1; p != this->points.end()-1; ++ p)
if (p->ccw_angle(*(p-1), *(p+1)) <= angle)
points.push_back(*p);
// check whether last point forms a concave angle
if (this->points.back().ccw_angle(*(this->points.end()-2), this->points.front()) <= angle)
@ -256,11 +195,10 @@ Polygon::concave_points(double angle) const
// find all convex vertices (i.e. having an internal angle smaller than the supplied angle)
// (external = right side, thus we consider ccw orientation)
Points
Polygon::convex_points(double angle) const
Points Polygon::convex_points(double angle) const
{
Points points;
angle = 2*PI - angle;
angle = 2*PI - angle - EPSILON;
// check whether first point forms a convex angle
if (this->points.front().ccw_angle(this->points.back(), *(this->points.begin()+1)) >= angle)
@ -316,6 +254,11 @@ Point Polygon::point_projection(const Point &point) const
return proj;
}
BoundingBox get_extents(const Points &points)
{
return BoundingBox(points);
}
BoundingBox get_extents(const Polygon &poly)
{
return poly.bounding_box();
@ -456,4 +399,45 @@ bool remove_small(Polygons &polys, double min_area)
return modified;
}
void remove_collinear(Polygon &poly)
{
if (poly.points.size() > 2) {
// copy points and append both 1 and last point in place to cover the boundaries
Points pp;
pp.reserve(poly.points.size()+2);
pp.push_back(poly.points.back());
pp.insert(pp.begin()+1, poly.points.begin(), poly.points.end());
pp.push_back(poly.points.front());
// delete old points vector. Will be re-filled in the loop
poly.points.clear();
size_t i = 0;
size_t k = 0;
while (i < pp.size()-2) {
k = i+1;
const Point &p1 = pp[i];
while (k < pp.size()-1) {
const Point &p2 = pp[k];
const Point &p3 = pp[k+1];
Line l(p1, p3);
if(l.distance_to(p2) < SCALED_EPSILON) {
k++;
} else {
if(i > 0) poly.points.push_back(p1); // implicitly removes the first point we appended above
i = k;
break;
}
}
if(k > pp.size()-2) break; // all remaining points are collinear and can be skipped
}
poly.points.push_back(pp[i]);
}
}
void remove_collinear(Polygons &polys)
{
for (Polygon &poly : polys)
remove_collinear(poly);
}
}

View file

@ -13,15 +13,17 @@ namespace Slic3r {
class Polygon;
typedef std::vector<Polygon> Polygons;
class Polygon : public MultiPoint {
class Polygon : public MultiPoint
{
public:
operator Polygons() const;
operator Polyline() const;
Point& operator[](Points::size_type idx);
const Point& operator[](Points::size_type idx) const;
operator Polygons() const { Polygons pp; pp.push_back(*this); return pp; }
operator Polyline() const { return this->split_at_first_point(); }
Point& operator[](Points::size_type idx) { return this->points[idx]; }
const Point& operator[](Points::size_type idx) const { return this->points[idx]; }
Polygon() {}
explicit Polygon(const Points &points): MultiPoint(points) {}
explicit Polygon(const Points &points) : MultiPoint(points) {}
Polygon(std::initializer_list<Point> points) : MultiPoint(points) {}
Polygon(const Polygon &other) : MultiPoint(other.points) {}
Polygon(Polygon &&other) : MultiPoint(std::move(other.points)) {}
static Polygon new_scale(const std::vector<Vec2d> &points) {
@ -34,20 +36,24 @@ public:
Polygon& operator=(const Polygon &other) { points = other.points; return *this; }
Polygon& operator=(Polygon &&other) { points = std::move(other.points); return *this; }
Point last_point() const;
// last point == first point for polygons
const Point& last_point() const override { return this->points.front(); }
virtual Lines lines() const;
Polyline split_at_vertex(const Point &point) const;
// Split a closed polygon into an open polyline, with the split point duplicated at both ends.
Polyline split_at_index(int index) const;
// Split a closed polygon into an open polyline, with the split point duplicated at both ends.
Polyline split_at_first_point() const;
Points equally_spaced_points(double distance) const;
Polyline split_at_first_point() const { return this->split_at_index(0); }
Points equally_spaced_points(double distance) const { return this->split_at_first_point().equally_spaced_points(distance); }
double area() const;
bool is_counter_clockwise() const;
bool is_clockwise() const;
bool make_counter_clockwise();
bool make_clockwise();
bool is_valid() const;
bool is_valid() const { return this->points.size() >= 3; }
// Does an unoriented polygon contain a point?
// Tested by counting intersections along a horizontal line.
bool contains(const Point &point) const;
@ -61,6 +67,10 @@ public:
Point point_projection(const Point &point) const;
};
inline bool operator==(const Polygon &lhs, const Polygon &rhs) { return lhs.points == rhs.points; }
inline bool operator!=(const Polygon &lhs, const Polygon &rhs) { return lhs.points != rhs.points; }
extern BoundingBox get_extents(const Points &points);
extern BoundingBox get_extents(const Polygon &poly);
extern BoundingBox get_extents(const Polygons &polygons);
extern BoundingBox get_extents_rotated(const Polygon &poly, double angle);
@ -81,6 +91,8 @@ extern bool remove_sticks(Polygons &polys);
// Remove polygons with less than 3 edges.
extern bool remove_degenerate(Polygons &polys);
extern bool remove_small(Polygons &polys, double min_area);
extern void remove_collinear(Polygon &poly);
extern void remove_collinear(Polygons &polys);
// Append a vector of polygons at the end of another vector of polygons.
inline void polygons_append(Polygons &dst, const Polygons &src) { dst.insert(dst.end(), src.begin(), src.end()); }
@ -95,6 +107,15 @@ inline void polygons_append(Polygons &dst, Polygons &&src)
}
}
inline Polygons polygons_simplify(const Polygons &polys, double tolerance)
{
Polygons out;
out.reserve(polys.size());
for (const Polygon &p : polys)
polygons_append(out, p.simplify(tolerance));
return out;
}
inline void polygons_rotate(Polygons &polys, double angle)
{
const double cos_angle = cos(angle);

View file

@ -12,12 +12,11 @@ TrimmedLoop trim_loop(const Polygon &loop, const EdgeGrid::Grid &grid)
TrimmedLoop out;
if (loop.size() >= 2) {
size_t cnt = loop.points.size();
struct Visitor {
Visitor(const EdgeGrid::Grid &grid, const Slic3r::Point *pt_prev, const Slic3r::Point *pt_this) : grid(grid), pt_prev(pt_prev), pt_this(pt_this) {}
void operator()(coord_t iy, coord_t ix) {
bool operator()(coord_t iy, coord_t ix) {
// Called with a row and colum of the grid cell, which is intersected by a line.
auto cell_data_range = grid.cell_data_range(iy, ix);
for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++ it_contour_and_segment) {
@ -27,6 +26,8 @@ TrimmedLoop trim_loop(const Polygon &loop, const EdgeGrid::Grid &grid)
// The two segments intersect. Add them to the output.
}
}
// Continue traversing the grid along the edge.
return true;
}
const EdgeGrid::Grid &grid;

View file

@ -23,24 +23,17 @@ Polyline::operator Line() const
return Line(this->points.front(), this->points.back());
}
Point
Polyline::last_point() const
const Point& Polyline::leftmost_point() const
{
return this->points.back();
}
Point
Polyline::leftmost_point() const
{
Point p = this->points.front();
for (Points::const_iterator it = this->points.begin() + 1; it != this->points.end(); ++it) {
if ((*it)(0) < p(0)) p = *it;
const Point *p = &this->points.front();
for (Points::const_iterator it = this->points.begin() + 1; it != this->points.end(); ++ it) {
if (it->x() < p->x())
p = &(*it);
}
return p;
return *p;
}
Lines
Polyline::lines() const
Lines Polyline::lines() const
{
Lines lines;
if (this->points.size() >= 2) {
@ -211,6 +204,20 @@ BoundingBox get_extents(const Polylines &polylines)
return bb;
}
const Point& leftmost_point(const Polylines &polylines)
{
if (polylines.empty())
throw std::invalid_argument("leftmost_point() called on empty PolylineCollection");
Polylines::const_iterator it = polylines.begin();
const Point *p = &it->leftmost_point();
for (++ it; it != polylines.end(); ++it) {
const Point *p2 = &it->leftmost_point();
if (p2->x() < p->x())
p = p2;
}
return *p;
}
bool remove_degenerate(Polylines &polylines)
{
bool modified = false;

View file

@ -62,8 +62,9 @@ public:
operator Polylines() const;
operator Line() const;
Point last_point() const;
Point leftmost_point() const;
const Point& last_point() const override { return this->points.back(); }
const Point& leftmost_point() const;
virtual Lines lines() const;
void clip_end(double distance);
void clip_start(double distance);
@ -76,6 +77,15 @@ public:
bool is_straight() const;
};
// Don't use this class in production code, it is used exclusively by the Perl binding for unit tests!
#ifdef PERL_UCHAR_MIN
class PolylineCollection
{
public:
Polylines polylines;
};
#endif /* PERL_UCHAR_MIN */
extern BoundingBox get_extents(const Polyline &polyline);
extern BoundingBox get_extents(const Polylines &polylines);
@ -128,6 +138,8 @@ inline void polylines_append(Polylines &dst, Polylines &&src)
}
}
const Point& leftmost_point(const Polylines &polylines);
bool remove_degenerate(Polylines &polylines);
class ThickPolyline : public Polyline {

View file

@ -1,92 +0,0 @@
#include "PolylineCollection.hpp"
namespace Slic3r {
struct Chaining
{
Point first;
Point last;
size_t idx;
};
template<typename T>
inline int nearest_point_index(const std::vector<Chaining> &pairs, const Point &start_near, bool no_reverse)
{
T dmin = std::numeric_limits<T>::max();
int idx = 0;
for (std::vector<Chaining>::const_iterator it = pairs.begin(); it != pairs.end(); ++it) {
T d = sqr(T(start_near(0) - it->first(0)));
if (d <= dmin) {
d += sqr(T(start_near(1) - it->first(1)));
if (d < dmin) {
idx = (it - pairs.begin()) * 2;
dmin = d;
if (dmin < EPSILON)
break;
}
}
if (! no_reverse) {
d = sqr(T(start_near(0) - it->last(0)));
if (d <= dmin) {
d += sqr(T(start_near(1) - it->last(1)));
if (d < dmin) {
idx = (it - pairs.begin()) * 2 + 1;
dmin = d;
if (dmin < EPSILON)
break;
}
}
}
}
return idx;
}
Polylines PolylineCollection::_chained_path_from(
const Polylines &src,
Point start_near,
bool no_reverse,
bool move_from_src)
{
std::vector<Chaining> endpoints;
endpoints.reserve(src.size());
for (size_t i = 0; i < src.size(); ++ i) {
Chaining c;
c.first = src[i].first_point();
if (! no_reverse)
c.last = src[i].last_point();
c.idx = i;
endpoints.push_back(c);
}
Polylines retval;
while (! endpoints.empty()) {
// find nearest point
int endpoint_index = nearest_point_index<double>(endpoints, start_near, no_reverse);
assert(endpoint_index >= 0 && size_t(endpoint_index) < endpoints.size() * 2);
if (move_from_src) {
retval.push_back(std::move(src[endpoints[endpoint_index/2].idx]));
} else {
retval.push_back(src[endpoints[endpoint_index/2].idx]);
}
if (endpoint_index & 1)
retval.back().reverse();
endpoints.erase(endpoints.begin() + endpoint_index/2);
start_near = retval.back().last_point();
}
return retval;
}
Point PolylineCollection::leftmost_point(const Polylines &polylines)
{
if (polylines.empty())
throw std::invalid_argument("leftmost_point() called on empty PolylineCollection");
Polylines::const_iterator it = polylines.begin();
Point p = it->leftmost_point();
for (++ it; it != polylines.end(); ++it) {
Point p2 = it->leftmost_point();
if (p2(0) < p(0))
p = p2;
}
return p;
}
} // namespace Slic3r

View file

@ -1,47 +0,0 @@
#ifndef slic3r_PolylineCollection_hpp_
#define slic3r_PolylineCollection_hpp_
#include "libslic3r.h"
#include "Polyline.hpp"
namespace Slic3r {
class PolylineCollection
{
static Polylines _chained_path_from(
const Polylines &src,
Point start_near,
bool no_reverse,
bool move_from_src);
public:
Polylines polylines;
void chained_path(PolylineCollection* retval, bool no_reverse = false) const
{ retval->polylines = chained_path(this->polylines, no_reverse); }
void chained_path_from(Point start_near, PolylineCollection* retval, bool no_reverse = false) const
{ retval->polylines = chained_path_from(this->polylines, start_near, no_reverse); }
Point leftmost_point() const
{ return leftmost_point(polylines); }
void append(const Polylines &polylines)
{ this->polylines.insert(this->polylines.end(), polylines.begin(), polylines.end()); }
static Point leftmost_point(const Polylines &polylines);
static Polylines chained_path(Polylines &&src, bool no_reverse = false) {
return (src.empty() || src.front().points.empty()) ?
Polylines() :
_chained_path_from(src, src.front().first_point(), no_reverse, true);
}
static Polylines chained_path_from(Polylines &&src, Point start_near, bool no_reverse = false)
{ return _chained_path_from(src, start_near, no_reverse, true); }
static Polylines chained_path(const Polylines &src, bool no_reverse = false) {
return (src.empty() || src.front().points.empty()) ?
Polylines() :
_chained_path_from(src, src.front().first_point(), no_reverse, false);
}
static Polylines chained_path_from(const Polylines &src, Point start_near, bool no_reverse = false)
{ return _chained_path_from(src, start_near, no_reverse, false); }
};
}
#endif

View file

@ -7,6 +7,7 @@
#include "Flow.hpp"
#include "Geometry.hpp"
#include "I18N.hpp"
#include "ShortestPath.hpp"
#include "SupportMaterial.hpp"
#include "GCode.hpp"
#include "GCode/WipeTower.hpp"
@ -142,10 +143,7 @@ bool Print::invalidate_state_by_config_options(const std::vector<t_config_option
"use_relative_e_distances",
"use_volumetric_e",
"variable_layer_height",
"wipe",
"wipe_tower_x",
"wipe_tower_y",
"wipe_tower_rotation_angle"
"wipe"
};
static std::unordered_set<std::string> steps_ignore;
@ -166,7 +164,10 @@ bool Print::invalidate_state_by_config_options(const std::vector<t_config_option
|| opt_key == "skirt_height"
|| opt_key == "skirt_distance"
|| opt_key == "min_skirt_length"
|| opt_key == "ooze_prevention") {
|| opt_key == "ooze_prevention"
|| opt_key == "wipe_tower_x"
|| opt_key == "wipe_tower_y"
|| opt_key == "wipe_tower_rotation_angle") {
steps.emplace_back(psSkirt);
} else if (opt_key == "brim_width") {
steps.emplace_back(psBrim);
@ -207,6 +208,7 @@ bool Print::invalidate_state_by_config_options(const std::vector<t_config_option
|| opt_key == "extra_loading_move"
|| opt_key == "z_offset") {
steps.emplace_back(psWipeTower);
steps.emplace_back(psSkirt);
} else if (
opt_key == "first_layer_extrusion_width"
|| opt_key == "min_layer_height"
@ -327,17 +329,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.;
@ -1113,6 +1104,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.
{
@ -1193,6 +1187,8 @@ std::string Print::validate() const
return L("The Wipe Tower is currently only supported with the relative extruder addressing (use_relative_e_distances=1).");
if (m_config.ooze_prevention)
return L("Ooze prevention is currently not supported with the wipe tower enabled.");
if (m_config.use_volumetric_e)
return L("The Wipe Tower currently does not support volumetric E (use_volumetric_e=0).");
if (m_objects.size() > 1) {
bool has_custom_layering = false;
@ -1270,10 +1266,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();
@ -1512,6 +1505,14 @@ void Print::process()
obj->infill();
for (PrintObject *obj : m_objects)
obj->generate_support_material();
if (this->set_started(psWipeTower)) {
m_wipe_tower_data.clear();
if (this->has_wipe_tower()) {
//this->set_status(95, L("Generating wipe tower"));
this->_make_wipe_tower();
}
this->set_done(psWipeTower);
}
if (this->set_started(psSkirt)) {
m_skirt.clear();
if (this->has_skirt()) {
@ -1528,14 +1529,6 @@ void Print::process()
}
this->set_done(psBrim);
}
if (this->set_started(psWipeTower)) {
m_wipe_tower_data.clear();
if (this->has_wipe_tower()) {
//this->set_status(95, L("Generating wipe tower"));
this->_make_wipe_tower();
}
this->set_done(psWipeTower);
}
BOOST_LOG_TRIVIAL(info) << "Slicing process finished." << log_memory_info();
}
@ -1543,7 +1536,11 @@ void Print::process()
// The export_gcode may die for various reasons (fails to process output_filename_format,
// write error into the G-code, cannot execute post-processing scripts).
// It is up to the caller to show an error message.
#if ENABLE_THUMBNAIL_GENERATOR
std::string Print::export_gcode(const std::string& path_template, GCodePreviewData* preview_data, const std::vector<ThumbnailData>* thumbnail_data)
#else
std::string Print::export_gcode(const std::string &path_template, GCodePreviewData *preview_data)
#endif // ENABLE_THUMBNAIL_GENERATOR
{
// output everything to a G-code file
// The following call may die if the output_filename_format template substitution fails.
@ -1560,7 +1557,11 @@ std::string Print::export_gcode(const std::string &path_template, GCodePreviewDa
// The following line may die for multiple reasons.
GCode gcode;
#if ENABLE_THUMBNAIL_GENERATOR
gcode.do_export(this, path.c_str(), preview_data, thumbnail_data);
#else
gcode.do_export(this, path.c_str(), preview_data);
#endif // ENABLE_THUMBNAIL_GENERATOR
return path.c_str();
}
@ -1592,7 +1593,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);
}
@ -1612,6 +1613,17 @@ void Print::_make_skirt()
}
}
// Include the wipe tower.
if (has_wipe_tower() && ! m_wipe_tower_data.tool_changes.empty()) {
double width = m_config.wipe_tower_width + 2*m_wipe_tower_data.brim_width;
double depth = m_wipe_tower_data.depth + 2*m_wipe_tower_data.brim_width;
Vec2d pt = Vec2d(m_config.wipe_tower_x-m_wipe_tower_data.brim_width, m_config.wipe_tower_y-m_wipe_tower_data.brim_width);
points.push_back(Point(scale_(pt.x()), scale_(pt.y())));
points.push_back(Point(scale_(pt.x()+width), scale_(pt.y())));
points.push_back(Point(scale_(pt.x()+width), scale_(pt.y()+depth)));
points.push_back(Point(scale_(pt.x()), scale_(pt.y()+depth)));
}
if (points.size() < 3)
// At least three points required for a convex hull.
return;
@ -1703,7 +1715,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));
@ -1824,8 +1836,8 @@ void Print::_make_brim()
[](const std::pair<const ClipperLib_Z::Path*, size_t> &l, const std::pair<const ClipperLib_Z::Path*, size_t> &r) {
return l.second < r.second;
});
Vec3f last_pt(0.f, 0.f, 0.f);
Point last_pt(0, 0);
for (size_t i = 0; i < loops_trimmed_order.size();) {
// Find all pieces that the initial loop was split into.
size_t j = i + 1;
@ -1841,16 +1853,23 @@ void Print::_make_brim()
points.emplace_back(coord_t(pt.X), coord_t(pt.Y));
i = j;
} else {
//FIXME this is not optimal as the G-code generator will follow the sequence of paths verbatim without respect to minimum travel distance.
//FIXME The path chaining here may not be optimal.
ExtrusionEntityCollection this_loop_trimmed;
this_loop_trimmed.entities.reserve(j - i);
for (; i < j; ++ i) {
m_brim.entities.emplace_back(new ExtrusionPath(erSkirt, float(flow.mm3_per_mm()), float(flow.width), float(this->skirt_first_layer_height())));
this_loop_trimmed.entities.emplace_back(new ExtrusionPath(erSkirt, float(flow.mm3_per_mm()), float(flow.width), float(this->skirt_first_layer_height())));
const ClipperLib_Z::Path &path = *loops_trimmed_order[i].first;
Points &points = static_cast<ExtrusionPath*>(m_brim.entities.back())->polyline.points;
Points &points = static_cast<ExtrusionPath*>(this_loop_trimmed.entities.back())->polyline.points;
points.reserve(path.size());
for (const ClipperLib_Z::IntPoint &pt : path)
points.emplace_back(coord_t(pt.X), coord_t(pt.Y));
}
chain_and_reorder_extrusion_entities(this_loop_trimmed.entities, &last_pt);
m_brim.entities.reserve(m_brim.entities.size() + this_loop_trimmed.entities.size());
append(m_brim.entities, std::move(this_loop_trimmed.entities));
this_loop_trimmed.entities.clear();
}
last_pt = m_brim.last_point();
}
}
} else {
@ -1867,6 +1886,22 @@ bool Print::has_wipe_tower() const
m_config.nozzle_diameter.values.size() > 1;
}
const WipeTowerData& Print::wipe_tower_data(size_t extruders_cnt, double first_layer_height, double nozzle_diameter) const
{
// If the wipe tower wasn't created yet, make sure the depth and brim_width members are set to default.
if (! is_step_done(psWipeTower) && extruders_cnt !=0) {
float width = m_config.wipe_tower_width;
float brim_spacing = nozzle_diameter * 1.25f - first_layer_height * (1. - M_PI_4);
const_cast<Print*>(this)->m_wipe_tower_data.depth = (900.f/width) * float(extruders_cnt - 1);
const_cast<Print*>(this)->m_wipe_tower_data.brim_width = 4.5f * brim_spacing;
}
return m_wipe_tower_data;
}
void Print::_make_wipe_tower()
{
m_wipe_tower_data.clear();
@ -1975,6 +2010,7 @@ void Print::_make_wipe_tower()
m_wipe_tower_data.tool_changes.reserve(m_wipe_tower_data.tool_ordering.layer_tools().size());
wipe_tower.generate(m_wipe_tower_data.tool_changes);
m_wipe_tower_data.depth = wipe_tower.get_depth();
m_wipe_tower_data.brim_width = wipe_tower.get_brim_width();
// Unload the current filament over the purge tower.
coordf_t layer_height = m_objects.front()->config().layer_height.value;
@ -2028,6 +2064,7 @@ DynamicConfig PrintStatistics::config() const
config.set_key_value("used_filament", new ConfigOptionFloat (this->total_used_filament / 1000.));
config.set_key_value("extruded_volume", new ConfigOptionFloat (this->total_extruded_volume));
config.set_key_value("total_cost", new ConfigOptionFloat (this->total_cost));
config.set_key_value("total_toolchanges", new ConfigOptionInt(this->total_toolchanges));
config.set_key_value("total_weight", new ConfigOptionFloat (this->total_weight));
config.set_key_value("total_wipe_tower_cost", new ConfigOptionFloat (this->total_wipe_tower_cost));
config.set_key_value("total_wipe_tower_filament", new ConfigOptionFloat (this->total_wipe_tower_filament));
@ -2040,7 +2077,7 @@ DynamicConfig PrintStatistics::placeholders()
for (const std::string &key : {
"print_time", "normal_print_time", "silent_print_time",
"used_filament", "extruded_volume", "total_cost", "total_weight",
"total_wipe_tower_cost", "total_wipe_tower_filament"})
"total_toolchanges", "total_wipe_tower_cost", "total_wipe_tower_filament"})
config.set_key_value(key, new ConfigOptionString(std::string("{") + key + "}"));
return config;
}

View file

@ -19,6 +19,9 @@ class PrintObject;
class ModelObject;
class GCode;
class GCodePreviewData;
#if ENABLE_THUMBNAIL_GENERATOR
struct ThumbnailData;
#endif // ENABLE_THUMBNAIL_GENERATOR
// Print step IDs for keeping track of the print state.
enum PrintStep {
@ -96,6 +99,7 @@ public:
const SupportLayerPtrs& support_layers() const { return m_support_layers; }
const Transform3d& trafo() const { return m_trafo; }
const Points& copies() const { return m_copies; }
const Point copy_center(size_t idx) const { return m_copies[idx] + m_copies_shift + Point(this->size.x() / 2, this->size.y() / 2); }
// since the object is aligned to origin, bounding box coincides with size
BoundingBox bounding_box() const { return BoundingBox(Point(0,0), to_2d(this->size)); }
@ -179,7 +183,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();
@ -226,6 +229,7 @@ struct WipeTowerData
// Depth of the wipe tower to pass to GLCanvas3D for exact bounding box:
float depth;
float brim_width;
void clear() {
tool_ordering.clear();
@ -235,6 +239,7 @@ struct WipeTowerData
used_filament.clear();
number_of_toolchanges = -1;
depth = 0.f;
brim_width = 0.f;
}
};
@ -248,6 +253,7 @@ struct PrintStatistics
double total_used_filament;
double total_extruded_volume;
double total_cost;
int total_toolchanges;
double total_weight;
double total_wipe_tower_cost;
double total_wipe_tower_filament;
@ -268,6 +274,7 @@ struct PrintStatistics
total_used_filament = 0.;
total_extruded_volume = 0.;
total_cost = 0.;
total_toolchanges = 0;
total_weight = 0.;
total_wipe_tower_cost = 0.;
total_wipe_tower_filament = 0.;
@ -303,7 +310,11 @@ public:
void process() override;
// Exports G-code into a file name based on the path_template, returns the file path of the generated G-code file.
// If preview_data is not null, the preview_data is filled in for the G-code visualization (not used by the command line Slic3r).
#if ENABLE_THUMBNAIL_GENERATOR
std::string export_gcode(const std::string& path_template, GCodePreviewData* preview_data, const std::vector<ThumbnailData>* thumbnail_data = nullptr);
#else
std::string export_gcode(const std::string &path_template, GCodePreviewData *preview_data);
#endif // ENABLE_THUMBNAIL_GENERATOR
// methods for handling state
bool is_step_done(PrintStep step) const { return Inherited::is_step_done(step); }
@ -314,7 +325,6 @@ public:
bool has_infinite_skirt() const;
bool has_skirt() const;
float get_wipe_tower_depth() const { return m_wipe_tower_data.depth; }
// Returns an empty string if valid, otherwise returns an error message.
std::string validate() const override;
@ -353,7 +363,7 @@ public:
// Wipe tower support.
bool has_wipe_tower() const;
const WipeTowerData& wipe_tower_data() const { return m_wipe_tower_data; }
const WipeTowerData& wipe_tower_data(size_t extruders_cnt = 0, double first_layer_height = 0., double nozzle_diameter = 0.) const;
std::string output_filename(const std::string &filename_base = std::string()) const override;
@ -382,7 +392,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);

View file

@ -29,7 +29,7 @@ PrintConfigDef::PrintConfigDef()
this->init_common_params();
assign_printer_technology_to_unknown(this->options, ptAny);
this->init_fff_params();
this->init_extruder_retract_keys();
this->init_extruder_option_keys();
assign_printer_technology_to_unknown(this->options, ptFFF);
this->init_sla_params();
assign_printer_technology_to_unknown(this->options, ptSLA);
@ -433,6 +433,7 @@ void PrintConfigDef::init_fff_params()
"If left zero, default extrusion width will be used if set, otherwise 1.125 x nozzle diameter will be used. "
"If expressed as percentage (for example 200%), it will be computed over layer height.");
def->sidetext = L("mm or %");
def->min = 0;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloatOrPercent(0, false));
@ -541,6 +542,7 @@ void PrintConfigDef::init_fff_params()
"(see the tooltips for perimeter extrusion width, infill extrusion width etc). "
"If expressed as percentage (for example: 230%), it will be computed over layer height.");
def->sidetext = L("mm or %");
def->min = 0;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloatOrPercent(0, false));
@ -749,6 +751,10 @@ void PrintConfigDef::init_fff_params()
def->set_default_value(new ConfigOptionStrings { "" });
def->cli = ConfigOptionDef::nocli;
def = this->add("filament_vendor", coString);
def->set_default_value(new ConfigOptionString(L("(Unknown)")));
def->cli = ConfigOptionDef::nocli;
def = this->add("fill_angle", coFloat);
def->label = L("Fill angle");
def->category = L("Infill");
@ -859,6 +865,7 @@ void PrintConfigDef::init_fff_params()
"If set to zero, it will use the default extrusion width.");
def->sidetext = L("mm or %");
def->ratio_over = "first_layer_height";
def->min = 0;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloatOrPercent(200, true));
@ -990,6 +997,7 @@ void PrintConfigDef::init_fff_params()
"You may want to use fatter extrudates to speed up the infill and make your parts stronger. "
"If expressed as percentage (for example 90%) it will be computed over layer height.");
def->sidetext = L("mm or %");
def->min = 0;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloatOrPercent(0, false));
@ -1319,8 +1327,10 @@ void PrintConfigDef::init_fff_params()
def->enum_keys_map = &ConfigOptionEnum<PrintHostType>::get_enum_values();
def->enum_values.push_back("octoprint");
def->enum_values.push_back("duet");
def->enum_values.push_back("flashair");
def->enum_labels.push_back("OctoPrint");
def->enum_labels.push_back("Duet");
def->enum_labels.push_back("FlashAir");
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionEnum<PrintHostType>(htOctoPrint));
@ -1402,6 +1412,7 @@ void PrintConfigDef::init_fff_params()
"If expressed as percentage (for example 200%) it will be computed over layer height.");
def->sidetext = L("mm or %");
def->aliases = { "perimeters_extrusion_width" };
def->min = 0;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloatOrPercent(0, false));
@ -1739,6 +1750,7 @@ void PrintConfigDef::init_fff_params()
"If left zero, default extrusion width will be used if set, otherwise 1.125 x nozzle diameter will be used. "
"If expressed as percentage (for example 90%) it will be computed over layer height.");
def->sidetext = L("mm or %");
def->min = 0;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloatOrPercent(0, false));
@ -1913,6 +1925,7 @@ void PrintConfigDef::init_fff_params()
"If left zero, default extrusion width will be used if set, otherwise nozzle diameter will be used. "
"If expressed as percentage (for example 90%) it will be computed over layer height.");
def->sidetext = L("mm or %");
def->min = 0;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloatOrPercent(0, false));
@ -2072,6 +2085,7 @@ void PrintConfigDef::init_fff_params()
"If left zero, default extrusion width will be used if set, otherwise nozzle diameter will be used. "
"If expressed as percentage (for example 90%) it will be computed over layer height.");
def->sidetext = L("mm or %");
def->min = 0;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloatOrPercent(0, false));
@ -2266,8 +2280,17 @@ void PrintConfigDef::init_fff_params()
}
}
void PrintConfigDef::init_extruder_retract_keys()
void PrintConfigDef::init_extruder_option_keys()
{
// ConfigOptionFloats, ConfigOptionPercents, ConfigOptionBools, ConfigOptionStrings
m_extruder_option_keys = {
"nozzle_diameter", "min_layer_height", "max_layer_height", "extruder_offset",
"retract_length", "retract_lift", "retract_lift_above", "retract_lift_below", "retract_speed", "deretract_speed",
"retract_before_wipe", "retract_restart_extra", "retract_before_travel", "wipe",
"retract_layer_change", "retract_length_toolchange", "retract_restart_extra_toolchange", "extruder_colour",
"default_filament_profile"
};
m_extruder_retract_keys = {
"deretract_speed",
"retract_before_travel",
@ -2392,11 +2415,24 @@ 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));
// SLA Material settings.
def = this->add("material_type", coString);
def->label = L("SLA material type");
def->tooltip = L("SLA material type");
def->gui_type = "f_enum_open"; // TODO: ???
def->gui_flags = "show_value";
def->enum_values.push_back("Tough");
def->enum_values.push_back("Flexible");
def->enum_values.push_back("Casting");
def->enum_values.push_back("Dental");
def->enum_values.push_back("Heat-resistant");
def->set_default_value(new ConfigOptionString("Tough"));
def = this->add("initial_layer_height", coFloat);
def->label = L("Initial layer height");
def->tooltip = L("Initial layer height");
@ -2502,6 +2538,10 @@ void PrintConfigDef::init_sla_params()
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionString(""));
def = this->add("material_vendor", coString);
def->set_default_value(new ConfigOptionString(L("(Unknown)")));
def->cli = ConfigOptionDef::nocli;
def = this->add("default_sla_material_profile", coString);
def->label = L("Default SLA material profile");
def->tooltip = L("Default print profile associated with the current printer profile. "
@ -2721,6 +2761,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");
@ -2761,6 +2812,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");
@ -2878,9 +2936,13 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va
const PrintConfigDef print_config_def;
DynamicPrintConfig* DynamicPrintConfig::new_from_defaults()
DynamicPrintConfig DynamicPrintConfig::full_print_config()
{
return DynamicPrintConfig((const PrintRegionConfig&)FullPrintConfig::defaults());
}
DynamicPrintConfig::DynamicPrintConfig(const StaticPrintConfig& rhs) : DynamicConfig(rhs, rhs.keys_ref())
{
return new_from_defaults_keys(FullPrintConfig::defaults().keys());
}
DynamicPrintConfig* DynamicPrintConfig::new_from_defaults_keys(const std::vector<std::string> &keys)
@ -2927,6 +2989,20 @@ void DynamicPrintConfig::normalize()
}
}
void DynamicPrintConfig::set_num_extruders(unsigned int num_extruders)
{
const auto &defaults = FullPrintConfig::defaults();
for (const std::string &key : print_config_def.extruder_option_keys()) {
if (key == "default_filament_profile")
continue;
auto *opt = this->option(key, false);
assert(opt != nullptr);
assert(opt->is_vector());
if (opt != nullptr && opt->is_vector())
static_cast<ConfigOptionVectorBase*>(opt)->resize(num_extruders, defaults.option(key));
}
}
std::string DynamicPrintConfig::validate()
{
// Full print config is initialized from the defaults.

View file

@ -30,7 +30,7 @@ enum GCodeFlavor : unsigned char {
};
enum PrintHostType {
htOctoPrint, htDuet
htOctoPrint, htDuet, htFlashAir
};
enum InfillPattern {
@ -52,6 +52,14 @@ enum FilamentType {
};
*/
enum SLAMaterial {
slamTough,
slamFlex,
slamCasting,
slamDental,
slamHeatResistant,
};
enum SLADisplayOrientation {
sladoLandscape,
sladoPortrait
@ -94,6 +102,7 @@ template<> inline const t_config_enum_values& ConfigOptionEnum<PrintHostType>::g
if (keys_map.empty()) {
keys_map["octoprint"] = htOctoPrint;
keys_map["duet"] = htDuet;
keys_map["flashair"] = htFlashAir;
}
return keys_map;
}
@ -185,6 +194,8 @@ public:
static void handle_legacy(t_config_option_key &opt_key, std::string &value);
// Array options growing with the number of extruders
const std::vector<std::string>& extruder_option_keys() const { return m_extruder_option_keys; }
// Options defining the extruder retract properties. These keys are sorted lexicographically.
// The extruder retract keys could be overidden by the same values defined at the Filament level
// (then the key is further prefixed with the "filament_" prefix).
@ -193,9 +204,10 @@ public:
private:
void init_common_params();
void init_fff_params();
void init_extruder_retract_keys();
void init_extruder_option_keys();
void init_sla_params();
std::vector<std::string> m_extruder_option_keys;
std::vector<std::string> m_extruder_retract_keys;
};
@ -203,6 +215,8 @@ private:
// This definition is constant.
extern const PrintConfigDef print_config_def;
class StaticPrintConfig;
// Slic3r dynamic configuration, used to override the configuration
// per object, per modification volume or per printing material.
// The dynamic configuration is also used to store user modifications of the print global parameters,
@ -213,9 +227,11 @@ class DynamicPrintConfig : public DynamicConfig
{
public:
DynamicPrintConfig() {}
DynamicPrintConfig(const DynamicPrintConfig &other) : DynamicConfig(other) {}
DynamicPrintConfig(const DynamicPrintConfig &rhs) : DynamicConfig(rhs) {}
explicit DynamicPrintConfig(const StaticPrintConfig &rhs);
explicit DynamicPrintConfig(const ConfigBase &rhs) : DynamicConfig(rhs) {}
static DynamicPrintConfig* new_from_defaults();
static DynamicPrintConfig full_print_config();
static DynamicPrintConfig* new_from_defaults_keys(const std::vector<std::string> &keys);
// Overrides ConfigBase::def(). Static configuration definition. Any value stored into this ConfigBase shall have its definition here.
@ -223,6 +239,8 @@ public:
void normalize();
void set_num_extruders(unsigned int num_extruders);
// Validate the PrintConfig. Returns an empty string on success, otherwise an error message is returned.
std::string validate();
@ -249,6 +267,8 @@ public:
// Overrides ConfigBase::def(). Static configuration definition. Any value stored into this ConfigBase shall have its definition here.
const ConfigDef* def() const override { return &print_config_def; }
// Reference to the cached list of keys.
virtual const t_config_option_keys& keys_ref() const = 0;
protected:
// Verify whether the opt_key has not been obsoleted or renamed.
@ -337,6 +357,7 @@ public: \
{ return s_cache_##CLASS_NAME.optptr(opt_key, this); } \
/* Overrides ConfigBase::keys(). Collect names of all configuration values maintained by this configuration store. */ \
t_config_option_keys keys() const override { return s_cache_##CLASS_NAME.keys(); } \
const t_config_option_keys& keys_ref() const override { return s_cache_##CLASS_NAME.keys(); } \
static const CLASS_NAME& defaults() { initialize_cache(); return s_cache_##CLASS_NAME.defaults(); } \
private: \
static void initialize_cache() \
@ -1022,6 +1043,9 @@ public:
// The height of the pad from the bottom to the top not considering the pit
ConfigOptionFloat pad_wall_height /*= 5*/;
// How far should the pad extend around the contained geometry
ConfigOptionFloat pad_brim_size;
// The greatest distance where two individual pads are merged into one. The
// distance is measured roughly from the centroids of the pads.
@ -1042,7 +1066,9 @@ public:
// /////////////////////////////////////////////////////////////////////////
// Disable the elevation (ignore its value) and use the zero elevation mode
ConfigOptionBool pad_around_object;
ConfigOptionBool pad_around_object;
ConfigOptionBool pad_around_object_everywhere;
// This is the gap between the object bottom and the generated pad
ConfigOptionFloat pad_object_gap;
@ -1082,10 +1108,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);

View file

@ -1,6 +1,7 @@
#include "Print.hpp"
#include "BoundingBox.hpp"
#include "ClipperUtils.hpp"
#include "ElephantFootCompensation.hpp"
#include "Geometry.hpp"
#include "I18N.hpp"
#include "SupportMaterial.hpp"
@ -12,7 +13,6 @@
#include <boost/log/trivial.hpp>
#include <float.h>
#include <tbb/task_scheduler_init.h>
#include <tbb/parallel_for.h>
#include <tbb/atomic.h>
@ -75,13 +75,9 @@ PrintBase::ApplyStatus PrintObject::set_copies(const Points &points)
{
// Order copies with a nearest-neighbor search.
std::vector<Point> copies;
{
std::vector<Points::size_type> ordered_copies;
Slic3r::Geometry::chained_path(points, ordered_copies);
copies.reserve(ordered_copies.size());
for (size_t point_idx : ordered_copies)
copies.emplace_back(points[point_idx] + m_copies_shift);
}
copies.reserve(points.size());
for (const Point &pt : points)
copies.emplace_back(pt + m_copies_shift);
// Invalidate and set copies.
PrintBase::ApplyStatus status = PrintBase::APPLY_STATUS_UNCHANGED;
if (copies != m_copies) {
@ -122,6 +118,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);
@ -870,7 +879,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);
}
}
);
@ -980,8 +989,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();
@ -1664,25 +1673,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]));
}
@ -1760,8 +1770,10 @@ end:
Layer *layer = m_layers[layer_id];
// Apply size compensation and perform clipping of multi-part objects.
float delta = float(scale_(m_config.xy_size_compensation.value));
//FIXME only apply the compensation if no raft is enabled.
float elephant_foot_compensation = 0.f;
if (layer_id == 0)
if (layer_id == 0 && m_config.raft_layers == 0)
// Only enable Elephant foot compensation if printing directly on the print bed.
elephant_foot_compensation = float(scale_(m_config.elefant_foot_compensation.value));
if (layer->m_regions.size() == 1) {
// Optimized version for a single region layer.
@ -1780,19 +1792,8 @@ end:
to_expolygons(std::move(layerm->slices.surfaces)) :
offset_ex(to_expolygons(std::move(layerm->slices.surfaces)), delta);
// Apply the elephant foot compensation.
if (elephant_foot_compensation > 0) {
float elephant_foot_spacing = float(layerm->flow(frExternalPerimeter).scaled_elephant_foot_spacing());
float external_perimeter_nozzle = float(scale_(this->print()->config().nozzle_diameter.get_at(layerm->region()->config().perimeter_extruder.value - 1)));
// Apply the elephant foot compensation by steps of 1/10 nozzle diameter.
float steps = std::ceil(elephant_foot_compensation / (0.1f * external_perimeter_nozzle));
size_t nsteps = size_t(steps);
float step = elephant_foot_compensation / steps;
for (size_t i = 0; i < nsteps; ++ i) {
Polygons tmp = offset(expolygons, - step);
append(tmp, diff(to_polygons(expolygons), offset(offset_ex(expolygons, -elephant_foot_spacing - step), elephant_foot_spacing + step)));
expolygons = union_ex(tmp);
}
}
if (elephant_foot_compensation > 0)
expolygons = union_ex(Slic3r::elephant_foot_compensation(expolygons, layerm->flow(frExternalPerimeter), unscale<double>(elephant_foot_compensation)));
layerm->slices.set(std::move(expolygons), stInternal);
}
} else {
@ -1816,33 +1817,18 @@ end:
layerm->slices.set(std::move(slices), stInternal);
}
}
if (delta < 0.f) {
if (delta < 0.f || elephant_foot_compensation > 0.f) {
// Apply the negative XY compensation.
Polygons trimming = offset(layer->merged(float(EPSILON)), delta - float(EPSILON));
Polygons trimming;
static const float eps = float(scale_(m_config.slice_closing_radius.value) * 1.5);
if (elephant_foot_compensation > 0.f) {
trimming = to_polygons(Slic3r::elephant_foot_compensation(offset_ex(layer->merged(eps), std::min(delta, 0.f) - eps),
layer->m_regions.front()->flow(frExternalPerimeter), unscale<double>(elephant_foot_compensation)));
} else
trimming = offset(layer->merged(float(SCALED_EPSILON)), delta - float(SCALED_EPSILON));
for (size_t region_id = 0; region_id < layer->m_regions.size(); ++ region_id)
layer->m_regions[region_id]->trim_surfaces(trimming);
}
if (elephant_foot_compensation > 0.f) {
// Apply the elephant foot compensation.
std::vector<float> elephant_foot_spacing;
elephant_foot_spacing.reserve(layer->m_regions.size());
float external_perimeter_nozzle = 0.f;
for (size_t region_id = 0; region_id < layer->m_regions.size(); ++ region_id) {
LayerRegion *layerm = layer->m_regions[region_id];
elephant_foot_spacing.emplace_back(float(layerm->flow(frExternalPerimeter).scaled_elephant_foot_spacing()));
external_perimeter_nozzle += float(scale_(this->print()->config().nozzle_diameter.get_at(layerm->region()->config().perimeter_extruder.value - 1)));
}
external_perimeter_nozzle /= (float)layer->m_regions.size();
// Apply the elephant foot compensation by steps of 1/10 nozzle diameter.
float steps = std::ceil(elephant_foot_compensation / (0.1f * external_perimeter_nozzle));
size_t nsteps = size_t(steps);
float step = elephant_foot_compensation / steps;
for (size_t i = 0; i < nsteps; ++ i) {
Polygons trimming_polygons = offset(layer->merged(float(EPSILON)), - step - float(EPSILON));
for (size_t region_id = 0; region_id < layer->m_regions.size(); ++ region_id)
layer->m_regions[region_id]->elephant_foot_compensation_step(elephant_foot_spacing[region_id] + step, trimming_polygons);
}
}
}
// Merge all regions' slices to get islands, chain them by a shortest path.
layer->make_slices();
@ -2145,7 +2131,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;
@ -2172,115 +2158,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 &region = *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, &region, 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.
@ -2306,7 +2194,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.

View file

@ -0,0 +1,171 @@
#include "ConcaveHull.hpp"
#include <libslic3r/MTUtils.hpp>
#include <libslic3r/ClipperUtils.hpp>
#include "SLASpatIndex.hpp"
#include <boost/log/trivial.hpp>
namespace Slic3r {
namespace sla {
inline Vec3d to_vec3(const Vec2crd &v2) { return {double(v2(X)), double(v2(Y)), 0.}; }
inline Vec3d to_vec3(const Vec2d &v2) { return {v2(X), v2(Y), 0.}; }
inline Vec2crd to_vec2(const Vec3d &v3) { return {coord_t(v3(X)), coord_t(v3(Y))}; }
Point ConcaveHull::centroid(const Points &pp)
{
Point c;
switch(pp.size()) {
case 0: break;
case 1: c = pp.front(); break;
case 2: c = (pp[0] + pp[1]) / 2; break;
default: {
auto MAX = std::numeric_limits<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;
}
// 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;
}
Points ConcaveHull::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 ConcaveHull::merge_polygons() { m_polys = get_contours(union_ex(m_polys)); }
void ConcaveHull::add_connector_rectangles(const Points &centroids,
coord_t max_dist,
ThrowOnCancel thr)
{
// Centroid of the centroids of islands. This is where the additional
// connector sticks are routed.
Point cc = centroid(centroids);
PointIndex ctrindex;
unsigned idx = 0;
for(const Point &ct : centroids) ctrindex.insert(to_vec3(ct), idx++);
m_polys.reserve(m_polys.size() + centroids.size());
idx = 0;
for (const Point &c : centroids) {
thr();
double dx = c.x() - cc.x(), dy = c.y() - cc.y();
double l = std::sqrt(dx * dx + dy * dy);
double nx = dx / l, ny = dy / l;
const Point &ct = centroids[idx];
std::vector<PointIndexEl> result = ctrindex.nearest(to_vec3(ct), 2);
double dist = max_dist;
for (const PointIndexEl &el : result)
if (el.second != idx) {
dist = Line(to_vec2(el.first), ct).length();
break;
}
idx++;
if (dist >= max_dist) return;
Polygon r;
r.points.reserve(3);
r.points.emplace_back(cc);
Point n(scaled(nx), scaled(ny));
r.points.emplace_back(c + Point(n.y(), -n.x()));
r.points.emplace_back(c + Point(-n.y(), n.x()));
offset(r, scaled<float>(1.));
m_polys.emplace_back(r);
}
}
ConcaveHull::ConcaveHull(const Polygons &polys, double mergedist, ThrowOnCancel thr)
{
if(polys.empty()) return;
m_polys = polys;
merge_polygons();
if(m_polys.size() == 1) return;
Points centroids = calculate_centroids();
add_connector_rectangles(centroids, scaled(mergedist), thr);
merge_polygons();
}
ExPolygons ConcaveHull::to_expolygons() const
{
auto ret = reserve_vector<ExPolygon>(m_polys.size());
for (const Polygon &p : m_polys) ret.emplace_back(ExPolygon(p));
return ret;
}
ExPolygons offset_waffle_style_ex(const ConcaveHull &hull, coord_t delta)
{
ClipperLib::Paths paths = Slic3rMultiPoints_to_ClipperPaths(hull.polygons());
paths = fast_offset(paths, 2 * delta, ClipperLib::jtRound);
paths = fast_offset(paths, -delta, ClipperLib::jtRound);
ExPolygons ret = ClipperPaths_to_Slic3rExPolygons(paths);
for (ExPolygon &p : ret) p.holes = {};
return ret;
}
Polygons offset_waffle_style(const ConcaveHull &hull, coord_t delta)
{
return to_polygons(offset_waffle_style_ex(hull, delta));
}
}} // namespace Slic3r::sla

View file

@ -0,0 +1,53 @@
#ifndef CONCAVEHULL_HPP
#define CONCAVEHULL_HPP
#include <libslic3r/ExPolygon.hpp>
namespace Slic3r {
namespace sla {
inline Polygons get_contours(const ExPolygons &poly)
{
Polygons ret; ret.reserve(poly.size());
for (const ExPolygon &p : poly) ret.emplace_back(p.contour);
return ret;
}
using ThrowOnCancel = std::function<void()>;
/// 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;
static Point centroid(const Points& pp);
static inline Point centroid(const Polygon &poly) { return poly.centroid(); }
Points calculate_centroids() const;
void merge_polygons();
void add_connector_rectangles(const Points &centroids,
coord_t max_dist,
ThrowOnCancel thr);
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);
const Polygons & polygons() const { return m_polys; }
ExPolygons to_expolygons() const;
};
ExPolygons offset_waffle_style_ex(const ConcaveHull &ccvhull, coord_t delta);
Polygons offset_waffle_style(const ConcaveHull &polys, coord_t delta);
}} // namespace Slic3r::sla
#endif // CONCAVEHULL_HPP

View file

@ -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

View file

@ -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

View file

@ -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),
[&centroids, &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)));
}
}
}

View file

@ -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

View file

@ -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};

View file

@ -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

View 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

View file

@ -0,0 +1,695 @@
#include "SLAPad.hpp"
#include "SLABoilerPlate.hpp"
#include "SLASpatIndex.hpp"
#include "ConcaveHull.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);
}
// 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;
}
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);
ExPolygons 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, 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.
ExPolygons 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 cchull{allin, get_merge_distance(cfg), thr};
return offset_waffle_style_ex(cchull, get_waffle_offset(cfg));
}
// 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, get_merge_distance(cfg), thr};
outer = offset_waffle_style_ex(ochull, get_waffle_offset(cfg));
}
};
// 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() ||
get_waffle_offset(*this) <= MIN_BRIM_SIZE_MM)
return L("Pad brim size is too small for the current configuration.");
return "";
}
}} // namespace Slic3r::sla

View 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

View file

@ -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

View file

@ -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

View file

@ -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,65 +21,51 @@ 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
std::string project = prjname.empty()?
boost::filesystem::path(fpath).stem().string() : prjname;
save(zipper, prjname);
zipper.finalize();
} catch(std::exception& e) {
BOOST_LOG_TRIVIAL(error) << e.what();
// Rethrow the exception
throw;
}
}
void RasterWriter::save(Zipper &zipper, const std::string &prjname)
{
try {
std::string project =
prjname.empty() ?
boost::filesystem::path(zipper.get_filename()).stem().string() :
prjname;
zipper.add_entry("config.ini");
zipper << createIniContent(project);
for(unsigned i = 0; i < m_layers_rst.size(); i++)
{
if(m_layers_rst[i].rawbytes.size() > 0) {
char lyrnum[6];
std::sprintf(lyrnum, "%.5d", i);
auto zfilename = project + lyrnum + ".png";
// Add binary entry to the zipper
zipper.add_entry(zfilename,
m_layers_rst[i].rawbytes.data(),
m_layers_rst[i].rawbytes.size());
}
}
zipper.finalize();
} catch(std::exception& e) {
BOOST_LOG_TRIVIAL(error) << e.what();
// Rethrow the exception
@ -103,7 +89,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 +100,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);

View file

@ -12,23 +12,21 @@
#include "libslic3r/PrintConfig.hpp"
#include "SLARaster.hpp"
#include "libslic3r/Zipper.hpp"
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 +43,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,78 +59,64 @@ 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();
}
}
void save(const std::string &fpath, const std::string &prjname = "");
void save(Zipper &zipper, const std::string &prjname = "");
void set_statistics(const PrintStatistics &statistics);
void set_config(const DynamicPrintConfig &cfg);
};

View file

@ -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

View file

@ -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; }
};
}

View 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);
}
}
}

View 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

Some files were not shown because too many files have changed in this diff Show more