mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-10-30 20:21:12 -06:00 
			
		
		
		
	Merge branch 'tm_sla_tests'
This commit is contained in:
		
						commit
						5bf3d5aabf
					
				
					 69 changed files with 54707 additions and 4293 deletions
				
			
		|  | @ -156,7 +156,7 @@ namespace agg | |||
| 
 | ||||
|         //-------------------------------------------------------------------
 | ||||
|         template<class VertexSource> | ||||
|         void add_path(VertexSource& vs, unsigned path_id=0) | ||||
|         void add_path(VertexSource &&vs, unsigned path_id=0) | ||||
|         { | ||||
|             double x; | ||||
|             double y; | ||||
|  |  | |||
|  | @ -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; | ||||
| }; | ||||
|  |  | |||
|  | @ -176,8 +176,13 @@ add_library(libslic3r STATIC | |||
|     miniz_extension.cpp | ||||
|     SLA/SLACommon.hpp | ||||
|     SLA/SLABoilerPlate.hpp | ||||
|     SLA/SLABasePool.hpp | ||||
|     SLA/SLABasePool.cpp | ||||
|     SLA/SLAPad.hpp | ||||
|     SLA/SLAPad.cpp | ||||
|     SLA/SLASupportTreeBuilder.hpp | ||||
|     SLA/SLASupportTreeBuildsteps.hpp | ||||
|     SLA/SLASupportTreeBuildsteps.cpp | ||||
|     SLA/SLASupportTreeBuilder.cpp | ||||
|     SLA/SLAConcurrency.hpp | ||||
|     SLA/SLASupportTree.hpp | ||||
|     SLA/SLASupportTree.cpp | ||||
|     SLA/SLASupportTreeIGL.cpp | ||||
|  | @ -215,6 +220,7 @@ target_link_libraries(libslic3r | |||
|     qhull | ||||
|     semver | ||||
|     tbb | ||||
|     ${CMAKE_DL_LIBS} | ||||
|     ) | ||||
| 
 | ||||
| if(WIN32) | ||||
|  |  | |||
|  | @ -194,6 +194,19 @@ ClipperLib::Paths Slic3rMultiPoints_to_ClipperPaths(const Polygons &input) | |||
|     return retval; | ||||
| } | ||||
| 
 | ||||
| ClipperLib::Paths  Slic3rMultiPoints_to_ClipperPaths(const ExPolygons &input) | ||||
| { | ||||
|     ClipperLib::Paths retval; | ||||
|     for (auto &ep : input) { | ||||
|         retval.emplace_back(Slic3rMultiPoint_to_ClipperPath(ep.contour)); | ||||
|          | ||||
|         for (auto &h : ep.holes) | ||||
|             retval.emplace_back(Slic3rMultiPoint_to_ClipperPath(h)); | ||||
|     } | ||||
|          | ||||
|     return retval; | ||||
| } | ||||
| 
 | ||||
| ClipperLib::Paths Slic3rMultiPoints_to_ClipperPaths(const Polylines &input) | ||||
| { | ||||
|     ClipperLib::Paths retval; | ||||
|  | @ -472,14 +485,16 @@ ExPolygons offset2_ex(const ExPolygons &expolygons, const float delta1, | |||
|     return union_ex(polys); | ||||
| } | ||||
| 
 | ||||
| template <class T> | ||||
| T | ||||
| _clipper_do(const ClipperLib::ClipType clipType, const Polygons &subject,  | ||||
|     const Polygons &clip, const ClipperLib::PolyFillType fillType, const bool safety_offset_) | ||||
| template<class T, class TSubj, class TClip> | ||||
| T _clipper_do(const ClipperLib::ClipType     clipType, | ||||
|               TSubj &&                        subject, | ||||
|               TClip &&                        clip, | ||||
|               const ClipperLib::PolyFillType fillType, | ||||
|               const bool                     safety_offset_) | ||||
| { | ||||
|     // read input
 | ||||
|     ClipperLib::Paths input_subject = Slic3rMultiPoints_to_ClipperPaths(subject); | ||||
|     ClipperLib::Paths input_clip    = Slic3rMultiPoints_to_ClipperPaths(clip); | ||||
|     ClipperLib::Paths input_subject = Slic3rMultiPoints_to_ClipperPaths(std::forward<TSubj>(subject)); | ||||
|     ClipperLib::Paths input_clip    = Slic3rMultiPoints_to_ClipperPaths(std::forward<TClip>(clip)); | ||||
|      | ||||
|     // perform safety offset
 | ||||
|     if (safety_offset_) { | ||||
|  | @ -648,12 +663,26 @@ _clipper_ln(ClipperLib::ClipType clipType, const Lines &subject, const Polygons | |||
|     return retval; | ||||
| } | ||||
| 
 | ||||
| ClipperLib::PolyTree | ||||
| union_pt(const Polygons &subject, bool safety_offset_) | ||||
| ClipperLib::PolyTree union_pt(const Polygons &subject, bool safety_offset_) | ||||
| { | ||||
|     return _clipper_do<ClipperLib::PolyTree>(ClipperLib::ctUnion, subject, Polygons(), ClipperLib::pftEvenOdd, safety_offset_); | ||||
| } | ||||
| 
 | ||||
| ClipperLib::PolyTree union_pt(const ExPolygons &subject, bool safety_offset_) | ||||
| { | ||||
|     return _clipper_do<ClipperLib::PolyTree>(ClipperLib::ctUnion, subject, Polygons(), ClipperLib::pftEvenOdd, safety_offset_); | ||||
| } | ||||
| 
 | ||||
| ClipperLib::PolyTree union_pt(Polygons &&subject, bool safety_offset_) | ||||
| { | ||||
|     return _clipper_do<ClipperLib::PolyTree>(ClipperLib::ctUnion, std::move(subject), Polygons(), ClipperLib::pftEvenOdd, safety_offset_); | ||||
| } | ||||
| 
 | ||||
| ClipperLib::PolyTree union_pt(ExPolygons &&subject, bool safety_offset_) | ||||
| { | ||||
|     return _clipper_do<ClipperLib::PolyTree>(ClipperLib::ctUnion, std::move(subject), Polygons(), ClipperLib::pftEvenOdd, safety_offset_); | ||||
| } | ||||
| 
 | ||||
| Polygons | ||||
| union_pt_chained(const Polygons &subject, bool safety_offset_) | ||||
| { | ||||
|  | @ -664,28 +693,123 @@ union_pt_chained(const Polygons &subject, bool safety_offset_) | |||
|     return retval; | ||||
| } | ||||
| 
 | ||||
| void traverse_pt(ClipperLib::PolyNodes &nodes, Polygons* retval) | ||||
| static ClipperLib::PolyNodes order_nodes(const ClipperLib::PolyNodes &nodes) | ||||
| { | ||||
|     // collect ordering points
 | ||||
|     Points ordering_points; | ||||
|     ordering_points.reserve(nodes.size()); | ||||
|     for (const ClipperLib::PolyNode *node : nodes) | ||||
|         ordering_points.emplace_back(Point(node->Contour.front().X, node->Contour.front().Y)); | ||||
|      | ||||
|     // perform the ordering
 | ||||
|     ClipperLib::PolyNodes ordered_nodes = chain_clipper_polynodes(ordering_points, nodes); | ||||
|      | ||||
|     return ordered_nodes; | ||||
| } | ||||
| 
 | ||||
| enum class e_ordering { | ||||
|     ORDER_POLYNODES, | ||||
|     DONT_ORDER_POLYNODES | ||||
| }; | ||||
| 
 | ||||
| template<e_ordering o> | ||||
| void foreach_node(const ClipperLib::PolyNodes &nodes, | ||||
|                   std::function<void(const ClipperLib::PolyNode *)> fn); | ||||
| 
 | ||||
| template<> void foreach_node<e_ordering::DONT_ORDER_POLYNODES>( | ||||
|     const ClipperLib::PolyNodes &                     nodes, | ||||
|     std::function<void(const ClipperLib::PolyNode *)> fn) | ||||
| { | ||||
|     for (auto &n : nodes) fn(n); | ||||
| } | ||||
| 
 | ||||
| template<> void foreach_node<e_ordering::ORDER_POLYNODES>( | ||||
|     const ClipperLib::PolyNodes &                     nodes, | ||||
|     std::function<void(const ClipperLib::PolyNode *)> fn) | ||||
| { | ||||
|     auto ordered_nodes = order_nodes(nodes); | ||||
|     for (auto &n : ordered_nodes) fn(n); | ||||
| } | ||||
| 
 | ||||
| template<e_ordering o> | ||||
| void _traverse_pt(const ClipperLib::PolyNodes &nodes, Polygons *retval) | ||||
| { | ||||
|     /* use a nearest neighbor search to order these children
 | ||||
|        TODO: supply start_near to chained_path() too? */ | ||||
|      | ||||
|     // collect ordering points
 | ||||
|     Points ordering_points; | ||||
|     ordering_points.reserve(nodes.size()); | ||||
|     for (ClipperLib::PolyNode *pn : nodes) | ||||
|         ordering_points.emplace_back(Point(pn->Contour.front().X, pn->Contour.front().Y)); | ||||
|      | ||||
|     // perform the ordering
 | ||||
|     ClipperLib::PolyNodes ordered_nodes = chain_clipper_polynodes(ordering_points, nodes); | ||||
| 
 | ||||
|     // push results recursively
 | ||||
|     for (ClipperLib::PolyNode *pn : ordered_nodes) { | ||||
|     foreach_node<o>(nodes, [&retval](const ClipperLib::PolyNode *node) { | ||||
|         // traverse the next depth
 | ||||
|         traverse_pt(pn->Childs, retval); | ||||
|         retval->emplace_back(ClipperPath_to_Slic3rPolygon(pn->Contour)); | ||||
|         if (pn->IsHole()) | ||||
|         	retval->back().reverse(); // ccw
 | ||||
|     } | ||||
|         _traverse_pt<o>(node->Childs, retval); | ||||
|         retval->emplace_back(ClipperPath_to_Slic3rPolygon(node->Contour)); | ||||
|         if (node->IsHole()) retval->back().reverse();  // ccw
 | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| template<e_ordering o> | ||||
| void _traverse_pt(const ClipperLib::PolyNode *tree, ExPolygons *retval) | ||||
| { | ||||
|     if (!retval || !tree) return; | ||||
|      | ||||
|     ExPolygons &retv = *retval; | ||||
|      | ||||
|     std::function<void(const ClipperLib::PolyNode*, ExPolygon&)> hole_fn; | ||||
|      | ||||
|     auto contour_fn = [&retv, &hole_fn](const ClipperLib::PolyNode *pptr) { | ||||
|         ExPolygon poly; | ||||
|         poly.contour.points = ClipperPath_to_Slic3rPolygon(pptr->Contour); | ||||
|         auto fn = std::bind(hole_fn, std::placeholders::_1, poly); | ||||
|         foreach_node<o>(pptr->Childs, fn); | ||||
|         retv.push_back(poly); | ||||
|     }; | ||||
|      | ||||
|     hole_fn = [&contour_fn](const ClipperLib::PolyNode *pptr, ExPolygon& poly) | ||||
|     {    | ||||
|         poly.holes.emplace_back(); | ||||
|         poly.holes.back().points = ClipperPath_to_Slic3rPolygon(pptr->Contour); | ||||
|         foreach_node<o>(pptr->Childs, contour_fn); | ||||
|     }; | ||||
|      | ||||
|     contour_fn(tree); | ||||
| } | ||||
| 
 | ||||
| template<e_ordering o> | ||||
| void _traverse_pt(const ClipperLib::PolyNodes &nodes, ExPolygons *retval) | ||||
| { | ||||
|     // Here is the actual traverse
 | ||||
|     foreach_node<o>(nodes, [&retval](const ClipperLib::PolyNode *node) { | ||||
|         _traverse_pt<o>(node, retval); | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| void traverse_pt(const ClipperLib::PolyNode *tree, ExPolygons *retval) | ||||
| { | ||||
|     _traverse_pt<e_ordering::ORDER_POLYNODES>(tree, retval); | ||||
| } | ||||
| 
 | ||||
| void traverse_pt_unordered(const ClipperLib::PolyNode *tree, ExPolygons *retval) | ||||
| { | ||||
|     _traverse_pt<e_ordering::DONT_ORDER_POLYNODES>(tree, retval); | ||||
| } | ||||
| 
 | ||||
| void traverse_pt(const ClipperLib::PolyNodes &nodes, Polygons *retval) | ||||
| { | ||||
|     _traverse_pt<e_ordering::ORDER_POLYNODES>(nodes, retval); | ||||
| } | ||||
| 
 | ||||
| void traverse_pt(const ClipperLib::PolyNodes &nodes, ExPolygons *retval) | ||||
| { | ||||
|     _traverse_pt<e_ordering::ORDER_POLYNODES>(nodes, retval); | ||||
| } | ||||
| 
 | ||||
| void traverse_pt_unordered(const ClipperLib::PolyNodes &nodes, Polygons *retval) | ||||
| { | ||||
|     _traverse_pt<e_ordering::DONT_ORDER_POLYNODES>(nodes, retval); | ||||
| } | ||||
| 
 | ||||
| void traverse_pt_unordered(const ClipperLib::PolyNodes &nodes, ExPolygons *retval) | ||||
| { | ||||
|     _traverse_pt<e_ordering::DONT_ORDER_POLYNODES>(nodes, retval); | ||||
| } | ||||
| 
 | ||||
| Polygons simplify_polygons(const Polygons &subject, bool preserve_collinear) | ||||
|  |  | |||
|  | @ -34,6 +34,7 @@ Slic3r::ExPolygons PolyTreeToExPolygons(ClipperLib::PolyTree& polytree); | |||
| 
 | ||||
| ClipperLib::Path   Slic3rMultiPoint_to_ClipperPath(const Slic3r::MultiPoint &input); | ||||
| ClipperLib::Paths  Slic3rMultiPoints_to_ClipperPaths(const Polygons &input); | ||||
| ClipperLib::Paths  Slic3rMultiPoints_to_ClipperPaths(const ExPolygons &input); | ||||
| ClipperLib::Paths  Slic3rMultiPoints_to_ClipperPaths(const Polylines &input); | ||||
| Slic3r::Polygon    ClipperPath_to_Slic3rPolygon(const ClipperLib::Path &input); | ||||
| Slic3r::Polyline   ClipperPath_to_Slic3rPolyline(const ClipperLib::Path &input); | ||||
|  | @ -215,8 +216,19 @@ inline Slic3r::ExPolygons union_ex(const Slic3r::Surfaces &subject, bool safety_ | |||
| 
 | ||||
| 
 | ||||
| ClipperLib::PolyTree union_pt(const Slic3r::Polygons &subject, bool safety_offset_ = false); | ||||
| ClipperLib::PolyTree union_pt(const Slic3r::ExPolygons &subject, bool safety_offset_ = false); | ||||
| ClipperLib::PolyTree union_pt(Slic3r::Polygons &&subject, bool safety_offset_ = false); | ||||
| ClipperLib::PolyTree union_pt(Slic3r::ExPolygons &&subject, bool safety_offset_ = false); | ||||
| 
 | ||||
| Slic3r::Polygons union_pt_chained(const Slic3r::Polygons &subject, bool safety_offset_ = false); | ||||
| void traverse_pt(ClipperLib::PolyNodes &nodes, Slic3r::Polygons* retval); | ||||
| 
 | ||||
| void traverse_pt(const ClipperLib::PolyNodes &nodes, Slic3r::Polygons *retval); | ||||
| void traverse_pt(const ClipperLib::PolyNodes &nodes, Slic3r::ExPolygons *retval); | ||||
| void traverse_pt(const ClipperLib::PolyNode  *tree,  Slic3r::ExPolygons *retval); | ||||
| 
 | ||||
| void traverse_pt_unordered(const ClipperLib::PolyNodes &nodes, Slic3r::Polygons *retval); | ||||
| void traverse_pt_unordered(const ClipperLib::PolyNodes &nodes, Slic3r::ExPolygons *retval); | ||||
| void traverse_pt_unordered(const ClipperLib::PolyNode  *tree,  Slic3r::ExPolygons *retval); | ||||
| 
 | ||||
| /* OTHER */ | ||||
| Slic3r::Polygons simplify_polygons(const Slic3r::Polygons &subject, bool preserve_collinear = false); | ||||
|  |  | |||
|  | @ -24,6 +24,9 @@ public: | |||
|     ExPolygon& operator=(const ExPolygon &other) { contour = other.contour; holes = other.holes; return *this; } | ||||
|     ExPolygon& operator=(ExPolygon &&other) { contour = std::move(other.contour); holes = std::move(other.holes); return *this; } | ||||
| 
 | ||||
|     inline explicit ExPolygon(const Polygon &p): contour(p) {} | ||||
|     inline explicit ExPolygon(Polygon &&p): contour(std::move(p)) {} | ||||
| 
 | ||||
|     Polygon contour; | ||||
|     Polygons holes; | ||||
| 
 | ||||
|  |  | |||
|  | @ -15,39 +15,41 @@ | |||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
| bool load_obj(const char *path, Model *model, const char *object_name_in) | ||||
| bool load_obj(const char *path, TriangleMesh *meshptr) | ||||
| { | ||||
|     if(meshptr == nullptr) return false; | ||||
|      | ||||
|     // Parse the OBJ file.
 | ||||
|     ObjParser::ObjData data; | ||||
|     if (! ObjParser::objparse(path, data)) { | ||||
| //    die "Failed to parse $file\n" if !-e $path;
 | ||||
|         //    die "Failed to parse $file\n" if !-e $path;
 | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|      | ||||
|     // Count the faces and verify, that all faces are triangular.
 | ||||
|     size_t num_faces = 0; | ||||
| 	size_t num_quads = 0; | ||||
|     size_t num_quads = 0; | ||||
|     for (size_t i = 0; i < data.vertices.size(); ) { | ||||
|         size_t j = i; | ||||
|         for (; j < data.vertices.size() && data.vertices[j].coordIdx != -1; ++ j) ; | ||||
|         if (i == j) | ||||
|             continue; | ||||
| 		size_t face_vertices = j - i; | ||||
| 		if (face_vertices != 3 && face_vertices != 4) { | ||||
|         size_t face_vertices = j - i; | ||||
|         if (face_vertices != 3 && face_vertices != 4) { | ||||
|             // Non-triangular and non-quad faces are not supported as of now.
 | ||||
|             return false; | ||||
|         } | ||||
| 		if (face_vertices == 4) | ||||
| 			++ num_quads; | ||||
| 		++ num_faces; | ||||
|         if (face_vertices == 4) | ||||
|             ++ num_quads; | ||||
|         ++ num_faces; | ||||
|         i = j + 1; | ||||
|     } | ||||
| 
 | ||||
|      | ||||
|     // Convert ObjData into STL.
 | ||||
|     TriangleMesh mesh; | ||||
|     TriangleMesh &mesh = *meshptr; | ||||
|     stl_file &stl = mesh.stl; | ||||
|     stl.stats.type = inmemory; | ||||
|     stl.stats.number_of_facets = int(num_faces + num_quads); | ||||
|     stl.stats.number_of_facets = uint32_t(num_faces + num_quads); | ||||
|     stl.stats.original_num_facets = int(num_faces + num_quads); | ||||
|     // stl_allocate clears all the allocated data to zero, all normals are set to zeros as well.
 | ||||
|     stl_allocate(&stl); | ||||
|  | @ -68,14 +70,14 @@ bool load_obj(const char *path, Model *model, const char *object_name_in) | |||
|                 ++ num_normals; | ||||
|             } | ||||
|         } | ||||
| 		if (data.vertices[i].coordIdx != -1) { | ||||
| 			// This is a quad. Produce the other triangle.
 | ||||
| 			stl_facet &facet2 = stl.facet_start[i_face++]; | ||||
|         if (data.vertices[i].coordIdx != -1) { | ||||
|             // This is a quad. Produce the other triangle.
 | ||||
|             stl_facet &facet2 = stl.facet_start[i_face++]; | ||||
|             facet2.vertex[0] = facet.vertex[0]; | ||||
|             facet2.vertex[1] = facet.vertex[2]; | ||||
| 			const ObjParser::ObjVertex &vertex = data.vertices[i++]; | ||||
| 			memcpy(facet2.vertex[2].data(), &data.coordinates[vertex.coordIdx * 4], 3 * sizeof(float)); | ||||
| 			if (vertex.normalIdx != -1) { | ||||
|             const ObjParser::ObjVertex &vertex = data.vertices[i++]; | ||||
|             memcpy(facet2.vertex[2].data(), &data.coordinates[vertex.coordIdx * 4], 3 * sizeof(float)); | ||||
|             if (vertex.normalIdx != -1) { | ||||
|                 normal(0) += data.normals[vertex.normalIdx*3]; | ||||
|                 normal(1) += data.normals[vertex.normalIdx*3+1]; | ||||
|                 normal(2) += data.normals[vertex.normalIdx*3+2]; | ||||
|  | @ -96,25 +98,37 @@ bool load_obj(const char *path, Model *model, const char *object_name_in) | |||
|             if (len > EPSILON) | ||||
|                 facet.normal = normal / len; | ||||
|         } | ||||
| 	} | ||||
|     } | ||||
|     stl_get_size(&stl); | ||||
|     mesh.repair(); | ||||
|     if (mesh.facets_count() == 0) { | ||||
|         // die "This STL file couldn't be read because it's empty.\n"
 | ||||
|         // die "This OBJ file couldn't be read because it's empty.\n"
 | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     std::string  object_name; | ||||
|     if (object_name_in == nullptr) { | ||||
|         const char *last_slash = strrchr(path, DIR_SEPARATOR); | ||||
|         object_name.assign((last_slash == nullptr) ? path : last_slash + 1); | ||||
|     } else | ||||
|        object_name.assign(object_name_in); | ||||
| 
 | ||||
|     model->add_object(object_name.c_str(), path, std::move(mesh)); | ||||
|      | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| bool load_obj(const char *path, Model *model, const char *object_name_in) | ||||
| { | ||||
|     TriangleMesh mesh; | ||||
|      | ||||
|     bool ret = load_obj(path, &mesh); | ||||
|      | ||||
|     if (ret) { | ||||
|         std::string  object_name; | ||||
|         if (object_name_in == nullptr) { | ||||
|             const char *last_slash = strrchr(path, DIR_SEPARATOR); | ||||
|             object_name.assign((last_slash == nullptr) ? path : last_slash + 1); | ||||
|         } else | ||||
|            object_name.assign(object_name_in); | ||||
|      | ||||
|         model->add_object(object_name.c_str(), path, std::move(mesh)); | ||||
|     } | ||||
|      | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| bool store_obj(const char *path, TriangleMesh *mesh) | ||||
| { | ||||
|     //FIXME returning false even if write failed.
 | ||||
|  |  | |||
|  | @ -5,8 +5,10 @@ namespace Slic3r { | |||
| 
 | ||||
| class TriangleMesh; | ||||
| class Model; | ||||
| class ModelObject; | ||||
| 
 | ||||
| // Load an OBJ file into a provided model.
 | ||||
| extern bool load_obj(const char *path, TriangleMesh *mesh); | ||||
| extern bool load_obj(const char *path, Model *model, const char *object_name = nullptr); | ||||
| 
 | ||||
| extern bool store_obj(const char *path, TriangleMesh *mesh); | ||||
|  |  | |||
|  | @ -14,6 +14,11 @@ | |||
| using boost::polygon::voronoi_builder; | ||||
| using boost::polygon::voronoi_diagram; | ||||
| 
 | ||||
| namespace ClipperLib { | ||||
| class PolyNode; | ||||
| using PolyNodes = std::vector<PolyNode*>; | ||||
| } | ||||
| 
 | ||||
| namespace Slic3r { namespace Geometry { | ||||
| 
 | ||||
| // Generic result of an orientation predicate.
 | ||||
|  |  | |||
|  | @ -252,22 +252,15 @@ template<class T> struct remove_cvref | |||
| 
 | ||||
| template<class T> using remove_cvref_t = typename remove_cvref<T>::type; | ||||
| 
 | ||||
| template<template<class> class C, class T> | ||||
| class Container : public C<remove_cvref_t<T>> | ||||
| { | ||||
| public: | ||||
|     explicit Container(size_t count, T &&initval) | ||||
|         : C<remove_cvref_t<T>>(count, initval) | ||||
|     {} | ||||
| }; | ||||
| 
 | ||||
| template<class T> using DefaultContainer = std::vector<T>; | ||||
| 
 | ||||
| /// Exactly like Matlab https://www.mathworks.com/help/matlab/ref/linspace.html
 | ||||
| template<class T, class I, template<class> class C = DefaultContainer> | ||||
| inline C<remove_cvref_t<T>> linspace(const T &start, const T &stop, const I &n) | ||||
| template<class T, class I, template<class> class Container = DefaultContainer> | ||||
| inline Container<remove_cvref_t<T>> linspace(const T &start, | ||||
|                                              const T &stop, | ||||
|                                              const I &n) | ||||
| { | ||||
|     Container<C, T> vals(n, T()); | ||||
|     Container<remove_cvref_t<T>> vals(n, T()); | ||||
| 
 | ||||
|     T      stride = (stop - start) / n; | ||||
|     size_t i      = 0; | ||||
|  | @ -282,10 +275,13 @@ inline C<remove_cvref_t<T>> linspace(const T &start, const T &stop, const I &n) | |||
| /// in the closest multiple of 'stride' less than or equal to 'end' and
 | ||||
| /// leaving 'stride' space between each value. 
 | ||||
| /// Very similar to Matlab [start:stride:end] notation.
 | ||||
| template<class T, template<class> class C = DefaultContainer> | ||||
| inline C<remove_cvref_t<T>> grid(const T &start, const T &stop, const T &stride) | ||||
| template<class T, template<class> class Container = DefaultContainer> | ||||
| inline Container<remove_cvref_t<T>> grid(const T &start, | ||||
|                                          const T &stop, | ||||
|                                          const T &stride) | ||||
| { | ||||
|     Container<C, T> vals(size_t(std::ceil((stop - start) / stride)), T()); | ||||
|     Container<remove_cvref_t<T>> | ||||
|         vals(size_t(std::ceil((stop - start) / stride)), T()); | ||||
|      | ||||
|     int i = 0; | ||||
|     std::generate(vals.begin(), vals.end(), [&i, start, stride] { | ||||
|  | @ -387,10 +383,12 @@ unscaled(const Eigen::Matrix<Tin, N, EigenArgs...> &v) noexcept | |||
|     return v.template cast<Tout>() * SCALING_FACTOR; | ||||
| } | ||||
| 
 | ||||
| template<class T> inline std::vector<T> reserve_vector(size_t capacity) | ||||
| template<class T, class I, class... Args> // Arbitrary allocator can be used
 | ||||
| inline IntegerOnly<I, std::vector<T, Args...>> reserve_vector(I capacity) | ||||
| { | ||||
|     std::vector<T> ret; | ||||
|     ret.reserve(capacity); | ||||
|     std::vector<T, Args...> ret; | ||||
|     if (capacity > I(0)) ret.reserve(size_t(capacity)); | ||||
|      | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -2694,6 +2694,17 @@ void PrintConfigDef::init_sla_params() | |||
|     def->max = 30; | ||||
|     def->mode = comExpert; | ||||
|     def->set_default_value(new ConfigOptionFloat(0.)); | ||||
|      | ||||
|     def = this->add("pad_brim_size", coFloat); | ||||
|     def->label = L("Pad brim size"); | ||||
|     def->tooltip = L("How far should the pad extend around the contained geometry"); | ||||
|     def->category = L("Pad"); | ||||
|     //     def->tooltip = L("");
 | ||||
|     def->sidetext = L("mm"); | ||||
|     def->min = 0; | ||||
|     def->max = 30; | ||||
|     def->mode = comAdvanced; | ||||
|     def->set_default_value(new ConfigOptionFloat(1.6)); | ||||
| 
 | ||||
|     def = this->add("pad_max_merge_distance", coFloat); | ||||
|     def->label = L("Max merge distance"); | ||||
|  | @ -2734,6 +2745,13 @@ void PrintConfigDef::init_sla_params() | |||
|     def->tooltip = L("Create pad around object and ignore the support elevation"); | ||||
|     def->mode = comSimple; | ||||
|     def->set_default_value(new ConfigOptionBool(false)); | ||||
|      | ||||
|     def = this->add("pad_around_object_everywhere", coBool); | ||||
|     def->label = L("Pad around object everywhere"); | ||||
|     def->category = L("Pad"); | ||||
|     def->tooltip = L("Force pad around object everywhere"); | ||||
|     def->mode = comSimple; | ||||
|     def->set_default_value(new ConfigOptionBool(false)); | ||||
| 
 | ||||
|     def = this->add("pad_object_gap", coFloat); | ||||
|     def->label = L("Pad object gap"); | ||||
|  |  | |||
|  | @ -1022,6 +1022,9 @@ public: | |||
| 
 | ||||
|     // The height of the pad from the bottom to the top not considering the pit
 | ||||
|     ConfigOptionFloat pad_wall_height /*= 5*/; | ||||
|      | ||||
|     // How far should the pad extend around the contained geometry
 | ||||
|     ConfigOptionFloat pad_brim_size; | ||||
| 
 | ||||
|     // The greatest distance where two individual pads are merged into one. The
 | ||||
|     // distance is measured roughly from the centroids of the pads.
 | ||||
|  | @ -1042,7 +1045,9 @@ public: | |||
|     // /////////////////////////////////////////////////////////////////////////
 | ||||
| 
 | ||||
|     // Disable the elevation (ignore its value) and use the zero elevation mode
 | ||||
|     ConfigOptionBool  pad_around_object; | ||||
|     ConfigOptionBool pad_around_object; | ||||
|      | ||||
|     ConfigOptionBool pad_around_object_everywhere; | ||||
| 
 | ||||
|     // This is the gap between the object bottom and the generated pad
 | ||||
|     ConfigOptionFloat pad_object_gap; | ||||
|  | @ -1082,10 +1087,12 @@ protected: | |||
|         OPT_PTR(pad_enable); | ||||
|         OPT_PTR(pad_wall_thickness); | ||||
|         OPT_PTR(pad_wall_height); | ||||
|         OPT_PTR(pad_brim_size); | ||||
|         OPT_PTR(pad_max_merge_distance); | ||||
|         // OPT_PTR(pad_edge_radius);
 | ||||
|         OPT_PTR(pad_wall_slope); | ||||
|         OPT_PTR(pad_around_object); | ||||
|         OPT_PTR(pad_around_object_everywhere); | ||||
|         OPT_PTR(pad_object_gap); | ||||
|         OPT_PTR(pad_object_connector_stride); | ||||
|         OPT_PTR(pad_object_connector_width); | ||||
|  |  | |||
|  | @ -16,6 +16,7 @@ | |||
| #include <random> | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| namespace sla { | ||||
| 
 | ||||
| /*float SLAAutoSupports::approximate_geodesic_distance(const Vec3d& p1, const Vec3d& p2, Vec3d& n1, Vec3d& n2)
 | ||||
| { | ||||
|  | @ -48,9 +49,16 @@ float SLAAutoSupports::distance_limit(float angle) const | |||
|     return 1./(2.4*get_required_density(angle)); | ||||
| }*/ | ||||
| 
 | ||||
| SLAAutoSupports::SLAAutoSupports(const TriangleMesh& mesh, const sla::EigenMesh3D& emesh, const std::vector<ExPolygons>& slices, const std::vector<float>& heights, | ||||
|                                    const Config& config, std::function<void(void)> throw_on_cancel, std::function<void(int)> statusfn) | ||||
| : m_config(config), m_emesh(emesh), m_throw_on_cancel(throw_on_cancel), m_statusfn(statusfn) | ||||
| SLAAutoSupports::SLAAutoSupports(const sla::EigenMesh3D &       emesh, | ||||
|                                  const std::vector<ExPolygons> &slices, | ||||
|                                  const std::vector<float> &     heights, | ||||
|                                  const Config &                 config, | ||||
|                                  std::function<void(void)> throw_on_cancel, | ||||
|                                  std::function<void(int)>  statusfn) | ||||
|     : m_config(config) | ||||
|     , m_emesh(emesh) | ||||
|     , m_throw_on_cancel(throw_on_cancel) | ||||
|     , m_statusfn(statusfn) | ||||
| { | ||||
|     process(slices, heights); | ||||
|     project_onto_mesh(m_output); | ||||
|  | @ -505,6 +513,21 @@ void SLAAutoSupports::uniformly_cover(const ExPolygons& islands, Structure& stru | |||
|     } | ||||
| } | ||||
| 
 | ||||
| void remove_bottom_points(std::vector<SupportPoint> &pts, double gnd_lvl, double tolerance) | ||||
| { | ||||
|     // get iterator to the reorganized vector end
 | ||||
|     auto endit = | ||||
|         std::remove_if(pts.begin(), pts.end(), | ||||
|                        [tolerance, gnd_lvl](const sla::SupportPoint &sp) { | ||||
|         double diff = std::abs(gnd_lvl - | ||||
|                                double(sp.pos(Z))); | ||||
|         return diff <= tolerance; | ||||
|     }); | ||||
| 
 | ||||
|     // erase all elements after the new end
 | ||||
|     pts.erase(endit, pts.end()); | ||||
| } | ||||
| 
 | ||||
| #ifdef SLA_AUTOSUPPORTS_DEBUG | ||||
| void SLAAutoSupports::output_structures(const std::vector<Structure>& structures) | ||||
| { | ||||
|  | @ -533,4 +556,5 @@ void SLAAutoSupports::output_expolygons(const ExPolygons& expolys, const std::st | |||
| } | ||||
| #endif | ||||
| 
 | ||||
| } // namespace sla
 | ||||
| } // namespace Slic3r
 | ||||
|  |  | |||
|  | @ -11,20 +11,22 @@ | |||
| // #define SLA_AUTOSUPPORTS_DEBUG
 | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| namespace sla { | ||||
| 
 | ||||
| class SLAAutoSupports { | ||||
| public: | ||||
|     struct Config { | ||||
|             float density_relative; | ||||
|             float minimal_distance; | ||||
|             float head_diameter; | ||||
|             float density_relative {1.f}; | ||||
|             float minimal_distance {1.f}; | ||||
|             float head_diameter {0.4f}; | ||||
|             ///////////////
 | ||||
|             inline float support_force() const { return 7.7f / density_relative; } // a force one point can support       (arbitrary force unit)
 | ||||
|             inline float tear_pressure() const { return 1.f; }  // pressure that the display exerts    (the force unit per mm2)
 | ||||
|         }; | ||||
| 
 | ||||
|     SLAAutoSupports(const TriangleMesh& mesh, const sla::EigenMesh3D& emesh, const std::vector<ExPolygons>& slices, | ||||
|     SLAAutoSupports(const sla::EigenMesh3D& emesh, const std::vector<ExPolygons>& slices, | ||||
|                      const std::vector<float>& heights, const Config& config, std::function<void(void)> throw_on_cancel, std::function<void(int)> statusfn); | ||||
|      | ||||
|     const std::vector<sla::SupportPoint>& output() { return m_output; } | ||||
| 
 | ||||
| 	struct MyLayer; | ||||
|  | @ -199,7 +201,9 @@ private: | |||
|     std::function<void(int)>  m_statusfn; | ||||
| }; | ||||
| 
 | ||||
| void remove_bottom_points(std::vector<SupportPoint> &pts, double gnd_lvl, double tolerance); | ||||
| 
 | ||||
| } // namespace sla
 | ||||
| } // namespace Slic3r
 | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,922 +0,0 @@ | |||
| #include "SLABasePool.hpp" | ||||
| #include "SLABoilerPlate.hpp" | ||||
| 
 | ||||
| #include "boost/log/trivial.hpp" | ||||
| #include "SLABoostAdapter.hpp" | ||||
| #include "ClipperUtils.hpp" | ||||
| #include "Tesselate.hpp" | ||||
| #include "MTUtils.hpp" | ||||
| 
 | ||||
| // For debugging:
 | ||||
| // #include <fstream>
 | ||||
| // #include <libnest2d/tools/benchmark.h>
 | ||||
| // #include "SVG.hpp"
 | ||||
| 
 | ||||
| namespace Slic3r { namespace sla { | ||||
| 
 | ||||
| /// This function will return a triangulation of a sheet connecting an upper
 | ||||
| /// and a lower plate given as input polygons. It will not triangulate the
 | ||||
| /// plates themselves only the sheet. The caller has to specify the lower and
 | ||||
| /// upper z levels in world coordinates as well as the offset difference
 | ||||
| /// between the sheets. If the lower_z_mm is higher than upper_z_mm or the
 | ||||
| /// offset difference is negative, the resulting triangle orientation will be
 | ||||
| /// reversed.
 | ||||
| ///
 | ||||
| /// IMPORTANT: This is not a universal triangulation algorithm. It assumes
 | ||||
| /// that the lower and upper polygons are offsetted versions of the same
 | ||||
| /// original polygon. In general, it assumes that one of the polygons is
 | ||||
| /// completely inside the other. The offset difference is the reference
 | ||||
| /// distance from the inner polygon's perimeter to the outer polygon's
 | ||||
| /// perimeter. The real distance will be variable as the clipper offset has
 | ||||
| /// different strategies (rounding, etc...). This algorithm should have
 | ||||
| /// O(2n + 3m) complexity where n is the number of upper vertices and m is the
 | ||||
| /// number of lower vertices.
 | ||||
| Contour3D walls(const Polygon& lower, const Polygon& upper, | ||||
|                 double lower_z_mm, double upper_z_mm, | ||||
|                 double offset_difference_mm, ThrowOnCancel thr) | ||||
| { | ||||
|     Contour3D ret; | ||||
| 
 | ||||
|     if(upper.points.size() < 3 || lower.size() < 3) return ret; | ||||
| 
 | ||||
|     // The concept of the algorithm is relatively simple. It will try to find
 | ||||
|     // the closest vertices from the upper and the lower polygon and use those
 | ||||
|     // as starting points. Then it will create the triangles sequentially using
 | ||||
|     // an edge from the upper polygon and a vertex from the lower or vice versa,
 | ||||
|     // depending on the resulting triangle's quality.
 | ||||
|     // The quality is measured by a scalar value. So far it looks like it is
 | ||||
|     // enough to derive it from the slope of the triangle's two edges connecting
 | ||||
|     // the upper and the lower part. A reference slope is calculated from the
 | ||||
|     // height and the offset difference.
 | ||||
| 
 | ||||
|     // Offset in the index array for the ceiling
 | ||||
|     const auto offs = upper.points.size(); | ||||
| 
 | ||||
|     // Shorthand for the vertex arrays
 | ||||
|     auto& upoints = upper.points, &lpoints = lower.points; | ||||
|     auto& rpts = ret.points; auto& ind = ret.indices; | ||||
| 
 | ||||
|     // If the Z levels are flipped, or the offset difference is negative, we
 | ||||
|     // will interpret that as the triangles normals should be inverted.
 | ||||
|     bool inverted = upper_z_mm < lower_z_mm || offset_difference_mm < 0; | ||||
| 
 | ||||
|     // Copy the points into the mesh, convert them from 2D to 3D
 | ||||
|     rpts.reserve(upoints.size() + lpoints.size()); | ||||
|     ind.reserve(2 * upoints.size() + 2 * lpoints.size()); | ||||
|     for (auto &p : upoints) | ||||
|         rpts.emplace_back(unscaled(p.x()), unscaled(p.y()), upper_z_mm); | ||||
|     for (auto &p : lpoints) | ||||
|         rpts.emplace_back(unscaled(p.x()), unscaled(p.y()), lower_z_mm); | ||||
| 
 | ||||
|     // Create pointing indices into vertex arrays. u-upper, l-lower
 | ||||
|     size_t uidx = 0, lidx = offs, unextidx = 1, lnextidx = offs + 1; | ||||
| 
 | ||||
|     // Simple squared distance calculation.
 | ||||
|     auto distfn = [](const Vec3d& p1, const Vec3d& p2) { | ||||
|         auto p = p1 - p2; return p.transpose() * p; | ||||
|     }; | ||||
| 
 | ||||
|     // We need to find the closest point on lower polygon to the first point on
 | ||||
|     // the upper polygon. These will be our starting points.
 | ||||
|     double distmin = std::numeric_limits<double>::max(); | ||||
|     for(size_t l = lidx; l < rpts.size(); ++l) { | ||||
|         thr(); | ||||
|         double d = distfn(rpts[l], rpts[uidx]); | ||||
|         if(d < distmin) { lidx = l; distmin = d; } | ||||
|     } | ||||
| 
 | ||||
|     // Set up lnextidx to be ahead of lidx in cyclic mode
 | ||||
|     lnextidx = lidx + 1; | ||||
|     if(lnextidx == rpts.size()) lnextidx = offs; | ||||
| 
 | ||||
|     // This will be the flip switch to toggle between upper and lower triangle
 | ||||
|     // creation mode
 | ||||
|     enum class Proceed { | ||||
|         UPPER, // A segment from the upper polygon and one vertex from the lower
 | ||||
|         LOWER  // A segment from the lower polygon and one vertex from the upper
 | ||||
|     } proceed = Proceed::UPPER; | ||||
| 
 | ||||
|     // Flags to help evaluating loop termination.
 | ||||
|     bool ustarted = false, lstarted = false; | ||||
| 
 | ||||
|     // The variables for the fitness values, one for the actual and one for the
 | ||||
|     // previous.
 | ||||
|     double current_fit = 0, prev_fit = 0; | ||||
| 
 | ||||
|     // Every triangle of the wall has two edges connecting the upper plate with
 | ||||
|     // the lower plate. From the length of these two edges and the zdiff we
 | ||||
|     // can calculate the momentary squared offset distance at a particular
 | ||||
|     // position on the wall. The average of the differences from the reference
 | ||||
|     // (squared) offset distance will give us the driving fitness value.
 | ||||
|     const double offsdiff2 = std::pow(offset_difference_mm, 2); | ||||
|     const double zdiff2 = std::pow(upper_z_mm - lower_z_mm, 2); | ||||
| 
 | ||||
|     // Mark the current vertex iterator positions. If the iterators return to
 | ||||
|     // the same position, the loop can be terminated.
 | ||||
|     size_t uendidx = uidx, lendidx = lidx; | ||||
| 
 | ||||
|     do { thr();  // check throw if canceled
 | ||||
| 
 | ||||
|         prev_fit = current_fit; | ||||
| 
 | ||||
|         switch(proceed) {   // proceed depending on the current state
 | ||||
|         case Proceed::UPPER: | ||||
|             if(!ustarted || uidx != uendidx) { // there are vertices remaining
 | ||||
|                 // Get the 3D vertices in order
 | ||||
|                 const Vec3d& p_up1 = rpts[uidx]; | ||||
|                 const Vec3d& p_low = rpts[lidx]; | ||||
|                 const Vec3d& p_up2 = rpts[unextidx]; | ||||
| 
 | ||||
|                 // Calculate fitness: the average of the two connecting edges
 | ||||
|                 double a = offsdiff2 - (distfn(p_up1, p_low) - zdiff2); | ||||
|                 double b = offsdiff2 - (distfn(p_up2, p_low) - zdiff2); | ||||
|                 current_fit = (std::abs(a) + std::abs(b)) / 2; | ||||
| 
 | ||||
|                 if(current_fit > prev_fit) { // fit is worse than previously
 | ||||
|                     proceed = Proceed::LOWER; | ||||
|                 } else {    // good to go, create the triangle
 | ||||
|                     inverted | ||||
|                         ? ind.emplace_back(int(unextidx), int(lidx), int(uidx)) | ||||
|                         : ind.emplace_back(int(uidx), int(lidx), int(unextidx)); | ||||
| 
 | ||||
|                     // Increment the iterators, rotate if necessary
 | ||||
|                     ++uidx; ++unextidx; | ||||
|                     if(unextidx == offs) unextidx = 0; | ||||
|                     if(uidx == offs) uidx = 0; | ||||
| 
 | ||||
|                     ustarted = true;    // mark the movement of the iterators
 | ||||
|                     // so that the comparison to uendidx can be made correctly
 | ||||
|                 } | ||||
|             } else proceed = Proceed::LOWER; | ||||
| 
 | ||||
|             break; | ||||
|         case Proceed::LOWER: | ||||
|             // Mode with lower segment, upper vertex. Same structure:
 | ||||
|             if(!lstarted || lidx != lendidx) { | ||||
|                 const Vec3d& p_low1 = rpts[lidx]; | ||||
|                 const Vec3d& p_low2 = rpts[lnextidx]; | ||||
|                 const Vec3d& p_up   = rpts[uidx]; | ||||
| 
 | ||||
|                 double a = offsdiff2 - (distfn(p_up, p_low1) - zdiff2); | ||||
|                 double b = offsdiff2 - (distfn(p_up, p_low2) - zdiff2); | ||||
|                 current_fit = (std::abs(a) + std::abs(b)) / 2; | ||||
| 
 | ||||
|                 if(current_fit > prev_fit) { | ||||
|                     proceed = Proceed::UPPER; | ||||
|                 } else { | ||||
|                     inverted | ||||
|                         ? ind.emplace_back(int(uidx), int(lnextidx), int(lidx)) | ||||
|                         : ind.emplace_back(int(lidx), int(lnextidx), int(uidx)); | ||||
| 
 | ||||
|                     ++lidx; ++lnextidx; | ||||
|                     if(lnextidx == rpts.size()) lnextidx = offs; | ||||
|                     if(lidx == rpts.size()) lidx = offs; | ||||
| 
 | ||||
|                     lstarted = true; | ||||
|                 } | ||||
|             } else proceed = Proceed::UPPER; | ||||
| 
 | ||||
|             break; | ||||
|         } // end of switch
 | ||||
|     } while(!ustarted || !lstarted || uidx != uendidx || lidx != lendidx); | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| /// Offsetting with clipper and smoothing the edges into a curvature.
 | ||||
| void offset(ExPolygon& sh, coord_t distance, bool edgerounding = true) { | ||||
|     using ClipperLib::ClipperOffset; | ||||
|     using ClipperLib::jtRound; | ||||
|     using ClipperLib::jtMiter; | ||||
|     using ClipperLib::etClosedPolygon; | ||||
|     using ClipperLib::Paths; | ||||
|     using ClipperLib::Path; | ||||
| 
 | ||||
|     auto&& ctour = Slic3rMultiPoint_to_ClipperPath(sh.contour); | ||||
|     auto&& holes = Slic3rMultiPoints_to_ClipperPaths(sh.holes); | ||||
| 
 | ||||
|     // If the input is not at least a triangle, we can not do this algorithm
 | ||||
|     if(ctour.size() < 3 || | ||||
|        std::any_of(holes.begin(), holes.end(), | ||||
|                    [](const Path& p) { return p.size() < 3; }) | ||||
|             ) { | ||||
|         BOOST_LOG_TRIVIAL(error) << "Invalid geometry for offsetting!"; | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     auto jointype = edgerounding? jtRound : jtMiter; | ||||
|      | ||||
|     ClipperOffset offs; | ||||
|     offs.ArcTolerance = scaled<double>(0.01); | ||||
|     Paths result; | ||||
|     offs.AddPath(ctour, jointype, etClosedPolygon); | ||||
|     offs.AddPaths(holes, jointype, etClosedPolygon); | ||||
|     offs.Execute(result, static_cast<double>(distance)); | ||||
| 
 | ||||
|     // Offsetting reverts the orientation and also removes the last vertex
 | ||||
|     // so boost will not have a closed polygon.
 | ||||
| 
 | ||||
|     bool found_the_contour = false; | ||||
|     sh.holes.clear(); | ||||
|     for(auto& r : result) { | ||||
|         if(ClipperLib::Orientation(r)) { | ||||
|             // We don't like if the offsetting generates more than one contour
 | ||||
|             // but throwing would be an overkill. Instead, we should warn the
 | ||||
|             // caller about the inability to create correct geometries
 | ||||
|             if(!found_the_contour) { | ||||
|                 auto rr = ClipperPath_to_Slic3rPolygon(r); | ||||
|                 sh.contour.points.swap(rr.points); | ||||
|                 found_the_contour = true; | ||||
|             } else { | ||||
|                 BOOST_LOG_TRIVIAL(warning) | ||||
|                         << "Warning: offsetting result is invalid!"; | ||||
|             } | ||||
|         } else { | ||||
|             // TODO If there are multiple contours we can't be sure which hole
 | ||||
|             // belongs to the first contour. (But in this case the situation is
 | ||||
|             // bad enough to let it go...)
 | ||||
|             sh.holes.emplace_back(ClipperPath_to_Slic3rPolygon(r)); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void offset(Polygon &sh, coord_t distance, bool edgerounding = true) | ||||
| { | ||||
|     using ClipperLib::ClipperOffset; | ||||
|     using ClipperLib::jtRound; | ||||
|     using ClipperLib::jtMiter; | ||||
|     using ClipperLib::etClosedPolygon; | ||||
|     using ClipperLib::Paths; | ||||
|     using ClipperLib::Path; | ||||
| 
 | ||||
|     auto &&ctour = Slic3rMultiPoint_to_ClipperPath(sh); | ||||
| 
 | ||||
|     // If the input is not at least a triangle, we can not do this algorithm
 | ||||
|     if (ctour.size() < 3) { | ||||
|         BOOST_LOG_TRIVIAL(error) << "Invalid geometry for offsetting!"; | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     ClipperOffset offs; | ||||
|     offs.ArcTolerance = 0.01 * scaled(1.); | ||||
|     Paths result; | ||||
|     offs.AddPath(ctour, edgerounding ? jtRound : jtMiter, etClosedPolygon); | ||||
|     offs.Execute(result, static_cast<double>(distance)); | ||||
| 
 | ||||
|     // Offsetting reverts the orientation and also removes the last vertex
 | ||||
|     // so boost will not have a closed polygon.
 | ||||
| 
 | ||||
|     bool found_the_contour = false; | ||||
|     for (auto &r : result) { | ||||
|         if (ClipperLib::Orientation(r)) { | ||||
|             // We don't like if the offsetting generates more than one contour
 | ||||
|             // but throwing would be an overkill. Instead, we should warn the
 | ||||
|             // caller about the inability to create correct geometries
 | ||||
|             if (!found_the_contour) { | ||||
|                 auto rr = ClipperPath_to_Slic3rPolygon(r); | ||||
|                 sh.points.swap(rr.points); | ||||
|                 found_the_contour = true; | ||||
|             } else { | ||||
|                 BOOST_LOG_TRIVIAL(warning) | ||||
|                     << "Warning: offsetting result is invalid!"; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// Unification of polygons (with clipper) preserving holes as well.
 | ||||
| ExPolygons unify(const ExPolygons& shapes) { | ||||
|     using ClipperLib::ptSubject; | ||||
| 
 | ||||
|     ExPolygons retv; | ||||
| 
 | ||||
|     bool closed = true; | ||||
|     bool valid = true; | ||||
| 
 | ||||
|     ClipperLib::Clipper clipper; | ||||
| 
 | ||||
|     for(auto& path : shapes) { | ||||
|         auto clipperpath = Slic3rMultiPoint_to_ClipperPath(path.contour); | ||||
| 
 | ||||
|         if(!clipperpath.empty()) | ||||
|             valid &= clipper.AddPath(clipperpath, ptSubject, closed); | ||||
| 
 | ||||
|         auto clipperholes = Slic3rMultiPoints_to_ClipperPaths(path.holes); | ||||
| 
 | ||||
|         for(auto& hole : clipperholes) { | ||||
|             if(!hole.empty()) | ||||
|                 valid &= clipper.AddPath(hole, ptSubject, closed); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if(!valid) BOOST_LOG_TRIVIAL(warning) << "Unification of invalid shapes!"; | ||||
| 
 | ||||
|     ClipperLib::PolyTree result; | ||||
|     clipper.Execute(ClipperLib::ctUnion, result, ClipperLib::pftNonZero); | ||||
| 
 | ||||
|     retv.reserve(static_cast<size_t>(result.Total())); | ||||
| 
 | ||||
|     // Now we will recursively traverse the polygon tree and serialize it
 | ||||
|     // into an ExPolygon with holes. The polygon tree has the clipper-ish
 | ||||
|     // PolyTree structure which alternates its nodes as contours and holes
 | ||||
| 
 | ||||
|     // A "declaration" of function for traversing leafs which are holes
 | ||||
|     std::function<void(ClipperLib::PolyNode*, ExPolygon&)> processHole; | ||||
| 
 | ||||
|     // Process polygon which calls processHoles which than calls processPoly
 | ||||
|     // again until no leafs are left.
 | ||||
|     auto processPoly = [&retv, &processHole](ClipperLib::PolyNode *pptr) { | ||||
|         ExPolygon poly; | ||||
|         poly.contour.points = ClipperPath_to_Slic3rPolygon(pptr->Contour); | ||||
|         for(auto h : pptr->Childs) { processHole(h, poly); } | ||||
|         retv.push_back(poly); | ||||
|     }; | ||||
| 
 | ||||
|     // Body of the processHole function
 | ||||
|     processHole = [&processPoly](ClipperLib::PolyNode *pptr, ExPolygon& poly) | ||||
|     { | ||||
|         poly.holes.emplace_back(); | ||||
|         poly.holes.back().points = ClipperPath_to_Slic3rPolygon(pptr->Contour); | ||||
|         for(auto c : pptr->Childs) processPoly(c); | ||||
|     }; | ||||
| 
 | ||||
|     // Wrapper for traversing.
 | ||||
|     auto traverse = [&processPoly] (ClipperLib::PolyNode *node) | ||||
|     { | ||||
|         for(auto ch : node->Childs) { | ||||
|             processPoly(ch); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     // Here is the actual traverse
 | ||||
|     traverse(&result); | ||||
| 
 | ||||
|     return retv; | ||||
| } | ||||
| 
 | ||||
| Polygons unify(const Polygons& shapes) { | ||||
|     using ClipperLib::ptSubject; | ||||
|      | ||||
|     bool closed = true; | ||||
|     bool valid = true; | ||||
| 
 | ||||
|     ClipperLib::Clipper clipper; | ||||
| 
 | ||||
|     for(auto& path : shapes) { | ||||
|         auto clipperpath = Slic3rMultiPoint_to_ClipperPath(path); | ||||
| 
 | ||||
|         if(!clipperpath.empty()) | ||||
|             valid &= clipper.AddPath(clipperpath, ptSubject, closed); | ||||
|     } | ||||
| 
 | ||||
|     if(!valid) BOOST_LOG_TRIVIAL(warning) << "Unification of invalid shapes!"; | ||||
| 
 | ||||
|     ClipperLib::Paths result; | ||||
|     clipper.Execute(ClipperLib::ctUnion, result, ClipperLib::pftNonZero); | ||||
| 
 | ||||
|     Polygons ret; | ||||
|     for (ClipperLib::Path &p : result) { | ||||
|         Polygon pp = ClipperPath_to_Slic3rPolygon(p); | ||||
|         if (!pp.is_clockwise()) ret.emplace_back(std::move(pp)); | ||||
|     } | ||||
|      | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| // Function to cut tiny connector cavities for a given polygon. The input poly
 | ||||
| // will be offsetted by "padding" and small rectangle shaped cavities will be
 | ||||
| // inserted along the perimeter in every "stride" distance. The stick rectangles
 | ||||
| // will have a with about "stick_width". The input dimensions are in world 
 | ||||
| // measure, not the scaled clipper units.
 | ||||
| void breakstick_holes(ExPolygon& poly, | ||||
|                       double padding, | ||||
|                       double stride, | ||||
|                       double stick_width, | ||||
|                       double penetration) | ||||
| {    | ||||
|     // SVG svg("bridgestick_plate.svg");
 | ||||
|     // svg.draw(poly);
 | ||||
| 
 | ||||
|     auto transf = [stick_width, penetration, padding, stride](Points &pts) { | ||||
|         // The connector stick will be a small rectangle with dimensions
 | ||||
|         // stick_width x (penetration + padding) to have some penetration
 | ||||
|         // into the input polygon.
 | ||||
| 
 | ||||
|         Points out; | ||||
|         out.reserve(2 * pts.size()); // output polygon points
 | ||||
| 
 | ||||
|         // stick bottom and right edge dimensions
 | ||||
|         double sbottom = scaled(stick_width); | ||||
|         double sright  = scaled(penetration + padding); | ||||
| 
 | ||||
|         // scaled stride distance
 | ||||
|         double sstride = scaled(stride); | ||||
|         double t       = 0; | ||||
| 
 | ||||
|         // process pairs of vertices as an edge, start with the last and
 | ||||
|         // first point
 | ||||
|         for (size_t i = pts.size() - 1, j = 0; j < pts.size(); i = j, ++j) { | ||||
|             // Get vertices and the direction vectors
 | ||||
|             const Point &a = pts[i], &b = pts[j]; | ||||
|             Vec2d        dir = b.cast<double>() - a.cast<double>(); | ||||
|             double       nrm = dir.norm(); | ||||
|             dir /= nrm; | ||||
|             Vec2d dirp(-dir(Y), dir(X)); | ||||
| 
 | ||||
|             // Insert start point
 | ||||
|             out.emplace_back(a); | ||||
| 
 | ||||
|             // dodge the start point, do not make sticks on the joins
 | ||||
|             while (t < sbottom) t += sbottom; | ||||
|             double tend = nrm - sbottom; | ||||
| 
 | ||||
|             while (t < tend) { // insert the stick on the polygon perimeter
 | ||||
| 
 | ||||
|                 // calculate the stick rectangle vertices and insert them
 | ||||
|                 // into the output.
 | ||||
|                 Point p1 = a + (t * dir).cast<coord_t>(); | ||||
|                 Point p2 = p1 + (sright * dirp).cast<coord_t>(); | ||||
|                 Point p3 = p2 + (sbottom * dir).cast<coord_t>(); | ||||
|                 Point p4 = p3 + (sright * -dirp).cast<coord_t>(); | ||||
|                 out.insert(out.end(), {p1, p2, p3, p4}); | ||||
| 
 | ||||
|                 // continue along the perimeter
 | ||||
|                 t += sstride; | ||||
|             } | ||||
| 
 | ||||
|             t = t - nrm; | ||||
| 
 | ||||
|             // Insert edge endpoint
 | ||||
|             out.emplace_back(b); | ||||
|         } | ||||
|          | ||||
|         // move the new points
 | ||||
|         out.shrink_to_fit(); | ||||
|         pts.swap(out); | ||||
|     }; | ||||
|      | ||||
|     if(stride > 0.0 && stick_width > 0.0 && padding > 0.0) { | ||||
|         transf(poly.contour.points); | ||||
|         for (auto &h : poly.holes) transf(h.points); | ||||
|     } | ||||
|      | ||||
|     // svg.draw(poly);
 | ||||
|     // svg.Close();
 | ||||
| } | ||||
| 
 | ||||
| /// This method will create a rounded edge around a flat polygon in 3d space.
 | ||||
| /// 'base_plate' parameter is the target plate.
 | ||||
| /// 'radius' is the radius of the edges.
 | ||||
| /// 'degrees' is tells how much of a circle should be created as the rounding.
 | ||||
| ///     It should be in degrees, not radians.
 | ||||
| /// 'ceilheight_mm' is the Z coordinate of the flat polygon in 3D space.
 | ||||
| /// 'dir' Is the direction of the round edges: inward or outward
 | ||||
| /// 'thr' Throws if a cancel signal was received
 | ||||
| /// 'last_offset' An auxiliary output variable to save the last offsetted
 | ||||
| ///     version of 'base_plate'
 | ||||
| /// 'last_height' An auxiliary output to save the last z coordinate of the
 | ||||
| /// offsetted base_plate. In other words, where the rounded edges end.
 | ||||
| Contour3D round_edges(const ExPolygon& base_plate, | ||||
|                       double radius_mm, | ||||
|                       double degrees, | ||||
|                       double ceilheight_mm, | ||||
|                       bool dir, | ||||
|                       ThrowOnCancel thr, | ||||
|                       ExPolygon& last_offset, double& last_height) | ||||
| { | ||||
|     auto ob = base_plate; | ||||
|     auto ob_prev = ob; | ||||
|     double wh = ceilheight_mm, wh_prev = wh; | ||||
|     Contour3D curvedwalls; | ||||
| 
 | ||||
|     int steps = 30; | ||||
|     double stepx = radius_mm / steps; | ||||
|     coord_t s = dir? 1 : -1; | ||||
|     degrees = std::fmod(degrees, 180); | ||||
| 
 | ||||
|     // we use sin for x distance because we interpret the angle starting from
 | ||||
|     // PI/2
 | ||||
|     int tos = degrees < 90? | ||||
|             int(radius_mm*std::cos(degrees * PI / 180 - PI/2) / stepx) : steps; | ||||
| 
 | ||||
|     for(int i = 1; i <= tos; ++i) { | ||||
|         thr(); | ||||
| 
 | ||||
|         ob = base_plate; | ||||
| 
 | ||||
|         double r2 = radius_mm * radius_mm; | ||||
|         double xx = i*stepx; | ||||
|         double x2 = xx*xx; | ||||
|         double stepy = std::sqrt(r2 - x2); | ||||
| 
 | ||||
|         offset(ob, s * scaled(xx)); | ||||
|         wh = ceilheight_mm - radius_mm + stepy; | ||||
| 
 | ||||
|         Contour3D pwalls; | ||||
|         double prev_x = xx - (i - 1) * stepx; | ||||
|         pwalls = walls(ob.contour, ob_prev.contour, wh, wh_prev, s*prev_x, thr); | ||||
| 
 | ||||
|         curvedwalls.merge(pwalls); | ||||
|         ob_prev = ob; | ||||
|         wh_prev = wh; | ||||
|     } | ||||
| 
 | ||||
|     if(degrees > 90) { | ||||
|         double tox = radius_mm - radius_mm*std::cos(degrees * PI / 180 - PI/2); | ||||
|         int tos = int(tox / stepx); | ||||
| 
 | ||||
|         for(int i = 1; i <= tos; ++i) { | ||||
|             thr(); | ||||
|             ob = base_plate; | ||||
| 
 | ||||
|             double r2 = radius_mm * radius_mm; | ||||
|             double xx = radius_mm - i*stepx; | ||||
|             double x2 = xx*xx; | ||||
|             double stepy = std::sqrt(r2 - x2); | ||||
|             offset(ob, s * scaled(xx)); | ||||
|             wh = ceilheight_mm - radius_mm - stepy; | ||||
| 
 | ||||
|             Contour3D pwalls; | ||||
|             double prev_x = xx - radius_mm + (i - 1)*stepx; | ||||
|             pwalls = | ||||
|                 walls(ob_prev.contour, ob.contour, wh_prev, wh, s*prev_x, thr); | ||||
| 
 | ||||
|             curvedwalls.merge(pwalls); | ||||
|             ob_prev = ob; | ||||
|             wh_prev = wh; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     last_offset = std::move(ob); | ||||
|     last_height = wh; | ||||
| 
 | ||||
|     return curvedwalls; | ||||
| } | ||||
| 
 | ||||
| inline Point centroid(Points& pp) { | ||||
|     Point c; | ||||
|     switch(pp.size()) { | ||||
|     case 0: break; | ||||
|     case 1: c = pp.front(); break; | ||||
|     case 2: c = (pp[0] + pp[1]) / 2; break; | ||||
|     default: { | ||||
|         auto MAX = std::numeric_limits<Point::coord_type>::max(); | ||||
|         auto MIN = std::numeric_limits<Point::coord_type>::min(); | ||||
|         Point min = {MAX, MAX}, max = {MIN, MIN}; | ||||
| 
 | ||||
|         for(auto& p : pp) { | ||||
|             if(p(0) < min(0)) min(0) = p(0); | ||||
|             if(p(1) < min(1)) min(1) = p(1); | ||||
|             if(p(0) > max(0)) max(0) = p(0); | ||||
|             if(p(1) > max(1)) max(1) = p(1); | ||||
|         } | ||||
|         c(0) = min(0) + (max(0) - min(0)) / 2; | ||||
|         c(1) = min(1) + (max(1) - min(1)) / 2; | ||||
| 
 | ||||
|         // TODO: fails for non convex cluster
 | ||||
| //        c = std::accumulate(pp.begin(), pp.end(), Point{0, 0});
 | ||||
| //        x(c) /= coord_t(pp.size()); y(c) /= coord_t(pp.size());
 | ||||
|         break; | ||||
|     } | ||||
|     } | ||||
| 
 | ||||
|     return c; | ||||
| } | ||||
| 
 | ||||
| inline Point centroid(const Polygon& poly) { | ||||
|     return poly.centroid(); | ||||
| } | ||||
| 
 | ||||
| /// A fake concave hull that is constructed by connecting separate shapes
 | ||||
| /// with explicit bridges. Bridges are generated from each shape's centroid
 | ||||
| /// to the center of the "scene" which is the centroid calculated from the shape
 | ||||
| /// centroids (a star is created...)
 | ||||
| Polygons concave_hull(const Polygons& polys, double maxd_mm, ThrowOnCancel thr) | ||||
| { | ||||
|     namespace bgi = boost::geometry::index; | ||||
|     using SpatElement = std::pair<Point, unsigned>; | ||||
|     using SpatIndex = bgi::rtree< SpatElement, bgi::rstar<16, 4> >; | ||||
| 
 | ||||
|     if(polys.empty()) return Polygons(); | ||||
|      | ||||
|     const double max_dist = scaled(maxd_mm); | ||||
| 
 | ||||
|     Polygons punion = unify(polys);   // could be redundant
 | ||||
| 
 | ||||
|     if(punion.size() == 1) return punion; | ||||
| 
 | ||||
|     // We get the centroids of all the islands in the 2D slice
 | ||||
|     Points centroids; centroids.reserve(punion.size()); | ||||
|     std::transform(punion.begin(), punion.end(), std::back_inserter(centroids), | ||||
|                    [](const Polygon& poly) { return centroid(poly); }); | ||||
| 
 | ||||
|     SpatIndex ctrindex; | ||||
|     unsigned  idx = 0; | ||||
|     for(const Point &ct : centroids) ctrindex.insert(std::make_pair(ct, idx++)); | ||||
|      | ||||
|     // Centroid of the centroids of islands. This is where the additional
 | ||||
|     // connector sticks are routed.
 | ||||
|     Point cc = centroid(centroids); | ||||
| 
 | ||||
|     punion.reserve(punion.size() + centroids.size()); | ||||
| 
 | ||||
|     idx = 0; | ||||
|     std::transform(centroids.begin(), centroids.end(), | ||||
|                    std::back_inserter(punion), | ||||
|                    [¢roids, &ctrindex, cc, max_dist, &idx, thr] | ||||
|                    (const Point& c) | ||||
|     { | ||||
|         thr(); | ||||
|         double dx = x(c) - x(cc), dy = y(c) - y(cc); | ||||
|         double l = std::sqrt(dx * dx + dy * dy); | ||||
|         double nx = dx / l, ny = dy / l; | ||||
|          | ||||
|         Point& ct = centroids[idx]; | ||||
|          | ||||
|         std::vector<SpatElement> result; | ||||
|         ctrindex.query(bgi::nearest(ct, 2), std::back_inserter(result)); | ||||
| 
 | ||||
|         double dist = max_dist; | ||||
|         for (const SpatElement &el : result) | ||||
|             if (el.second != idx) { | ||||
|                 dist = Line(el.first, ct).length(); | ||||
|                 break; | ||||
|             } | ||||
|          | ||||
|         idx++; | ||||
|          | ||||
|         if (dist >= max_dist) return Polygon(); | ||||
|          | ||||
|         Polygon r; | ||||
|         auto& ctour = r.points; | ||||
| 
 | ||||
|         ctour.reserve(3); | ||||
|         ctour.emplace_back(cc); | ||||
| 
 | ||||
|         Point d(scaled(nx), scaled(ny)); | ||||
|         ctour.emplace_back(c + Point( -y(d),  x(d) )); | ||||
|         ctour.emplace_back(c + Point(  y(d), -x(d) )); | ||||
|         offset(r, scaled(1.)); | ||||
| 
 | ||||
|         return r; | ||||
|     }); | ||||
| 
 | ||||
|     // This is unavoidable...
 | ||||
|     punion = unify(punion); | ||||
| 
 | ||||
|     return punion; | ||||
| } | ||||
| 
 | ||||
| void base_plate(const TriangleMesh &      mesh, | ||||
|                 ExPolygons &              output, | ||||
|                 const std::vector<float> &heights, | ||||
|                 ThrowOnCancel             thrfn) | ||||
| { | ||||
|     if (mesh.empty()) return; | ||||
|     //    m.require_shared_vertices(); // TriangleMeshSlicer needs this    
 | ||||
|     TriangleMeshSlicer slicer(&mesh); | ||||
|      | ||||
|     std::vector<ExPolygons> out; out.reserve(heights.size()); | ||||
|     slicer.slice(heights, 0.f, &out, thrfn); | ||||
|      | ||||
|     size_t count = 0; for(auto& o : out) count += o.size(); | ||||
|      | ||||
|     // Now we have to unify all slice layers which can be an expensive operation
 | ||||
|     // so we will try to simplify the polygons
 | ||||
|     ExPolygons tmp; tmp.reserve(count); | ||||
|     for(ExPolygons& o : out) | ||||
|         for(ExPolygon& e : o) { | ||||
|             auto&& exss = e.simplify(scaled<double>(0.1)); | ||||
|             for(ExPolygon& ep : exss) tmp.emplace_back(std::move(ep)); | ||||
|         } | ||||
|      | ||||
|     ExPolygons utmp = unify(tmp); | ||||
|      | ||||
|     for(auto& o : utmp) { | ||||
|         auto&& smp = o.simplify(scaled<double>(0.1)); | ||||
|         output.insert(output.end(), smp.begin(), smp.end()); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void base_plate(const TriangleMesh &mesh, | ||||
|                 ExPolygons &        output, | ||||
|                 float               h, | ||||
|                 float               layerh, | ||||
|                 ThrowOnCancel       thrfn) | ||||
| { | ||||
|     auto bb = mesh.bounding_box(); | ||||
|     float gnd = float(bb.min(Z)); | ||||
|     std::vector<float> heights = {float(bb.min(Z))}; | ||||
|      | ||||
|     for(float hi = gnd + layerh; hi <= gnd + h; hi += layerh) | ||||
|         heights.emplace_back(hi); | ||||
|      | ||||
|     base_plate(mesh, output, heights, thrfn); | ||||
| } | ||||
| 
 | ||||
| Contour3D create_base_pool(const Polygons &ground_layer,  | ||||
|                            const ExPolygons &obj_self_pad = {}, | ||||
|                            const PoolConfig& cfg = PoolConfig())  | ||||
| { | ||||
|     // for debugging:
 | ||||
|     // Benchmark bench;
 | ||||
|     // bench.start();
 | ||||
| 
 | ||||
|     double mergedist = 2*(1.8*cfg.min_wall_thickness_mm + 4*cfg.edge_radius_mm)+ | ||||
|                        cfg.max_merge_distance_mm; | ||||
| 
 | ||||
|     // Here we get the base polygon from which the pad has to be generated.
 | ||||
|     // We create an artificial concave hull from this polygon and that will
 | ||||
|     // serve as the bottom plate of the pad. We will offset this concave hull
 | ||||
|     // and then offset back the result with clipper with rounding edges ON. This
 | ||||
|     // trick will create a nice rounded pad shape.
 | ||||
|     Polygons concavehs = concave_hull(ground_layer, mergedist, cfg.throw_on_cancel); | ||||
| 
 | ||||
|     const double thickness      = cfg.min_wall_thickness_mm; | ||||
|     const double wingheight     = cfg.min_wall_height_mm; | ||||
|     const double fullheight     = wingheight + thickness; | ||||
|     const double slope          = cfg.wall_slope; | ||||
|     const double wingdist       = wingheight / std::tan(slope); | ||||
|     const double bottom_offs    = (thickness + wingheight) / std::tan(slope); | ||||
| 
 | ||||
|     // scaled values
 | ||||
|     const coord_t s_thickness   = scaled(thickness); | ||||
|     const coord_t s_eradius     = scaled(cfg.edge_radius_mm); | ||||
|     const coord_t s_safety_dist = 2*s_eradius + coord_t(0.8*s_thickness); | ||||
|     const coord_t s_wingdist    = scaled(wingdist); | ||||
|     const coord_t s_bottom_offs = scaled(bottom_offs); | ||||
| 
 | ||||
|     auto& thrcl = cfg.throw_on_cancel; | ||||
| 
 | ||||
|     Contour3D pool; | ||||
| 
 | ||||
|     for(Polygon& concaveh : concavehs) { | ||||
|         if(concaveh.points.empty()) return pool; | ||||
| 
 | ||||
|         // Here lies the trick that does the smoothing only with clipper offset
 | ||||
|         // calls. The offset is configured to round edges. Inner edges will
 | ||||
|         // be rounded because we offset twice: ones to get the outer (top) plate
 | ||||
|         // and again to get the inner (bottom) plate
 | ||||
|         auto outer_base = concaveh; | ||||
|         offset(outer_base, s_safety_dist + s_wingdist + s_thickness); | ||||
| 
 | ||||
|         ExPolygon bottom_poly; bottom_poly.contour = outer_base; | ||||
|         offset(bottom_poly, -s_bottom_offs); | ||||
| 
 | ||||
|         // Punching a hole in the top plate for the cavity
 | ||||
|         ExPolygon top_poly; | ||||
|         ExPolygon middle_base; | ||||
|         ExPolygon inner_base; | ||||
|         top_poly.contour = outer_base; | ||||
| 
 | ||||
|         if(wingheight > 0) { | ||||
|             inner_base.contour = outer_base; | ||||
|             offset(inner_base, -(s_thickness + s_wingdist + s_eradius)); | ||||
| 
 | ||||
|             middle_base.contour = outer_base; | ||||
|             offset(middle_base, -s_thickness); | ||||
|             top_poly.holes.emplace_back(middle_base.contour); | ||||
|             auto& tph = top_poly.holes.back().points; | ||||
|             std::reverse(tph.begin(), tph.end()); | ||||
|         } | ||||
| 
 | ||||
|         ExPolygon ob; ob.contour = outer_base; double wh = 0; | ||||
| 
 | ||||
|         // now we will calculate the angle or portion of the circle from
 | ||||
|         // pi/2 that will connect perfectly with the bottom plate.
 | ||||
|         // this is a tangent point calculation problem and the equation can
 | ||||
|         // be found for example here:
 | ||||
|         // http://www.ambrsoft.com/TrigoCalc/Circles2/CirclePoint/CirclePointDistance.htm
 | ||||
|         // the y coordinate would be:
 | ||||
|         // y = cy + (r^2*py - r*px*sqrt(px^2 + py^2 - r^2) / (px^2 + py^2)
 | ||||
|         // where px and py are the coordinates of the point outside the circle
 | ||||
|         // cx and cy are the circle center, r is the radius
 | ||||
|         // We place the circle center to (0, 0) in the calculation the make
 | ||||
|         // things easier.
 | ||||
|         // to get the angle we use arcsin function and subtract 90 degrees then
 | ||||
|         // flip the sign to get the right input to the round_edge function.
 | ||||
|         double r = cfg.edge_radius_mm; | ||||
|         double cy = 0; | ||||
|         double cx = 0; | ||||
|         double px = thickness + wingdist; | ||||
|         double py = r - fullheight; | ||||
| 
 | ||||
|         double pxcx = px - cx; | ||||
|         double pycy = py - cy; | ||||
|         double b_2 = pxcx*pxcx + pycy*pycy; | ||||
|         double r_2 = r*r; | ||||
|         double D = std::sqrt(b_2 - r_2); | ||||
|         double vy = (r_2*pycy - r*pxcx*D) / b_2; | ||||
|         double phi = -(std::asin(vy/r) * 180 / PI - 90); | ||||
| 
 | ||||
| 
 | ||||
|         // Generate the smoothed edge geometry
 | ||||
|         if(s_eradius > 0) pool.merge(round_edges(ob, | ||||
|                                r, | ||||
|                                phi, | ||||
|                                0,    // z position of the input plane
 | ||||
|                                true, | ||||
|                                thrcl, | ||||
|                                ob, wh)); | ||||
| 
 | ||||
|         // Now that we have the rounded edge connecting the top plate with
 | ||||
|         // the outer side walls, we can generate and merge the sidewall geometry
 | ||||
|         pool.merge(walls(ob.contour, bottom_poly.contour, wh, -fullheight, | ||||
|                          bottom_offs, thrcl)); | ||||
| 
 | ||||
|         if(wingheight > 0) { | ||||
|             // Generate the smoothed edge geometry
 | ||||
|             wh = 0; | ||||
|             ob = middle_base; | ||||
|             if(s_eradius) pool.merge(round_edges(middle_base, | ||||
|                                    r, | ||||
|                                    phi - 90, // from tangent lines
 | ||||
|                                    0,  // z position of the input plane
 | ||||
|                                    false, | ||||
|                                    thrcl, | ||||
|                                    ob, wh)); | ||||
| 
 | ||||
|             // Next is the cavity walls connecting to the top plate's
 | ||||
|             // artificially created hole.
 | ||||
|             pool.merge(walls(inner_base.contour, ob.contour, -wingheight, | ||||
|                              wh, -wingdist, thrcl)); | ||||
|         } | ||||
| 
 | ||||
|         if (cfg.embed_object) { | ||||
|             ExPolygons bttms = diff_ex(to_polygons(bottom_poly), | ||||
|                                        to_polygons(obj_self_pad)); | ||||
|              | ||||
|             assert(!bttms.empty()); | ||||
|              | ||||
|             std::sort(bttms.begin(), bttms.end(), | ||||
|                       [](const ExPolygon& e1, const ExPolygon& e2) { | ||||
|                           return e1.contour.area() > e2.contour.area(); | ||||
|                       }); | ||||
|              | ||||
|             if(wingheight > 0) inner_base.holes = bttms.front().holes; | ||||
|             else top_poly.holes = bttms.front().holes; | ||||
| 
 | ||||
|             auto straight_walls = | ||||
|                 [&pool](const Polygon &cntr, coord_t z_low, coord_t z_high) { | ||||
|                      | ||||
|                 auto lines = cntr.lines(); | ||||
|                  | ||||
|                 for (auto &l : lines) { | ||||
|                     auto s = coord_t(pool.points.size()); | ||||
|                     auto& pts = pool.points; | ||||
|                     pts.emplace_back(unscale(l.a.x(), l.a.y(), z_low)); | ||||
|                     pts.emplace_back(unscale(l.b.x(), l.b.y(), z_low)); | ||||
|                     pts.emplace_back(unscale(l.a.x(), l.a.y(), z_high)); | ||||
|                     pts.emplace_back(unscale(l.b.x(), l.b.y(), z_high)); | ||||
|                      | ||||
|                     pool.indices.emplace_back(s, s + 1, s + 3); | ||||
|                     pool.indices.emplace_back(s, s + 3, s + 2); | ||||
|                 } | ||||
|             }; | ||||
|              | ||||
|             coord_t z_lo = -scaled(fullheight), z_hi = -scaled(wingheight); | ||||
|             for (ExPolygon &ep : bttms) { | ||||
|                 pool.merge(triangulate_expolygon_3d(ep, -fullheight, true)); | ||||
|                 for (auto &h : ep.holes) straight_walls(h, z_lo, z_hi); | ||||
|             } | ||||
|              | ||||
|             // Skip the outer contour, triangulate the holes
 | ||||
|             for (auto it = std::next(bttms.begin()); it != bttms.end(); ++it) { | ||||
|                 pool.merge(triangulate_expolygon_3d(*it, -wingheight)); | ||||
|                 straight_walls(it->contour, z_lo, z_hi); | ||||
|             } | ||||
|              | ||||
|         } else { | ||||
|             // Now we need to triangulate the top and bottom plates as well as
 | ||||
|             // the cavity bottom plate which is the same as the bottom plate
 | ||||
|             // but it is elevated by the thickness.
 | ||||
|              | ||||
|             pool.merge(triangulate_expolygon_3d(bottom_poly, -fullheight, true)); | ||||
|         } | ||||
|          | ||||
|         pool.merge(triangulate_expolygon_3d(top_poly)); | ||||
| 
 | ||||
|         if(wingheight > 0) | ||||
|             pool.merge(triangulate_expolygon_3d(inner_base, -wingheight)); | ||||
| 
 | ||||
|     } | ||||
|      | ||||
|     return pool; | ||||
| } | ||||
| 
 | ||||
| void create_base_pool(const Polygons &ground_layer, TriangleMesh& out, | ||||
|                       const ExPolygons &holes, const PoolConfig& cfg) | ||||
| { | ||||
|      | ||||
| 
 | ||||
|     // For debugging:
 | ||||
|     // bench.stop();
 | ||||
|     // std::cout << "Pad creation time: " << bench.getElapsedSec() << std::endl;
 | ||||
|     // std::fstream fout("pad_debug.obj", std::fstream::out);
 | ||||
|     // if(fout.good()) pool.to_obj(fout);
 | ||||
| 
 | ||||
|     out.merge(mesh(create_base_pool(ground_layer, holes, cfg))); | ||||
| } | ||||
| 
 | ||||
| } | ||||
| } | ||||
|  | @ -1,92 +0,0 @@ | |||
| #ifndef SLABASEPOOL_HPP | ||||
| #define SLABASEPOOL_HPP | ||||
| 
 | ||||
| #include <vector> | ||||
| #include <functional> | ||||
| #include <cmath> | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
| class ExPolygon; | ||||
| class Polygon; | ||||
| using ExPolygons = std::vector<ExPolygon>; | ||||
| using Polygons = std::vector<Polygon>; | ||||
| 
 | ||||
| class TriangleMesh; | ||||
| 
 | ||||
| namespace sla { | ||||
| 
 | ||||
| using ThrowOnCancel = std::function<void(void)>; | ||||
| 
 | ||||
| /// Calculate the polygon representing the silhouette from the specified height
 | ||||
| void base_plate(const TriangleMesh& mesh,       // input mesh
 | ||||
|                 ExPolygons& output,             // Output will be merged with
 | ||||
|                 float samplingheight = 0.1f,    // The height range to sample
 | ||||
|                 float layerheight = 0.05f,      // The sampling height
 | ||||
|                 ThrowOnCancel thrfn = [](){});  // Will be called frequently
 | ||||
| 
 | ||||
| void base_plate(const TriangleMesh& mesh,       // input mesh
 | ||||
|                 ExPolygons& output,             // Output will be merged with
 | ||||
|                 const std::vector<float>&,      // Exact Z levels to sample
 | ||||
|                 ThrowOnCancel thrfn = [](){});  // Will be called frequently
 | ||||
| 
 | ||||
| // Function to cut tiny connector cavities for a given polygon. The input poly
 | ||||
| // will be offsetted by "padding" and small rectangle shaped cavities will be
 | ||||
| // inserted along the perimeter in every "stride" distance. The stick rectangles
 | ||||
| // will have a with about "stick_width". The input dimensions are in world 
 | ||||
| // measure, not the scaled clipper units.
 | ||||
| void breakstick_holes(ExPolygon &poly, | ||||
|                       double     padding, | ||||
|                       double     stride, | ||||
|                       double     stick_width, | ||||
|                       double     penetration = 0.0); | ||||
| 
 | ||||
| Polygons concave_hull(const Polygons& polys, double max_dist_mm = 50, | ||||
|                       ThrowOnCancel throw_on_cancel = [](){}); | ||||
| 
 | ||||
| struct PoolConfig { | ||||
|     double min_wall_thickness_mm = 2; | ||||
|     double min_wall_height_mm = 5; | ||||
|     double max_merge_distance_mm = 50; | ||||
|     double edge_radius_mm = 1; | ||||
|     double wall_slope = std::atan(1.0);          // Universal constant for Pi/4
 | ||||
|     struct EmbedObject { | ||||
|         double object_gap_mm = 0.5; | ||||
|         double stick_stride_mm = 10; | ||||
|         double stick_width_mm = 0.3; | ||||
|         double stick_penetration_mm = 0.1; | ||||
|         bool enabled = false; | ||||
|         operator bool() const { return enabled; } | ||||
|     } embed_object; | ||||
| 
 | ||||
|     ThrowOnCancel throw_on_cancel = [](){}; | ||||
| 
 | ||||
|     inline PoolConfig() {} | ||||
|     inline PoolConfig(double wt, double wh, double md, double er, double slope): | ||||
|         min_wall_thickness_mm(wt), | ||||
|         min_wall_height_mm(wh), | ||||
|         max_merge_distance_mm(md), | ||||
|         edge_radius_mm(er), | ||||
|         wall_slope(slope) {} | ||||
| }; | ||||
| 
 | ||||
| /// Calculate the pool for the mesh for SLA printing
 | ||||
| void create_base_pool(const Polygons& base_plate, | ||||
|                       TriangleMesh& output_mesh, | ||||
|                       const ExPolygons& holes, | ||||
|                       const PoolConfig& = PoolConfig()); | ||||
| 
 | ||||
| /// Returns the elevation needed for compensating the pad.
 | ||||
| inline double get_pad_elevation(const PoolConfig& cfg) { | ||||
|     return cfg.min_wall_thickness_mm; | ||||
| } | ||||
| 
 | ||||
| inline double get_pad_fullheight(const PoolConfig& cfg) { | ||||
|     return cfg.min_wall_height_mm + cfg.min_wall_thickness_mm; | ||||
| } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| #endif // SLABASEPOOL_HPP
 | ||||
|  | @ -8,35 +8,19 @@ | |||
| #include <libslic3r/ExPolygon.hpp> | ||||
| #include <libslic3r/TriangleMesh.hpp> | ||||
| 
 | ||||
| #include "SLACommon.hpp" | ||||
| #include "SLASpatIndex.hpp" | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| namespace sla { | ||||
| 
 | ||||
| /// Get x and y coordinates (because we are eigenizing...)
 | ||||
| inline coord_t x(const Point& p) { return p(0); } | ||||
| inline coord_t y(const Point& p) { return p(1); } | ||||
| inline coord_t& x(Point& p) { return p(0); } | ||||
| inline coord_t& y(Point& p) { return p(1); } | ||||
| 
 | ||||
| inline coordf_t x(const Vec3d& p) { return p(0); } | ||||
| inline coordf_t y(const Vec3d& p) { return p(1); } | ||||
| inline coordf_t z(const Vec3d& p) { return p(2); } | ||||
| inline coordf_t& x(Vec3d& p) { return p(0); } | ||||
| inline coordf_t& y(Vec3d& p) { return p(1); } | ||||
| inline coordf_t& z(Vec3d& p) { return p(2); } | ||||
| 
 | ||||
| inline coord_t& x(Vec3crd& p) { return p(0); } | ||||
| inline coord_t& y(Vec3crd& p) { return p(1); } | ||||
| inline coord_t& z(Vec3crd& p) { return p(2); } | ||||
| inline coord_t x(const Vec3crd& p) { return p(0); } | ||||
| inline coord_t y(const Vec3crd& p) { return p(1); } | ||||
| inline coord_t z(const Vec3crd& p) { return p(2); } | ||||
| 
 | ||||
| /// Intermediate struct for a 3D mesh
 | ||||
| struct Contour3D { | ||||
|     Pointf3s points; | ||||
|     std::vector<Vec3i> indices; | ||||
| 
 | ||||
|     void merge(const Contour3D& ctr) { | ||||
|     Contour3D& merge(const Contour3D& ctr) | ||||
|     { | ||||
|         auto s3 = coord_t(points.size()); | ||||
|         auto s = indices.size(); | ||||
| 
 | ||||
|  | @ -44,21 +28,27 @@ struct Contour3D { | |||
|         indices.insert(indices.end(), ctr.indices.begin(), ctr.indices.end()); | ||||
| 
 | ||||
|         for(size_t n = s; n < indices.size(); n++) { | ||||
|             auto& idx = indices[n]; x(idx) += s3; y(idx) += s3; z(idx) += s3; | ||||
|             auto& idx = indices[n]; idx.x() += s3; idx.y() += s3; idx.z() += s3; | ||||
|         } | ||||
|          | ||||
|         return *this; | ||||
|     } | ||||
| 
 | ||||
|     void merge(const Pointf3s& triangles) { | ||||
|     Contour3D& merge(const Pointf3s& triangles) | ||||
|     { | ||||
|         const size_t offs = points.size(); | ||||
|         points.insert(points.end(), triangles.begin(), triangles.end()); | ||||
|         indices.reserve(indices.size() + points.size() / 3); | ||||
| 
 | ||||
|         for(int i = (int)offs; i < (int)points.size(); i += 3) | ||||
|          | ||||
|         for(int i = int(offs); i < int(points.size()); i += 3) | ||||
|             indices.emplace_back(i, i + 1, i + 2); | ||||
|          | ||||
|         return *this; | ||||
|     } | ||||
| 
 | ||||
|     // Write the index triangle structure to OBJ file for debugging purposes.
 | ||||
|     void to_obj(std::ostream& stream) { | ||||
|     void to_obj(std::ostream& stream) | ||||
|     { | ||||
|         for(auto& p : points) { | ||||
|             stream << "v " << p.transpose() << "\n"; | ||||
|         } | ||||
|  | @ -72,6 +62,31 @@ struct Contour3D { | |||
| using ClusterEl = std::vector<unsigned>; | ||||
| using ClusteredPoints = std::vector<ClusterEl>; | ||||
| 
 | ||||
| // Clustering a set of points by the given distance.
 | ||||
| ClusteredPoints cluster(const std::vector<unsigned>& indices, | ||||
|                         std::function<Vec3d(unsigned)> pointfn, | ||||
|                         double dist, | ||||
|                         unsigned max_points); | ||||
| 
 | ||||
| ClusteredPoints cluster(const PointSet& points, | ||||
|                         double dist, | ||||
|                         unsigned max_points); | ||||
| 
 | ||||
| ClusteredPoints cluster( | ||||
|     const std::vector<unsigned>& indices, | ||||
|     std::function<Vec3d(unsigned)> pointfn, | ||||
|     std::function<bool(const PointIndexEl&, const PointIndexEl&)> predicate, | ||||
|     unsigned max_points); | ||||
| 
 | ||||
| 
 | ||||
| // Calculate the normals for the selected points (from 'points' set) on the
 | ||||
| // mesh. This will call squared distance for each point.
 | ||||
| PointSet normals(const PointSet& points, | ||||
|                  const EigenMesh3D& mesh, | ||||
|                  double eps = 0.05,  // min distance from edges
 | ||||
|                  std::function<void()> throw_on_cancel = [](){}, | ||||
|                  const std::vector<unsigned>& selected_points = {}); | ||||
| 
 | ||||
| /// Mesh from an existing contour.
 | ||||
| inline TriangleMesh mesh(const Contour3D& ctour) { | ||||
|     return {ctour.points, ctour.indices}; | ||||
|  |  | |||
|  | @ -1,8 +1,9 @@ | |||
| #ifndef SLACOMMON_HPP | ||||
| #define SLACOMMON_HPP | ||||
| 
 | ||||
| #include <Eigen/Geometry> | ||||
| #include <memory> | ||||
| #include <vector> | ||||
| #include <Eigen/Geometry> | ||||
| 
 | ||||
| // #define SLIC3R_SLA_NEEDS_WINDTREE
 | ||||
| 
 | ||||
|  | @ -69,6 +70,8 @@ struct SupportPoint | |||
|     } | ||||
| }; | ||||
| 
 | ||||
| using SupportPoints = std::vector<SupportPoint>; | ||||
| 
 | ||||
| /// An index-triangle structure for libIGL functions. Also serves as an
 | ||||
| /// alternative (raw) input format for the SLASupportTree
 | ||||
| class EigenMesh3D { | ||||
|  | @ -175,6 +178,8 @@ public: | |||
|     } | ||||
| }; | ||||
| 
 | ||||
| using PointSet = Eigen::MatrixXd; | ||||
| 
 | ||||
| } // namespace sla
 | ||||
| } // namespace Slic3r
 | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										56
									
								
								src/libslic3r/SLA/SLAConcurrency.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								src/libslic3r/SLA/SLAConcurrency.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,56 @@ | |||
| #ifndef SLACONCURRENCY_H | ||||
| #define SLACONCURRENCY_H | ||||
| 
 | ||||
| #include <tbb/spin_mutex.h> | ||||
| #include <tbb/mutex.h> | ||||
| #include <tbb/parallel_for.h> | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| namespace sla { | ||||
| 
 | ||||
| // Set this to true to enable full parallelism in this module.
 | ||||
| // Only the well tested parts will be concurrent if this is set to false.
 | ||||
| const constexpr bool USE_FULL_CONCURRENCY = true; | ||||
| 
 | ||||
| template<bool> struct _ccr {}; | ||||
| 
 | ||||
| template<> struct _ccr<true> | ||||
| { | ||||
|     using SpinningMutex = tbb::spin_mutex; | ||||
|     using BlockingMutex  = tbb::mutex; | ||||
|      | ||||
|     template<class It, class Fn> | ||||
|     static inline void enumerate(It from, It to, Fn fn) | ||||
|     { | ||||
|         auto   iN = to - from; | ||||
|         size_t N  = iN < 0 ? 0 : size_t(iN); | ||||
|          | ||||
|         tbb::parallel_for(size_t(0), N, [from, fn](size_t n) { | ||||
|             fn(*(from + decltype(iN)(n)), n); | ||||
|         }); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| template<> struct _ccr<false> | ||||
| { | ||||
| private: | ||||
|     struct _Mtx { inline void lock() {} inline void unlock() {} }; | ||||
|      | ||||
| public: | ||||
|     using SpinningMutex = _Mtx; | ||||
|     using BlockingMutex = _Mtx; | ||||
|      | ||||
|     template<class It, class Fn> | ||||
|     static inline void enumerate(It from, It to, Fn fn) | ||||
|     { | ||||
|         for (auto it = from; it != to; ++it) fn(*it, size_t(it - from)); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| using ccr = _ccr<USE_FULL_CONCURRENCY>; | ||||
| using ccr_seq = _ccr<false>; | ||||
| using ccr_par = _ccr<true>; | ||||
| 
 | ||||
| }} // namespace Slic3r::sla
 | ||||
| 
 | ||||
| #endif // SLACONCURRENCY_H
 | ||||
							
								
								
									
										870
									
								
								src/libslic3r/SLA/SLAPad.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										870
									
								
								src/libslic3r/SLA/SLAPad.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,870 @@ | |||
| #include "SLAPad.hpp" | ||||
| #include "SLABoilerPlate.hpp" | ||||
| #include "SLASpatIndex.hpp" | ||||
| 
 | ||||
| #include "boost/log/trivial.hpp" | ||||
| #include "SLABoostAdapter.hpp" | ||||
| #include "ClipperUtils.hpp" | ||||
| #include "Tesselate.hpp" | ||||
| #include "MTUtils.hpp" | ||||
| 
 | ||||
| // For debugging:
 | ||||
| // #include <fstream>
 | ||||
| // #include <libnest2d/tools/benchmark.h>
 | ||||
| #include "SVG.hpp" | ||||
| 
 | ||||
| #include "I18N.hpp" | ||||
| #include <boost/log/trivial.hpp> | ||||
| 
 | ||||
| //! macro used to mark string used at localization,
 | ||||
| //! return same string
 | ||||
| #define L(s) Slic3r::I18N::translate(s) | ||||
| 
 | ||||
| namespace Slic3r { namespace sla { | ||||
| 
 | ||||
| namespace { | ||||
| 
 | ||||
| /// This function will return a triangulation of a sheet connecting an upper
 | ||||
| /// and a lower plate given as input polygons. It will not triangulate the
 | ||||
| /// plates themselves only the sheet. The caller has to specify the lower and
 | ||||
| /// upper z levels in world coordinates as well as the offset difference
 | ||||
| /// between the sheets. If the lower_z_mm is higher than upper_z_mm or the
 | ||||
| /// offset difference is negative, the resulting triangle orientation will be
 | ||||
| /// reversed.
 | ||||
| ///
 | ||||
| /// IMPORTANT: This is not a universal triangulation algorithm. It assumes
 | ||||
| /// that the lower and upper polygons are offsetted versions of the same
 | ||||
| /// original polygon. In general, it assumes that one of the polygons is
 | ||||
| /// completely inside the other. The offset difference is the reference
 | ||||
| /// distance from the inner polygon's perimeter to the outer polygon's
 | ||||
| /// perimeter. The real distance will be variable as the clipper offset has
 | ||||
| /// different strategies (rounding, etc...). This algorithm should have
 | ||||
| /// O(2n + 3m) complexity where n is the number of upper vertices and m is the
 | ||||
| /// number of lower vertices.
 | ||||
| Contour3D walls( | ||||
|     const Polygon &lower, | ||||
|     const Polygon &upper, | ||||
|     double         lower_z_mm, | ||||
|     double         upper_z_mm, | ||||
|     double         offset_difference_mm, | ||||
|     ThrowOnCancel  thr = [] {}) | ||||
| { | ||||
|     Contour3D ret; | ||||
| 
 | ||||
|     if(upper.points.size() < 3 || lower.size() < 3) return ret; | ||||
| 
 | ||||
|     // The concept of the algorithm is relatively simple. It will try to find
 | ||||
|     // the closest vertices from the upper and the lower polygon and use those
 | ||||
|     // as starting points. Then it will create the triangles sequentially using
 | ||||
|     // an edge from the upper polygon and a vertex from the lower or vice versa,
 | ||||
|     // depending on the resulting triangle's quality.
 | ||||
|     // The quality is measured by a scalar value. So far it looks like it is
 | ||||
|     // enough to derive it from the slope of the triangle's two edges connecting
 | ||||
|     // the upper and the lower part. A reference slope is calculated from the
 | ||||
|     // height and the offset difference.
 | ||||
| 
 | ||||
|     // Offset in the index array for the ceiling
 | ||||
|     const auto offs = upper.points.size(); | ||||
| 
 | ||||
|     // Shorthand for the vertex arrays
 | ||||
|     auto& upts = upper.points, &lpts = lower.points; | ||||
|     auto& rpts = ret.points; auto& ind = ret.indices; | ||||
| 
 | ||||
|     // If the Z levels are flipped, or the offset difference is negative, we
 | ||||
|     // will interpret that as the triangles normals should be inverted.
 | ||||
|     bool inverted = upper_z_mm < lower_z_mm || offset_difference_mm < 0; | ||||
| 
 | ||||
|     // Copy the points into the mesh, convert them from 2D to 3D
 | ||||
|     rpts.reserve(upts.size() + lpts.size()); | ||||
|     ind.reserve(2 * upts.size() + 2 * lpts.size()); | ||||
|     for (auto &p : upts) | ||||
|         rpts.emplace_back(unscaled(p.x()), unscaled(p.y()), upper_z_mm); | ||||
|     for (auto &p : lpts) | ||||
|         rpts.emplace_back(unscaled(p.x()), unscaled(p.y()), lower_z_mm); | ||||
| 
 | ||||
|     // Create pointing indices into vertex arrays. u-upper, l-lower
 | ||||
|     size_t uidx = 0, lidx = offs, unextidx = 1, lnextidx = offs + 1; | ||||
| 
 | ||||
|     // Simple squared distance calculation.
 | ||||
|     auto distfn = [](const Vec3d& p1, const Vec3d& p2) { | ||||
|         auto p = p1 - p2; return p.transpose() * p; | ||||
|     }; | ||||
| 
 | ||||
|     // We need to find the closest point on lower polygon to the first point on
 | ||||
|     // the upper polygon. These will be our starting points.
 | ||||
|     double distmin = std::numeric_limits<double>::max(); | ||||
|     for(size_t l = lidx; l < rpts.size(); ++l) { | ||||
|         thr(); | ||||
|         double d = distfn(rpts[l], rpts[uidx]); | ||||
|         if(d < distmin) { lidx = l; distmin = d; } | ||||
|     } | ||||
| 
 | ||||
|     // Set up lnextidx to be ahead of lidx in cyclic mode
 | ||||
|     lnextidx = lidx + 1; | ||||
|     if(lnextidx == rpts.size()) lnextidx = offs; | ||||
| 
 | ||||
|     // This will be the flip switch to toggle between upper and lower triangle
 | ||||
|     // creation mode
 | ||||
|     enum class Proceed { | ||||
|         UPPER, // A segment from the upper polygon and one vertex from the lower
 | ||||
|         LOWER  // A segment from the lower polygon and one vertex from the upper
 | ||||
|     } proceed = Proceed::UPPER; | ||||
| 
 | ||||
|     // Flags to help evaluating loop termination.
 | ||||
|     bool ustarted = false, lstarted = false; | ||||
| 
 | ||||
|     // The variables for the fitness values, one for the actual and one for the
 | ||||
|     // previous.
 | ||||
|     double current_fit = 0, prev_fit = 0; | ||||
| 
 | ||||
|     // Every triangle of the wall has two edges connecting the upper plate with
 | ||||
|     // the lower plate. From the length of these two edges and the zdiff we
 | ||||
|     // can calculate the momentary squared offset distance at a particular
 | ||||
|     // position on the wall. The average of the differences from the reference
 | ||||
|     // (squared) offset distance will give us the driving fitness value.
 | ||||
|     const double offsdiff2 = std::pow(offset_difference_mm, 2); | ||||
|     const double zdiff2 = std::pow(upper_z_mm - lower_z_mm, 2); | ||||
| 
 | ||||
|     // Mark the current vertex iterator positions. If the iterators return to
 | ||||
|     // the same position, the loop can be terminated.
 | ||||
|     size_t uendidx = uidx, lendidx = lidx; | ||||
| 
 | ||||
|     do { thr();  // check throw if canceled
 | ||||
| 
 | ||||
|         prev_fit = current_fit; | ||||
| 
 | ||||
|         switch(proceed) {   // proceed depending on the current state
 | ||||
|         case Proceed::UPPER: | ||||
|             if(!ustarted || uidx != uendidx) { // there are vertices remaining
 | ||||
|                 // Get the 3D vertices in order
 | ||||
|                 const Vec3d& p_up1 = rpts[uidx]; | ||||
|                 const Vec3d& p_low = rpts[lidx]; | ||||
|                 const Vec3d& p_up2 = rpts[unextidx]; | ||||
| 
 | ||||
|                 // Calculate fitness: the average of the two connecting edges
 | ||||
|                 double a = offsdiff2 - (distfn(p_up1, p_low) - zdiff2); | ||||
|                 double b = offsdiff2 - (distfn(p_up2, p_low) - zdiff2); | ||||
|                 current_fit = (std::abs(a) + std::abs(b)) / 2; | ||||
| 
 | ||||
|                 if(current_fit > prev_fit) { // fit is worse than previously
 | ||||
|                     proceed = Proceed::LOWER; | ||||
|                 } else {    // good to go, create the triangle
 | ||||
|                     inverted | ||||
|                         ? ind.emplace_back(int(unextidx), int(lidx), int(uidx)) | ||||
|                         : ind.emplace_back(int(uidx), int(lidx), int(unextidx)); | ||||
| 
 | ||||
|                     // Increment the iterators, rotate if necessary
 | ||||
|                     ++uidx; ++unextidx; | ||||
|                     if(unextidx == offs) unextidx = 0; | ||||
|                     if(uidx == offs) uidx = 0; | ||||
| 
 | ||||
|                     ustarted = true;    // mark the movement of the iterators
 | ||||
|                     // so that the comparison to uendidx can be made correctly
 | ||||
|                 } | ||||
|             } else proceed = Proceed::LOWER; | ||||
| 
 | ||||
|             break; | ||||
|         case Proceed::LOWER: | ||||
|             // Mode with lower segment, upper vertex. Same structure:
 | ||||
|             if(!lstarted || lidx != lendidx) { | ||||
|                 const Vec3d& p_low1 = rpts[lidx]; | ||||
|                 const Vec3d& p_low2 = rpts[lnextidx]; | ||||
|                 const Vec3d& p_up   = rpts[uidx]; | ||||
| 
 | ||||
|                 double a = offsdiff2 - (distfn(p_up, p_low1) - zdiff2); | ||||
|                 double b = offsdiff2 - (distfn(p_up, p_low2) - zdiff2); | ||||
|                 current_fit = (std::abs(a) + std::abs(b)) / 2; | ||||
| 
 | ||||
|                 if(current_fit > prev_fit) { | ||||
|                     proceed = Proceed::UPPER; | ||||
|                 } else { | ||||
|                     inverted | ||||
|                         ? ind.emplace_back(int(uidx), int(lnextidx), int(lidx)) | ||||
|                         : ind.emplace_back(int(lidx), int(lnextidx), int(uidx)); | ||||
| 
 | ||||
|                     ++lidx; ++lnextidx; | ||||
|                     if(lnextidx == rpts.size()) lnextidx = offs; | ||||
|                     if(lidx == rpts.size()) lidx = offs; | ||||
| 
 | ||||
|                     lstarted = true; | ||||
|                 } | ||||
|             } else proceed = Proceed::UPPER; | ||||
| 
 | ||||
|             break; | ||||
|         } // end of switch
 | ||||
|     } while(!ustarted || !lstarted || uidx != uendidx || lidx != lendidx); | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| // Same as walls() but with identical higher and lower polygons.
 | ||||
| Contour3D inline straight_walls(const Polygon &plate, | ||||
|                                 double         lo_z, | ||||
|                                 double         hi_z, | ||||
|                                 ThrowOnCancel  thr) | ||||
| { | ||||
|     return walls(plate, plate, lo_z, hi_z, .0 /*offset_diff*/, thr); | ||||
| } | ||||
| 
 | ||||
| // As it shows, the current offset_ex in ClipperUtils hangs if used in jtRound
 | ||||
| // mode
 | ||||
| ClipperLib::Paths fast_offset(const ClipperLib::Paths &paths, | ||||
|                               coord_t                  delta, | ||||
|                               ClipperLib::JoinType     jointype) | ||||
| { | ||||
|     using ClipperLib::ClipperOffset; | ||||
|     using ClipperLib::etClosedPolygon; | ||||
|     using ClipperLib::Paths; | ||||
|     using ClipperLib::Path; | ||||
| 
 | ||||
|     ClipperOffset offs; | ||||
|     offs.ArcTolerance = scaled<double>(0.01); | ||||
| 
 | ||||
|     for (auto &p : paths) | ||||
|         // If the input is not at least a triangle, we can not do this algorithm
 | ||||
|         if(p.size() < 3) { | ||||
|             BOOST_LOG_TRIVIAL(error) << "Invalid geometry for offsetting!"; | ||||
|             return {}; | ||||
|         } | ||||
| 
 | ||||
|     offs.AddPaths(paths, jointype, etClosedPolygon); | ||||
| 
 | ||||
|     Paths result; | ||||
|     offs.Execute(result, static_cast<double>(delta)); | ||||
| 
 | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| // Function to cut tiny connector cavities for a given polygon. The input poly
 | ||||
| // will be offsetted by "padding" and small rectangle shaped cavities will be
 | ||||
| // inserted along the perimeter in every "stride" distance. The stick rectangles
 | ||||
| // will have a with about "stick_width". The input dimensions are in world
 | ||||
| // measure, not the scaled clipper units.
 | ||||
| void breakstick_holes(Points& pts, | ||||
|                       double padding, | ||||
|                       double stride, | ||||
|                       double stick_width, | ||||
|                       double penetration) | ||||
| { | ||||
|     if(stride <= EPSILON || stick_width <= EPSILON || padding <= EPSILON) | ||||
|         return; | ||||
| 
 | ||||
|     // SVG svg("bridgestick_plate.svg");
 | ||||
|     // svg.draw(poly);
 | ||||
| 
 | ||||
|     // The connector stick will be a small rectangle with dimensions
 | ||||
|     // stick_width x (penetration + padding) to have some penetration
 | ||||
|     // into the input polygon.
 | ||||
| 
 | ||||
|     Points out; | ||||
|     out.reserve(2 * pts.size()); // output polygon points
 | ||||
| 
 | ||||
|     // stick bottom and right edge dimensions
 | ||||
|     double sbottom = scaled(stick_width); | ||||
|     double sright  = scaled(penetration + padding); | ||||
| 
 | ||||
|     // scaled stride distance
 | ||||
|     double sstride = scaled(stride); | ||||
|     double t       = 0; | ||||
| 
 | ||||
|     // process pairs of vertices as an edge, start with the last and
 | ||||
|     // first point
 | ||||
|     for (size_t i = pts.size() - 1, j = 0; j < pts.size(); i = j, ++j) { | ||||
|         // Get vertices and the direction vectors
 | ||||
|         const Point &a = pts[i], &b = pts[j]; | ||||
|         Vec2d        dir = b.cast<double>() - a.cast<double>(); | ||||
|         double       nrm = dir.norm(); | ||||
|         dir /= nrm; | ||||
|         Vec2d dirp(-dir(Y), dir(X)); | ||||
| 
 | ||||
|         // Insert start point
 | ||||
|         out.emplace_back(a); | ||||
| 
 | ||||
|         // dodge the start point, do not make sticks on the joins
 | ||||
|         while (t < sbottom) t += sbottom; | ||||
|         double tend = nrm - sbottom; | ||||
| 
 | ||||
|         while (t < tend) { // insert the stick on the polygon perimeter
 | ||||
| 
 | ||||
|             // calculate the stick rectangle vertices and insert them
 | ||||
|             // into the output.
 | ||||
|             Point p1 = a + (t * dir).cast<coord_t>(); | ||||
|             Point p2 = p1 + (sright * dirp).cast<coord_t>(); | ||||
|             Point p3 = p2 + (sbottom * dir).cast<coord_t>(); | ||||
|             Point p4 = p3 + (sright * -dirp).cast<coord_t>(); | ||||
|             out.insert(out.end(), {p1, p2, p3, p4}); | ||||
| 
 | ||||
|             // continue along the perimeter
 | ||||
|             t += sstride; | ||||
|         } | ||||
| 
 | ||||
|         t = t - nrm; | ||||
| 
 | ||||
|         // Insert edge endpoint
 | ||||
|         out.emplace_back(b); | ||||
|     } | ||||
| 
 | ||||
|     // move the new points
 | ||||
|     out.shrink_to_fit(); | ||||
|     pts.swap(out); | ||||
| } | ||||
| 
 | ||||
| template<class...Args> | ||||
| ExPolygons breakstick_holes(const ExPolygons &input, Args...args) | ||||
| { | ||||
|     ExPolygons ret = input; | ||||
|     for (ExPolygon &p : ret) { | ||||
|         breakstick_holes(p.contour.points, args...); | ||||
|         for (auto &h : p.holes) breakstick_holes(h.points, args...); | ||||
|     } | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| /// A fake concave hull that is constructed by connecting separate shapes
 | ||||
| /// with explicit bridges. Bridges are generated from each shape's centroid
 | ||||
| /// to the center of the "scene" which is the centroid calculated from the shape
 | ||||
| /// centroids (a star is created...)
 | ||||
| class ConcaveHull { | ||||
|     Polygons m_polys; | ||||
| 
 | ||||
|     Point centroid(const Points& pp) const | ||||
|     { | ||||
|         Point c; | ||||
|         switch(pp.size()) { | ||||
|         case 0: break; | ||||
|         case 1: c = pp.front(); break; | ||||
|         case 2: c = (pp[0] + pp[1]) / 2; break; | ||||
|         default: { | ||||
|             auto MAX = std::numeric_limits<Point::coord_type>::max(); | ||||
|             auto MIN = std::numeric_limits<Point::coord_type>::min(); | ||||
|             Point min = {MAX, MAX}, max = {MIN, MIN}; | ||||
| 
 | ||||
|             for(auto& p : pp) { | ||||
|                 if(p(0) < min(0)) min(0) = p(0); | ||||
|                 if(p(1) < min(1)) min(1) = p(1); | ||||
|                 if(p(0) > max(0)) max(0) = p(0); | ||||
|                 if(p(1) > max(1)) max(1) = p(1); | ||||
|             } | ||||
|             c(0) = min(0) + (max(0) - min(0)) / 2; | ||||
|             c(1) = min(1) + (max(1) - min(1)) / 2; | ||||
|             break; | ||||
|         } | ||||
|         } | ||||
| 
 | ||||
|         return c; | ||||
|     } | ||||
| 
 | ||||
|     inline Point centroid(const Polygon &poly) const { return poly.centroid(); } | ||||
| 
 | ||||
|     Points calculate_centroids() const | ||||
|     { | ||||
|         // We get the centroids of all the islands in the 2D slice
 | ||||
|         Points centroids = reserve_vector<Point>(m_polys.size()); | ||||
|         std::transform(m_polys.begin(), m_polys.end(), | ||||
|                        std::back_inserter(centroids), | ||||
|                        [this](const Polygon &poly) { return centroid(poly); }); | ||||
| 
 | ||||
|         return centroids; | ||||
|     } | ||||
| 
 | ||||
|     void merge_polygons() { m_polys = union_(m_polys); } | ||||
| 
 | ||||
|     void add_connector_rectangles(const Points ¢roids, | ||||
|                                   coord_t       max_dist, | ||||
|                                   ThrowOnCancel thr) | ||||
|     { | ||||
|         namespace bgi = boost::geometry::index; | ||||
|         using PointIndexElement = std::pair<Point, unsigned>; | ||||
|         using PointIndex = bgi::rtree<PointIndexElement, bgi::rstar<16, 4>>; | ||||
| 
 | ||||
|         // Centroid of the centroids of islands. This is where the additional
 | ||||
|         // connector sticks are routed.
 | ||||
|         Point cc = centroid(centroids); | ||||
| 
 | ||||
|         PointIndex ctrindex; | ||||
|         unsigned  idx = 0; | ||||
|         for(const Point &ct : centroids) | ||||
|             ctrindex.insert(std::make_pair(ct, idx++)); | ||||
| 
 | ||||
|         m_polys.reserve(m_polys.size() + centroids.size()); | ||||
| 
 | ||||
|         idx = 0; | ||||
|         for (const Point &c : centroids) { | ||||
|             thr(); | ||||
| 
 | ||||
|             double dx = c.x() - cc.x(), dy = c.y() - cc.y(); | ||||
|             double l  = std::sqrt(dx * dx + dy * dy); | ||||
|             double nx = dx / l, ny = dy / l; | ||||
| 
 | ||||
|             const Point &ct = centroids[idx]; | ||||
| 
 | ||||
|             std::vector<PointIndexElement> result; | ||||
|             ctrindex.query(bgi::nearest(ct, 2), std::back_inserter(result)); | ||||
| 
 | ||||
|             double dist = max_dist; | ||||
|             for (const PointIndexElement &el : result) | ||||
|                 if (el.second != idx) { | ||||
|                     dist = Line(el.first, ct).length(); | ||||
|                     break; | ||||
|                 } | ||||
| 
 | ||||
|             idx++; | ||||
| 
 | ||||
|             if (dist >= max_dist) return; | ||||
| 
 | ||||
|             Polygon r; | ||||
|             r.points.reserve(3); | ||||
|             r.points.emplace_back(cc); | ||||
| 
 | ||||
|             Point d(scaled(nx), scaled(ny)); | ||||
|             r.points.emplace_back(c + Point(-d.y(), d.x())); | ||||
|             r.points.emplace_back(c + Point(d.y(), -d.x())); | ||||
|             offset(r, scaled<float>(1.)); | ||||
| 
 | ||||
|             m_polys.emplace_back(r); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| public: | ||||
| 
 | ||||
|     ConcaveHull(const ExPolygons& polys, double merge_dist, ThrowOnCancel thr) | ||||
|         : ConcaveHull{to_polygons(polys), merge_dist, thr} {} | ||||
| 
 | ||||
|     ConcaveHull(const Polygons& polys, double mergedist, ThrowOnCancel thr) | ||||
|     { | ||||
|         if(polys.empty()) return; | ||||
| 
 | ||||
|         m_polys = polys; | ||||
|         merge_polygons(); | ||||
| 
 | ||||
|         if(m_polys.size() == 1) return; | ||||
| 
 | ||||
|         Points centroids = calculate_centroids(); | ||||
| 
 | ||||
|         add_connector_rectangles(centroids, scaled(mergedist), thr); | ||||
| 
 | ||||
|         merge_polygons(); | ||||
|     } | ||||
| 
 | ||||
|     // const Polygons & polygons() const { return m_polys; }
 | ||||
| 
 | ||||
|     ExPolygons to_expolygons() const | ||||
|     { | ||||
|         auto ret = reserve_vector<ExPolygon>(m_polys.size()); | ||||
|         for (const Polygon &p : m_polys) ret.emplace_back(ExPolygon(p)); | ||||
|         return ret; | ||||
|     } | ||||
| 
 | ||||
|     void offset_waffle_style(coord_t delta) { | ||||
|         ClipperLib::Paths paths = Slic3rMultiPoints_to_ClipperPaths(m_polys); | ||||
|         paths = fast_offset(paths, 2 * delta, ClipperLib::jtRound); | ||||
|         paths = fast_offset(paths, -delta, ClipperLib::jtRound); | ||||
|         m_polys = ClipperPaths_to_Slic3rPolygons(paths); | ||||
|     } | ||||
| 
 | ||||
|     static inline coord_t get_waffle_offset(const PadConfig &c) | ||||
|     { | ||||
|         return scaled(c.brim_size_mm + c.wing_distance()); | ||||
|     } | ||||
| 
 | ||||
|     static inline double get_merge_distance(const PadConfig &c) | ||||
|     { | ||||
|         return 2. * (1.8 * c.wall_thickness_mm) + c.max_merge_dist_mm; | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| // Part of the pad configuration that is used for 3D geometry generation
 | ||||
| struct PadConfig3D { | ||||
|     double thickness, height, wing_height, slope; | ||||
| 
 | ||||
|     explicit PadConfig3D(const PadConfig &cfg2d) | ||||
|         : thickness{cfg2d.wall_thickness_mm} | ||||
|         , height{cfg2d.full_height()} | ||||
|         , wing_height{cfg2d.wall_height_mm} | ||||
|         , slope{cfg2d.wall_slope} | ||||
|     {} | ||||
| 
 | ||||
|     inline double bottom_offset() const | ||||
|     { | ||||
|         return (thickness + wing_height) / std::tan(slope); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| // Outer part of the skeleton is used to generate the waffled edges of the pad.
 | ||||
| // Inner parts will not be waffled or offsetted. Inner parts are only used if
 | ||||
| // pad is generated around the object and correspond to holes and inner polygons
 | ||||
| // in the model blueprint.
 | ||||
| struct PadSkeleton { ExPolygons inner, outer; }; | ||||
| 
 | ||||
| PadSkeleton divide_blueprint(const ExPolygons &bp) | ||||
| { | ||||
|     ClipperLib::PolyTree ptree = union_pt(bp); | ||||
| 
 | ||||
|     PadSkeleton ret; | ||||
|     ret.inner.reserve(size_t(ptree.Total())); | ||||
|     ret.outer.reserve(size_t(ptree.Total())); | ||||
| 
 | ||||
|     for (ClipperLib::PolyTree::PolyNode *node : ptree.Childs) { | ||||
|         ExPolygon poly(ClipperPath_to_Slic3rPolygon(node->Contour)); | ||||
|         for (ClipperLib::PolyTree::PolyNode *child : node->Childs) { | ||||
|             if (child->IsHole()) { | ||||
|                 poly.holes.emplace_back( | ||||
|                     ClipperPath_to_Slic3rPolygon(child->Contour)); | ||||
| 
 | ||||
|                 traverse_pt_unordered(child->Childs, &ret.inner); | ||||
|             } | ||||
|             else traverse_pt_unordered(child, &ret.inner); | ||||
|         } | ||||
| 
 | ||||
|         ret.outer.emplace_back(poly); | ||||
|     } | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| // A helper class for storing polygons and maintaining a spatial index of their
 | ||||
| // bounding boxes.
 | ||||
| class Intersector { | ||||
|     BoxIndex       m_index; | ||||
|     ExPolygons     m_polys; | ||||
| 
 | ||||
| public: | ||||
| 
 | ||||
|     // Add a new polygon to the index
 | ||||
|     void add(const ExPolygon &ep) | ||||
|     { | ||||
|         m_polys.emplace_back(ep); | ||||
|         m_index.insert(BoundingBox{ep}, unsigned(m_index.size())); | ||||
|     } | ||||
| 
 | ||||
|     // Check an arbitrary polygon for intersection with the indexed polygons
 | ||||
|     bool intersects(const ExPolygon &poly) | ||||
|     { | ||||
|         // Create a suitable query bounding box.
 | ||||
|         auto bb = poly.contour.bounding_box(); | ||||
| 
 | ||||
|         std::vector<BoxIndexEl> qres = m_index.query(bb, BoxIndex::qtIntersects); | ||||
| 
 | ||||
|         // Now check intersections on the actual polygons (not just the boxes)
 | ||||
|         bool is_overlap = false; | ||||
|         auto qit        = qres.begin(); | ||||
|         while (!is_overlap && qit != qres.end()) | ||||
|             is_overlap = is_overlap || poly.overlaps(m_polys[(qit++)->second]); | ||||
| 
 | ||||
|         return is_overlap; | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| // This dummy intersector to implement the "force pad everywhere" feature
 | ||||
| struct DummyIntersector | ||||
| { | ||||
|     inline void add(const ExPolygon &) {} | ||||
|     inline bool intersects(const ExPolygon &) { return true; } | ||||
| }; | ||||
| 
 | ||||
| template<class _Intersector> | ||||
| class _AroundPadSkeleton : public PadSkeleton | ||||
| { | ||||
|     // A spatial index used to be able to efficiently find intersections of
 | ||||
|     // support polygons with the model polygons.
 | ||||
|     _Intersector m_intersector; | ||||
| 
 | ||||
| public: | ||||
|     _AroundPadSkeleton(const ExPolygons &support_blueprint, | ||||
|                        const ExPolygons &model_blueprint, | ||||
|                        const PadConfig & cfg, | ||||
|                        ThrowOnCancel     thr) | ||||
|     { | ||||
|         // We need to merge the support and the model contours in a special
 | ||||
|         // way in which the model contours have to be substracted from the
 | ||||
|         // support contours. The pad has to have a hole in which the model can
 | ||||
|         // fit perfectly (thus the substraction -- diff_ex). Also, the pad has
 | ||||
|         // to be eliminated from areas where there is no need for a pad, due
 | ||||
|         // to missing supports.
 | ||||
| 
 | ||||
|         add_supports_to_index(support_blueprint); | ||||
| 
 | ||||
|         auto model_bp_offs = | ||||
|             offset_ex(model_blueprint, | ||||
|                       scaled<float>(cfg.embed_object.object_gap_mm), | ||||
|                       ClipperLib::jtMiter, 1); | ||||
| 
 | ||||
|         ConcaveHull fullcvh = | ||||
|             wafflized_concave_hull(support_blueprint, model_bp_offs, cfg, thr); | ||||
| 
 | ||||
|         auto model_bp_sticks = | ||||
|             breakstick_holes(model_bp_offs, cfg.embed_object.object_gap_mm, | ||||
|                              cfg.embed_object.stick_stride_mm, | ||||
|                              cfg.embed_object.stick_width_mm, | ||||
|                              cfg.embed_object.stick_penetration_mm); | ||||
| 
 | ||||
|         ExPolygons fullpad = diff_ex(fullcvh.to_expolygons(), model_bp_sticks); | ||||
| 
 | ||||
|         remove_redundant_parts(fullpad); | ||||
| 
 | ||||
|         PadSkeleton divided = divide_blueprint(fullpad); | ||||
|         outer = std::move(divided.outer); | ||||
|         inner = std::move(divided.inner); | ||||
|     } | ||||
| 
 | ||||
| private: | ||||
| 
 | ||||
|     // Add the support blueprint to the search index to be queried later
 | ||||
|     void add_supports_to_index(const ExPolygons &supp_bp) | ||||
|     { | ||||
|         for (auto &ep : supp_bp) m_intersector.add(ep); | ||||
|     } | ||||
| 
 | ||||
|     // Create the wafflized pad around all object in the scene. This pad doesnt
 | ||||
|     // have any holes yet.
 | ||||
|     ConcaveHull wafflized_concave_hull(const ExPolygons &supp_bp, | ||||
|                                        const ExPolygons &model_bp, | ||||
|                                        const PadConfig  &cfg, | ||||
|                                        ThrowOnCancel     thr) | ||||
|     { | ||||
|         auto allin = reserve_vector<ExPolygon>(supp_bp.size() + model_bp.size()); | ||||
| 
 | ||||
|         for (auto &ep : supp_bp) allin.emplace_back(ep.contour); | ||||
|         for (auto &ep : model_bp) allin.emplace_back(ep.contour); | ||||
| 
 | ||||
|         ConcaveHull ret{allin, ConcaveHull::get_merge_distance(cfg), thr}; | ||||
|         ret.offset_waffle_style(ConcaveHull::get_waffle_offset(cfg)); | ||||
| 
 | ||||
|         return ret; | ||||
|     } | ||||
| 
 | ||||
|     // To remove parts of the pad skeleton which do not host any supports
 | ||||
|     void remove_redundant_parts(ExPolygons &parts) | ||||
|     { | ||||
|         auto endit = std::remove_if(parts.begin(), parts.end(), | ||||
|                                     [this](const ExPolygon &p) { | ||||
|                                         return !m_intersector.intersects(p); | ||||
|                                     }); | ||||
| 
 | ||||
|         parts.erase(endit, parts.end()); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| using AroundPadSkeleton = _AroundPadSkeleton<Intersector>; | ||||
| using BrimPadSkeleton   = _AroundPadSkeleton<DummyIntersector>; | ||||
| 
 | ||||
| class BelowPadSkeleton : public PadSkeleton | ||||
| { | ||||
| public: | ||||
|     BelowPadSkeleton(const ExPolygons &support_blueprint, | ||||
|                      const ExPolygons &model_blueprint, | ||||
|                      const PadConfig & cfg, | ||||
|                      ThrowOnCancel     thr) | ||||
|     { | ||||
|         outer.reserve(support_blueprint.size() + model_blueprint.size()); | ||||
| 
 | ||||
|         for (auto &ep : support_blueprint) outer.emplace_back(ep.contour); | ||||
|         for (auto &ep : model_blueprint) outer.emplace_back(ep.contour); | ||||
| 
 | ||||
|         ConcaveHull ochull{outer, ConcaveHull::get_merge_distance(cfg), thr}; | ||||
| 
 | ||||
|         ochull.offset_waffle_style(ConcaveHull::get_waffle_offset(cfg)); | ||||
|         outer = ochull.to_expolygons(); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| // Offset the contour only, leave the holes untouched
 | ||||
| template<class...Args> | ||||
| ExPolygon offset_contour_only(const ExPolygon &poly, coord_t delta, Args...args) | ||||
| { | ||||
|     ExPolygons tmp = offset_ex(poly.contour, float(delta), args...); | ||||
| 
 | ||||
|     if (tmp.empty()) return {}; | ||||
| 
 | ||||
|     Polygons holes = poly.holes; | ||||
|     for (auto &h : holes) h.reverse(); | ||||
| 
 | ||||
|     tmp = diff_ex(to_polygons(tmp), holes); | ||||
| 
 | ||||
|     if (tmp.empty()) return {}; | ||||
| 
 | ||||
|     return tmp.front(); | ||||
| } | ||||
| 
 | ||||
| bool add_cavity(Contour3D &pad, ExPolygon &top_poly, const PadConfig3D &cfg, | ||||
|                 ThrowOnCancel thr) | ||||
| { | ||||
|     auto logerr = []{BOOST_LOG_TRIVIAL(error)<<"Could not create pad cavity";}; | ||||
| 
 | ||||
|     double    wing_distance = cfg.wing_height / std::tan(cfg.slope); | ||||
|     coord_t   delta_inner   = -scaled(cfg.thickness + wing_distance); | ||||
|     coord_t   delta_middle  = -scaled(cfg.thickness); | ||||
|     ExPolygon inner_base    = offset_contour_only(top_poly, delta_inner); | ||||
|     ExPolygon middle_base   = offset_contour_only(top_poly, delta_middle); | ||||
| 
 | ||||
|     if (inner_base.empty() || middle_base.empty()) { logerr(); return false; } | ||||
| 
 | ||||
|     ExPolygons pdiff = diff_ex(top_poly, middle_base.contour); | ||||
| 
 | ||||
|     if (pdiff.size() != 1) { logerr(); return false; } | ||||
| 
 | ||||
|     top_poly = pdiff.front(); | ||||
| 
 | ||||
|     double z_min = -cfg.wing_height, z_max = 0; | ||||
|     double offset_difference = -wing_distance; | ||||
|     pad.merge(walls(inner_base.contour, middle_base.contour, z_min, z_max, | ||||
|                     offset_difference, thr)); | ||||
| 
 | ||||
|     pad.merge(triangulate_expolygon_3d(inner_base, z_min, NORMALS_UP)); | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| Contour3D create_outer_pad_geometry(const ExPolygons & skeleton, | ||||
|                                     const PadConfig3D &cfg, | ||||
|                                     ThrowOnCancel      thr) | ||||
| { | ||||
|     Contour3D ret; | ||||
| 
 | ||||
|     for (const ExPolygon &pad_part : skeleton) { | ||||
|         ExPolygon top_poly{pad_part}; | ||||
|         ExPolygon bottom_poly = | ||||
|             offset_contour_only(pad_part, -scaled(cfg.bottom_offset())); | ||||
| 
 | ||||
|         if (bottom_poly.empty()) continue; | ||||
| 
 | ||||
|         double z_min = -cfg.height, z_max = 0; | ||||
|         ret.merge(walls(top_poly.contour, bottom_poly.contour, z_max, z_min, | ||||
|                         cfg.bottom_offset(), thr)); | ||||
| 
 | ||||
|         if (cfg.wing_height > 0. && add_cavity(ret, top_poly, cfg, thr)) | ||||
|             z_max = -cfg.wing_height; | ||||
| 
 | ||||
|         for (auto &h : bottom_poly.holes) | ||||
|             ret.merge(straight_walls(h, z_max, z_min, thr)); | ||||
| 
 | ||||
|         ret.merge(triangulate_expolygon_3d(bottom_poly, z_min, NORMALS_DOWN)); | ||||
|         ret.merge(triangulate_expolygon_3d(top_poly, NORMALS_UP)); | ||||
|     } | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| Contour3D create_inner_pad_geometry(const ExPolygons & skeleton, | ||||
|                                     const PadConfig3D &cfg, | ||||
|                                     ThrowOnCancel      thr) | ||||
| { | ||||
|     Contour3D ret; | ||||
| 
 | ||||
|     double z_max = 0., z_min = -cfg.height; | ||||
|     for (const ExPolygon &pad_part : skeleton) { | ||||
|         ret.merge(straight_walls(pad_part.contour, z_max, z_min,thr)); | ||||
| 
 | ||||
|         for (auto &h : pad_part.holes) | ||||
|             ret.merge(straight_walls(h, z_max, z_min, thr)); | ||||
| 
 | ||||
|         ret.merge(triangulate_expolygon_3d(pad_part, z_min, NORMALS_DOWN)); | ||||
|         ret.merge(triangulate_expolygon_3d(pad_part, z_max, NORMALS_UP)); | ||||
|     } | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| Contour3D create_pad_geometry(const PadSkeleton &skelet, | ||||
|                               const PadConfig &  cfg, | ||||
|                               ThrowOnCancel      thr) | ||||
| { | ||||
| #ifndef NDEBUG | ||||
|     SVG svg("pad_skeleton.svg"); | ||||
|     svg.draw(skelet.outer, "green"); | ||||
|     svg.draw(skelet.inner, "blue"); | ||||
|     svg.Close(); | ||||
| #endif | ||||
| 
 | ||||
|     PadConfig3D cfg3d(cfg); | ||||
|     return create_outer_pad_geometry(skelet.outer, cfg3d, thr) | ||||
|         .merge(create_inner_pad_geometry(skelet.inner, cfg3d, thr)); | ||||
| } | ||||
| 
 | ||||
| Contour3D create_pad_geometry(const ExPolygons &supp_bp, | ||||
|                               const ExPolygons &model_bp, | ||||
|                               const PadConfig & cfg, | ||||
|                               ThrowOnCancel thr) | ||||
| { | ||||
|     PadSkeleton skelet; | ||||
| 
 | ||||
|     if (cfg.embed_object.enabled) { | ||||
|         if (cfg.embed_object.everywhere) | ||||
|             skelet = BrimPadSkeleton(supp_bp, model_bp, cfg, thr); | ||||
|         else | ||||
|             skelet = AroundPadSkeleton(supp_bp, model_bp, cfg, thr); | ||||
|     } else | ||||
|         skelet = BelowPadSkeleton(supp_bp, model_bp, cfg, thr); | ||||
| 
 | ||||
|     return create_pad_geometry(skelet, cfg, thr); | ||||
| } | ||||
| 
 | ||||
| } // namespace
 | ||||
| 
 | ||||
| void pad_blueprint(const TriangleMesh &      mesh, | ||||
|                    ExPolygons &              output, | ||||
|                    const std::vector<float> &heights, | ||||
|                    ThrowOnCancel             thrfn) | ||||
| { | ||||
|     if (mesh.empty()) return; | ||||
|     TriangleMeshSlicer slicer(&mesh); | ||||
| 
 | ||||
|     auto out = reserve_vector<ExPolygons>(heights.size()); | ||||
|     slicer.slice(heights, 0.f, &out, thrfn); | ||||
| 
 | ||||
|     size_t count = 0; | ||||
|     for(auto& o : out) count += o.size(); | ||||
| 
 | ||||
|     // Unification is expensive, a simplify also speeds up the pad generation
 | ||||
|     auto tmp = reserve_vector<ExPolygon>(count); | ||||
|     for(ExPolygons& o : out) | ||||
|         for(ExPolygon& e : o) { | ||||
|             auto&& exss = e.simplify(scaled<double>(0.1)); | ||||
|             for(ExPolygon& ep : exss) tmp.emplace_back(std::move(ep)); | ||||
|         } | ||||
| 
 | ||||
|     ExPolygons utmp = union_ex(tmp); | ||||
| 
 | ||||
|     for(auto& o : utmp) { | ||||
|         auto&& smp = o.simplify(scaled<double>(0.1)); | ||||
|         output.insert(output.end(), smp.begin(), smp.end()); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void pad_blueprint(const TriangleMesh &mesh, | ||||
|                    ExPolygons &        output, | ||||
|                    float               h, | ||||
|                    float               layerh, | ||||
|                    ThrowOnCancel       thrfn) | ||||
| { | ||||
|     float gnd = float(mesh.bounding_box().min(Z)); | ||||
| 
 | ||||
|     std::vector<float> slicegrid = grid(gnd, gnd + h, layerh); | ||||
|     pad_blueprint(mesh, output, slicegrid, thrfn); | ||||
| } | ||||
| 
 | ||||
| void create_pad(const ExPolygons &sup_blueprint, | ||||
|                 const ExPolygons &model_blueprint, | ||||
|                 TriangleMesh &    out, | ||||
|                 const PadConfig & cfg, | ||||
|                 ThrowOnCancel thr) | ||||
| { | ||||
|     Contour3D t = create_pad_geometry(sup_blueprint, model_blueprint, cfg, thr); | ||||
|     out.merge(mesh(std::move(t))); | ||||
| } | ||||
| 
 | ||||
| std::string PadConfig::validate() const | ||||
| { | ||||
|     static const double constexpr MIN_BRIM_SIZE_MM = .1; | ||||
| 
 | ||||
|     if (brim_size_mm < MIN_BRIM_SIZE_MM || | ||||
|         bottom_offset() > brim_size_mm + wing_distance() || | ||||
|         ConcaveHull::get_waffle_offset(*this) <= MIN_BRIM_SIZE_MM) | ||||
|         return L("Pad brim size is too small for the current configuration."); | ||||
| 
 | ||||
|     return ""; | ||||
| } | ||||
| 
 | ||||
| }} // namespace Slic3r::sla
 | ||||
							
								
								
									
										94
									
								
								src/libslic3r/SLA/SLAPad.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								src/libslic3r/SLA/SLAPad.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,94 @@ | |||
| #ifndef SLABASEPOOL_HPP | ||||
| #define SLABASEPOOL_HPP | ||||
| 
 | ||||
| #include <vector> | ||||
| #include <functional> | ||||
| #include <cmath> | ||||
| #include <string> | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
| class ExPolygon; | ||||
| class Polygon; | ||||
| using ExPolygons = std::vector<ExPolygon>; | ||||
| using Polygons = std::vector<Polygon>; | ||||
| 
 | ||||
| class TriangleMesh; | ||||
| 
 | ||||
| namespace sla { | ||||
| 
 | ||||
| using ThrowOnCancel = std::function<void(void)>; | ||||
| 
 | ||||
| /// Calculate the polygon representing the silhouette.
 | ||||
| void pad_blueprint( | ||||
|     const TriangleMesh &mesh,       // input mesh
 | ||||
|     ExPolygons &        output,     // Output will be merged with
 | ||||
|     const std::vector<float> &,     // Exact Z levels to sample
 | ||||
|     ThrowOnCancel thrfn = [] {}); // Function that throws if cancel was requested
 | ||||
| 
 | ||||
| void pad_blueprint( | ||||
|     const TriangleMesh &mesh, | ||||
|     ExPolygons &        output, | ||||
|     float               samplingheight = 0.1f,  // The height range to sample
 | ||||
|     float               layerheight    = 0.05f, // The sampling height
 | ||||
|     ThrowOnCancel       thrfn = [] {}); | ||||
| 
 | ||||
| struct PadConfig { | ||||
|     double wall_thickness_mm = 1.; | ||||
|     double wall_height_mm = 1.; | ||||
|     double max_merge_dist_mm = 50; | ||||
|     double wall_slope = std::atan(1.0);          // Universal constant for Pi/4
 | ||||
|     double brim_size_mm = 1.6; | ||||
| 
 | ||||
|     struct EmbedObject { | ||||
|         double object_gap_mm = 1.; | ||||
|         double stick_stride_mm = 10.; | ||||
|         double stick_width_mm = 0.5; | ||||
|         double stick_penetration_mm = 0.1; | ||||
|         bool enabled = false; | ||||
|         bool everywhere = false; | ||||
|         operator bool() const { return enabled; } | ||||
|     } embed_object; | ||||
| 
 | ||||
|     inline PadConfig() = default; | ||||
|     inline PadConfig(double thickness, | ||||
|                      double height, | ||||
|                      double mergedist, | ||||
|                      double slope) | ||||
|         : wall_thickness_mm(thickness) | ||||
|         , wall_height_mm(height) | ||||
|         , max_merge_dist_mm(mergedist) | ||||
|         , wall_slope(slope) | ||||
|     {} | ||||
| 
 | ||||
|     inline double bottom_offset() const | ||||
|     { | ||||
|         return (wall_thickness_mm + wall_height_mm) / std::tan(wall_slope); | ||||
|     } | ||||
| 
 | ||||
|     inline double wing_distance() const | ||||
|     { | ||||
|         return wall_height_mm / std::tan(wall_slope); | ||||
|     } | ||||
| 
 | ||||
|     inline double full_height() const | ||||
|     { | ||||
|         return wall_height_mm + wall_thickness_mm; | ||||
|     } | ||||
| 
 | ||||
|     /// Returns the elevation needed for compensating the pad.
 | ||||
|     inline double required_elevation() const { return wall_thickness_mm; } | ||||
| 
 | ||||
|     std::string validate() const; | ||||
| }; | ||||
| 
 | ||||
| void create_pad(const ExPolygons &support_contours, | ||||
|                 const ExPolygons &model_contours, | ||||
|                 TriangleMesh &    output_mesh, | ||||
|                 const PadConfig & = PadConfig(), | ||||
|                 ThrowOnCancel throw_on_cancel = []{}); | ||||
| 
 | ||||
| } // namespace sla
 | ||||
| } // namespace Slic3r
 | ||||
| 
 | ||||
| #endif // SLABASEPOOL_HPP
 | ||||
|  | @ -5,6 +5,7 @@ | |||
| 
 | ||||
| #include "SLARaster.hpp" | ||||
| #include "libslic3r/ExPolygon.hpp" | ||||
| #include "libslic3r/MTUtils.hpp" | ||||
| #include <libnest2d/backends/clipper/clipper_polygon.hpp> | ||||
| 
 | ||||
| // For rasterizing
 | ||||
|  | @ -32,25 +33,30 @@ inline const ClipperLib::Paths& holes(const ClipperLib::Polygon& p) { return p.H | |||
| 
 | ||||
| namespace sla { | ||||
| 
 | ||||
| const Raster::TMirroring Raster::NoMirror = {false, false}; | ||||
| const Raster::TMirroring Raster::MirrorX  = {true, false}; | ||||
| const Raster::TMirroring Raster::MirrorY  = {false, true}; | ||||
| const Raster::TMirroring Raster::MirrorXY = {true, true}; | ||||
| 
 | ||||
| 
 | ||||
| using TPixelRenderer = agg::pixfmt_gray8; // agg::pixfmt_rgb24;
 | ||||
| using TRawRenderer = agg::renderer_base<TPixelRenderer>; | ||||
| using TPixel = TPixelRenderer::color_type; | ||||
| using TRawBuffer = agg::rendering_buffer; | ||||
| using TBuffer = std::vector<TPixelRenderer::pixel_type>; | ||||
| 
 | ||||
| using TRendererAA = agg::renderer_scanline_aa_solid<TRawRenderer>; | ||||
| 
 | ||||
| class Raster::Impl { | ||||
| public: | ||||
|     using TPixelRenderer = agg::pixfmt_gray8; // agg::pixfmt_rgb24;
 | ||||
|     using TRawRenderer = agg::renderer_base<TPixelRenderer>; | ||||
|     using TPixel = TPixelRenderer::color_type; | ||||
|     using TRawBuffer = agg::rendering_buffer; | ||||
| 
 | ||||
|     using TBuffer = std::vector<TPixelRenderer::pixel_type>; | ||||
| 
 | ||||
|     using TRendererAA = agg::renderer_scanline_aa_solid<TRawRenderer>; | ||||
| 
 | ||||
|     static const TPixel ColorWhite; | ||||
|     static const TPixel ColorBlack; | ||||
| 
 | ||||
|     using Format = Raster::Format; | ||||
|     using Format = Raster::RawData; | ||||
| 
 | ||||
| private: | ||||
|     Raster::Resolution m_resolution; | ||||
| //    Raster::PixelDim m_pxdim;
 | ||||
|     Raster::PixelDim m_pxdim_scaled;    // used for scaled coordinate polygons
 | ||||
|     TBuffer m_buf; | ||||
|     TRawBuffer m_rbuf; | ||||
|  | @ -59,74 +65,49 @@ private: | |||
|     TRendererAA m_renderer; | ||||
|      | ||||
|     std::function<double(double)> m_gammafn; | ||||
|     std::array<bool, 2> m_mirror; | ||||
|     Format m_fmt = Format::PNG; | ||||
|     Trafo m_trafo; | ||||
|      | ||||
|     inline void flipy(agg::path_storage& path) const { | ||||
|         path.flip_y(0, m_resolution.height_px); | ||||
|         path.flip_y(0, double(m_resolution.height_px)); | ||||
|     } | ||||
|      | ||||
|     inline void flipx(agg::path_storage& path) const { | ||||
|         path.flip_x(0, m_resolution.width_px); | ||||
|         path.flip_x(0, double(m_resolution.width_px)); | ||||
|     } | ||||
| 
 | ||||
| public: | ||||
| 
 | ||||
|     inline Impl(const Raster::Resolution& res, const Raster::PixelDim &pd, | ||||
|                 const std::array<bool, 2>& mirror, double gamma = 1.0): | ||||
|         m_resolution(res),  | ||||
| //        m_pxdim(pd), 
 | ||||
|         m_pxdim_scaled(SCALING_FACTOR / pd.w_mm, SCALING_FACTOR / pd.h_mm), | ||||
|         m_buf(res.pixels()), | ||||
|         m_rbuf(reinterpret_cast<TPixelRenderer::value_type*>(m_buf.data()), | ||||
|               res.width_px, res.height_px, | ||||
|               int(res.width_px*TPixelRenderer::num_components)), | ||||
|         m_pixfmt(m_rbuf), | ||||
|         m_raw_renderer(m_pixfmt), | ||||
|         m_renderer(m_raw_renderer), | ||||
|         m_mirror(mirror) | ||||
|     inline Impl(const Raster::Resolution & res, | ||||
|                 const Raster::PixelDim &   pd, | ||||
|                 const Trafo &trafo) | ||||
|         : m_resolution(res) | ||||
|         , m_pxdim_scaled(SCALING_FACTOR / pd.w_mm, SCALING_FACTOR / pd.h_mm) | ||||
|         , m_buf(res.pixels()) | ||||
|         , m_rbuf(reinterpret_cast<TPixelRenderer::value_type *>(m_buf.data()), | ||||
|                  unsigned(res.width_px), | ||||
|                  unsigned(res.height_px), | ||||
|                  int(res.width_px * TPixelRenderer::num_components)) | ||||
|         , m_pixfmt(m_rbuf) | ||||
|         , m_raw_renderer(m_pixfmt) | ||||
|         , m_renderer(m_raw_renderer) | ||||
|         , m_trafo(trafo) | ||||
|     { | ||||
|         m_renderer.color(ColorWhite); | ||||
|          | ||||
|         if(gamma > 0) m_gammafn = agg::gamma_power(gamma); | ||||
|         if (trafo.gamma > 0) m_gammafn = agg::gamma_power(trafo.gamma); | ||||
|         else m_gammafn = agg::gamma_threshold(0.5); | ||||
|          | ||||
|         clear(); | ||||
|     } | ||||
|      | ||||
|     inline Impl(const Raster::Resolution& res,  | ||||
|                 const Raster::PixelDim &pd, | ||||
|                 Format fmt,  | ||||
|                 double gamma = 1.0):  | ||||
|         Impl(res, pd, {false, false}, gamma)  | ||||
|     { | ||||
|         switch (fmt) { | ||||
|         case Format::PNG: m_mirror = {false, true}; break; | ||||
|         case Format::RAW: m_mirror = {false, false}; break; | ||||
|         } | ||||
|         m_fmt = fmt; | ||||
|     } | ||||
| 
 | ||||
|     template<class P> void draw(const P &poly) { | ||||
|         agg::rasterizer_scanline_aa<> ras; | ||||
|         agg::scanline_p8 scanlines; | ||||
|          | ||||
|         ras.gamma(m_gammafn); | ||||
| 
 | ||||
|         auto&& path = to_path(contour(poly)); | ||||
|          | ||||
|         if(m_mirror[X]) flipx(path); | ||||
|         if(m_mirror[Y]) flipy(path); | ||||
| 
 | ||||
|         ras.add_path(path); | ||||
| 
 | ||||
|         for(auto& h : holes(poly)) { | ||||
|             auto&& holepath = to_path(h); | ||||
|             if(m_mirror[X]) flipx(holepath); | ||||
|             if(m_mirror[Y]) flipy(holepath); | ||||
|             ras.add_path(holepath); | ||||
|         } | ||||
| 
 | ||||
|         ras.add_path(to_path(contour(poly))); | ||||
|         for(auto& h : holes(poly)) ras.add_path(to_path(h)); | ||||
|          | ||||
|         agg::render_scanlines(ras, scanlines, m_renderer); | ||||
|     } | ||||
| 
 | ||||
|  | @ -135,11 +116,16 @@ public: | |||
|     } | ||||
| 
 | ||||
|     inline TBuffer& buffer()  { return m_buf; } | ||||
|     inline const TBuffer& buffer() const { return m_buf; } | ||||
|      | ||||
|     inline Format format() const { return m_fmt; } | ||||
| 
 | ||||
|     inline const Raster::Resolution resolution() { return m_resolution; } | ||||
|     | ||||
|     inline const Raster::PixelDim   pixdim() | ||||
|     { | ||||
|         return {SCALING_FACTOR / m_pxdim_scaled.w_mm, | ||||
|                 SCALING_FACTOR / m_pxdim_scaled.h_mm}; | ||||
|     } | ||||
| 
 | ||||
| private: | ||||
|     inline double getPx(const Point& p) { | ||||
|         return p(0) * m_pxdim_scaled.w_mm; | ||||
|  | @ -162,49 +148,67 @@ private: | |||
|         return p.Y * m_pxdim_scaled.h_mm; | ||||
|     } | ||||
| 
 | ||||
|     template<class PointVec> agg::path_storage to_path(const PointVec& poly) | ||||
|     template<class PointVec> agg::path_storage _to_path(const PointVec& v) | ||||
|     { | ||||
|         agg::path_storage path; | ||||
|          | ||||
|         auto it = poly.begin(); | ||||
|         auto it = v.begin(); | ||||
|         path.move_to(getPx(*it), getPy(*it)); | ||||
|         while(++it != v.end()) path.line_to(getPx(*it), getPy(*it)); | ||||
|         path.line_to(getPx(v.front()), getPy(v.front())); | ||||
|          | ||||
|         return path; | ||||
|     } | ||||
|     | ||||
|     template<class PointVec> agg::path_storage _to_path_flpxy(const PointVec& v) | ||||
|     { | ||||
|         agg::path_storage path; | ||||
|          | ||||
|         auto it = v.begin(); | ||||
|         path.move_to(getPy(*it), getPx(*it)); | ||||
|         while(++it != v.end()) path.line_to(getPy(*it), getPx(*it)); | ||||
|         path.line_to(getPy(v.front()), getPx(v.front())); | ||||
|          | ||||
|         return path; | ||||
|     } | ||||
|      | ||||
|     template<class PointVec> agg::path_storage to_path(const PointVec &v) | ||||
|     { | ||||
|         auto path = m_trafo.flipXY ? _to_path_flpxy(v) : _to_path(v); | ||||
|          | ||||
|         path.translate_all_paths(m_trafo.origin_x * m_pxdim_scaled.w_mm, | ||||
|                                  m_trafo.origin_y * m_pxdim_scaled.h_mm); | ||||
|          | ||||
|         if(m_trafo.mirror_x) flipx(path); | ||||
|         if(m_trafo.mirror_y) flipy(path); | ||||
|          | ||||
|         while(++it != poly.end()) | ||||
|             path.line_to(getPx(*it), getPy(*it)); | ||||
| 
 | ||||
|         path.line_to(getPx(poly.front()), getPy(poly.front())); | ||||
|         return path; | ||||
|     } | ||||
| 
 | ||||
| }; | ||||
| 
 | ||||
| const Raster::Impl::TPixel Raster::Impl::ColorWhite = Raster::Impl::TPixel(255); | ||||
| const Raster::Impl::TPixel Raster::Impl::ColorBlack = Raster::Impl::TPixel(0); | ||||
| const TPixel Raster::Impl::ColorWhite = TPixel(255); | ||||
| const TPixel Raster::Impl::ColorBlack = TPixel(0); | ||||
| 
 | ||||
| Raster::Raster() { reset(); } | ||||
| 
 | ||||
| Raster::Raster(const Raster::Resolution &r, | ||||
|                const Raster::PixelDim &  pd, | ||||
|                const Raster::Trafo &     tr) | ||||
| { | ||||
|     reset(r, pd, tr); | ||||
| } | ||||
| 
 | ||||
| template<> Raster::Raster() { reset(); }; | ||||
| Raster::~Raster() = default; | ||||
| 
 | ||||
| // Raster::Raster(Raster &&m) = default;
 | ||||
| // Raster& Raster::operator=(Raster&&) = default;
 | ||||
| 
 | ||||
| // FIXME: remove after migrating to higher version of windows compiler
 | ||||
| Raster::Raster(Raster &&m): m_impl(std::move(m.m_impl)) {} | ||||
| Raster& Raster::operator=(Raster &&m) { | ||||
|     m_impl = std::move(m.m_impl); return *this; | ||||
| } | ||||
| Raster::Raster(Raster &&m) = default; | ||||
| Raster &Raster::operator=(Raster &&) = default; | ||||
| 
 | ||||
| void Raster::reset(const Raster::Resolution &r, const Raster::PixelDim &pd, | ||||
|                    Format fmt, double gamma) | ||||
|                    const Trafo &trafo) | ||||
| { | ||||
|     m_impl.reset(); | ||||
|     m_impl.reset(new Impl(r, pd, fmt, gamma)); | ||||
| } | ||||
| 
 | ||||
| void Raster::reset(const Raster::Resolution &r, const Raster::PixelDim &pd, | ||||
|                    const std::array<bool, 2>& mirror, double gamma) | ||||
| { | ||||
|     m_impl.reset(); | ||||
|     m_impl.reset(new Impl(r, pd, mirror, gamma)); | ||||
|     m_impl.reset(new Impl(r, pd, trafo)); | ||||
| } | ||||
| 
 | ||||
| void Raster::reset() | ||||
|  | @ -214,9 +218,16 @@ void Raster::reset() | |||
| 
 | ||||
| Raster::Resolution Raster::resolution() const | ||||
| { | ||||
|     if(m_impl) return m_impl->resolution(); | ||||
|     if (m_impl) return m_impl->resolution(); | ||||
|      | ||||
|     return Resolution{0, 0}; | ||||
| } | ||||
| 
 | ||||
|     return Resolution(0, 0); | ||||
| Raster::PixelDim Raster::pixel_dimensions() const | ||||
| { | ||||
|     if (m_impl) return m_impl->pixdim(); | ||||
|      | ||||
|     return PixelDim{0., 0.}; | ||||
| } | ||||
| 
 | ||||
| void Raster::clear() | ||||
|  | @ -227,103 +238,83 @@ void Raster::clear() | |||
| 
 | ||||
| void Raster::draw(const ExPolygon &expoly) | ||||
| { | ||||
|     assert(m_impl); | ||||
|     m_impl->draw(expoly); | ||||
| } | ||||
| 
 | ||||
| void Raster::draw(const ClipperLib::Polygon &poly) | ||||
| { | ||||
|     assert(m_impl); | ||||
|     m_impl->draw(poly); | ||||
| } | ||||
| 
 | ||||
| void Raster::save(std::ostream& stream, Format fmt) | ||||
| uint8_t Raster::read_pixel(size_t x, size_t y) const | ||||
| { | ||||
|     assert(m_impl); | ||||
|     if(!stream.good()) return; | ||||
| 
 | ||||
|     switch(fmt) { | ||||
|     case Format::PNG: { | ||||
|         auto& b = m_impl->buffer(); | ||||
|         size_t out_len = 0; | ||||
|         void * rawdata = tdefl_write_image_to_png_file_in_memory( | ||||
|                     b.data(), | ||||
|                     int(resolution().width_px), | ||||
|                     int(resolution().height_px), 1, &out_len); | ||||
| 
 | ||||
|         if(rawdata == nullptr) break; | ||||
| 
 | ||||
|         stream.write(static_cast<const char*>(rawdata), | ||||
|                      std::streamsize(out_len)); | ||||
| 
 | ||||
|         MZ_FREE(rawdata); | ||||
| 
 | ||||
|         break; | ||||
|     } | ||||
|     case Format::RAW: { | ||||
|         stream << "P5 " | ||||
|                << m_impl->resolution().width_px << " " | ||||
|                << m_impl->resolution().height_px << " " | ||||
|                << "255 "; | ||||
| 
 | ||||
|         auto sz = m_impl->buffer().size()*sizeof(Impl::TBuffer::value_type); | ||||
|         stream.write(reinterpret_cast<const char*>(m_impl->buffer().data()), | ||||
|                      std::streamsize(sz)); | ||||
|     } | ||||
|     } | ||||
|     assert (m_impl); | ||||
|     TPixel::value_type px; | ||||
|     m_impl->buffer()[y * resolution().width_px + x].get(px); | ||||
|     return px; | ||||
| } | ||||
| 
 | ||||
| void Raster::save(std::ostream &stream) | ||||
| PNGImage & PNGImage::serialize(const Raster &raster) | ||||
| { | ||||
|     save(stream, m_impl->format()); | ||||
|     size_t s = 0; | ||||
|     m_buffer.clear(); | ||||
|      | ||||
|     void *rawdata = tdefl_write_image_to_png_file_in_memory( | ||||
|         get_internals(raster).buffer().data(), | ||||
|         int(raster.resolution().width_px), | ||||
|         int(raster.resolution().height_px), 1, &s); | ||||
|      | ||||
|     // On error, data() will return an empty vector. No other info can be
 | ||||
|     // retrieved from miniz anyway...
 | ||||
|     if (rawdata == nullptr) return *this; | ||||
|      | ||||
|     auto ptr = static_cast<std::uint8_t*>(rawdata); | ||||
|      | ||||
|     m_buffer.reserve(s); | ||||
|     std::copy(ptr, ptr + s, std::back_inserter(m_buffer)); | ||||
|      | ||||
|     MZ_FREE(rawdata); | ||||
|     return *this; | ||||
| } | ||||
| 
 | ||||
| RawBytes Raster::save(Format fmt) | ||||
| std::ostream &operator<<(std::ostream &stream, const Raster::RawData &bytes) | ||||
| { | ||||
|     assert(m_impl); | ||||
| 
 | ||||
|     std::vector<std::uint8_t> data; size_t s = 0; | ||||
| 
 | ||||
|     switch(fmt) { | ||||
|     case Format::PNG: { | ||||
|         void *rawdata = tdefl_write_image_to_png_file_in_memory( | ||||
|                     m_impl->buffer().data(), | ||||
|                     int(resolution().width_px), | ||||
|                     int(resolution().height_px), 1, &s); | ||||
| 
 | ||||
|         if(rawdata == nullptr) break; | ||||
|         auto ptr = static_cast<std::uint8_t*>(rawdata); | ||||
|          | ||||
|         data.reserve(s); std::copy(ptr, ptr + s, std::back_inserter(data)); | ||||
|          | ||||
|         MZ_FREE(rawdata); | ||||
|         break; | ||||
|     } | ||||
|     case Format::RAW: { | ||||
|         auto header = std::string("P5 ") + | ||||
|                 std::to_string(m_impl->resolution().width_px) + " " + | ||||
|                 std::to_string(m_impl->resolution().height_px) + " " + "255 "; | ||||
| 
 | ||||
|         auto sz = m_impl->buffer().size()*sizeof(Impl::TBuffer::value_type); | ||||
|         s = sz + header.size(); | ||||
|          | ||||
|         data.reserve(s); | ||||
|          | ||||
|         auto buff = reinterpret_cast<std::uint8_t*>(m_impl->buffer().data()); | ||||
|         std::copy(header.begin(), header.end(), std::back_inserter(data)); | ||||
|         std::copy(buff, buff+sz, std::back_inserter(data)); | ||||
|          | ||||
|         break; | ||||
|     } | ||||
|     } | ||||
| 
 | ||||
|     return {std::move(data)}; | ||||
|     stream.write(reinterpret_cast<const char *>(bytes.data()), | ||||
|                  std::streamsize(bytes.size())); | ||||
|      | ||||
|     return stream; | ||||
| } | ||||
| 
 | ||||
| RawBytes Raster::save() | ||||
| Raster::RawData::~RawData() = default; | ||||
| 
 | ||||
| PPMImage & PPMImage::serialize(const Raster &raster) | ||||
| { | ||||
|     return save(m_impl->format()); | ||||
|     auto header = std::string("P5 ") + | ||||
|             std::to_string(raster.resolution().width_px) + " " + | ||||
|             std::to_string(raster.resolution().height_px) + " " + "255 "; | ||||
|      | ||||
|     const auto &impl = get_internals(raster); | ||||
|     auto sz = impl.buffer().size() * sizeof(TBuffer::value_type); | ||||
|     size_t s = sz + header.size(); | ||||
|      | ||||
|     m_buffer.clear(); | ||||
|     m_buffer.reserve(s); | ||||
| 
 | ||||
|     auto buff = reinterpret_cast<const std::uint8_t*>(impl.buffer().data()); | ||||
|     std::copy(header.begin(), header.end(), std::back_inserter(m_buffer)); | ||||
|     std::copy(buff, buff+sz, std::back_inserter(m_buffer)); | ||||
|      | ||||
|     return *this; | ||||
| } | ||||
| 
 | ||||
| const Raster::Impl &Raster::RawData::get_internals(const Raster &raster) | ||||
| { | ||||
|     return *raster.m_impl; | ||||
| } | ||||
| } | ||||
| 
 | ||||
| } // namespace sla
 | ||||
| } // namespace Slic3r
 | ||||
| 
 | ||||
| #endif // SLARASTER_CPP
 | ||||
|  |  | |||
|  | @ -8,45 +8,13 @@ | |||
| #include <utility> | ||||
| #include <cstdint> | ||||
| 
 | ||||
| #include <libslic3r/ExPolygon.hpp> | ||||
| 
 | ||||
| namespace ClipperLib { struct Polygon; } | ||||
| 
 | ||||
| namespace Slic3r {  | ||||
| 
 | ||||
| class ExPolygon; | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| namespace sla { | ||||
| 
 | ||||
| // Raw byte buffer paired with its size. Suitable for compressed PNG data.
 | ||||
| class RawBytes { | ||||
| 
 | ||||
|     std::vector<std::uint8_t> m_buffer; | ||||
| public: | ||||
| 
 | ||||
|     RawBytes() = default; | ||||
|     RawBytes(std::vector<std::uint8_t>&& data): m_buffer(std::move(data)) {} | ||||
|      | ||||
|     size_t size() const { return m_buffer.size(); } | ||||
|     const uint8_t * data() { return m_buffer.data(); } | ||||
|      | ||||
|     RawBytes(const RawBytes&) = delete; | ||||
|     RawBytes& operator=(const RawBytes&) = delete; | ||||
| 
 | ||||
|     // /////////////////////////////////////////////////////////////////////////
 | ||||
|     // FIXME: the following is needed for MSVC2013 compatibility
 | ||||
|     // /////////////////////////////////////////////////////////////////////////
 | ||||
| 
 | ||||
|     // RawBytes(RawBytes&&) = default;
 | ||||
|     // RawBytes& operator=(RawBytes&&) = default;
 | ||||
| 
 | ||||
|     RawBytes(RawBytes&& mv) : m_buffer(std::move(mv.m_buffer)) {} | ||||
|     RawBytes& operator=(RawBytes&& mv) { | ||||
|         m_buffer = std::move(mv.m_buffer); | ||||
|         return *this; | ||||
|     } | ||||
| 
 | ||||
|     // /////////////////////////////////////////////////////////////////////////
 | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Raster captures an anti-aliased monochrome canvas where vectorial | ||||
|  * polygons can be rasterized. Fill color is always white and the background is | ||||
|  | @ -60,10 +28,28 @@ class Raster { | |||
|     std::unique_ptr<Impl> m_impl; | ||||
| public: | ||||
| 
 | ||||
|     /// Supported compression types
 | ||||
|     enum class Format { | ||||
|         RAW,    //!> Uncompressed pixel data
 | ||||
|         PNG     //!> PNG compression
 | ||||
|     // Raw byte buffer paired with its size. Suitable for compressed image data.
 | ||||
|     class RawData | ||||
|     { | ||||
|     protected: | ||||
|         std::vector<std::uint8_t> m_buffer; | ||||
|         const Impl& get_internals(const Raster& raster); | ||||
|     public: | ||||
|         RawData() = default; | ||||
|         RawData(std::vector<std::uint8_t>&& data): m_buffer(std::move(data)) {} | ||||
|         virtual ~RawData(); | ||||
| 
 | ||||
|         RawData(const RawData &) = delete; | ||||
|         RawData &operator=(const RawData &) = delete; | ||||
| 
 | ||||
|         RawData(RawData &&) = default; | ||||
|         RawData &operator=(RawData &&) = default; | ||||
| 
 | ||||
|         size_t size() const { return m_buffer.size(); } | ||||
|         const uint8_t * data() const { return m_buffer.data(); } | ||||
| 
 | ||||
|         virtual RawData& serialize(const Raster &/*raster*/)  { return *this; } | ||||
|         virtual std::string get_file_extension() const = 0; | ||||
|     }; | ||||
| 
 | ||||
|     /// Type that represents a resolution in pixels.
 | ||||
|  | @ -86,11 +72,36 @@ public: | |||
|             w_mm(px_width_mm), h_mm(px_height_mm) {} | ||||
|     }; | ||||
| 
 | ||||
|     /// Constructor taking the resolution and the pixel dimension.
 | ||||
|     template <class...Args> Raster(Args...args) {  | ||||
|         reset(std::forward<Args>(args)...);  | ||||
|     } | ||||
|      | ||||
|     enum Orientation { roLandscape, roPortrait }; | ||||
| 
 | ||||
|     using TMirroring = std::array<bool, 2>; | ||||
|     static const TMirroring NoMirror; | ||||
|     static const TMirroring MirrorX; | ||||
|     static const TMirroring MirrorY; | ||||
|     static const TMirroring MirrorXY; | ||||
| 
 | ||||
|     struct Trafo { | ||||
|         bool mirror_x = false, mirror_y = false, flipXY = false; | ||||
|         coord_t origin_x = 0, origin_y = 0; | ||||
| 
 | ||||
|         // If gamma is zero, thresholding will be performed which disables AA.
 | ||||
|         double gamma = 1.; | ||||
| 
 | ||||
|         // Portrait orientation will make sure the drawed polygons are rotated
 | ||||
|         // by 90 degrees.
 | ||||
|         Trafo(Orientation o = roLandscape, const TMirroring &mirror = NoMirror) | ||||
|             // XY flipping implicitly does an X mirror
 | ||||
|             : mirror_x(o == roPortrait ? !mirror[0] : mirror[0]) | ||||
|             , mirror_y(!mirror[1]) // Makes raster origin to be top left corner
 | ||||
|             , flipXY(o == roPortrait) | ||||
|         {} | ||||
|     }; | ||||
| 
 | ||||
|     Raster(); | ||||
|     Raster(const Resolution &r, | ||||
|            const PixelDim &  pd, | ||||
|            const Trafo &     tr = {}); | ||||
| 
 | ||||
|     Raster(const Raster& cpy) = delete; | ||||
|     Raster& operator=(const Raster& cpy) = delete; | ||||
|     Raster(Raster&& m); | ||||
|  | @ -98,18 +109,10 @@ public: | |||
|     ~Raster(); | ||||
| 
 | ||||
|     /// Reallocated everything for the given resolution and pixel dimension.
 | ||||
|     /// The third parameter is either the X, Y mirroring or a supported format 
 | ||||
|     /// for which the correct mirroring will be configured.
 | ||||
|     void reset(const Resolution&,  | ||||
|                const PixelDim&,  | ||||
|                const std::array<bool, 2>& mirror,  | ||||
|                double gamma = 1.0); | ||||
|      | ||||
|     void reset(const Resolution& r,  | ||||
|                const PixelDim& pd,  | ||||
|                Format o,  | ||||
|                double gamma = 1.0); | ||||
|      | ||||
|     void reset(const Resolution& r, | ||||
|                const PixelDim& pd, | ||||
|                const Trafo &tr = {}); | ||||
| 
 | ||||
|     /**
 | ||||
|      * Release the allocated resources. Drawing in this state ends in | ||||
|      * unspecified behavior. | ||||
|  | @ -118,6 +121,7 @@ public: | |||
| 
 | ||||
|     /// Get the resolution of the raster.
 | ||||
|     Resolution resolution() const; | ||||
|     PixelDim   pixel_dimensions() const; | ||||
| 
 | ||||
|     /// Clear the raster with black color.
 | ||||
|     void clear(); | ||||
|  | @ -126,24 +130,28 @@ public: | |||
|     void draw(const ExPolygon& poly); | ||||
|     void draw(const ClipperLib::Polygon& poly); | ||||
| 
 | ||||
|     // Saving the raster: 
 | ||||
|     // It is possible to override the format given in the constructor but
 | ||||
|     // be aware that the mirroring will not be modified.
 | ||||
|      | ||||
|     /// Save the raster on the specified stream.
 | ||||
|     void save(std::ostream& stream, Format); | ||||
|     void save(std::ostream& stream); | ||||
|     uint8_t read_pixel(size_t w, size_t h) const; | ||||
| 
 | ||||
|     inline bool empty() const { return ! bool(m_impl); } | ||||
| 
 | ||||
|     /// Save into a continuous byte stream which is returned.
 | ||||
|     RawBytes save(Format fmt); | ||||
|     RawBytes save(); | ||||
| }; | ||||
| 
 | ||||
| // This prevents the duplicate default constructor warning on MSVC2013
 | ||||
| template<> Raster::Raster(); | ||||
| class PNGImage: public Raster::RawData { | ||||
| public: | ||||
|     PNGImage& serialize(const Raster &raster) override; | ||||
|     std::string get_file_extension() const override { return "png"; } | ||||
| }; | ||||
| 
 | ||||
| class PPMImage: public Raster::RawData { | ||||
| public: | ||||
|     PPMImage& serialize(const Raster &raster) override; | ||||
|     std::string get_file_extension() const override { return "ppm"; } | ||||
| }; | ||||
| 
 | ||||
| std::ostream& operator<<(std::ostream &stream, const Raster::RawData &bytes); | ||||
| 
 | ||||
| } // sla
 | ||||
| } // Slic3r
 | ||||
| 
 | ||||
| 
 | ||||
| #endif // SLARASTER_HPP
 | ||||
|  |  | |||
|  | @ -10,7 +10,7 @@ | |||
| 
 | ||||
| namespace Slic3r { namespace sla { | ||||
| 
 | ||||
| std::string SLARasterWriter::createIniContent(const std::string& projectname) const  | ||||
| std::string RasterWriter::createIniContent(const std::string& projectname) const  | ||||
| { | ||||
|     std::string out("action = print\njobDir = "); | ||||
|     out += projectname + "\n"; | ||||
|  | @ -21,39 +21,14 @@ std::string SLARasterWriter::createIniContent(const std::string& projectname) co | |||
|     return out; | ||||
| } | ||||
| 
 | ||||
| void SLARasterWriter::flpXY(ClipperLib::Polygon &poly) | ||||
| { | ||||
|     for(auto& p : poly.Contour) std::swap(p.X, p.Y); | ||||
|     std::reverse(poly.Contour.begin(), poly.Contour.end()); | ||||
|      | ||||
|     for(auto& h : poly.Holes) { | ||||
|         for(auto& p : h) std::swap(p.X, p.Y); | ||||
|         std::reverse(h.begin(), h.end()); | ||||
|     } | ||||
| } | ||||
| RasterWriter::RasterWriter(const Raster::Resolution &res, | ||||
|                            const Raster::PixelDim &  pixdim, | ||||
|                            const Raster::Trafo &     trafo, | ||||
|                            double                    gamma) | ||||
|     : m_res(res), m_pxdim(pixdim), m_trafo(trafo), m_gamma(gamma) | ||||
| {} | ||||
| 
 | ||||
| void SLARasterWriter::flpXY(ExPolygon &poly) | ||||
| { | ||||
|     for(auto& p : poly.contour.points) p = Point(p.y(), p.x()); | ||||
|     std::reverse(poly.contour.points.begin(), poly.contour.points.end()); | ||||
|      | ||||
|     for(auto& h : poly.holes) { | ||||
|         for(auto& p : h.points) p = Point(p.y(), p.x()); | ||||
|         std::reverse(h.points.begin(), h.points.end()); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| SLARasterWriter::SLARasterWriter(const Raster::Resolution  &res, | ||||
|                                  const Raster::PixelDim    &pixdim, | ||||
|                                  const std::array<bool, 2> &mirror, | ||||
|                                  double gamma) | ||||
|     : m_res(res), m_pxdim(pixdim), m_mirror(mirror), m_gamma(gamma) | ||||
| { | ||||
|     // PNG raster will implicitly do an Y mirror
 | ||||
|     m_mirror[1] = !m_mirror[1]; | ||||
| } | ||||
| 
 | ||||
| void SLARasterWriter::save(const std::string &fpath, const std::string &prjname) | ||||
| void RasterWriter::save(const std::string &fpath, const std::string &prjname) | ||||
| { | ||||
|     try { | ||||
|         Zipper zipper(fpath); // zipper with no compression
 | ||||
|  | @ -103,7 +78,7 @@ std::string get_cfg_value(const DynamicPrintConfig &cfg, const std::string &key) | |||
| 
 | ||||
| } // namespace
 | ||||
| 
 | ||||
| void SLARasterWriter::set_config(const DynamicPrintConfig &cfg) | ||||
| void RasterWriter::set_config(const DynamicPrintConfig &cfg) | ||||
| { | ||||
|     m_config["layerHeight"]    = get_cfg_value(cfg, "layer_height"); | ||||
|     m_config["expTime"]        = get_cfg_value(cfg, "exposure_time"); | ||||
|  | @ -118,7 +93,7 @@ void SLARasterWriter::set_config(const DynamicPrintConfig &cfg) | |||
|     m_config["prusaSlicerVersion"]    = SLIC3R_BUILD_ID; | ||||
| } | ||||
| 
 | ||||
| void SLARasterWriter::set_statistics(const PrintStatistics &stats) | ||||
| void RasterWriter::set_statistics(const PrintStatistics &stats) | ||||
| { | ||||
|     m_config["usedMaterial"] = std::to_string(stats.used_material); | ||||
|     m_config["numFade"]      = std::to_string(stats.num_fade); | ||||
|  |  | |||
|  | @ -15,20 +15,17 @@ | |||
| 
 | ||||
| namespace Slic3r { namespace sla { | ||||
| 
 | ||||
| // Implementation for PNG raster output
 | ||||
| // API to write the zipped sla output layers and metadata.
 | ||||
| // Implementation uses PNG raster output.
 | ||||
| // Be aware that if a large number of layers are allocated, it can very well
 | ||||
| // exhaust the available memory especially on 32 bit platform.
 | ||||
| // This class is designed to be used in parallel mode. Layers have an ID and
 | ||||
| // each layer can be written and compressed independently (in parallel).
 | ||||
| // At the end when all layers where written, the save method can be used to 
 | ||||
| // write out the result into a zipped archive.
 | ||||
| class SLARasterWriter | ||||
| class RasterWriter | ||||
| { | ||||
| public: | ||||
|     enum Orientation { | ||||
|         roLandscape, | ||||
|         roPortrait | ||||
|     }; | ||||
|      | ||||
|     // Used for addressing parameters of set_statistics()
 | ||||
|     struct PrintStatistics | ||||
|  | @ -45,7 +42,7 @@ private: | |||
|     // A struct to bind the raster image data and its compressed bytes together.
 | ||||
|     struct Layer { | ||||
|         Raster raster; | ||||
|         RawBytes rawbytes; | ||||
|         PNGImage rawbytes; | ||||
| 
 | ||||
|         Layer() = default; | ||||
|          | ||||
|  | @ -61,70 +58,55 @@ private: | |||
|     // parallel. Later we can write every layer to the disk sequentially.
 | ||||
|     std::vector<Layer> m_layers_rst; | ||||
|     Raster::Resolution m_res; | ||||
|     Raster::PixelDim m_pxdim; | ||||
|     std::array<bool, 2> m_mirror; | ||||
|     double m_gamma; | ||||
|      | ||||
|     Raster::PixelDim   m_pxdim; | ||||
|     Raster::Trafo      m_trafo; | ||||
|     double             m_gamma; | ||||
| 
 | ||||
|     std::map<std::string, std::string> m_config; | ||||
|      | ||||
|     std::string createIniContent(const std::string& projectname) const; | ||||
|      | ||||
|     static void flpXY(ClipperLib::Polygon& poly); | ||||
|     static void flpXY(ExPolygon& poly); | ||||
| 
 | ||||
| public: | ||||
|     SLARasterWriter(const Raster::Resolution  &res, | ||||
|                     const Raster::PixelDim    &pixdim, | ||||
|                     const std::array<bool, 2> &mirror, | ||||
|                     double gamma = 1.); | ||||
|      | ||||
|     // SLARasterWriter is using Raster in custom mirroring mode
 | ||||
|     RasterWriter(const Raster::Resolution &res, | ||||
|                  const Raster::PixelDim &  pixdim, | ||||
|                  const Raster::Trafo &     trafo, | ||||
|                  double                    gamma = 1.); | ||||
| 
 | ||||
|     SLARasterWriter(const SLARasterWriter& ) = delete; | ||||
|     SLARasterWriter& operator=(const SLARasterWriter&) = delete; | ||||
|     SLARasterWriter(SLARasterWriter&& m) = default; | ||||
|     SLARasterWriter& operator=(SLARasterWriter&&) = default; | ||||
|     RasterWriter(const RasterWriter& ) = delete; | ||||
|     RasterWriter& operator=(const RasterWriter&) = delete; | ||||
|     RasterWriter(RasterWriter&& m) = default; | ||||
|     RasterWriter& operator=(RasterWriter&&) = default; | ||||
| 
 | ||||
|     inline void layers(unsigned cnt) { if(cnt > 0) m_layers_rst.resize(cnt); } | ||||
|     inline unsigned layers() const { return unsigned(m_layers_rst.size()); } | ||||
|      | ||||
|     template<class Poly> void draw_polygon(const Poly& p, unsigned lyr, | ||||
|                       Orientation o = roPortrait) | ||||
|     template<class Poly> void draw_polygon(const Poly& p, unsigned lyr) | ||||
|     { | ||||
|         assert(lyr < m_layers_rst.size()); | ||||
|          | ||||
|         switch (o) { | ||||
|         case roPortrait: { | ||||
|             Poly poly(p); | ||||
|             flpXY(poly); | ||||
|             m_layers_rst[lyr].raster.draw(poly); | ||||
|             break; | ||||
|         } | ||||
|         case roLandscape: | ||||
|             m_layers_rst[lyr].raster.draw(p); | ||||
|             break; | ||||
|         } | ||||
|         m_layers_rst[lyr].raster.draw(p); | ||||
|     } | ||||
| 
 | ||||
|     inline void begin_layer(unsigned lyr) { | ||||
|         if(m_layers_rst.size() <= lyr) m_layers_rst.resize(lyr+1); | ||||
|         m_layers_rst[lyr].raster.reset(m_res, m_pxdim, m_mirror, m_gamma); | ||||
|         m_layers_rst[lyr].raster.reset(m_res, m_pxdim, m_trafo); | ||||
|     } | ||||
| 
 | ||||
|     inline void begin_layer() { | ||||
|         m_layers_rst.emplace_back(); | ||||
|         m_layers_rst.front().raster.reset(m_res, m_pxdim, m_mirror, m_gamma); | ||||
|         m_layers_rst.front().raster.reset(m_res, m_pxdim, m_trafo); | ||||
|     } | ||||
| 
 | ||||
|     inline void finish_layer(unsigned lyr_id) { | ||||
|         assert(lyr_id < m_layers_rst.size()); | ||||
|         m_layers_rst[lyr_id].rawbytes = | ||||
|                 m_layers_rst[lyr_id].raster.save(Raster::Format::PNG); | ||||
|         m_layers_rst[lyr_id].rawbytes.serialize(m_layers_rst[lyr_id].raster); | ||||
|         m_layers_rst[lyr_id].raster.reset(); | ||||
|     } | ||||
| 
 | ||||
|     inline void finish_layer() { | ||||
|         if(!m_layers_rst.empty()) { | ||||
|             m_layers_rst.back().rawbytes = | ||||
|                     m_layers_rst.back().raster.save(Raster::Format::PNG); | ||||
|             m_layers_rst.back().rawbytes.serialize(m_layers_rst.back().raster); | ||||
|             m_layers_rst.back().raster.reset(); | ||||
|         } | ||||
|     } | ||||
|  |  | |||
|  | @ -39,14 +39,19 @@ public: | |||
|         insert(std::make_pair(v, unsigned(idx))); | ||||
|     } | ||||
| 
 | ||||
|     std::vector<PointIndexEl> query(std::function<bool(const PointIndexEl&)>); | ||||
|     std::vector<PointIndexEl> nearest(const Vec3d&, unsigned k); | ||||
|     std::vector<PointIndexEl> query(std::function<bool(const PointIndexEl&)>) const; | ||||
|     std::vector<PointIndexEl> nearest(const Vec3d&, unsigned k) const; | ||||
|     std::vector<PointIndexEl> query(const Vec3d &v, unsigned k) const // wrapper
 | ||||
|     { | ||||
|         return nearest(v, k); | ||||
|     } | ||||
| 
 | ||||
|     // For testing
 | ||||
|     size_t size() const; | ||||
|     bool empty() const { return size() == 0; } | ||||
| 
 | ||||
|     void foreach(std::function<void(const PointIndexEl& el)> fn); | ||||
|     void foreach(std::function<void(const PointIndexEl& el)> fn) const; | ||||
| }; | ||||
| 
 | ||||
| using BoxIndexEl = std::pair<Slic3r::BoundingBox, unsigned>; | ||||
|  |  | |||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -2,24 +2,14 @@ | |||
| #define SLASUPPORTTREE_HPP | ||||
| 
 | ||||
| #include <vector> | ||||
| #include <array> | ||||
| #include <cstdint> | ||||
| #include <memory> | ||||
| #include <Eigen/Geometry> | ||||
| 
 | ||||
| #include "SLACommon.hpp" | ||||
| 
 | ||||
| #include "SLAPad.hpp" | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
| // Needed types from Point.hpp
 | ||||
| typedef int32_t coord_t; | ||||
| typedef Eigen::Matrix<double,   3, 1, Eigen::DontAlign> Vec3d; | ||||
| typedef Eigen::Matrix<float,    3, 1, Eigen::DontAlign> Vec3f; | ||||
| typedef Eigen::Matrix<coord_t,  3, 1, Eigen::DontAlign> Vec3crd; | ||||
| typedef std::vector<Vec3d>                              Pointf3s; | ||||
| typedef std::vector<Vec3crd>                            Points3; | ||||
| 
 | ||||
| class TriangleMesh; | ||||
| class Model; | ||||
| class ModelInstance; | ||||
|  | @ -32,13 +22,17 @@ using ExPolygons = std::vector<ExPolygon>; | |||
| 
 | ||||
| namespace sla { | ||||
| 
 | ||||
| enum class PillarConnectionMode { | ||||
| enum class PillarConnectionMode | ||||
| { | ||||
|     zigzag, | ||||
|     cross, | ||||
|     dynamic | ||||
| }; | ||||
| 
 | ||||
| struct SupportConfig { | ||||
| struct SupportConfig | ||||
| { | ||||
|     bool   enabled = true; | ||||
|      | ||||
|     // Radius in mm of the pointing side of the head.
 | ||||
|     double head_front_radius_mm = 0.2; | ||||
| 
 | ||||
|  | @ -85,6 +79,11 @@ struct SupportConfig { | |||
|     // The shortest distance between a pillar base perimeter from the model
 | ||||
|     // body. This is only useful when elevation is set to zero.
 | ||||
|     double pillar_base_safety_distance_mm = 0.5; | ||||
|      | ||||
|     double head_fullwidth() const { | ||||
|         return 2 * head_front_radius_mm + head_width_mm + | ||||
|                2 * head_back_radius_mm - head_penetration_mm; | ||||
|     } | ||||
| 
 | ||||
|     // /////////////////////////////////////////////////////////////////////////
 | ||||
|     // Compile time configuration values (candidates for runtime)
 | ||||
|  | @ -104,101 +103,78 @@ struct SupportConfig { | |||
|     static const unsigned max_bridges_on_pillar; | ||||
| }; | ||||
| 
 | ||||
| struct PoolConfig; | ||||
| enum class MeshType { Support, Pad }; | ||||
| 
 | ||||
| /// A Control structure for the support calculation. Consists of the status
 | ||||
| /// indicator callback and the stop condition predicate.
 | ||||
| struct Controller { | ||||
| 
 | ||||
| struct JobController | ||||
| { | ||||
|     using StatusFn = std::function<void(unsigned, const std::string&)>; | ||||
|     using StopCond = std::function<bool(void)>; | ||||
|     using CancelFn = std::function<void(void)>; | ||||
|      | ||||
|     // This will signal the status of the calculation to the front-end
 | ||||
|     std::function<void(unsigned, const std::string&)> statuscb = | ||||
|             [](unsigned, const std::string&){}; | ||||
| 
 | ||||
|     StatusFn statuscb = [](unsigned, const std::string&){}; | ||||
|      | ||||
|     // Returns true if the calculation should be aborted.
 | ||||
|     std::function<bool(void)> stopcondition = [](){ return false; }; | ||||
| 
 | ||||
|     StopCond stopcondition = [](){ return false; }; | ||||
|      | ||||
|     // Similar to cancel callback. This should check the stop condition and
 | ||||
|     // if true, throw an appropriate exception. (TriangleMeshSlicer needs this)
 | ||||
|     // consider it a hard abort. stopcondition is permits the algorithm to
 | ||||
|     // terminate itself
 | ||||
|     std::function<void(void)> cancelfn = [](){}; | ||||
|     CancelFn cancelfn = [](){}; | ||||
| }; | ||||
| 
 | ||||
| using PointSet = Eigen::MatrixXd; | ||||
| struct SupportableMesh | ||||
| { | ||||
|     EigenMesh3D   emesh; | ||||
|     SupportPoints pts; | ||||
|     SupportConfig cfg; | ||||
| 
 | ||||
| //EigenMesh3D to_eigenmesh(const TriangleMesh& m);
 | ||||
| 
 | ||||
| // needed for find best rotation
 | ||||
| //EigenMesh3D to_eigenmesh(const ModelObject& model);
 | ||||
| 
 | ||||
| // Simple conversion of 'vector of points' to an Eigen matrix
 | ||||
| //PointSet    to_point_set(const std::vector<sla::SupportPoint>&);
 | ||||
| 
 | ||||
| 
 | ||||
| /* ************************************************************************** */ | ||||
|     explicit SupportableMesh(const TriangleMesh & trmsh, | ||||
|                              const SupportPoints &sp, | ||||
|                              const SupportConfig &c) | ||||
|         : emesh{trmsh}, pts{sp}, cfg{c} | ||||
|     {} | ||||
|      | ||||
|     explicit SupportableMesh(const EigenMesh3D   &em, | ||||
|                              const SupportPoints &sp, | ||||
|                              const SupportConfig &c) | ||||
|         : emesh{em}, pts{sp}, cfg{c} | ||||
|     {} | ||||
| }; | ||||
| 
 | ||||
| /// The class containing mesh data for the generated supports.
 | ||||
| class SLASupportTree { | ||||
|     class Impl;     // persistent support data
 | ||||
|     std::unique_ptr<Impl> m_impl; | ||||
| 
 | ||||
|     Impl& get() { return *m_impl; } | ||||
|     const Impl& get() const { return *m_impl; } | ||||
| 
 | ||||
|     friend void add_sla_supports(Model&, | ||||
|                                  const SupportConfig&, | ||||
|                                  const Controller&); | ||||
| 
 | ||||
|     // The generation algorithm is quite long and will be captured in a separate
 | ||||
|     // class with private data, helper methods, etc... This data is only needed
 | ||||
|     // during the calculation whereas the Impl class contains the persistent
 | ||||
|     // data, mostly the meshes.
 | ||||
|     class Algorithm; | ||||
| 
 | ||||
|     // Generate the 3D supports for a model intended for SLA print. This
 | ||||
|     // will instantiate the Algorithm class and call its appropriate methods
 | ||||
|     // with status indication.
 | ||||
|     bool generate(const std::vector<SupportPoint>& pts, | ||||
|                   const EigenMesh3D& mesh, | ||||
|                   const SupportConfig& cfg = {}, | ||||
|                   const Controller& ctl = {}); | ||||
| 
 | ||||
| class SupportTree | ||||
| { | ||||
|     JobController m_ctl; | ||||
| public: | ||||
| 
 | ||||
|     SLASupportTree(double ground_level = 0.0); | ||||
| 
 | ||||
|     SLASupportTree(const std::vector<SupportPoint>& pts, | ||||
|                    const EigenMesh3D& em, | ||||
|                    const SupportConfig& cfg = {}, | ||||
|                    const Controller& ctl = {}); | ||||
|     using UPtr = std::unique_ptr<SupportTree>; | ||||
|      | ||||
|     SLASupportTree(const SLASupportTree&) = delete; | ||||
|     SLASupportTree& operator=(const SLASupportTree&) = delete; | ||||
|     static UPtr create(const SupportableMesh &input, | ||||
|                        const JobController &ctl = {}); | ||||
| 
 | ||||
|     ~SLASupportTree(); | ||||
|     virtual ~SupportTree() = default; | ||||
| 
 | ||||
|     /// Get the whole mesh united into the output TriangleMesh
 | ||||
|     /// WITHOUT THE PAD
 | ||||
|     const TriangleMesh& merged_mesh() const; | ||||
|     virtual const TriangleMesh &retrieve_mesh(MeshType meshtype) const = 0; | ||||
| 
 | ||||
|     void merged_mesh_with_pad(TriangleMesh&) const; | ||||
| 
 | ||||
|     std::vector<ExPolygons> slice(const std::vector<float> &, | ||||
|                                   float closing_radius) const; | ||||
| 
 | ||||
|     /// Adding the "pad" (base pool) under the supports
 | ||||
|     /// Adding the "pad" under the supports.
 | ||||
|     /// modelbase will be used according to the embed_object flag in PoolConfig.
 | ||||
|     /// If set, the plate will interpreted as the model's intrinsic pad. 
 | ||||
|     /// If set, the plate will be interpreted as the model's intrinsic pad. 
 | ||||
|     /// Otherwise, the modelbase will be unified with the base plate calculated
 | ||||
|     /// from the supports.
 | ||||
|     const TriangleMesh& add_pad(const ExPolygons& modelbase, | ||||
|                                 const PoolConfig& pcfg) const; | ||||
| 
 | ||||
|     /// Get the pad geometry
 | ||||
|     const TriangleMesh& get_pad() const; | ||||
| 
 | ||||
|     void remove_pad(); | ||||
| 
 | ||||
|     virtual const TriangleMesh &add_pad(const ExPolygons &modelbase, | ||||
|                                         const PadConfig & pcfg) = 0; | ||||
|      | ||||
|     virtual void remove_pad() = 0; | ||||
|      | ||||
|     std::vector<ExPolygons> slice(const std::vector<float> &, | ||||
|                                   float closing_radius) const; | ||||
|      | ||||
|     void retrieve_full_mesh(TriangleMesh &outmesh) const; | ||||
|      | ||||
|     const JobController &ctl() const { return m_ctl; } | ||||
| }; | ||||
| 
 | ||||
| } | ||||
|  |  | |||
							
								
								
									
										525
									
								
								src/libslic3r/SLA/SLASupportTreeBuilder.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										525
									
								
								src/libslic3r/SLA/SLASupportTreeBuilder.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,525 @@ | |||
| #include "SLASupportTreeBuilder.hpp" | ||||
| #include "SLASupportTreeBuildsteps.hpp" | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| namespace sla { | ||||
| 
 | ||||
| Contour3D sphere(double rho, Portion portion, double fa) { | ||||
|      | ||||
|     Contour3D ret; | ||||
|      | ||||
|     // prohibit close to zero radius
 | ||||
|     if(rho <= 1e-6 && rho >= -1e-6) return ret; | ||||
|      | ||||
|     auto& vertices = ret.points; | ||||
|     auto& facets = ret.indices; | ||||
|      | ||||
|     // Algorithm:
 | ||||
|     // Add points one-by-one to the sphere grid and form facets using relative
 | ||||
|     // coordinates. Sphere is composed effectively of a mesh of stacked circles.
 | ||||
|      | ||||
|     // adjust via rounding to get an even multiple for any provided angle.
 | ||||
|     double angle = (2*PI / floor(2*PI / fa)); | ||||
|      | ||||
|     // Ring to be scaled to generate the steps of the sphere
 | ||||
|     std::vector<double> ring; | ||||
|      | ||||
|     for (double i = 0; i < 2*PI; i+=angle) ring.emplace_back(i); | ||||
|      | ||||
|     const auto sbegin = size_t(2*std::get<0>(portion)/angle); | ||||
|     const auto send = size_t(2*std::get<1>(portion)/angle); | ||||
|      | ||||
|     const size_t steps = ring.size(); | ||||
|     const double increment = 1.0 / double(steps); | ||||
|      | ||||
|     // special case: first ring connects to 0,0,0
 | ||||
|     // insert and form facets.
 | ||||
|     if(sbegin == 0) | ||||
|         vertices.emplace_back(Vec3d(0.0, 0.0, -rho + increment*sbegin*2.0*rho)); | ||||
|      | ||||
|     auto id = coord_t(vertices.size()); | ||||
|     for (size_t i = 0; i < ring.size(); i++) { | ||||
|         // Fixed scaling
 | ||||
|         const double z = -rho + increment*rho*2.0 * (sbegin + 1.0); | ||||
|         // radius of the circle for this step.
 | ||||
|         const double r = std::sqrt(std::abs(rho*rho - z*z)); | ||||
|         Vec2d b = Eigen::Rotation2Dd(ring[i]) * Eigen::Vector2d(0, r); | ||||
|         vertices.emplace_back(Vec3d(b(0), b(1), z)); | ||||
|          | ||||
|         if (sbegin == 0) | ||||
|             facets.emplace_back((i == 0) ? | ||||
|                                     Vec3crd(coord_t(ring.size()), 0, 1) : | ||||
|                                     Vec3crd(id - 1, 0, id)); | ||||
|         ++id; | ||||
|     } | ||||
|      | ||||
|     // General case: insert and form facets for each step,
 | ||||
|     // joining it to the ring below it.
 | ||||
|     for (size_t s = sbegin + 2; s < send - 1; s++) { | ||||
|         const double z = -rho + increment*double(s*2.0*rho); | ||||
|         const double r = std::sqrt(std::abs(rho*rho - z*z)); | ||||
|          | ||||
|         for (size_t i = 0; i < ring.size(); i++) { | ||||
|             Vec2d b = Eigen::Rotation2Dd(ring[i]) * Eigen::Vector2d(0, r); | ||||
|             vertices.emplace_back(Vec3d(b(0), b(1), z)); | ||||
|             auto id_ringsize = coord_t(id - int(ring.size())); | ||||
|             if (i == 0) { | ||||
|                 // wrap around
 | ||||
|                 facets.emplace_back(Vec3crd(id - 1, id, | ||||
|                                             id + coord_t(ring.size() - 1))); | ||||
|                 facets.emplace_back(Vec3crd(id - 1, id_ringsize, id)); | ||||
|             } else { | ||||
|                 facets.emplace_back(Vec3crd(id_ringsize - 1, id_ringsize, id)); | ||||
|                 facets.emplace_back(Vec3crd(id - 1, id_ringsize - 1, id)); | ||||
|             } | ||||
|             id++; | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     // special case: last ring connects to 0,0,rho*2.0
 | ||||
|     // only form facets.
 | ||||
|     if(send >= size_t(2*PI / angle)) { | ||||
|         vertices.emplace_back(Vec3d(0.0, 0.0, -rho + increment*send*2.0*rho)); | ||||
|         for (size_t i = 0; i < ring.size(); i++) { | ||||
|             auto id_ringsize = coord_t(id - int(ring.size())); | ||||
|             if (i == 0) { | ||||
|                 // third vertex is on the other side of the ring.
 | ||||
|                 facets.emplace_back(Vec3crd(id - 1, id_ringsize, id)); | ||||
|             } else { | ||||
|                 auto ci = coord_t(id_ringsize + coord_t(i)); | ||||
|                 facets.emplace_back(Vec3crd(ci - 1, ci, id)); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     id++; | ||||
|      | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| Contour3D cylinder(double r, double h, size_t ssteps, const Vec3d &sp) | ||||
| { | ||||
|     Contour3D ret; | ||||
|      | ||||
|     auto steps = int(ssteps); | ||||
|     auto& points = ret.points; | ||||
|     auto& indices = ret.indices; | ||||
|     points.reserve(2*ssteps); | ||||
|     double a = 2*PI/steps; | ||||
|      | ||||
|     Vec3d jp = sp; | ||||
|     Vec3d endp = {sp(X), sp(Y), sp(Z) + h}; | ||||
|      | ||||
|     // Upper circle points
 | ||||
|     for(int i = 0; i < steps; ++i) { | ||||
|         double phi = i*a; | ||||
|         double ex = endp(X) + r*std::cos(phi); | ||||
|         double ey = endp(Y) + r*std::sin(phi); | ||||
|         points.emplace_back(ex, ey, endp(Z)); | ||||
|     } | ||||
|      | ||||
|     // Lower circle points
 | ||||
|     for(int i = 0; i < steps; ++i) { | ||||
|         double phi = i*a; | ||||
|         double x = jp(X) + r*std::cos(phi); | ||||
|         double y = jp(Y) + r*std::sin(phi); | ||||
|         points.emplace_back(x, y, jp(Z)); | ||||
|     } | ||||
|      | ||||
|     // Now create long triangles connecting upper and lower circles
 | ||||
|     indices.reserve(2*ssteps); | ||||
|     auto offs = steps; | ||||
|     for(int i = 0; i < steps - 1; ++i) { | ||||
|         indices.emplace_back(i, i + offs, offs + i + 1); | ||||
|         indices.emplace_back(i, offs + i + 1, i + 1); | ||||
|     } | ||||
|      | ||||
|     // Last triangle connecting the first and last vertices
 | ||||
|     auto last = steps - 1; | ||||
|     indices.emplace_back(0, last, offs); | ||||
|     indices.emplace_back(last, offs + last, offs); | ||||
|      | ||||
|     // According to the slicing algorithms, we need to aid them with generating
 | ||||
|     // a watertight body. So we create a triangle fan for the upper and lower
 | ||||
|     // ending of the cylinder to close the geometry.
 | ||||
|     points.emplace_back(jp); int ci = int(points.size() - 1); | ||||
|     for(int i = 0; i < steps - 1; ++i) | ||||
|         indices.emplace_back(i + offs + 1, i + offs, ci); | ||||
|      | ||||
|     indices.emplace_back(offs, steps + offs - 1, ci); | ||||
|      | ||||
|     points.emplace_back(endp); ci = int(points.size() - 1); | ||||
|     for(int i = 0; i < steps - 1; ++i) | ||||
|         indices.emplace_back(ci, i, i + 1); | ||||
|      | ||||
|     indices.emplace_back(steps - 1, 0, ci); | ||||
|      | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| Head::Head(double       r_big_mm, | ||||
|            double       r_small_mm, | ||||
|            double       length_mm, | ||||
|            double       penetration, | ||||
|            const Vec3d &direction, | ||||
|            const Vec3d &offset, | ||||
|            const size_t circlesteps) | ||||
|     : steps(circlesteps) | ||||
|     , dir(direction) | ||||
|     , tr(offset) | ||||
|     , r_back_mm(r_big_mm) | ||||
|     , r_pin_mm(r_small_mm) | ||||
|     , width_mm(length_mm) | ||||
|     , penetration_mm(penetration) | ||||
| { | ||||
|     assert(width_mm > 0.); | ||||
|     assert(r_back_mm > 0.); | ||||
|     assert(r_pin_mm > 0.); | ||||
|      | ||||
|     // We create two spheres which will be connected with a robe that fits
 | ||||
|     // both circles perfectly.
 | ||||
|      | ||||
|     // Set up the model detail level
 | ||||
|     const double detail = 2*PI/steps; | ||||
|      | ||||
|     // We don't generate whole circles. Instead, we generate only the
 | ||||
|     // portions which are visible (not covered by the robe) To know the
 | ||||
|     // exact portion of the bottom and top circles we need to use some
 | ||||
|     // rules of tangent circles from which we can derive (using simple
 | ||||
|     // triangles the following relations:
 | ||||
|      | ||||
|     // The height of the whole mesh
 | ||||
|     const double h = r_big_mm + r_small_mm + width_mm; | ||||
|     double phi = PI/2 - std::acos( (r_big_mm - r_small_mm) / h ); | ||||
|      | ||||
|     // To generate a whole circle we would pass a portion of (0, Pi)
 | ||||
|     // To generate only a half horizontal circle we can pass (0, Pi/2)
 | ||||
|     // The calculated phi is an offset to the half circles needed to smooth
 | ||||
|     // the transition from the circle to the robe geometry
 | ||||
|      | ||||
|     auto&& s1 = sphere(r_big_mm, make_portion(0, PI/2 + phi), detail); | ||||
|     auto&& s2 = sphere(r_small_mm, make_portion(PI/2 + phi, PI), detail); | ||||
|      | ||||
|     for(auto& p : s2.points) p.z() += h; | ||||
|      | ||||
|     mesh.merge(s1); | ||||
|     mesh.merge(s2); | ||||
|      | ||||
|     for(size_t idx1 = s1.points.size() - steps, idx2 = s1.points.size(); | ||||
|         idx1 < s1.points.size() - 1; | ||||
|         idx1++, idx2++) | ||||
|     { | ||||
|         coord_t i1s1 = coord_t(idx1), i1s2 = coord_t(idx2); | ||||
|         coord_t i2s1 = i1s1 + 1, i2s2 = i1s2 + 1; | ||||
|          | ||||
|         mesh.indices.emplace_back(i1s1, i2s1, i2s2); | ||||
|         mesh.indices.emplace_back(i1s1, i2s2, i1s2); | ||||
|     } | ||||
|      | ||||
|     auto i1s1 = coord_t(s1.points.size()) - coord_t(steps); | ||||
|     auto i2s1 = coord_t(s1.points.size()) - 1; | ||||
|     auto i1s2 = coord_t(s1.points.size()); | ||||
|     auto i2s2 = coord_t(s1.points.size()) + coord_t(steps) - 1; | ||||
|      | ||||
|     mesh.indices.emplace_back(i2s2, i2s1, i1s1); | ||||
|     mesh.indices.emplace_back(i1s2, i2s2, i1s1); | ||||
|      | ||||
|     // To simplify further processing, we translate the mesh so that the
 | ||||
|     // last vertex of the pointing sphere (the pinpoint) will be at (0,0,0)
 | ||||
|     for(auto& p : mesh.points) p.z() -= (h + r_small_mm - penetration_mm); | ||||
| } | ||||
| 
 | ||||
| Pillar::Pillar(const Vec3d &jp, const Vec3d &endp, double radius, size_t st): | ||||
|     r(radius), steps(st), endpt(endp), starts_from_head(false) | ||||
| { | ||||
|     assert(steps > 0); | ||||
|      | ||||
|     height = jp(Z) - endp(Z); | ||||
|     if(height > EPSILON) { // Endpoint is below the starting point
 | ||||
|          | ||||
|         // We just create a bridge geometry with the pillar parameters and
 | ||||
|         // move the data.
 | ||||
|         Contour3D body = cylinder(radius, height, st, endp); | ||||
|         mesh.points.swap(body.points); | ||||
|         mesh.indices.swap(body.indices); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| Pillar &Pillar::add_base(double baseheight, double radius) | ||||
| { | ||||
|     if(baseheight <= 0) return *this; | ||||
|     if(baseheight > height) baseheight = height; | ||||
|      | ||||
|     assert(steps >= 0); | ||||
|     auto last = int(steps - 1); | ||||
|      | ||||
|     if(radius < r ) radius = r; | ||||
|      | ||||
|     double a = 2*PI/steps; | ||||
|     double z = endpt(Z) + baseheight; | ||||
|      | ||||
|     for(size_t i = 0; i < steps; ++i) { | ||||
|         double phi = i*a; | ||||
|         double x = endpt(X) + r*std::cos(phi); | ||||
|         double y = endpt(Y) + r*std::sin(phi); | ||||
|         base.points.emplace_back(x, y, z); | ||||
|     } | ||||
|      | ||||
|     for(size_t i = 0; i < steps; ++i) { | ||||
|         double phi = i*a; | ||||
|         double x = endpt(X) + radius*std::cos(phi); | ||||
|         double y = endpt(Y) + radius*std::sin(phi); | ||||
|         base.points.emplace_back(x, y, z - baseheight); | ||||
|     } | ||||
|      | ||||
|     auto ep = endpt; ep(Z) += baseheight; | ||||
|     base.points.emplace_back(endpt); | ||||
|     base.points.emplace_back(ep); | ||||
|      | ||||
|     auto& indices = base.indices; | ||||
|     auto hcenter = int(base.points.size() - 1); | ||||
|     auto lcenter = int(base.points.size() - 2); | ||||
|     auto offs = int(steps); | ||||
|     for(int i = 0; i < last; ++i) { | ||||
|         indices.emplace_back(i, i + offs, offs + i + 1); | ||||
|         indices.emplace_back(i, offs + i + 1, i + 1); | ||||
|         indices.emplace_back(i, i + 1, hcenter); | ||||
|         indices.emplace_back(lcenter, offs + i + 1, offs + i); | ||||
|     } | ||||
|      | ||||
|     indices.emplace_back(0, last, offs); | ||||
|     indices.emplace_back(last, offs + last, offs); | ||||
|     indices.emplace_back(hcenter, last, 0); | ||||
|     indices.emplace_back(offs, offs + last, lcenter); | ||||
|     return *this; | ||||
| } | ||||
| 
 | ||||
| Bridge::Bridge(const Vec3d &j1, const Vec3d &j2, double r_mm, size_t steps): | ||||
|     r(r_mm), startp(j1), endp(j2) | ||||
| { | ||||
|     using Quaternion = Eigen::Quaternion<double>; | ||||
|     Vec3d dir = (j2 - j1).normalized(); | ||||
|     double d = distance(j2, j1); | ||||
|      | ||||
|     mesh = cylinder(r, d, steps); | ||||
|      | ||||
|     auto quater = Quaternion::FromTwoVectors(Vec3d{0,0,1}, dir); | ||||
|     for(auto& p : mesh.points) p = quater * p + j1; | ||||
| } | ||||
| 
 | ||||
| CompactBridge::CompactBridge(const Vec3d &sp, | ||||
|                              const Vec3d &ep, | ||||
|                              const Vec3d &n, | ||||
|                              double       r, | ||||
|                              bool         endball, | ||||
|                              size_t       steps) | ||||
| { | ||||
|     Vec3d startp = sp + r * n; | ||||
|     Vec3d dir = (ep - startp).normalized(); | ||||
|     Vec3d endp = ep - r * dir; | ||||
|      | ||||
|     Bridge br(startp, endp, r, steps); | ||||
|     mesh.merge(br.mesh); | ||||
|      | ||||
|     // now add the pins
 | ||||
|     double fa = 2*PI/steps; | ||||
|     auto upperball = sphere(r, Portion{PI / 2 - fa, PI}, fa); | ||||
|     for(auto& p : upperball.points) p += startp; | ||||
|      | ||||
|     if(endball) { | ||||
|         auto lowerball = sphere(r, Portion{0, PI/2 + 2*fa}, fa); | ||||
|         for(auto& p : lowerball.points) p += endp; | ||||
|         mesh.merge(lowerball); | ||||
|     } | ||||
|      | ||||
|     mesh.merge(upperball); | ||||
| } | ||||
| 
 | ||||
| Pad::Pad(const TriangleMesh &support_mesh, | ||||
|          const ExPolygons &  model_contours, | ||||
|          double              ground_level, | ||||
|          const PadConfig &   pcfg, | ||||
|          ThrowOnCancel       thr) | ||||
|     : cfg(pcfg) | ||||
|     , zlevel(ground_level + pcfg.full_height() - pcfg.required_elevation()) | ||||
| { | ||||
|     thr(); | ||||
|      | ||||
|     ExPolygons sup_contours; | ||||
|      | ||||
|     float zstart = float(zlevel); | ||||
|     float zend   = zstart + float(pcfg.full_height() + EPSILON); | ||||
|      | ||||
|     pad_blueprint(support_mesh, sup_contours, grid(zstart, zend, 0.1f), thr); | ||||
|     create_pad(sup_contours, model_contours, tmesh, pcfg); | ||||
|      | ||||
|     tmesh.translate(0, 0, float(zlevel)); | ||||
|     if (!tmesh.empty()) tmesh.require_shared_vertices(); | ||||
| } | ||||
| 
 | ||||
| const TriangleMesh &SupportTreeBuilder::add_pad(const ExPolygons &modelbase, | ||||
|                                                 const PadConfig & cfg) | ||||
| { | ||||
|     m_pad = Pad{merged_mesh(), modelbase, ground_level, cfg, ctl().cancelfn}; | ||||
|     return m_pad.tmesh; | ||||
| } | ||||
| 
 | ||||
| SupportTreeBuilder::SupportTreeBuilder(SupportTreeBuilder &&o) | ||||
|     : m_heads(std::move(o.m_heads)) | ||||
|     , m_head_indices{std::move(o.m_head_indices)} | ||||
|     , m_pillars{std::move(o.m_pillars)} | ||||
|     , m_bridges{std::move(o.m_bridges)} | ||||
|     , m_crossbridges{std::move(o.m_crossbridges)} | ||||
|     , m_compact_bridges{std::move(o.m_compact_bridges)} | ||||
|     , m_pad{std::move(o.m_pad)} | ||||
|     , m_meshcache{std::move(o.m_meshcache)} | ||||
|     , m_meshcache_valid{o.m_meshcache_valid} | ||||
|     , m_model_height{o.m_model_height} | ||||
|     , ground_level{o.ground_level} | ||||
| {} | ||||
| 
 | ||||
| SupportTreeBuilder::SupportTreeBuilder(const SupportTreeBuilder &o) | ||||
|     : m_heads(o.m_heads) | ||||
|     , m_head_indices{o.m_head_indices} | ||||
|     , m_pillars{o.m_pillars} | ||||
|     , m_bridges{o.m_bridges} | ||||
|     , m_crossbridges{o.m_crossbridges} | ||||
|     , m_compact_bridges{o.m_compact_bridges} | ||||
|     , m_pad{o.m_pad} | ||||
|     , m_meshcache{o.m_meshcache} | ||||
|     , m_meshcache_valid{o.m_meshcache_valid} | ||||
|     , m_model_height{o.m_model_height} | ||||
|     , ground_level{o.ground_level} | ||||
| {} | ||||
| 
 | ||||
| SupportTreeBuilder &SupportTreeBuilder::operator=(SupportTreeBuilder &&o) | ||||
| { | ||||
|     m_heads = std::move(o.m_heads); | ||||
|     m_head_indices = std::move(o.m_head_indices); | ||||
|     m_pillars = std::move(o.m_pillars); | ||||
|     m_bridges = std::move(o.m_bridges); | ||||
|     m_crossbridges = std::move(o.m_crossbridges); | ||||
|     m_compact_bridges = std::move(o.m_compact_bridges); | ||||
|     m_pad = std::move(o.m_pad); | ||||
|     m_meshcache = std::move(o.m_meshcache); | ||||
|     m_meshcache_valid = o.m_meshcache_valid; | ||||
|     m_model_height = o.m_model_height; | ||||
|     ground_level = o.ground_level; | ||||
|     return *this; | ||||
| } | ||||
| 
 | ||||
| SupportTreeBuilder &SupportTreeBuilder::operator=(const SupportTreeBuilder &o) | ||||
| { | ||||
|     m_heads = o.m_heads; | ||||
|     m_head_indices = o.m_head_indices; | ||||
|     m_pillars = o.m_pillars; | ||||
|     m_bridges = o.m_bridges; | ||||
|     m_crossbridges = o.m_crossbridges; | ||||
|     m_compact_bridges = o.m_compact_bridges; | ||||
|     m_pad = o.m_pad; | ||||
|     m_meshcache = o.m_meshcache; | ||||
|     m_meshcache_valid = o.m_meshcache_valid; | ||||
|     m_model_height = o.m_model_height; | ||||
|     ground_level = o.ground_level; | ||||
|     return *this; | ||||
| } | ||||
| 
 | ||||
| const TriangleMesh &SupportTreeBuilder::merged_mesh() const | ||||
| { | ||||
|     if (m_meshcache_valid) return m_meshcache; | ||||
|      | ||||
|     Contour3D merged; | ||||
|      | ||||
|     for (auto &head : m_heads) { | ||||
|         if (ctl().stopcondition()) break; | ||||
|         if (head.is_valid()) merged.merge(head.mesh); | ||||
|     } | ||||
|      | ||||
|     for (auto &stick : m_pillars) { | ||||
|         if (ctl().stopcondition()) break; | ||||
|         merged.merge(stick.mesh); | ||||
|         merged.merge(stick.base); | ||||
|     } | ||||
|      | ||||
|     for (auto &j : m_junctions) { | ||||
|         if (ctl().stopcondition()) break; | ||||
|         merged.merge(j.mesh); | ||||
|     } | ||||
|      | ||||
|     for (auto &cb : m_compact_bridges) { | ||||
|         if (ctl().stopcondition()) break; | ||||
|         merged.merge(cb.mesh); | ||||
|     } | ||||
|      | ||||
|     for (auto &bs : m_bridges) { | ||||
|         if (ctl().stopcondition()) break; | ||||
|         merged.merge(bs.mesh); | ||||
|     } | ||||
|      | ||||
|     for (auto &bs : m_crossbridges) { | ||||
|         if (ctl().stopcondition()) break; | ||||
|         merged.merge(bs.mesh); | ||||
|     } | ||||
|      | ||||
|     if (ctl().stopcondition()) { | ||||
|         // In case of failure we have to return an empty mesh
 | ||||
|         m_meshcache = TriangleMesh(); | ||||
|         return m_meshcache; | ||||
|     } | ||||
|      | ||||
|     m_meshcache = mesh(merged); | ||||
|      | ||||
|     // The mesh will be passed by const-pointer to TriangleMeshSlicer,
 | ||||
|     // which will need this.
 | ||||
|     if (!m_meshcache.empty()) m_meshcache.require_shared_vertices(); | ||||
|      | ||||
|     BoundingBoxf3 &&bb = m_meshcache.bounding_box(); | ||||
|     m_model_height       = bb.max(Z) - bb.min(Z); | ||||
|      | ||||
|     m_meshcache_valid = true; | ||||
|     return m_meshcache; | ||||
| } | ||||
| 
 | ||||
| double SupportTreeBuilder::full_height() const | ||||
| { | ||||
|     if (merged_mesh().empty() && !pad().empty()) | ||||
|         return pad().cfg.full_height(); | ||||
|      | ||||
|     double h = mesh_height(); | ||||
|     if (!pad().empty()) h += pad().cfg.required_elevation(); | ||||
|     return h; | ||||
| } | ||||
| 
 | ||||
| const TriangleMesh &SupportTreeBuilder::merge_and_cleanup() | ||||
| { | ||||
|     // in case the mesh is not generated, it should be...
 | ||||
|     auto &ret = merged_mesh();  | ||||
|      | ||||
|     // Doing clear() does not garantee to release the memory.
 | ||||
|     m_heads = {}; | ||||
|     m_head_indices = {}; | ||||
|     m_pillars = {}; | ||||
|     m_junctions = {}; | ||||
|     m_bridges = {}; | ||||
|     m_compact_bridges = {}; | ||||
|      | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| const TriangleMesh &SupportTreeBuilder::retrieve_mesh(MeshType meshtype) const | ||||
| { | ||||
|     switch(meshtype) { | ||||
|     case MeshType::Support: return merged_mesh(); | ||||
|     case MeshType::Pad:     return pad().tmesh; | ||||
|     } | ||||
|      | ||||
|     return m_meshcache; | ||||
| } | ||||
| 
 | ||||
| bool SupportTreeBuilder::build(const SupportableMesh &sm) | ||||
| { | ||||
|     ground_level = sm.emesh.ground_level() - sm.cfg.object_elevation_mm; | ||||
|     return SupportTreeBuildsteps::execute(*this, sm); | ||||
| } | ||||
| 
 | ||||
| } | ||||
| } | ||||
							
								
								
									
										496
									
								
								src/libslic3r/SLA/SLASupportTreeBuilder.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										496
									
								
								src/libslic3r/SLA/SLASupportTreeBuilder.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,496 @@ | |||
| #ifndef SUPPORTTREEBUILDER_HPP | ||||
| #define SUPPORTTREEBUILDER_HPP | ||||
| 
 | ||||
| #include "SLAConcurrency.hpp" | ||||
| #include "SLABoilerPlate.hpp" | ||||
| #include "SLASupportTree.hpp" | ||||
| #include "SLAPad.hpp" | ||||
| #include <libslic3r/MTUtils.hpp> | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| namespace sla { | ||||
| 
 | ||||
| /**
 | ||||
|  * Terminology: | ||||
|  * | ||||
|  * Support point: | ||||
|  * The point on the model surface that needs support. | ||||
|  * | ||||
|  * Pillar: | ||||
|  * A thick column that spans from a support point to the ground and has | ||||
|  * a thick cone shaped base where it touches the ground. | ||||
|  * | ||||
|  * Ground facing support point: | ||||
|  * A support point that can be directly connected with the ground with a pillar | ||||
|  * that does not collide or cut through the model. | ||||
|  * | ||||
|  * Non ground facing support point: | ||||
|  * A support point that cannot be directly connected with the ground (only with | ||||
|  * the model surface). | ||||
|  * | ||||
|  * Head: | ||||
|  * The pinhead that connects to the model surface with the sharp end end | ||||
|  * to a pillar or bridge stick with the dull end. | ||||
|  * | ||||
|  * Headless support point: | ||||
|  * A support point on the model surface for which there is not enough place for | ||||
|  * the head. It is either in a hole or there is some barrier that would collide | ||||
|  * with the head geometry. The headless support point can be ground facing and | ||||
|  * non ground facing as well. | ||||
|  * | ||||
|  * Bridge: | ||||
|  * A stick that connects two pillars or a head with a pillar. | ||||
|  * | ||||
|  * Junction: | ||||
|  * A small ball in the intersection of two or more sticks (pillar, bridge, ...) | ||||
|  * | ||||
|  * CompactBridge: | ||||
|  * A bridge that connects a headless support point with the model surface or a | ||||
|  * nearby pillar. | ||||
|  */ | ||||
| 
 | ||||
| using Coordf = double; | ||||
| using Portion = std::tuple<double, double>; | ||||
| 
 | ||||
| inline Portion make_portion(double a, double b) { | ||||
|     return std::make_tuple(a, b); | ||||
| } | ||||
| 
 | ||||
| template<class Vec> double distance(const Vec& p) { | ||||
|     return std::sqrt(p.transpose() * p); | ||||
| } | ||||
| 
 | ||||
| template<class Vec> double distance(const Vec& pp1, const Vec& pp2) { | ||||
|     auto p = pp2 - pp1; | ||||
|     return distance(p); | ||||
| } | ||||
| 
 | ||||
| Contour3D sphere(double rho, Portion portion = make_portion(0.0, 2.0*PI), | ||||
|                  double fa=(2*PI/360)); | ||||
| 
 | ||||
| // Down facing cylinder in Z direction with arguments:
 | ||||
| // r: radius
 | ||||
| // h: Height
 | ||||
| // ssteps: how many edges will create the base circle
 | ||||
| // sp: starting point
 | ||||
| Contour3D cylinder(double r, double h, size_t ssteps, const Vec3d &sp = {0,0,0}); | ||||
| 
 | ||||
| const constexpr long ID_UNSET = -1; | ||||
| 
 | ||||
| struct Head { | ||||
|     Contour3D mesh; | ||||
|      | ||||
|     size_t steps = 45; | ||||
|     Vec3d dir = {0, 0, -1}; | ||||
|     Vec3d tr = {0, 0, 0}; | ||||
|      | ||||
|     double r_back_mm = 1; | ||||
|     double r_pin_mm = 0.5; | ||||
|     double width_mm = 2; | ||||
|     double penetration_mm = 0.5; | ||||
|      | ||||
|     // For identification purposes. This will be used as the index into the
 | ||||
|     // container holding the head structures. See SLASupportTree::Impl
 | ||||
|     long id = ID_UNSET; | ||||
|      | ||||
|     // If there is a pillar connecting to this head, then the id will be set.
 | ||||
|     long pillar_id = ID_UNSET; | ||||
|      | ||||
|     long bridge_id = ID_UNSET; | ||||
|      | ||||
|     inline void invalidate() { id = ID_UNSET; } | ||||
|     inline bool is_valid() const { return id >= 0; } | ||||
|      | ||||
|     Head(double r_big_mm, | ||||
|          double r_small_mm, | ||||
|          double length_mm, | ||||
|          double penetration, | ||||
|          const Vec3d &direction = {0, 0, -1},  // direction (normal to the dull end)
 | ||||
|          const Vec3d &offset = {0, 0, 0},      // displacement
 | ||||
|          const size_t circlesteps = 45); | ||||
|      | ||||
|     void transform() | ||||
|     { | ||||
|         using Quaternion = Eigen::Quaternion<double>; | ||||
|          | ||||
|         // We rotate the head to the specified direction The head's pointing
 | ||||
|         // side is facing upwards so this means that it would hold a support
 | ||||
|         // point with a normal pointing straight down. This is the reason of
 | ||||
|         // the -1 z coordinate
 | ||||
|         auto quatern = Quaternion::FromTwoVectors(Vec3d{0, 0, -1}, dir); | ||||
|          | ||||
|         for(auto& p : mesh.points) p = quatern * p + tr; | ||||
|     } | ||||
|      | ||||
|     inline double fullwidth() const | ||||
|     { | ||||
|         return 2 * r_pin_mm + width_mm + 2*r_back_mm - penetration_mm; | ||||
|     } | ||||
|      | ||||
|     inline Vec3d junction_point() const | ||||
|     { | ||||
|         return tr + ( 2 * r_pin_mm + width_mm + r_back_mm - penetration_mm)*dir; | ||||
|     } | ||||
|      | ||||
|     inline double request_pillar_radius(double radius) const | ||||
|     { | ||||
|         const double rmax = r_back_mm; | ||||
|         return radius > 0 && radius < rmax ? radius : rmax; | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| struct Junction { | ||||
|     Contour3D mesh; | ||||
|     double r = 1; | ||||
|     size_t steps = 45; | ||||
|     Vec3d pos; | ||||
|      | ||||
|     long id = ID_UNSET; | ||||
|      | ||||
|     Junction(const Vec3d& tr, double r_mm, size_t stepnum = 45): | ||||
|         r(r_mm), steps(stepnum), pos(tr) | ||||
|     { | ||||
|         mesh = sphere(r_mm, make_portion(0, PI), 2*PI/steps); | ||||
|         for(auto& p : mesh.points) p += tr; | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| struct Pillar { | ||||
|     Contour3D mesh; | ||||
|     Contour3D base; | ||||
|     double r = 1; | ||||
|     size_t steps = 0; | ||||
|     Vec3d endpt; | ||||
|     double height = 0; | ||||
|      | ||||
|     long id = ID_UNSET; | ||||
|      | ||||
|     // If the pillar connects to a head, this is the id of that head
 | ||||
|     bool starts_from_head = true; // Could start from a junction as well
 | ||||
|     long start_junction_id = ID_UNSET; | ||||
|      | ||||
|     // How many bridges are connected to this pillar
 | ||||
|     unsigned bridges = 0; | ||||
|      | ||||
|     // How many pillars are cascaded with this one
 | ||||
|     unsigned links = 0; | ||||
|      | ||||
|     Pillar(const Vec3d& jp, const Vec3d& endp, | ||||
|            double radius = 1, size_t st = 45); | ||||
|      | ||||
|     Pillar(const Junction &junc, const Vec3d &endp) | ||||
|         : Pillar(junc.pos, endp, junc.r, junc.steps) | ||||
|     {} | ||||
|      | ||||
|     Pillar(const Head &head, const Vec3d &endp, double radius = 1) | ||||
|         : Pillar(head.junction_point(), endp, | ||||
|                  head.request_pillar_radius(radius), head.steps) | ||||
|     {} | ||||
|      | ||||
|     inline Vec3d startpoint() const | ||||
|     { | ||||
|         return {endpt(X), endpt(Y), endpt(Z) + height}; | ||||
|     } | ||||
|      | ||||
|     inline const Vec3d& endpoint() const { return endpt; } | ||||
|      | ||||
|     Pillar& add_base(double baseheight = 3, double radius = 2); | ||||
| }; | ||||
| 
 | ||||
| // A Bridge between two pillars (with junction endpoints)
 | ||||
| struct Bridge { | ||||
|     Contour3D mesh; | ||||
|     double r = 0.8; | ||||
|     long id = ID_UNSET; | ||||
|     Vec3d startp = Vec3d::Zero(), endp = Vec3d::Zero(); | ||||
|      | ||||
|     Bridge(const Vec3d &j1, | ||||
|            const Vec3d &j2, | ||||
|            double       r_mm  = 0.8, | ||||
|            size_t       steps = 45); | ||||
| }; | ||||
| 
 | ||||
| // A bridge that spans from model surface to model surface with small connecting
 | ||||
| // edges on the endpoints. Used for headless support points.
 | ||||
| struct CompactBridge { | ||||
|     Contour3D mesh; | ||||
|     long id = ID_UNSET; | ||||
|      | ||||
|     CompactBridge(const Vec3d& sp, | ||||
|                   const Vec3d& ep, | ||||
|                   const Vec3d& n, | ||||
|                   double r, | ||||
|                   bool endball = true, | ||||
|                   size_t steps = 45); | ||||
| }; | ||||
| 
 | ||||
| // A wrapper struct around the pad
 | ||||
| struct Pad { | ||||
|     TriangleMesh tmesh; | ||||
|     PadConfig cfg; | ||||
|     double zlevel = 0; | ||||
|      | ||||
|     Pad() = default; | ||||
|      | ||||
|     Pad(const TriangleMesh &support_mesh, | ||||
|         const ExPolygons &  model_contours, | ||||
|         double              ground_level, | ||||
|         const PadConfig &   pcfg, | ||||
|         ThrowOnCancel       thr); | ||||
|      | ||||
|     bool empty() const { return tmesh.facets_count() == 0; } | ||||
| }; | ||||
| 
 | ||||
| // This class will hold the support tree meshes with some additional
 | ||||
| // bookkeeping as well. Various parts of the support geometry are stored
 | ||||
| // separately and are merged when the caller queries the merged mesh. The
 | ||||
| // merged result is cached for fast subsequent delivery of the merged mesh
 | ||||
| // which can be quite complex. The support tree creation algorithm can use an
 | ||||
| // instance of this class as a somewhat higher level tool for crafting the 3D
 | ||||
| // support mesh. Parts can be added with the appropriate methods such as
 | ||||
| // add_head or add_pillar which forwards the constructor arguments and fills
 | ||||
| // the IDs of these substructures. The IDs are basically indices into the
 | ||||
| // arrays of the appropriate type (heads, pillars, etc...). One can later query
 | ||||
| // e.g. a pillar for a specific head...
 | ||||
| //
 | ||||
| // The support pad is considered an auxiliary geometry and is not part of the
 | ||||
| // merged mesh. It can be retrieved using a dedicated method (pad())
 | ||||
| class SupportTreeBuilder: public SupportTree { | ||||
|     // For heads it is beneficial to use the same IDs as for the support points.
 | ||||
|     std::vector<Head> m_heads; | ||||
|     std::vector<size_t> m_head_indices; | ||||
|     std::vector<Pillar> m_pillars; | ||||
|     std::vector<Junction> m_junctions; | ||||
|     std::vector<Bridge> m_bridges; | ||||
|     std::vector<Bridge> m_crossbridges; | ||||
|     std::vector<CompactBridge> m_compact_bridges;     | ||||
|     Pad m_pad; | ||||
|      | ||||
|     using Mutex = ccr::SpinningMutex; | ||||
|      | ||||
|     mutable TriangleMesh m_meshcache; | ||||
|     mutable Mutex m_mutex; | ||||
|     mutable bool m_meshcache_valid = false; | ||||
|     mutable double m_model_height = 0; // the full height of the model
 | ||||
|      | ||||
|     template<class...Args> | ||||
|     const Bridge& _add_bridge(std::vector<Bridge> &br, Args&&... args) | ||||
|     { | ||||
|         std::lock_guard<Mutex> lk(m_mutex); | ||||
|         br.emplace_back(std::forward<Args>(args)...); | ||||
|         br.back().id = long(br.size() - 1); | ||||
|         m_meshcache_valid = false; | ||||
|         return br.back(); | ||||
|     } | ||||
|      | ||||
| public: | ||||
|     double ground_level = 0; | ||||
|      | ||||
|     SupportTreeBuilder() = default; | ||||
|     SupportTreeBuilder(SupportTreeBuilder &&o); | ||||
|     SupportTreeBuilder(const SupportTreeBuilder &o); | ||||
|     SupportTreeBuilder& operator=(SupportTreeBuilder &&o); | ||||
|     SupportTreeBuilder& operator=(const SupportTreeBuilder &o); | ||||
| 
 | ||||
|     template<class...Args> Head& add_head(unsigned id, Args&&... args) | ||||
|     { | ||||
|         std::lock_guard<Mutex> lk(m_mutex); | ||||
|         m_heads.emplace_back(std::forward<Args>(args)...); | ||||
|         m_heads.back().id = id; | ||||
|          | ||||
|         if (id >= m_head_indices.size()) m_head_indices.resize(id + 1); | ||||
|         m_head_indices[id] = m_heads.size() - 1; | ||||
|          | ||||
|         m_meshcache_valid = false; | ||||
|         return m_heads.back(); | ||||
|     } | ||||
|      | ||||
|     template<class...Args> long add_pillar(long headid, Args&&... args) | ||||
|     { | ||||
|         std::lock_guard<Mutex> lk(m_mutex); | ||||
|         if (m_pillars.capacity() < m_heads.size()) | ||||
|             m_pillars.reserve(m_heads.size() * 10); | ||||
|          | ||||
|         assert(headid >= 0 && size_t(headid) < m_head_indices.size()); | ||||
|         Head &head = m_heads[m_head_indices[size_t(headid)]]; | ||||
|          | ||||
|         m_pillars.emplace_back(head, std::forward<Args>(args)...); | ||||
|         Pillar& pillar = m_pillars.back(); | ||||
|         pillar.id = long(m_pillars.size() - 1); | ||||
|         head.pillar_id = pillar.id; | ||||
|         pillar.start_junction_id = head.id; | ||||
|         pillar.starts_from_head = true; | ||||
|          | ||||
|         m_meshcache_valid = false; | ||||
|         return pillar.id; | ||||
|     } | ||||
|      | ||||
|     void add_pillar_base(long pid, double baseheight = 3, double radius = 2) | ||||
|     { | ||||
|         std::lock_guard<Mutex> lk(m_mutex); | ||||
|         assert(pid >= 0 && size_t(pid) < m_pillars.size()); | ||||
|         m_pillars[size_t(pid)].add_base(baseheight, radius); | ||||
|     } | ||||
|      | ||||
|     void increment_bridges(const Pillar& pillar) | ||||
|     { | ||||
|         std::lock_guard<Mutex> lk(m_mutex); | ||||
|         assert(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size()); | ||||
|          | ||||
|         if(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size()) | ||||
|             m_pillars[size_t(pillar.id)].bridges++; | ||||
|     } | ||||
|      | ||||
|     void increment_links(const Pillar& pillar) | ||||
|     { | ||||
|         std::lock_guard<Mutex> lk(m_mutex); | ||||
|         assert(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size()); | ||||
|          | ||||
|         if(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size())  | ||||
|             m_pillars[size_t(pillar.id)].links++; | ||||
|     } | ||||
|      | ||||
|     unsigned bridgecount(const Pillar &pillar) const { | ||||
|         std::lock_guard<Mutex> lk(m_mutex); | ||||
|         assert(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size()); | ||||
|         return pillar.bridges; | ||||
|     } | ||||
|      | ||||
|     template<class...Args> long add_pillar(Args&&...args) | ||||
|     { | ||||
|         std::lock_guard<Mutex> lk(m_mutex); | ||||
|         if (m_pillars.capacity() < m_heads.size()) | ||||
|             m_pillars.reserve(m_heads.size() * 10); | ||||
|          | ||||
|         m_pillars.emplace_back(std::forward<Args>(args)...); | ||||
|         Pillar& pillar = m_pillars.back(); | ||||
|         pillar.id = long(m_pillars.size() - 1); | ||||
|         pillar.starts_from_head = false; | ||||
|         m_meshcache_valid = false; | ||||
|         return pillar.id; | ||||
|     } | ||||
|      | ||||
|     const Pillar& head_pillar(unsigned headid) const | ||||
|     { | ||||
|         std::lock_guard<Mutex> lk(m_mutex); | ||||
|         assert(headid < m_head_indices.size()); | ||||
|          | ||||
|         const Head& h = m_heads[m_head_indices[headid]]; | ||||
|         assert(h.pillar_id >= 0 && h.pillar_id < long(m_pillars.size())); | ||||
|          | ||||
|         return m_pillars[size_t(h.pillar_id)]; | ||||
|     } | ||||
|      | ||||
|     template<class...Args> const Junction& add_junction(Args&&... args) | ||||
|     { | ||||
|         std::lock_guard<Mutex> lk(m_mutex); | ||||
|         m_junctions.emplace_back(std::forward<Args>(args)...); | ||||
|         m_junctions.back().id = long(m_junctions.size() - 1); | ||||
|         m_meshcache_valid = false; | ||||
|         return m_junctions.back(); | ||||
|     } | ||||
|      | ||||
|     const Bridge& add_bridge(const Vec3d &s, const Vec3d &e, double r, size_t n = 45) | ||||
|     { | ||||
|         return _add_bridge(m_bridges, s, e, r, n); | ||||
|     } | ||||
|      | ||||
|     const Bridge& add_bridge(long headid, const Vec3d &endp, size_t s = 45) | ||||
|     { | ||||
|         std::lock_guard<Mutex> lk(m_mutex); | ||||
|         assert(headid >= 0 && size_t(headid) < m_head_indices.size()); | ||||
|          | ||||
|         Head &h = m_heads[m_head_indices[size_t(headid)]]; | ||||
|         m_bridges.emplace_back(h.junction_point(), endp, h.r_back_mm, s); | ||||
|         m_bridges.back().id = long(m_bridges.size() - 1); | ||||
|          | ||||
|         h.bridge_id = m_bridges.back().id; | ||||
|         m_meshcache_valid = false; | ||||
|         return m_bridges.back(); | ||||
|     } | ||||
|      | ||||
|     template<class...Args> const Bridge& add_crossbridge(Args&&... args) | ||||
|     { | ||||
|         return _add_bridge(m_crossbridges, std::forward<Args>(args)...); | ||||
|     } | ||||
|      | ||||
|     template<class...Args> const CompactBridge& add_compact_bridge(Args&&...args) | ||||
|     { | ||||
|         std::lock_guard<Mutex> lk(m_mutex); | ||||
|         m_compact_bridges.emplace_back(std::forward<Args>(args)...); | ||||
|         m_compact_bridges.back().id = long(m_compact_bridges.size() - 1); | ||||
|         m_meshcache_valid = false; | ||||
|         return m_compact_bridges.back(); | ||||
|     } | ||||
|      | ||||
|     Head &head(unsigned id) | ||||
|     { | ||||
|         std::lock_guard<Mutex> lk(m_mutex); | ||||
|         assert(id < m_head_indices.size()); | ||||
|          | ||||
|         m_meshcache_valid = false; | ||||
|         return m_heads[m_head_indices[id]]; | ||||
|     } | ||||
|      | ||||
|     inline size_t pillarcount() const { | ||||
|         std::lock_guard<Mutex> lk(m_mutex); | ||||
|         return m_pillars.size(); | ||||
|     } | ||||
|      | ||||
|     inline const std::vector<Pillar> &pillars() const { return m_pillars; } | ||||
|     inline const std::vector<Head> &heads() const { return m_heads; } | ||||
|     inline const std::vector<Bridge> &bridges() const { return m_bridges; } | ||||
|     inline const std::vector<Bridge> &crossbridges() const { return m_crossbridges; } | ||||
|      | ||||
|     template<class T> inline IntegerOnly<T, const Pillar&> pillar(T id) const | ||||
|     { | ||||
|         std::lock_guard<Mutex> lk(m_mutex); | ||||
|         assert(id >= 0 && size_t(id) < m_pillars.size() && | ||||
|                size_t(id) < std::numeric_limits<size_t>::max()); | ||||
|          | ||||
|         return m_pillars[size_t(id)]; | ||||
|     } | ||||
|      | ||||
|     template<class T> inline IntegerOnly<T, Pillar&> pillar(T id)  | ||||
|     { | ||||
|         std::lock_guard<Mutex> lk(m_mutex); | ||||
|         assert(id >= 0 && size_t(id) < m_pillars.size() && | ||||
|                size_t(id) < std::numeric_limits<size_t>::max()); | ||||
|          | ||||
|         return m_pillars[size_t(id)]; | ||||
|     } | ||||
|      | ||||
|     const Pad& pad() const { return m_pad; } | ||||
|      | ||||
|     // WITHOUT THE PAD!!!
 | ||||
|     const TriangleMesh &merged_mesh() const; | ||||
|      | ||||
|     // WITH THE PAD
 | ||||
|     double full_height() const; | ||||
|      | ||||
|     // WITHOUT THE PAD!!!
 | ||||
|     inline double mesh_height() const | ||||
|     { | ||||
|         if (!m_meshcache_valid) merged_mesh(); | ||||
|         return m_model_height; | ||||
|     } | ||||
|      | ||||
|     // Intended to be called after the generation is fully complete
 | ||||
|     const TriangleMesh & merge_and_cleanup(); | ||||
|      | ||||
|     // Implement SupportTree interface:
 | ||||
| 
 | ||||
|     const TriangleMesh &add_pad(const ExPolygons &modelbase, | ||||
|                                 const PadConfig & pcfg) override; | ||||
|      | ||||
|     void remove_pad() override { m_pad = Pad(); } | ||||
|      | ||||
|     virtual const TriangleMesh &retrieve_mesh( | ||||
|         MeshType meshtype = MeshType::Support) const override; | ||||
| 
 | ||||
|     bool build(const SupportableMesh &supportable_mesh); | ||||
| }; | ||||
| 
 | ||||
| }} // namespace Slic3r::sla
 | ||||
| 
 | ||||
| #endif // SUPPORTTREEBUILDER_HPP
 | ||||
							
								
								
									
										1387
									
								
								src/libslic3r/SLA/SLASupportTreeBuildsteps.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1387
									
								
								src/libslic3r/SLA/SLASupportTreeBuildsteps.cpp
									
										
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										289
									
								
								src/libslic3r/SLA/SLASupportTreeBuildsteps.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										289
									
								
								src/libslic3r/SLA/SLASupportTreeBuildsteps.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,289 @@ | |||
| #ifndef SLASUPPORTTREEALGORITHM_H | ||||
| #define SLASUPPORTTREEALGORITHM_H | ||||
| 
 | ||||
| #include <cstdint> | ||||
| 
 | ||||
| #include "SLASupportTreeBuilder.hpp" | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| namespace sla { | ||||
| 
 | ||||
| // The minimum distance for two support points to remain valid.
 | ||||
| const double /*constexpr*/ D_SP = 0.1; | ||||
| 
 | ||||
| enum { // For indexing Eigen vectors as v(X), v(Y), v(Z) instead of numbers
 | ||||
|     X, Y, Z | ||||
| }; | ||||
| 
 | ||||
| inline Vec2d to_vec2(const Vec3d& v3) { | ||||
|     return {v3(X), v3(Y)}; | ||||
| } | ||||
| 
 | ||||
| // This function returns the position of the centroid in the input 'clust'
 | ||||
| // vector of point indices.
 | ||||
| template<class DistFn> | ||||
| long cluster_centroid(const ClusterEl& clust, | ||||
|                       const std::function<Vec3d(size_t)> &pointfn, | ||||
|                       DistFn df) | ||||
| { | ||||
|     switch(clust.size()) { | ||||
|     case 0: /* empty cluster */ return ID_UNSET; | ||||
|     case 1: /* only one element */ return 0; | ||||
|     case 2: /* if two elements, there is no center */ return 0; | ||||
|     default: ; | ||||
|     } | ||||
| 
 | ||||
|     // The function works by calculating for each point the average distance
 | ||||
|     // from all the other points in the cluster. We create a selector bitmask of
 | ||||
|     // the same size as the cluster. The bitmask will have two true bits and
 | ||||
|     // false bits for the rest of items and we will loop through all the
 | ||||
|     // permutations of the bitmask (combinations of two points). Get the
 | ||||
|     // distance for the two points and add the distance to the averages.
 | ||||
|     // The point with the smallest average than wins.
 | ||||
| 
 | ||||
|     // The complexity should be O(n^2) but we will mostly apply this function
 | ||||
|     // for small clusters only (cca 3 elements)
 | ||||
| 
 | ||||
|     std::vector<bool> sel(clust.size(), false);   // create full zero bitmask
 | ||||
|     std::fill(sel.end() - 2, sel.end(), true);    // insert the two ones
 | ||||
|     std::vector<double> avgs(clust.size(), 0.0);  // store the average distances
 | ||||
| 
 | ||||
|     do { | ||||
|         std::array<size_t, 2> idx; | ||||
|         for(size_t i = 0, j = 0; i < clust.size(); i++) if(sel[i]) idx[j++] = i; | ||||
| 
 | ||||
|         double d = df(pointfn(clust[idx[0]]), | ||||
|                       pointfn(clust[idx[1]])); | ||||
| 
 | ||||
|         // add the distance to the sums for both associated points
 | ||||
|         for(auto i : idx) avgs[i] += d; | ||||
| 
 | ||||
|         // now continue with the next permutation of the bitmask with two 1s
 | ||||
|     } while(std::next_permutation(sel.begin(), sel.end())); | ||||
| 
 | ||||
|     // Divide by point size in the cluster to get the average (may be redundant)
 | ||||
|     for(auto& a : avgs) a /= clust.size(); | ||||
| 
 | ||||
|     // get the lowest average distance and return the index
 | ||||
|     auto minit = std::min_element(avgs.begin(), avgs.end()); | ||||
|     return long(minit - avgs.begin()); | ||||
| } | ||||
| 
 | ||||
| inline Vec3d dirv(const Vec3d& startp, const Vec3d& endp) { | ||||
|     return (endp - startp).normalized(); | ||||
| } | ||||
| 
 | ||||
| class PillarIndex { | ||||
|     PointIndex m_index; | ||||
|     using Mutex = ccr::BlockingMutex; | ||||
|     mutable Mutex m_mutex; | ||||
| 
 | ||||
| public: | ||||
| 
 | ||||
|     template<class...Args> inline void guarded_insert(Args&&...args) | ||||
|     { | ||||
|         std::lock_guard<Mutex> lck(m_mutex); | ||||
|         m_index.insert(std::forward<Args>(args)...); | ||||
|     } | ||||
| 
 | ||||
|     template<class...Args> | ||||
|     inline std::vector<PointIndexEl> guarded_query(Args&&...args) const | ||||
|     { | ||||
|         std::lock_guard<Mutex> lck(m_mutex); | ||||
|         return m_index.query(std::forward<Args>(args)...); | ||||
|     } | ||||
| 
 | ||||
|     template<class...Args> inline void insert(Args&&...args) | ||||
|     { | ||||
|         m_index.insert(std::forward<Args>(args)...); | ||||
|     } | ||||
| 
 | ||||
|     template<class...Args> | ||||
|     inline std::vector<PointIndexEl> query(Args&&...args) const | ||||
|     { | ||||
|         return m_index.query(std::forward<Args>(args)...); | ||||
|     } | ||||
| 
 | ||||
|     template<class Fn> inline void foreach(Fn fn) { m_index.foreach(fn); } | ||||
|     template<class Fn> inline void guarded_foreach(Fn fn) | ||||
|     { | ||||
|         std::lock_guard<Mutex> lck(m_mutex); | ||||
|         m_index.foreach(fn); | ||||
|     } | ||||
| 
 | ||||
|     PointIndex guarded_clone() | ||||
|     { | ||||
|         std::lock_guard<Mutex> lck(m_mutex); | ||||
|         return m_index; | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| // Helper function for pillar interconnection where pairs of already connected
 | ||||
| // pillars should be checked for not to be processed again. This can be done
 | ||||
| // in constant time with a set of hash values uniquely representing a pair of
 | ||||
| // integers. The order of numbers within the pair should not matter, it has
 | ||||
| // the same unique hash. The hash value has to have twice as many bits as the
 | ||||
| // arguments need. If the same integral type is used for args and return val,
 | ||||
| // make sure the arguments use only the half of the type's bit depth.
 | ||||
| template<class I, class DoubleI = IntegerOnly<I>> | ||||
| IntegerOnly<DoubleI> pairhash(I a, I b) | ||||
| { | ||||
|     using std::ceil; using std::log2; using std::max; using std::min; | ||||
|     static const auto constexpr Ibits = int(sizeof(I) * CHAR_BIT); | ||||
|     static const auto constexpr DoubleIbits = int(sizeof(DoubleI) * CHAR_BIT); | ||||
|     static const auto constexpr shift = DoubleIbits / 2 < Ibits ? Ibits / 2 : Ibits; | ||||
| 
 | ||||
|     I g = min(a, b), l = max(a, b); | ||||
| 
 | ||||
|     // Assume the hash will fit into the output variable
 | ||||
|     assert((g ? (ceil(log2(g))) : 0) <= shift); | ||||
|     assert((l ? (ceil(log2(l))) : 0) <= shift); | ||||
| 
 | ||||
|     return (DoubleI(g) << shift) + l; | ||||
| } | ||||
| 
 | ||||
| class SupportTreeBuildsteps { | ||||
|     const SupportConfig& m_cfg; | ||||
|     const EigenMesh3D& m_mesh; | ||||
|     const std::vector<SupportPoint>& m_support_pts; | ||||
| 
 | ||||
|     using PtIndices = std::vector<unsigned>; | ||||
| 
 | ||||
|     PtIndices m_iheads;            // support points with pinhead
 | ||||
|     PtIndices m_iheadless;         // headless support points
 | ||||
| 
 | ||||
|     // supp. pts. connecting to model: point index and the ray hit data
 | ||||
|     std::vector<std::pair<unsigned, EigenMesh3D::hit_result>> m_iheads_onmodel; | ||||
| 
 | ||||
|     // normals for support points from model faces.
 | ||||
|     PointSet  m_support_nmls; | ||||
| 
 | ||||
|     // Clusters of points which can reach the ground directly and can be
 | ||||
|     // bridged to one central pillar
 | ||||
|     std::vector<PtIndices> m_pillar_clusters; | ||||
| 
 | ||||
|     // This algorithm uses the SupportTreeBuilder class to fill gradually
 | ||||
|     // the support elements (heads, pillars, bridges, ...)
 | ||||
|     SupportTreeBuilder& m_builder; | ||||
| 
 | ||||
|     // support points in Eigen/IGL format
 | ||||
|     PointSet m_points; | ||||
| 
 | ||||
|     // throw if canceled: It will be called many times so a shorthand will
 | ||||
|     // come in handy.
 | ||||
|     ThrowOnCancel m_thr; | ||||
| 
 | ||||
|     // A spatial index to easily find strong pillars to connect to.
 | ||||
|     PillarIndex m_pillar_index; | ||||
|      | ||||
|     // When bridging heads to pillars... TODO: find a cleaner solution
 | ||||
|     ccr::BlockingMutex m_bridge_mutex; | ||||
| 
 | ||||
|     inline double ray_mesh_intersect(const Vec3d& s, | ||||
|                                      const Vec3d& dir) | ||||
|     { | ||||
|         return m_mesh.query_ray_hit(s, dir).distance(); | ||||
|     } | ||||
| 
 | ||||
|     // This function will test if a future pinhead would not collide with the
 | ||||
|     // model geometry. It does not take a 'Head' object because those are
 | ||||
|     // created after this test. Parameters: s: The touching point on the model
 | ||||
|     // surface. dir: This is the direction of the head from the pin to the back
 | ||||
|     // r_pin, r_back: the radiuses of the pin and the back sphere width: This
 | ||||
|     // is the full width from the pin center to the back center m: The object
 | ||||
|     // mesh.
 | ||||
|     // The return value is the hit result from the ray casting. If the starting
 | ||||
|     // point was inside the model, an "invalid" hit_result will be returned
 | ||||
|     // with a zero distance value instead of a NAN. This way the result can
 | ||||
|     // be used safely for comparison with other distances.
 | ||||
|     EigenMesh3D::hit_result pinhead_mesh_intersect( | ||||
|         const Vec3d& s, | ||||
|         const Vec3d& dir, | ||||
|         double r_pin, | ||||
|         double r_back, | ||||
|         double width); | ||||
| 
 | ||||
|     // Checking bridge (pillar and stick as well) intersection with the model.
 | ||||
|     // If the function is used for headless sticks, the ins_check parameter
 | ||||
|     // have to be true as the beginning of the stick might be inside the model
 | ||||
|     // geometry.
 | ||||
|     // The return value is the hit result from the ray casting. If the starting
 | ||||
|     // point was inside the model, an "invalid" hit_result will be returned
 | ||||
|     // with a zero distance value instead of a NAN. This way the result can
 | ||||
|     // be used safely for comparison with other distances.
 | ||||
|     EigenMesh3D::hit_result bridge_mesh_intersect( | ||||
|         const Vec3d& s, | ||||
|         const Vec3d& dir, | ||||
|         double r, | ||||
|         bool ins_check = false); | ||||
| 
 | ||||
|     // Helper function for interconnecting two pillars with zig-zag bridges.
 | ||||
|     bool interconnect(const Pillar& pillar, const Pillar& nextpillar); | ||||
| 
 | ||||
|     // For connecting a head to a nearby pillar.
 | ||||
|     bool connect_to_nearpillar(const Head& head, long nearpillar_id); | ||||
| 
 | ||||
|     bool search_pillar_and_connect(const Head& head); | ||||
| 
 | ||||
|     // This is a proxy function for pillar creation which will mind the gap
 | ||||
|     // between the pad and the model bottom in zero elevation mode.
 | ||||
|     void create_ground_pillar(const Vec3d &jp, | ||||
|                               const Vec3d &sourcedir, | ||||
|                               double       radius, | ||||
|                               long         head_id = ID_UNSET); | ||||
| public: | ||||
|     SupportTreeBuildsteps(SupportTreeBuilder & builder, const SupportableMesh &sm); | ||||
| 
 | ||||
|     // Now let's define the individual steps of the support generation algorithm
 | ||||
| 
 | ||||
|     // Filtering step: here we will discard inappropriate support points
 | ||||
|     // and decide the future of the appropriate ones. We will check if a
 | ||||
|     // pinhead is applicable and adjust its angle at each support point. We
 | ||||
|     // will also merge the support points that are just too close and can
 | ||||
|     // be considered as one.
 | ||||
|     void filter(); | ||||
| 
 | ||||
|     // Pinhead creation: based on the filtering results, the Head objects
 | ||||
|     // will be constructed (together with their triangle meshes).
 | ||||
|     void add_pinheads(); | ||||
| 
 | ||||
|     // Further classification of the support points with pinheads. If the
 | ||||
|     // ground is directly reachable through a vertical line parallel to the
 | ||||
|     // Z axis we consider a support point as pillar candidate. If touches
 | ||||
|     // the model geometry, it will be marked as non-ground facing and
 | ||||
|     // further steps will process it. Also, the pillars will be grouped
 | ||||
|     // into clusters that can be interconnected with bridges. Elements of
 | ||||
|     // these groups may or may not be interconnected. Here we only run the
 | ||||
|     // clustering algorithm.
 | ||||
|     void classify(); | ||||
| 
 | ||||
|     // Step: Routing the ground connected pinheads, and interconnecting
 | ||||
|     // them with additional (angled) bridges. Not all of these pinheads
 | ||||
|     // will be a full pillar (ground connected). Some will connect to a
 | ||||
|     // nearby pillar using a bridge. The max number of such side-heads for
 | ||||
|     // a central pillar is limited to avoid bad weight distribution.
 | ||||
|     void routing_to_ground(); | ||||
| 
 | ||||
|     // Step: routing the pinheads that would connect to the model surface
 | ||||
|     // along the Z axis downwards. For now these will actually be connected with
 | ||||
|     // the model surface with a flipped pinhead. In the future here we could use
 | ||||
|     // some smart algorithms to search for a safe path to the ground or to a
 | ||||
|     // nearby pillar that can hold the supported weight.
 | ||||
|     void routing_to_model(); | ||||
| 
 | ||||
|     void interconnect_pillars(); | ||||
| 
 | ||||
|     // Step: process the support points where there is not enough space for a
 | ||||
|     // full pinhead. In this case we will use a rounded sphere as a touching
 | ||||
|     // point and use a thinner bridge (let's call it a stick).
 | ||||
|     void routing_headless (); | ||||
| 
 | ||||
|     inline void merge_result() { m_builder.merged_mesh(); } | ||||
| 
 | ||||
|     static bool execute(SupportTreeBuilder & builder, const SupportableMesh &sm); | ||||
| }; | ||||
| 
 | ||||
| } | ||||
| } | ||||
| 
 | ||||
| #endif // SLASUPPORTTREEALGORITHM_H
 | ||||
|  | @ -77,7 +77,7 @@ bool PointIndex::remove(const PointIndexEl& el) | |||
| } | ||||
| 
 | ||||
| std::vector<PointIndexEl> | ||||
| PointIndex::query(std::function<bool(const PointIndexEl &)> fn) | ||||
| PointIndex::query(std::function<bool(const PointIndexEl &)> fn) const | ||||
| { | ||||
|     namespace bgi = boost::geometry::index; | ||||
| 
 | ||||
|  | @ -86,7 +86,7 @@ PointIndex::query(std::function<bool(const PointIndexEl &)> fn) | |||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| std::vector<PointIndexEl> PointIndex::nearest(const Vec3d &el, unsigned k = 1) | ||||
| std::vector<PointIndexEl> PointIndex::nearest(const Vec3d &el, unsigned k = 1) const | ||||
| { | ||||
|     namespace bgi = boost::geometry::index; | ||||
|     std::vector<PointIndexEl> ret; ret.reserve(k); | ||||
|  | @ -104,6 +104,11 @@ void PointIndex::foreach(std::function<void (const PointIndexEl &)> fn) | |||
|     for(auto& el : m_impl->m_store) fn(el); | ||||
| } | ||||
| 
 | ||||
| void PointIndex::foreach(std::function<void (const PointIndexEl &)> fn) const | ||||
| { | ||||
|     for(const auto &el : m_impl->m_store) fn(el); | ||||
| } | ||||
| 
 | ||||
| /* **************************************************************************
 | ||||
|  * BoxIndex implementation | ||||
|  * ************************************************************************** */ | ||||
|  | @ -274,6 +279,8 @@ double EigenMesh3D::squared_distance(const Vec3d &p, int& i, Vec3d& c) const { | |||
|  * Misc functions | ||||
|  * ****************************************************************************/ | ||||
| 
 | ||||
| namespace  { | ||||
| 
 | ||||
| bool point_on_edge(const Vec3d& p, const Vec3d& e1, const Vec3d& e2, | ||||
|                    double eps = 0.05) | ||||
| { | ||||
|  | @ -289,11 +296,13 @@ template<class Vec> double distance(const Vec& pp1, const Vec& pp2) { | |||
|     return std::sqrt(p.transpose() * p); | ||||
| } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| PointSet normals(const PointSet& points, | ||||
|                  const EigenMesh3D& mesh, | ||||
|                  double eps, | ||||
|                  std::function<void()> thr, // throw on cancel
 | ||||
|                  const std::vector<unsigned>& pt_indices = {}) | ||||
|                  const std::vector<unsigned>& pt_indices) | ||||
| { | ||||
|     if(points.rows() == 0 || mesh.V().rows() == 0 || mesh.F().rows() == 0) | ||||
|         return {}; | ||||
|  | @ -419,9 +428,17 @@ PointSet normals(const PointSet& points, | |||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| namespace bgi = boost::geometry::index; | ||||
| using Index3D = bgi::rtree< PointIndexEl, bgi::rstar<16, 4> /* ? */ >; | ||||
| 
 | ||||
| namespace { | ||||
| 
 | ||||
| bool cmp_ptidx_elements(const PointIndexEl& e1, const PointIndexEl& e2) | ||||
| { | ||||
|     return e1.second < e2.second; | ||||
| }; | ||||
| 
 | ||||
| ClusteredPoints cluster(Index3D &sindex, | ||||
|                         unsigned max_points, | ||||
|                         std::function<std::vector<PointIndexEl>( | ||||
|  | @ -433,25 +450,22 @@ ClusteredPoints cluster(Index3D &sindex, | |||
|     // each other
 | ||||
|     std::function<void(Elems&, Elems&)> group = | ||||
|     [&sindex, &group, max_points, qfn](Elems& pts, Elems& cluster) | ||||
|     { | ||||
|     {         | ||||
|         for(auto& p : pts) { | ||||
|             std::vector<PointIndexEl> tmp = qfn(sindex, p); | ||||
|             auto cmp = [](const PointIndexEl& e1, const PointIndexEl& e2){ | ||||
|                 return e1.second < e2.second; | ||||
|             }; | ||||
| 
 | ||||
|             std::sort(tmp.begin(), tmp.end(), cmp); | ||||
|             | ||||
|             std::sort(tmp.begin(), tmp.end(), cmp_ptidx_elements); | ||||
| 
 | ||||
|             Elems newpts; | ||||
|             std::set_difference(tmp.begin(), tmp.end(), | ||||
|                                 cluster.begin(), cluster.end(), | ||||
|                                 std::back_inserter(newpts), cmp); | ||||
|                                 std::back_inserter(newpts), cmp_ptidx_elements); | ||||
| 
 | ||||
|             int c = max_points && newpts.size() + cluster.size() > max_points? | ||||
|                         int(max_points - cluster.size()) : int(newpts.size()); | ||||
| 
 | ||||
|             cluster.insert(cluster.end(), newpts.begin(), newpts.begin() + c); | ||||
|             std::sort(cluster.begin(), cluster.end(), cmp); | ||||
|             std::sort(cluster.begin(), cluster.end(), cmp_ptidx_elements); | ||||
| 
 | ||||
|             if(!newpts.empty() && (!max_points || cluster.size() < max_points)) | ||||
|                 group(newpts, cluster); | ||||
|  | @ -479,7 +493,6 @@ ClusteredPoints cluster(Index3D &sindex, | |||
|     return result; | ||||
| } | ||||
| 
 | ||||
| namespace { | ||||
| std::vector<PointIndexEl> distance_queryfn(const Index3D& sindex, | ||||
|                                           const PointIndexEl& p, | ||||
|                                           double dist, | ||||
|  | @ -496,7 +509,8 @@ std::vector<PointIndexEl> distance_queryfn(const Index3D& sindex, | |||
| 
 | ||||
|     return tmp; | ||||
| } | ||||
| } | ||||
| 
 | ||||
| } // namespace
 | ||||
| 
 | ||||
| // Clustering a set of points by the given criteria
 | ||||
| ClusteredPoints cluster( | ||||
|  | @ -558,5 +572,5 @@ ClusteredPoints cluster(const PointSet& pts, double dist, unsigned max_points) | |||
|     }); | ||||
| } | ||||
| 
 | ||||
| } | ||||
| } | ||||
| } // namespace sla
 | ||||
| } // namespace Slic3r
 | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| #include "SLAPrint.hpp" | ||||
| #include "SLA/SLASupportTree.hpp" | ||||
| #include "SLA/SLABasePool.hpp" | ||||
| #include "SLA/SLAPad.hpp" | ||||
| #include "SLA/SLAAutoSupports.hpp" | ||||
| #include "ClipperUtils.hpp" | ||||
| #include "Geometry.hpp" | ||||
|  | @ -32,17 +32,19 @@ | |||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
| using SupportTreePtr = std::unique_ptr<sla::SLASupportTree>; | ||||
| 
 | ||||
| class SLAPrintObject::SupportData | ||||
| class SLAPrintObject::SupportData : public sla::SupportableMesh | ||||
| { | ||||
| public: | ||||
|     sla::EigenMesh3D emesh;              // index-triangle representation
 | ||||
|     std::vector<sla::SupportPoint> support_points;     // all the support points (manual/auto)
 | ||||
|     SupportTreePtr                 support_tree_ptr;   // the supports
 | ||||
|     sla::SupportTree::UPtr         support_tree_ptr;   // the supports
 | ||||
|     std::vector<ExPolygons>        support_slices;     // sliced supports
 | ||||
| 
 | ||||
|     inline SupportData(const TriangleMesh &trmesh) : emesh(trmesh) {} | ||||
|      | ||||
|     inline SupportData(const TriangleMesh &t): sla::SupportableMesh{t, {}, {}} {} | ||||
|      | ||||
|     sla::SupportTree::UPtr &create_support_tree(const sla::JobController &ctl) | ||||
|     { | ||||
|         support_tree_ptr = sla::SupportTree::create(*this, ctl); | ||||
|         return support_tree_ptr; | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| namespace { | ||||
|  | @ -53,7 +55,7 @@ const std::array<unsigned, slaposCount>     OBJ_STEP_LEVELS = | |||
|     30,     // slaposObjectSlice,
 | ||||
|     20,     // slaposSupportPoints,
 | ||||
|     10,     // slaposSupportTree,
 | ||||
|     10,     // slaposBasePool,
 | ||||
|     10,     // slaposPad,
 | ||||
|     30,     // slaposSliceSupports,
 | ||||
| }; | ||||
| 
 | ||||
|  | @ -64,7 +66,7 @@ std::string OBJ_STEP_LABELS(size_t idx) | |||
|     case slaposObjectSlice:     return L("Slicing model"); | ||||
|     case slaposSupportPoints:   return L("Generating support points"); | ||||
|     case slaposSupportTree:     return L("Generating support tree"); | ||||
|     case slaposBasePool:        return L("Generating pad"); | ||||
|     case slaposPad:             return L("Generating pad"); | ||||
|     case slaposSliceSupports:   return L("Slicing supports"); | ||||
|     default:; | ||||
|     } | ||||
|  | @ -583,7 +585,8 @@ bool is_zero_elevation(const SLAPrintObjectConfig &c) { | |||
| // Compile the argument for support creation from the static print config.
 | ||||
| sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c) { | ||||
|     sla::SupportConfig scfg; | ||||
| 
 | ||||
|      | ||||
|     scfg.enabled = c.supports_enable.getBool(); | ||||
|     scfg.head_front_radius_mm = 0.5*c.support_head_front_diameter.getFloat(); | ||||
|     scfg.head_back_radius_mm = 0.5*c.support_pillar_diameter.getFloat(); | ||||
|     scfg.head_penetration_mm = c.support_head_penetration.getFloat(); | ||||
|  | @ -612,12 +615,13 @@ sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c) { | |||
|     return scfg; | ||||
| } | ||||
| 
 | ||||
| sla::PoolConfig::EmbedObject builtin_pad_cfg(const SLAPrintObjectConfig& c) { | ||||
|     sla::PoolConfig::EmbedObject ret; | ||||
| sla::PadConfig::EmbedObject builtin_pad_cfg(const SLAPrintObjectConfig& c) { | ||||
|     sla::PadConfig::EmbedObject ret; | ||||
| 
 | ||||
|     ret.enabled = is_zero_elevation(c); | ||||
| 
 | ||||
|     if(ret.enabled) { | ||||
|         ret.everywhere           = c.pad_around_object_everywhere.getBool(); | ||||
|         ret.object_gap_mm        = c.pad_object_gap.getFloat(); | ||||
|         ret.stick_width_mm       = c.pad_object_connector_width.getFloat(); | ||||
|         ret.stick_stride_mm      = c.pad_object_connector_stride.getFloat(); | ||||
|  | @ -628,17 +632,15 @@ sla::PoolConfig::EmbedObject builtin_pad_cfg(const SLAPrintObjectConfig& c) { | |||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| sla::PoolConfig make_pool_config(const SLAPrintObjectConfig& c) { | ||||
|     sla::PoolConfig pcfg; | ||||
| sla::PadConfig make_pad_cfg(const SLAPrintObjectConfig& c) { | ||||
|     sla::PadConfig pcfg; | ||||
| 
 | ||||
|     pcfg.min_wall_thickness_mm = c.pad_wall_thickness.getFloat(); | ||||
|     pcfg.wall_thickness_mm = c.pad_wall_thickness.getFloat(); | ||||
|     pcfg.wall_slope = c.pad_wall_slope.getFloat() * PI / 180.0; | ||||
| 
 | ||||
|     // We do not support radius for now
 | ||||
|     pcfg.edge_radius_mm = 0.0; //c.pad_edge_radius.getFloat();
 | ||||
| 
 | ||||
|     pcfg.max_merge_distance_mm = c.pad_max_merge_distance.getFloat(); | ||||
|     pcfg.min_wall_height_mm = c.pad_wall_height.getFloat(); | ||||
|     pcfg.max_merge_dist_mm = c.pad_max_merge_distance.getFloat(); | ||||
|     pcfg.wall_height_mm = c.pad_wall_height.getFloat(); | ||||
|     pcfg.brim_size_mm = c.pad_brim_size.getFloat(); | ||||
| 
 | ||||
|     // set builtin pad implicitly ON
 | ||||
|     pcfg.embed_object = builtin_pad_cfg(c); | ||||
|  | @ -646,6 +648,13 @@ sla::PoolConfig make_pool_config(const SLAPrintObjectConfig& c) { | |||
|     return pcfg; | ||||
| } | ||||
| 
 | ||||
| bool validate_pad(const TriangleMesh &pad, const sla::PadConfig &pcfg)  | ||||
| { | ||||
|     // An empty pad can only be created if embed_object mode is enabled
 | ||||
|     // and the pad is not forced everywhere
 | ||||
|     return !pad.empty() || (pcfg.embed_object.enabled && !pcfg.embed_object.everywhere); | ||||
| } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| std::string SLAPrint::validate() const | ||||
|  | @ -663,17 +672,12 @@ std::string SLAPrint::validate() const | |||
| 
 | ||||
|         sla::SupportConfig cfg = make_support_cfg(po->config()); | ||||
| 
 | ||||
|         double pinhead_width = | ||||
|                 2 * cfg.head_front_radius_mm + | ||||
|                 cfg.head_width_mm + | ||||
|                 2 * cfg.head_back_radius_mm - | ||||
|                 cfg.head_penetration_mm; | ||||
| 
 | ||||
|         double elv = cfg.object_elevation_mm; | ||||
| 
 | ||||
|         sla::PoolConfig::EmbedObject builtinpad = builtin_pad_cfg(po->config()); | ||||
|          | ||||
|         if(supports_en && !builtinpad.enabled && elv < pinhead_width ) | ||||
|         sla::PadConfig padcfg = make_pad_cfg(po->config()); | ||||
|         sla::PadConfig::EmbedObject &builtinpad = padcfg.embed_object; | ||||
|          | ||||
|         if(supports_en && !builtinpad.enabled && elv < cfg.head_fullwidth()) | ||||
|             return L( | ||||
|                 "Elevation is too low for object. Use the \"Pad around " | ||||
|                 "object\" feature to print the object without elevation."); | ||||
|  | @ -686,6 +690,9 @@ std::string SLAPrint::validate() const | |||
|                 "distance' has to be greater than the 'Pad object gap' " | ||||
|                 "parameter to avoid this."); | ||||
|         } | ||||
|          | ||||
|         std::string pval = padcfg.validate(); | ||||
|         if (!pval.empty()) return pval; | ||||
|     } | ||||
| 
 | ||||
|     double expt_max = m_printer_config.max_exposure_time.getFloat(); | ||||
|  | @ -876,8 +883,7 @@ void SLAPrint::process() | |||
| 
 | ||||
|             // Construction of this object does the calculation.
 | ||||
|             this->throw_if_canceled(); | ||||
|             SLAAutoSupports auto_supports(po.transformed_mesh(), | ||||
|                                           po.m_supportdata->emesh, | ||||
|             SLAAutoSupports auto_supports(po.m_supportdata->emesh, | ||||
|                                           po.get_model_slices(), | ||||
|                                           heights, | ||||
|                                           config, | ||||
|  | @ -887,10 +893,10 @@ void SLAPrint::process() | |||
|             // Now let's extract the result.
 | ||||
|             const std::vector<sla::SupportPoint>& points = auto_supports.output(); | ||||
|             this->throw_if_canceled(); | ||||
|             po.m_supportdata->support_points = points; | ||||
|             po.m_supportdata->pts = points; | ||||
| 
 | ||||
|             BOOST_LOG_TRIVIAL(debug) << "Automatic support points: " | ||||
|                                      << po.m_supportdata->support_points.size(); | ||||
|                                      << po.m_supportdata->pts.size(); | ||||
| 
 | ||||
|             // Using RELOAD_SLA_SUPPORT_POINTS to tell the Plater to pass
 | ||||
|             // the update status to GLGizmoSlaSupports
 | ||||
|  | @ -902,29 +908,19 @@ void SLAPrint::process() | |||
|         else { | ||||
|             // There are either some points on the front-end, or the user
 | ||||
|             // removed them on purpose. No calculation will be done.
 | ||||
|             po.m_supportdata->support_points = po.transformed_support_points(); | ||||
|             po.m_supportdata->pts = po.transformed_support_points(); | ||||
|         } | ||||
| 
 | ||||
|         // If the zero elevation mode is engaged, we have to filter out all the
 | ||||
|         // points that are on the bottom of the object
 | ||||
|         if (is_zero_elevation(po.config())) { | ||||
|             double gnd       = po.m_supportdata->emesh.ground_level(); | ||||
|             auto & pts       = po.m_supportdata->support_points; | ||||
|             double tolerance = po.config().pad_enable.getBool() | ||||
|                                    ? po.m_config.pad_wall_thickness.getFloat() | ||||
|                                    : po.m_config.support_base_height.getFloat(); | ||||
| 
 | ||||
|             // get iterator to the reorganized vector end
 | ||||
|             auto endit = std::remove_if( | ||||
|                 pts.begin(), | ||||
|                 pts.end(), | ||||
|                 [tolerance, gnd](const sla::SupportPoint &sp) { | ||||
|                     double diff = std::abs(gnd - double(sp.pos(Z))); | ||||
|                     return diff <= tolerance; | ||||
|                 }); | ||||
| 
 | ||||
|             // erase all elements after the new end
 | ||||
|             pts.erase(endit, pts.end()); | ||||
|             remove_bottom_points(po.m_supportdata->pts, | ||||
|                                  po.m_supportdata->emesh.ground_level(), | ||||
|                                  tolerance); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|  | @ -933,45 +929,31 @@ void SLAPrint::process() | |||
|     { | ||||
|         if(!po.m_supportdata) return; | ||||
| 
 | ||||
|         sla::PoolConfig pcfg = make_pool_config(po.m_config); | ||||
|         sla::PadConfig pcfg = make_pad_cfg(po.m_config); | ||||
| 
 | ||||
|         if (pcfg.embed_object) | ||||
|             po.m_supportdata->emesh.ground_level_offset( | ||||
|                 pcfg.min_wall_thickness_mm); | ||||
| 
 | ||||
|         if(!po.m_config.supports_enable.getBool()) { | ||||
| 
 | ||||
|             // Generate empty support tree. It can still host a pad
 | ||||
|             po.m_supportdata->support_tree_ptr.reset( | ||||
|                     new SLASupportTree(po.m_supportdata->emesh.ground_level())); | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         sla::SupportConfig scfg = make_support_cfg(po.m_config); | ||||
|         sla::Controller ctl; | ||||
| 
 | ||||
|             po.m_supportdata->emesh.ground_level_offset(pcfg.wall_thickness_mm); | ||||
|          | ||||
|         po.m_supportdata->cfg = make_support_cfg(po.m_config); | ||||
|          | ||||
|         // scaling for the sub operations
 | ||||
|         double d = ostepd * OBJ_STEP_LEVELS[slaposSupportTree] / 100.0; | ||||
|         double init = m_report_status.status(); | ||||
|         JobController ctl; | ||||
| 
 | ||||
|         ctl.statuscb = [this, d, init](unsigned st, const std::string &logmsg) | ||||
|         { | ||||
|         ctl.statuscb = [this, d, init](unsigned st, const std::string &logmsg) { | ||||
|             double current = init + st * d; | ||||
|             if(std::round(m_report_status.status()) < std::round(current)) | ||||
|             if (std::round(m_report_status.status()) < std::round(current)) | ||||
|                 m_report_status(*this, current, | ||||
|                                 OBJ_STEP_LABELS(slaposSupportTree), | ||||
|                                 SlicingStatus::DEFAULT, | ||||
|                                 logmsg); | ||||
| 
 | ||||
|                                 SlicingStatus::DEFAULT, logmsg); | ||||
|         }; | ||||
| 
 | ||||
|         ctl.stopcondition = [this](){ return canceled(); }; | ||||
|         ctl.stopcondition = [this]() { return canceled(); }; | ||||
|         ctl.cancelfn = [this]() { throw_if_canceled(); }; | ||||
| 
 | ||||
|         po.m_supportdata->support_tree_ptr.reset( | ||||
|                     new SLASupportTree(po.m_supportdata->support_points, | ||||
|                                        po.m_supportdata->emesh, scfg, ctl)); | ||||
|          | ||||
|         po.m_supportdata->create_support_tree(ctl); | ||||
|          | ||||
|         if (!po.m_config.supports_enable.getBool()) return; | ||||
| 
 | ||||
|         throw_if_canceled(); | ||||
| 
 | ||||
|  | @ -980,10 +962,9 @@ void SLAPrint::process() | |||
| 
 | ||||
|         // This is to prevent "Done." being displayed during merged_mesh()
 | ||||
|         m_report_status(*this, -1, L("Visualizing supports")); | ||||
|         po.m_supportdata->support_tree_ptr->merged_mesh(); | ||||
| 
 | ||||
|         BOOST_LOG_TRIVIAL(debug) << "Processed support point count " | ||||
|                                  << po.m_supportdata->support_points.size(); | ||||
|                                  << po.m_supportdata->pts.size(); | ||||
| 
 | ||||
|         // Check the mesh for later troubleshooting.
 | ||||
|         if(po.support_mesh().empty()) | ||||
|  | @ -993,7 +974,7 @@ void SLAPrint::process() | |||
|     }; | ||||
| 
 | ||||
|     // This step generates the sla base pad
 | ||||
|     auto base_pool = [this](SLAPrintObject& po) { | ||||
|     auto generate_pad = [this](SLAPrintObject& po) { | ||||
|         // this step can only go after the support tree has been created
 | ||||
|         // and before the supports had been sliced. (or the slicing has to be
 | ||||
|         // repeated)
 | ||||
|  | @ -1001,10 +982,10 @@ void SLAPrint::process() | |||
|         if(po.m_config.pad_enable.getBool()) | ||||
|         { | ||||
|             // Get the distilled pad configuration from the config
 | ||||
|             sla::PoolConfig pcfg = make_pool_config(po.m_config); | ||||
|             sla::PadConfig pcfg = make_pad_cfg(po.m_config); | ||||
| 
 | ||||
|             ExPolygons bp; // This will store the base plate of the pad.
 | ||||
|             double   pad_h             = sla::get_pad_fullheight(pcfg); | ||||
|             double   pad_h             = pcfg.full_height(); | ||||
|             const TriangleMesh &trmesh = po.transformed_mesh(); | ||||
| 
 | ||||
|             // This call can get pretty time consuming
 | ||||
|  | @ -1015,15 +996,19 @@ void SLAPrint::process() | |||
|                 // we sometimes call it "builtin pad" is enabled so we will
 | ||||
|                 // get a sample from the bottom of the mesh and use it for pad
 | ||||
|                 // creation.
 | ||||
|                 sla::base_plate(trmesh, | ||||
|                                 bp, | ||||
|                                 float(pad_h), | ||||
|                                 float(po.m_config.layer_height.getFloat()), | ||||
|                                 thrfn); | ||||
|                 sla::pad_blueprint(trmesh, bp, float(pad_h), | ||||
|                                float(po.m_config.layer_height.getFloat()), | ||||
|                                thrfn); | ||||
|             } | ||||
| 
 | ||||
|             pcfg.throw_on_cancel = thrfn; | ||||
|             po.m_supportdata->support_tree_ptr->add_pad(bp, pcfg); | ||||
|             auto &pad_mesh = po.m_supportdata->support_tree_ptr->retrieve_mesh(MeshType::Pad); | ||||
|              | ||||
|             if (!validate_pad(pad_mesh, pcfg)) | ||||
|                 throw std::runtime_error( | ||||
|                     L("No pad can be generated for this model with the " | ||||
|                       "current configuration")); | ||||
| 
 | ||||
|         } else if(po.m_supportdata && po.m_supportdata->support_tree_ptr) { | ||||
|             po.m_supportdata->support_tree_ptr->remove_pad(); | ||||
|         } | ||||
|  | @ -1191,9 +1176,8 @@ void SLAPrint::process() | |||
|                 { | ||||
|                     ClipperPolygon poly; | ||||
| 
 | ||||
|                     // We need to reverse if flpXY OR is_lefthanded is true but
 | ||||
|                     // not if both are true which is a logical inequality (XOR)
 | ||||
|                     bool needreverse = /*flpXY !=*/ is_lefthanded; | ||||
|                     // We need to reverse if is_lefthanded is true but
 | ||||
|                     bool needreverse = is_lefthanded; | ||||
| 
 | ||||
|                     // should be a move
 | ||||
|                     poly.Contour.reserve(polygon.contour.size() + 1); | ||||
|  | @ -1396,7 +1380,7 @@ void SLAPrint::process() | |||
|         if(canceled()) return; | ||||
|          | ||||
|         // Set up the printer, allocate space for all the layers
 | ||||
|         sla::SLARasterWriter &printer = init_printer(); | ||||
|         sla::RasterWriter &printer = init_printer(); | ||||
| 
 | ||||
|         auto lvlcnt = unsigned(m_printer_input.size()); | ||||
|         printer.layers(lvlcnt); | ||||
|  | @ -1416,11 +1400,9 @@ void SLAPrint::process() | |||
| 
 | ||||
|         SpinMutex slck; | ||||
|          | ||||
|         auto orientation = get_printer_orientation(); | ||||
| 
 | ||||
|         // procedure to process one height level. This will run in parallel
 | ||||
|         auto lvlfn = | ||||
|         [this, &slck, &printer, increment, &dstatus, &pst, orientation] | ||||
|         [this, &slck, &printer, increment, &dstatus, &pst] | ||||
|             (unsigned level_id) | ||||
|         { | ||||
|             if(canceled()) return; | ||||
|  | @ -1431,7 +1413,7 @@ void SLAPrint::process() | |||
|             printer.begin_layer(level_id); | ||||
| 
 | ||||
|             for(const ClipperLib::Polygon& poly : printlayer.transformed_slices()) | ||||
|                 printer.draw_polygon(poly, level_id, orientation); | ||||
|                 printer.draw_polygon(poly, level_id); | ||||
| 
 | ||||
|             // Finish the layer for later saving it.
 | ||||
|             printer.finish_layer(level_id); | ||||
|  | @ -1459,7 +1441,7 @@ void SLAPrint::process() | |||
|         tbb::parallel_for<unsigned, decltype(lvlfn)>(0, lvlcnt, lvlfn); | ||||
| 
 | ||||
|         // Set statistics values to the printer
 | ||||
|         sla::SLARasterWriter::PrintStatistics stats; | ||||
|         sla::RasterWriter::PrintStatistics stats; | ||||
|         stats.used_material = (m_print_statistics.objects_used_material + | ||||
|                                m_print_statistics.support_used_material) / | ||||
|                               1000; | ||||
|  | @ -1478,12 +1460,12 @@ void SLAPrint::process() | |||
| 
 | ||||
|     slaposFn pobj_program[] = | ||||
|     { | ||||
|         slice_model, support_points, support_tree, base_pool, slice_supports | ||||
|         slice_model, support_points, support_tree, generate_pad, slice_supports | ||||
|     }; | ||||
| 
 | ||||
|     // We want to first process all objects...
 | ||||
|     std::vector<SLAPrintObjectStep> level1_obj_steps = { | ||||
|         slaposObjectSlice, slaposSupportPoints, slaposSupportTree, slaposBasePool | ||||
|         slaposObjectSlice, slaposSupportPoints, slaposSupportTree, slaposPad | ||||
|     }; | ||||
| 
 | ||||
|     // and then slice all supports to allow preview to be displayed ASAP
 | ||||
|  | @ -1654,12 +1636,11 @@ bool SLAPrint::invalidate_state_by_config_options(const std::vector<t_config_opt | |||
|     return invalidated; | ||||
| } | ||||
| 
 | ||||
| sla::SLARasterWriter & SLAPrint::init_printer() | ||||
| sla::RasterWriter & SLAPrint::init_printer() | ||||
| { | ||||
|     sla::Raster::Resolution res; | ||||
|     sla::Raster::PixelDim   pxdim; | ||||
|     std::array<bool, 2>     mirror; | ||||
|     double                  gamma; | ||||
| 
 | ||||
|     double w  = m_printer_config.display_width.getFloat(); | ||||
|     double h  = m_printer_config.display_height.getFloat(); | ||||
|  | @ -1669,20 +1650,18 @@ sla::SLARasterWriter & SLAPrint::init_printer() | |||
|     mirror[X] = m_printer_config.display_mirror_x.getBool(); | ||||
|     mirror[Y] = m_printer_config.display_mirror_y.getBool(); | ||||
| 
 | ||||
|     if (get_printer_orientation() == sla::SLARasterWriter::roPortrait) { | ||||
|     auto orientation = get_printer_orientation(); | ||||
|     if (orientation == sla::Raster::roPortrait) { | ||||
|         std::swap(w, h); | ||||
|         std::swap(pw, ph); | ||||
| 
 | ||||
|         // XY flipping implicitly does an X mirror
 | ||||
|         mirror[X] = !mirror[X]; | ||||
|     } | ||||
| 
 | ||||
|     res   = sla::Raster::Resolution{pw, ph}; | ||||
|     pxdim = sla::Raster::PixelDim{w / pw, h / ph}; | ||||
| 
 | ||||
|     gamma = m_printer_config.gamma_correction.getFloat(); | ||||
| 
 | ||||
|     m_printer.reset(new sla::SLARasterWriter(res, pxdim, mirror, gamma)); | ||||
|     sla::Raster::Trafo tr{orientation, mirror}; | ||||
|     tr.gamma = m_printer_config.gamma_correction.getFloat(); | ||||
|      | ||||
|     m_printer.reset(new sla::RasterWriter(res, pxdim, tr)); | ||||
|     m_printer->set_config(m_full_print_config); | ||||
|     return *m_printer; | ||||
| } | ||||
|  | @ -1730,6 +1709,7 @@ bool SLAPrintObject::invalidate_state_by_config_options(const std::vector<t_conf | |||
|             || opt_key == "supports_enable" | ||||
|             || opt_key == "support_object_elevation" | ||||
|             || opt_key == "pad_around_object" | ||||
|             || opt_key == "pad_around_object_everywhere" | ||||
|             || opt_key == "slice_closing_radius") { | ||||
|             steps.emplace_back(slaposObjectSlice); | ||||
|         } else if ( | ||||
|  | @ -1754,6 +1734,7 @@ bool SLAPrintObject::invalidate_state_by_config_options(const std::vector<t_conf | |||
|             steps.emplace_back(slaposSupportTree); | ||||
|         } else if ( | ||||
|                opt_key == "pad_wall_height" | ||||
|             || opt_key == "pad_brim_size" | ||||
|             || opt_key == "pad_max_merge_distance" | ||||
|             || opt_key == "pad_wall_slope" | ||||
|             || opt_key == "pad_edge_radius" | ||||
|  | @ -1762,7 +1743,7 @@ bool SLAPrintObject::invalidate_state_by_config_options(const std::vector<t_conf | |||
|             || opt_key == "pad_object_connector_width" | ||||
|             || opt_key == "pad_object_connector_penetration" | ||||
|             ) { | ||||
|             steps.emplace_back(slaposBasePool); | ||||
|             steps.emplace_back(slaposPad); | ||||
|         } else { | ||||
|             // All keys should be covered.
 | ||||
|             assert(false); | ||||
|  | @ -1782,12 +1763,12 @@ bool SLAPrintObject::invalidate_step(SLAPrintObjectStep step) | |||
|     if (step == slaposObjectSlice) { | ||||
|         invalidated |= this->invalidate_all_steps(); | ||||
|     } else if (step == slaposSupportPoints) { | ||||
|         invalidated |= this->invalidate_steps({ slaposSupportTree, slaposBasePool, slaposSliceSupports }); | ||||
|         invalidated |= this->invalidate_steps({ slaposSupportTree, slaposPad, slaposSliceSupports }); | ||||
|         invalidated |= m_print->invalidate_step(slapsMergeSlicesAndEval); | ||||
|     } else if (step == slaposSupportTree) { | ||||
|         invalidated |= this->invalidate_steps({ slaposBasePool, slaposSliceSupports }); | ||||
|         invalidated |= this->invalidate_steps({ slaposPad, slaposSliceSupports }); | ||||
|         invalidated |= m_print->invalidate_step(slapsMergeSlicesAndEval); | ||||
|     } else if (step == slaposBasePool) { | ||||
|     } else if (step == slaposPad) { | ||||
|         invalidated |= this->invalidate_steps({slaposSliceSupports}); | ||||
|         invalidated |= m_print->invalidate_step(slapsMergeSlicesAndEval); | ||||
|     } else if (step == slaposSliceSupports) { | ||||
|  | @ -1813,8 +1794,8 @@ double SLAPrintObject::get_elevation() const { | |||
|         // its walls but currently it is half of its thickness. Whatever it
 | ||||
|         // will be in the future, we provide the config to the get_pad_elevation
 | ||||
|         // method and we will have the correct value
 | ||||
|         sla::PoolConfig pcfg = make_pool_config(m_config); | ||||
|         if(!pcfg.embed_object) ret += sla::get_pad_elevation(pcfg); | ||||
|         sla::PadConfig pcfg = make_pad_cfg(m_config); | ||||
|         if(!pcfg.embed_object) ret += pcfg.required_elevation(); | ||||
|     } | ||||
| 
 | ||||
|     return ret; | ||||
|  | @ -1825,7 +1806,7 @@ double SLAPrintObject::get_current_elevation() const | |||
|     if (is_zero_elevation(m_config)) return 0.; | ||||
| 
 | ||||
|     bool has_supports = is_step_done(slaposSupportTree); | ||||
|     bool has_pad      = is_step_done(slaposBasePool); | ||||
|     bool has_pad      = is_step_done(slaposPad); | ||||
| 
 | ||||
|     if(!has_supports && !has_pad) | ||||
|         return 0; | ||||
|  | @ -1866,7 +1847,7 @@ const SliceRecord SliceRecord::EMPTY(0, std::nanf(""), 0.f); | |||
| 
 | ||||
| const std::vector<sla::SupportPoint>& SLAPrintObject::get_support_points() const | ||||
| { | ||||
|     return m_supportdata? m_supportdata->support_points : EMPTY_SUPPORT_POINTS; | ||||
|     return m_supportdata? m_supportdata->pts : EMPTY_SUPPORT_POINTS; | ||||
| } | ||||
| 
 | ||||
| const std::vector<ExPolygons> &SLAPrintObject::get_support_slices() const | ||||
|  | @ -1896,7 +1877,7 @@ bool SLAPrintObject::has_mesh(SLAPrintObjectStep step) const | |||
|     switch (step) { | ||||
|     case slaposSupportTree: | ||||
|         return ! this->support_mesh().empty(); | ||||
|     case slaposBasePool: | ||||
|     case slaposPad: | ||||
|         return ! this->pad_mesh().empty(); | ||||
|     default: | ||||
|         return false; | ||||
|  | @ -1908,7 +1889,7 @@ TriangleMesh SLAPrintObject::get_mesh(SLAPrintObjectStep step) const | |||
|     switch (step) { | ||||
|     case slaposSupportTree: | ||||
|         return this->support_mesh(); | ||||
|     case slaposBasePool: | ||||
|     case slaposPad: | ||||
|         return this->pad_mesh(); | ||||
|     default: | ||||
|         return TriangleMesh(); | ||||
|  | @ -1917,18 +1898,20 @@ TriangleMesh SLAPrintObject::get_mesh(SLAPrintObjectStep step) const | |||
| 
 | ||||
| const TriangleMesh& SLAPrintObject::support_mesh() const | ||||
| { | ||||
|     if(m_config.supports_enable.getBool() && m_supportdata && | ||||
|        m_supportdata->support_tree_ptr) { | ||||
|         return m_supportdata->support_tree_ptr->merged_mesh(); | ||||
|     } | ||||
| 
 | ||||
|     sla::SupportTree::UPtr &stree = m_supportdata->support_tree_ptr; | ||||
|      | ||||
|     if(m_config.supports_enable.getBool() && m_supportdata && stree) | ||||
|         return stree->retrieve_mesh(sla::MeshType::Support); | ||||
|      | ||||
|     return EMPTY_MESH; | ||||
| } | ||||
| 
 | ||||
| const TriangleMesh& SLAPrintObject::pad_mesh() const | ||||
| { | ||||
|     if(m_config.pad_enable.getBool() && m_supportdata && m_supportdata->support_tree_ptr) | ||||
|         return m_supportdata->support_tree_ptr->get_pad(); | ||||
|     sla::SupportTree::UPtr &stree = m_supportdata->support_tree_ptr; | ||||
|      | ||||
|     if(m_config.pad_enable.getBool() && m_supportdata && stree) | ||||
|         return stree->retrieve_mesh(sla::MeshType::Pad); | ||||
| 
 | ||||
|     return EMPTY_MESH; | ||||
| } | ||||
|  |  | |||
|  | @ -21,7 +21,7 @@ enum SLAPrintObjectStep : unsigned int { | |||
| 	slaposObjectSlice, | ||||
| 	slaposSupportPoints, | ||||
| 	slaposSupportTree, | ||||
| 	slaposBasePool, | ||||
| 	slaposPad, | ||||
|     slaposSliceSupports, | ||||
| 	slaposCount | ||||
| }; | ||||
|  | @ -54,7 +54,7 @@ public: | |||
|     bool                        is_left_handed() const { return m_left_handed; } | ||||
| 
 | ||||
|     struct Instance { | ||||
|         Instance(ObjectID instance_id, const Point &shift, float rotation) : instance_id(instance_id), shift(shift), rotation(rotation) {} | ||||
|         Instance(ObjectID inst_id, const Point &shft, float rot) : instance_id(inst_id), shift(shft), rotation(rot) {} | ||||
|         bool operator==(const Instance &rhs) const { return this->instance_id == rhs.instance_id && this->shift == rhs.shift && this->rotation == rhs.rotation; } | ||||
|         // ID of the corresponding ModelInstance.
 | ||||
|         ObjectID instance_id; | ||||
|  | @ -440,7 +440,7 @@ private: | |||
|     std::vector<PrintLayer>                 m_printer_input; | ||||
| 
 | ||||
|     // The printer itself
 | ||||
|     std::unique_ptr<sla::SLARasterWriter>   m_printer; | ||||
|     std::unique_ptr<sla::RasterWriter>   m_printer; | ||||
| 
 | ||||
|     // Estimated print time, material consumed.
 | ||||
|     SLAPrintStatistics                      m_print_statistics; | ||||
|  | @ -459,14 +459,13 @@ private: | |||
|         double status() const { return m_st; } | ||||
|     } m_report_status; | ||||
|      | ||||
|     sla::SLARasterWriter &init_printer(); | ||||
|     sla::RasterWriter &init_printer(); | ||||
|      | ||||
|     inline sla::SLARasterWriter::Orientation get_printer_orientation() const | ||||
|     inline sla::Raster::Orientation get_printer_orientation() const | ||||
|     { | ||||
|         auto ro = m_printer_config.display_orientation.getInt(); | ||||
|         return ro == sla::SLARasterWriter::roPortrait ? | ||||
|                    sla::SLARasterWriter::roPortrait : | ||||
|                    sla::SLARasterWriter::roLandscape; | ||||
|         return ro == sla::Raster::roPortrait ? sla::Raster::roPortrait : | ||||
|                                                sla::Raster::roLandscape; | ||||
|     } | ||||
| 
 | ||||
| 	friend SLAPrintObject; | ||||
|  |  | |||
|  | @ -10,12 +10,15 @@ namespace Slic3r { | |||
| class ExPolygon; | ||||
| typedef std::vector<ExPolygon> ExPolygons; | ||||
| 
 | ||||
| extern std::vector<Vec3d> triangulate_expolygon_3d (const ExPolygon  &poly,  coordf_t z = 0, bool flip = false); | ||||
| extern std::vector<Vec3d> triangulate_expolygons_3d(const ExPolygons &polys, coordf_t z = 0, bool flip = false); | ||||
| extern std::vector<Vec2d> triangulate_expolygon_2d (const ExPolygon  &poly,  bool flip = false); | ||||
| extern std::vector<Vec2d> triangulate_expolygons_2d(const ExPolygons &polys, bool flip = false); | ||||
| extern std::vector<Vec2f> triangulate_expolygon_2f (const ExPolygon  &poly,  bool flip = false); | ||||
| extern std::vector<Vec2f> triangulate_expolygons_2f(const ExPolygons &polys, bool flip = false); | ||||
| const bool constexpr NORMALS_UP = false; | ||||
| const bool constexpr NORMALS_DOWN = true; | ||||
| 
 | ||||
| extern std::vector<Vec3d> triangulate_expolygon_3d (const ExPolygon  &poly,  coordf_t z = 0, bool flip = NORMALS_UP); | ||||
| extern std::vector<Vec3d> triangulate_expolygons_3d(const ExPolygons &polys, coordf_t z = 0, bool flip = NORMALS_UP); | ||||
| extern std::vector<Vec2d> triangulate_expolygon_2d (const ExPolygon  &poly,  bool flip = NORMALS_UP); | ||||
| extern std::vector<Vec2d> triangulate_expolygons_2d(const ExPolygons &polys, bool flip = NORMALS_UP); | ||||
| extern std::vector<Vec2f> triangulate_expolygon_2f (const ExPolygon  &poly,  bool flip = NORMALS_UP); | ||||
| extern std::vector<Vec2f> triangulate_expolygons_2f(const ExPolygons &polys, bool flip = NORMALS_UP); | ||||
| 
 | ||||
| } // namespace Slic3r
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -236,7 +236,7 @@ bool TriangleMesh::needed_repair() const | |||
|         || this->stl.stats.backwards_edges      > 0; | ||||
| } | ||||
| 
 | ||||
| void TriangleMesh::WriteOBJFile(const char* output_file) | ||||
| void TriangleMesh::WriteOBJFile(const char* output_file) const | ||||
| { | ||||
|     its_write_obj(this->its, output_file); | ||||
| } | ||||
|  |  | |||
|  | @ -31,7 +31,7 @@ public: | |||
|     float volume(); | ||||
|     void check_topology(); | ||||
|     bool is_manifold() const { return this->stl.stats.connected_facets_3_edge == (int)this->stl.stats.number_of_facets; } | ||||
|     void WriteOBJFile(const char* output_file); | ||||
|     void WriteOBJFile(const char* output_file) const; | ||||
|     void scale(float factor); | ||||
|     void scale(const Vec3d &versor); | ||||
|     void translate(float x, float y, float z); | ||||
|  |  | |||
|  | @ -417,7 +417,7 @@ void GLVolume::render(int color_id, int detection_id, int worldmatrix_id) const | |||
| } | ||||
| 
 | ||||
| bool GLVolume::is_sla_support() const { return this->composite_id.volume_id == -int(slaposSupportTree); } | ||||
| bool GLVolume::is_sla_pad() const { return this->composite_id.volume_id == -int(slaposBasePool); } | ||||
| bool GLVolume::is_sla_pad() const { return this->composite_id.volume_id == -int(slaposPad); } | ||||
| 
 | ||||
| std::vector<int> GLVolumeCollection::load_object( | ||||
|     const ModelObject       *model_object, | ||||
|  | @ -501,7 +501,7 @@ void GLVolumeCollection::load_object_auxiliary( | |||
|     TriangleMesh convex_hull = mesh.convex_hull_3d(); | ||||
|     for (const std::pair<size_t, size_t>& instance_idx : instances) { | ||||
|         const ModelInstance& model_instance = *print_object->model_object()->instances[instance_idx.first]; | ||||
|         this->volumes.emplace_back(new GLVolume((milestone == slaposBasePool) ? GLVolume::SLA_PAD_COLOR : GLVolume::SLA_SUPPORT_COLOR)); | ||||
|         this->volumes.emplace_back(new GLVolume((milestone == slaposPad) ? GLVolume::SLA_PAD_COLOR : GLVolume::SLA_SUPPORT_COLOR)); | ||||
|         GLVolume& v = *this->volumes.back(); | ||||
|         v.indexed_vertex_array.load_mesh(mesh); | ||||
| 	    v.indexed_vertex_array.finalize_geometry(opengl_initialized); | ||||
|  |  | |||
|  | @ -349,15 +349,18 @@ void ConfigManipulation::toggle_print_sla_options(DynamicPrintConfig* config) | |||
| 
 | ||||
|     toggle_field("pad_wall_thickness", pad_en); | ||||
|     toggle_field("pad_wall_height", pad_en); | ||||
|     toggle_field("pad_brim_size", pad_en); | ||||
|     toggle_field("pad_max_merge_distance", pad_en); | ||||
|  // toggle_field("pad_edge_radius", supports_en);
 | ||||
|     toggle_field("pad_wall_slope", pad_en); | ||||
|     toggle_field("pad_around_object", pad_en); | ||||
|     toggle_field("pad_around_object_everywhere", pad_en); | ||||
| 
 | ||||
|     bool zero_elev = config->opt_bool("pad_around_object") && pad_en; | ||||
| 
 | ||||
|     toggle_field("support_object_elevation", supports_en && !zero_elev); | ||||
|     toggle_field("pad_object_gap", zero_elev); | ||||
|     toggle_field("pad_around_object_everywhere", zero_elev); | ||||
|     toggle_field("pad_object_connector_stride", zero_elev); | ||||
|     toggle_field("pad_object_connector_width", zero_elev); | ||||
|     toggle_field("pad_object_connector_penetration", zero_elev); | ||||
|  |  | |||
|  | @ -1767,7 +1767,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re | |||
| 
 | ||||
|     // SLA steps to pull the preview meshes for.
 | ||||
| 	typedef std::array<SLAPrintObjectStep, 2> SLASteps; | ||||
| 	SLASteps sla_steps = { slaposSupportTree, slaposBasePool }; | ||||
| 	SLASteps sla_steps = { slaposSupportTree, slaposPad }; | ||||
|     struct SLASupportState { | ||||
| 		std::array<PrintStateBase::StateWithTimeStamp, std::tuple_size<SLASteps>::value> step; | ||||
|     }; | ||||
|  | @ -5340,8 +5340,8 @@ void GLCanvas3D::_load_sla_shells() | |||
|                 m_volumes.volumes.back()->extruder_id = obj->model_object()->volumes.front()->extruder_id(); | ||||
|                 if (obj->is_step_done(slaposSupportTree) && obj->has_mesh(slaposSupportTree)) | ||||
|                     add_volume(*obj, -int(slaposSupportTree), instance, obj->support_mesh(), GLVolume::SLA_SUPPORT_COLOR, true); | ||||
|                 if (obj->is_step_done(slaposBasePool) && obj->has_mesh(slaposBasePool)) | ||||
|                     add_volume(*obj, -int(slaposBasePool), instance, obj->pad_mesh(), GLVolume::SLA_PAD_COLOR, false); | ||||
|                 if (obj->is_step_done(slaposPad) && obj->has_mesh(slaposPad)) | ||||
|                     add_volume(*obj, -int(slaposPad), instance, obj->pad_mesh(), GLVolume::SLA_PAD_COLOR, false); | ||||
|             } | ||||
|             double shift_z = obj->get_current_elevation(); | ||||
|             for (unsigned int i = initial_volumes_count; i < m_volumes.volumes.size(); ++ i) { | ||||
|  |  | |||
|  | @ -261,7 +261,7 @@ bool MainFrame::can_export_supports() const | |||
|     const PrintObjects& objects = m_plater->sla_print().objects(); | ||||
|     for (const SLAPrintObject* object : objects) | ||||
|     { | ||||
|         if (object->has_mesh(slaposBasePool) || object->has_mesh(slaposSupportTree)) | ||||
|         if (object->has_mesh(slaposPad) || object->has_mesh(slaposSupportTree)) | ||||
|         { | ||||
|             can_export = true; | ||||
|             break; | ||||
|  |  | |||
|  | @ -4462,10 +4462,10 @@ void Plater::export_stl(bool extended, bool selection_only) | |||
|                 bool is_left_handed = object->is_left_handed(); | ||||
| 
 | ||||
|                 TriangleMesh pad_mesh; | ||||
|                 bool has_pad_mesh = object->has_mesh(slaposBasePool); | ||||
|                 bool has_pad_mesh = object->has_mesh(slaposPad); | ||||
|                 if (has_pad_mesh) | ||||
|                 { | ||||
|                     pad_mesh = object->get_mesh(slaposBasePool); | ||||
|                     pad_mesh = object->get_mesh(slaposPad); | ||||
|                     pad_mesh.transform(mesh_trafo_inv); | ||||
|                 } | ||||
| 
 | ||||
|  | @ -4641,7 +4641,7 @@ void Plater::reslice_SLA_supports(const ModelObject &object, bool postpone_error | |||
|     // Otherwise calculate everything, but start with the provided object.
 | ||||
|     if (!this->p->background_processing_enabled()) { | ||||
|         task.single_model_instance_only = true; | ||||
|         task.to_object_step = slaposBasePool; | ||||
|         task.to_object_step = slaposPad; | ||||
|     } | ||||
|     this->p->background_process.set_task(task); | ||||
|     // and let the background processing start.
 | ||||
|  |  | |||
|  | @ -476,11 +476,13 @@ const std::vector<std::string>& Preset::sla_print_options() | |||
|             "pad_enable", | ||||
|             "pad_wall_thickness", | ||||
|             "pad_wall_height", | ||||
|             "pad_brim_size", | ||||
|             "pad_max_merge_distance", | ||||
|             // "pad_edge_radius",
 | ||||
|             "pad_wall_slope", | ||||
|             "pad_object_gap", | ||||
|             "pad_around_object", | ||||
|             "pad_around_object_everywhere", | ||||
|             "pad_object_connector_stride", | ||||
|             "pad_object_connector_width", | ||||
|             "pad_object_connector_penetration", | ||||
|  |  | |||
|  | @ -3551,12 +3551,14 @@ void TabSLAPrint::build() | |||
|     optgroup->append_single_option_line("pad_enable"); | ||||
|     optgroup->append_single_option_line("pad_wall_thickness"); | ||||
|     optgroup->append_single_option_line("pad_wall_height"); | ||||
|     optgroup->append_single_option_line("pad_brim_size"); | ||||
|     optgroup->append_single_option_line("pad_max_merge_distance"); | ||||
|     // TODO: Disabling this parameter for the beta release
 | ||||
| //    optgroup->append_single_option_line("pad_edge_radius");
 | ||||
|     optgroup->append_single_option_line("pad_wall_slope"); | ||||
| 
 | ||||
|     optgroup->append_single_option_line("pad_around_object"); | ||||
|     optgroup->append_single_option_line("pad_around_object_everywhere"); | ||||
|     optgroup->append_single_option_line("pad_object_gap"); | ||||
|     optgroup->append_single_option_line("pad_object_connector_stride"); | ||||
|     optgroup->append_single_option_line("pad_object_connector_width"); | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 tamasmeszaros
						tamasmeszaros