Initial port of organic tree support from PrusaSlicer (#1938)

* Initial port of organic tree support from PrusaSlicer

* Port missing Organic support parameters from PrusaSlicer

* Update parameter naming

* Reorganize the `raft_first_layer_expansion` and `raft_first_layer_density` parameters as they are not only used by rafts

* Reset support style only in simple mode

* Sync latest update from PrusaSlicer & copyrights

* Fix organic tree support crash with invalid parameters

---------

Co-authored-by: Vojtech Bubnik <bubnikv@gmail.com>
This commit is contained in:
Noisyfox 2023-09-02 17:29:43 +08:00 committed by GitHub
parent b50dfb69a2
commit a1464735ce
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 13299 additions and 38 deletions

View file

@ -556,9 +556,9 @@ class clipperException : public std::exception
//------------------------------------------------------------------------------
template<typename PathsProvider>
inline Paths SimplifyPolygons(PathsProvider &&in_polys, PolyFillType fillType = pftEvenOdd) {
inline Paths SimplifyPolygons(PathsProvider &&in_polys, PolyFillType fillType = pftEvenOdd, bool strictly_simple = true) {
Clipper c;
c.StrictlySimple(true);
c.StrictlySimple(strictly_simple);
c.AddPaths(std::forward<PathsProvider>(in_polys), ptSubject, true);
Paths out;
c.Execute(ctUnion, out, fillType, fillType);

View file

@ -261,6 +261,23 @@ set(lisbslic3r_sources
SlicesToTriangleMesh.cpp
SlicingAdaptive.cpp
SlicingAdaptive.hpp
Support/SupportCommon.cpp
Support/SupportCommon.hpp
Support/SupportDebug.cpp
Support/SupportDebug.hpp
Support/SupportLayer.hpp
# Support/SupportMaterial.cpp
# Support/SupportMaterial.hpp
Support/SupportParameters.cpp
Support/SupportParameters.hpp
Support/OrganicSupport.cpp
Support/OrganicSupport.hpp
Support/TreeSupport.cpp
Support/TreeSupport.hpp
Support/TreeSupportCommon.cpp
Support/TreeSupportCommon.hpp
Support/TreeModelVolumes.cpp
Support/TreeModelVolumes.hpp
SupportMaterial.cpp
SupportMaterial.hpp
PrincipalComponents2D.cpp
@ -496,6 +513,7 @@ target_link_libraries(libslic3r
qhull
semver
TBB::tbb
TBB::tbbmalloc
libslic3r_cgal
${CMAKE_DL_LIBS}
PNG::PNG

View file

@ -655,6 +655,8 @@ Slic3r::Polygons diff(const Slic3r::Polygon &subject, const Slic3r::Polygon &cli
{ return _clipper(ClipperLib::ctDifference, ClipperUtils::SinglePathProvider(subject.points), ClipperUtils::SinglePathProvider(clip.points), do_safety_offset); }
Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset)
{ return _clipper(ClipperLib::ctDifference, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); }
Slic3r::Polygons diff_clipped(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset)
{ return diff(subject, ClipperUtils::clip_clipper_polygons_with_subject_bbox(clip, get_extents(subject).inflated(SCALED_EPSILON)), do_safety_offset); }
Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset)
{ return _clipper(ClipperLib::ctDifference, ClipperUtils::PolygonsProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); }
Slic3r::Polygons diff(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset)
@ -665,6 +667,8 @@ Slic3r::Polygons diff(const Slic3r::Surfaces &subject, const Slic3r::Polygons &c
{ return _clipper(ClipperLib::ctDifference, ClipperUtils::SurfacesProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); }
Slic3r::Polygons intersection(const Slic3r::Polygon &subject, const Slic3r::Polygon &clip, ApplySafetyOffset do_safety_offset)
{ return _clipper(ClipperLib::ctIntersection, ClipperUtils::SinglePathProvider(subject.points), ClipperUtils::SinglePathProvider(clip.points), do_safety_offset); }
Slic3r::Polygons intersection_clipped(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset)
{ return intersection(subject, ClipperUtils::clip_clipper_polygons_with_subject_bbox(clip, get_extents(subject).inflated(SCALED_EPSILON)), do_safety_offset); }
Slic3r::Polygons intersection(const Slic3r::Polygons &subject, const Slic3r::ExPolygon &clip, ApplySafetyOffset do_safety_offset)
{ return _clipper(ClipperLib::ctIntersection, ClipperUtils::PolygonsProvider(subject), ClipperUtils::ExPolygonProvider(clip), do_safety_offset); }
Slic3r::Polygons intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset)

View file

@ -362,6 +362,8 @@ inline Slic3r::Polygons expand(const Slic3r::Polygon &polygon, const float del
{ assert(delta > 0); return offset(polygon, delta, joinType, miterLimit); }
inline Slic3r::Polygons expand(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
{ assert(delta > 0); return offset(polygons, delta, joinType, miterLimit); }
inline Slic3r::Polygons expand(const Slic3r::ExPolygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
{ assert(delta > 0); return offset(polygons, delta, joinType, miterLimit); }
inline Slic3r::ExPolygons expand_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
{ assert(delta > 0); return offset_ex(polygons, delta, joinType, miterLimit); }
// Input polygons for shrinking shall be "normalized": There must be no overlap / intersections between the input polygons.
@ -415,6 +417,9 @@ Slic3r::Lines _clipper_ln(ClipperLib::ClipType clipType, const Slic3r::Lines &su
Slic3r::Polygons diff(const Slic3r::Polygon &subject, const Slic3r::Polygon &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
// Optimized version clipping the "clipping" polygon using clip_clipper_polygon_with_subject_bbox().
// To be used with complex clipping polygons, where majority of the clipping polygons are outside of the source polygon.
Slic3r::Polygons diff_clipped(const Slic3r::Polygons &src, const Slic3r::Polygons &clipping, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
Slic3r::Polygons diff(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
Slic3r::Polygons diff(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
Slic3r::Polygons diff(const Slic3r::Surfaces &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
@ -475,6 +480,9 @@ Slic3r::Polygons intersection(const Slic3r::Polygon &subject, const Slic3r::Po
Slic3r::Polygons intersection(const Slic3r::Polygons &subject, const Slic3r::ExPolygon &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
Slic3r::Polygons intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
Slic3r::Polygons intersection(const Slic3r::ExPolygon &subject, const Slic3r::ExPolygon &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
// Optimized version clipping the "clipping" polygon using clip_clipper_polygon_with_subject_bbox().
// To be used with complex clipping polygons, where majority of the clipping polygons are outside of the source polygon.
Slic3r::Polygons intersection_clipped(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
Slic3r::Polygons intersection(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
Slic3r::Polygons intersection(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
Slic3r::Polygons intersection(const Slic3r::Surfaces &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);

View file

@ -298,6 +298,16 @@ private:
bool m_no_extrusion = false;
};
class ExtrusionPathOriented : public ExtrusionPath
{
public:
ExtrusionPathOriented(ExtrusionRole role, double mm3_per_mm, float width, float height) : ExtrusionPath(role, mm3_per_mm, width, height) {}
ExtrusionEntity* clone() const override { return new ExtrusionPathOriented(*this); }
// Create a new object, initialize it with this object using the move semantics.
ExtrusionEntity* clone_move() override { return new ExtrusionPathOriented(std::move(*this)); }
virtual bool can_reverse() const override { return false; }
};
typedef std::vector<ExtrusionPath> ExtrusionPaths;
// Single continuous extrusion path, possibly with varying extrusion thickness, extrusion height or bridging / non bridging.
@ -488,23 +498,23 @@ inline void extrusion_paths_append(ExtrusionPaths &dst, Polylines &&polylines, i
polylines.clear();
}
inline void extrusion_entities_append_paths(ExtrusionEntitiesPtr &dst, Polylines &polylines, ExtrusionRole role, double mm3_per_mm, float width, float height)
inline void extrusion_entities_append_paths(ExtrusionEntitiesPtr &dst, Polylines &polylines, ExtrusionRole role, double mm3_per_mm, float width, float height, bool can_reverse = true)
{
dst.reserve(dst.size() + polylines.size());
for (Polyline &polyline : polylines)
if (polyline.is_valid()) {
ExtrusionPath *extrusion_path = new ExtrusionPath(role, mm3_per_mm, width, height);
ExtrusionPath *extrusion_path = can_reverse ? new ExtrusionPath(role, mm3_per_mm, width, height) : new ExtrusionPathOriented(role, mm3_per_mm, width, height);
dst.push_back(extrusion_path);
extrusion_path->polyline = polyline;
}
}
inline void extrusion_entities_append_paths(ExtrusionEntitiesPtr &dst, Polylines &&polylines, ExtrusionRole role, double mm3_per_mm, float width, float height)
inline void extrusion_entities_append_paths(ExtrusionEntitiesPtr &dst, Polylines &&polylines, ExtrusionRole role, double mm3_per_mm, float width, float height, bool can_reverse = true)
{
dst.reserve(dst.size() + polylines.size());
for (Polyline &polyline : polylines)
if (polyline.is_valid()) {
ExtrusionPath *extrusion_path = new ExtrusionPath(role, mm3_per_mm, width, height);
ExtrusionPath *extrusion_path = can_reverse ? new ExtrusionPath(role, mm3_per_mm, width, height) : new ExtrusionPathOriented(role, mm3_per_mm, width, height);
dst.push_back(extrusion_path);
extrusion_path->polyline = std::move(polyline);
}

View file

@ -93,8 +93,13 @@ inline typename Derived::Scalar cross2(const Eigen::MatrixBase<Derived> &v1, con
return v1.x() * v2.y() - v1.y() * v2.x();
}
template<typename T, int Options>
inline Eigen::Matrix<T, 2, 1, Eigen::DontAlign> perp(const Eigen::MatrixBase<Eigen::Matrix<T, 2, 1, Options>> &v) { return Eigen::Matrix<T, 2, 1, Eigen::DontAlign>(- v.y(), v.x()); }
// 2D vector perpendicular to the argument.
template<typename Derived>
inline Eigen::Matrix<typename Derived::Scalar, 2, 1, Eigen::DontAlign> perp(const Eigen::MatrixBase<Derived> &v)
{
static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "perp(): parameter is not a 2D vector");
return { - v.y(), v.x() };
}
// Angle from v1 to v2, returning double atan2(y, x) normalized to <-PI, PI>.
template <typename Derived, typename Derived2>
@ -107,12 +112,17 @@ inline double angle(const Eigen::MatrixBase<Derived>& v1, const Eigen::MatrixBas
return atan2(cross2(v1d, v2d), v1d.dot(v2d));
}
template<typename Derived>
Eigen::Matrix<typename Derived::Scalar, 2, 1, Eigen::DontAlign> to_2d(const Eigen::MatrixBase<Derived> &ptN) {
static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) >= 3, "to_2d(): first parameter is not a 3D or higher dimensional vector");
return ptN.template head<2>();
}
template<class T, int N, int Options>
Eigen::Matrix<T, 2, 1, Eigen::DontAlign> to_2d(const Eigen::MatrixBase<Eigen::Matrix<T, N, 1, Options>> &ptN) { return { ptN.x(), ptN.y() }; }
template<class T, int Options>
Eigen::Matrix<T, 3, 1, Eigen::DontAlign> to_3d(const Eigen::MatrixBase<Eigen::Matrix<T, 2, 1, Options>> & pt, const T z) { return { pt.x(), pt.y(), z }; }
template<typename Derived>
inline Eigen::Matrix<typename Derived::Scalar, 3, 1, Eigen::DontAlign> to_3d(const Eigen::MatrixBase<Derived> &pt, const typename Derived::Scalar z) {
static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "to_3d(): first parameter is not a 2D vector");
return { pt.x(), pt.y(), z };
}
inline Vec2d unscale(coord_t x, coord_t y) { return Vec2d(unscale<double>(x), unscale<double>(y)); }
inline Vec2d unscale(const Vec2crd &pt) { return Vec2d(unscale<double>(pt.x()), unscale<double>(pt.y())); }

View file

@ -594,7 +594,7 @@ void remove_collinear(Polygons &polys)
remove_collinear(poly);
}
Polygons polygons_simplify(const Polygons &source_polygons, double tolerance)
Polygons polygons_simplify(const Polygons &source_polygons, double tolerance, bool strictly_simple /* = true */)
{
Polygons out;
out.reserve(source_polygons.size());
@ -605,7 +605,7 @@ Polygons polygons_simplify(const Polygons &source_polygons, double tolerance)
simplified.pop_back();
// Simplify the decimated contour by ClipperLib.
bool ccw = ClipperLib::Area(simplified) > 0.;
for (Points &path : ClipperLib::SimplifyPolygons(ClipperUtils::SinglePathProvider(simplified), ClipperLib::pftNonZero)) {
for (Points &path : ClipperLib::SimplifyPolygons(ClipperUtils::SinglePathProvider(simplified), ClipperLib::pftNonZero, strictly_simple)) {
if (! ccw)
// ClipperLib likely reoriented negative area contours to become positive. Reverse holes back to CW.
std::reverse(path.begin(), path.end());
@ -664,4 +664,24 @@ bool contains(const Polygons &polygons, const Point &p, bool border_result)
}
return (poly_count_inside % 2) == 1;
}
Polygon make_circle(double radius, double error)
{
double angle = 2. * acos(1. - error / radius);
size_t num_segments = size_t(ceil(2. * M_PI / angle));
return make_circle_num_segments(radius, num_segments);
}
Polygon make_circle_num_segments(double radius, size_t num_segments)
{
Polygon out;
out.points.reserve(num_segments);
double angle_inc = 2.0 * M_PI / num_segments;
for (size_t i = 0; i < num_segments; ++ i) {
const double angle = angle_inc * i;
out.points.emplace_back(coord_t(cos(angle) * radius), coord_t(sin(angle) * radius));
}
return out;
}
}

View file

@ -152,7 +152,7 @@ inline void polygons_append(Polygons &dst, Polygons &&src)
}
}
Polygons polygons_simplify(const Polygons &polys, double tolerance);
Polygons polygons_simplify(const Polygons &polys, double tolerance, bool strictly_simple = true);
inline void polygons_rotate(Polygons &polys, double angle)
{
@ -267,6 +267,9 @@ inline Polygons to_polygons(std::vector<Points> &&paths)
// however their contours may be rotated.
bool polygons_match(const Polygon &l, const Polygon &r);
Polygon make_circle(double radius, double error);
Polygon make_circle_num_segments(double radius, size_t num_segments);
bool overlaps(const Polygons& polys1, const Polygons& polys2);
} // Slic3r

View file

@ -748,8 +748,8 @@ static std::vector<std::string> s_Preset_print_options {
"prime_tower_width", "prime_tower_brim_width", "prime_volume",
"wipe_tower_no_sparse_layers", "compatible_printers", "compatible_printers_condition", "inherits",
"flush_into_infill", "flush_into_objects", "flush_into_support",
"tree_support_branch_angle", "tree_support_wall_count", "tree_support_branch_distance",
"tree_support_branch_diameter",
"tree_support_branch_angle", "tree_support_angle_slow", "tree_support_wall_count", "tree_support_top_rate", "tree_support_branch_distance", "tree_support_tip_diameter",
"tree_support_branch_diameter", "tree_support_branch_diameter_angle", "tree_support_branch_diameter_double_wall",
"detect_narrow_internal_solid_infill",
"gcode_add_line_number", "enable_arc_fitting", "infill_combination", /*"adaptive_layer_height",*/
"support_bottom_interface_spacing", "enable_overhang_speed", "overhang_1_4_speed", "overhang_2_4_speed", "overhang_3_4_speed", "overhang_4_4_speed",

View file

@ -1173,6 +1173,20 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
return {L("The prime tower requires that support has the same layer height with object."), object, "support_filament"};
}
#endif
// Prusa: Fixing crashes with invalid tip diameter or branch diameter
// https://github.com/prusa3d/PrusaSlicer/commit/96b3ae85013ac363cd1c3e98ec6b7938aeacf46d
if (object->config().support_style == smsOrganic) {
float extrusion_width = std::min(
support_material_flow(object).width(),
support_material_interface_flow(object).width());
if (object->config().tree_support_tip_diameter < extrusion_width - EPSILON)
return { L("Organic support tree tip diameter must not be smaller than support material extrusion width."), object, "tree_support_tip_diameter" };
if (object->config().tree_support_branch_diameter < 2. * extrusion_width - EPSILON)
return { L("Organic support branch diameter must not be smaller than 2x support material extrusion width."), object, "tree_support_branch_diameter" };
if (object->config().tree_support_branch_diameter < object->config().tree_support_tip_diameter)
return { L("Organic support branch diameter must not be smaller than support tree tip diameter."), object, "tree_support_branch_diameter" };
}
}
// Do we have custom support data that would not be used?

View file

@ -188,7 +188,8 @@ static t_config_enum_values s_keys_map_SupportMaterialStyle {
{ "snug", smsSnug },
{ "tree_slim", smsTreeSlim },
{ "tree_strong", smsTreeStrong },
{ "tree_hybrid", smsTreeHybrid }
{ "tree_hybrid", smsTreeHybrid },
{ "organic", smsOrganic }
};
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SupportMaterialStyle)
@ -3354,12 +3355,14 @@ void PrintConfigDef::init_fff_params()
def->enum_values.push_back("tree_slim");
def->enum_values.push_back("tree_strong");
def->enum_values.push_back("tree_hybrid");
def->enum_values.push_back("organic");
def->enum_labels.push_back(L("Default"));
def->enum_labels.push_back(L("Grid"));
def->enum_labels.push_back(L("Snug"));
def->enum_labels.push_back(L("Tree Slim"));
def->enum_labels.push_back(L("Tree Strong"));
def->enum_labels.push_back(L("Tree Hybrid"));
def->enum_labels.push_back(L("Organic"));
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionEnum<SupportMaterialStyle>(smsDefault));
@ -3392,6 +3395,18 @@ void PrintConfigDef::init_fff_params()
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(40.));
def = this->add("tree_support_angle_slow", coFloat);
def->label = L("Preferred Branch Angle");
def->category = L("Support");
// TRN PrintSettings: "Organic supports" > "Preferred Branch Angle"
def->tooltip = L("The preferred angle of the branches, when they do not have to avoid the model. "
"Use a lower angle to make them more vertical and more stable. Use a higher angle for branches to merge faster.");
def->sidetext = L("°");
def->min = 10;
def->max = 85;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(25));
def = this->add("tree_support_branch_distance", coFloat);
def->label = L("Tree support branch distance");
def->category = L("Support");
@ -3402,6 +3417,20 @@ void PrintConfigDef::init_fff_params()
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(5.));
def = this->add("tree_support_top_rate", coPercent);
def->label = L("Branch Density");
def->category = L("Support");
// TRN PrintSettings: "Organic supports" > "Branch Density"
def->tooltip = L("Adjusts the density of the support structure used to generate the tips of the branches. "
"A higher value results in better overhangs but the supports are harder to remove, "
"thus it is recommended to enable top support interfaces instead of a high branch density value "
"if dense interfaces are needed.");
def->sidetext = L("%");
def->min = 5;
def->max_literal = 35;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionPercent(15));
def = this->add("tree_support_adaptive_layer_height", coBool);
def->label = L("Adaptive layer height");
def->category = L("Quality");
@ -3421,6 +3450,17 @@ void PrintConfigDef::init_fff_params()
def->tooltip = L("Distance from tree branch to the outermost brim line");
def->set_default_value(new ConfigOptionFloat(3));
def = this->add("tree_support_tip_diameter", coFloat);
def->label = L("Tip Diameter");
def->category = L("Support");
// TRN PrintSettings: "Organic supports" > "Tip Diameter"
def->tooltip = L("Branch tip diameter for organic supports.");
def->sidetext = L("mm");
def->min = 0.1f;
def->max = 100.f;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(0.8));
def = this->add("tree_support_branch_diameter", coFloat);
def->label = L("Tree support branch diameter");
def->category = L("Support");
@ -3431,6 +3471,32 @@ void PrintConfigDef::init_fff_params()
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(5.));
def = this->add("tree_support_branch_diameter_angle", coFloat);
// TRN PrintSettings: #lmFIXME
def->label = L("Branch Diameter Angle");
def->category = L("Support");
// TRN PrintSettings: "Organic supports" > "Branch Diameter Angle"
def->tooltip = L("The angle of the branches' diameter as they gradually become thicker towards the bottom. "
"An angle of 0 will cause the branches to have uniform thickness over their length. "
"A bit of an angle can increase stability of the organic support.");
def->sidetext = L("°");
def->min = 0;
def->max = 15;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(5));
def = this->add("tree_support_branch_diameter_double_wall", coFloat);
def->label = L("Branch Diameter with double walls");
def->category = L("Support");
// TRN PrintSettings: "Organic supports" > "Branch Diameter"
def->tooltip = L("Branches with area larger than the area of a circle of this diameter will be printed with double walls for stability. "
"Set this value to zero for no double walls.");
def->sidetext = L("mm");
def->min = 0;
def->max = 100.f;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(3.));
def = this->add("tree_support_wall_count", coInt);
def->label = L("Tree support wall loops");
def->category = L("Support");
@ -4640,7 +4706,7 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va
#endif /* HAS_PRESSURE_EQUALIZER */
// BBS
, "support_sharp_tails","support_remove_small_overhangs", "support_with_sheath",
"tree_support_branch_diameter_angle", "tree_support_collision_resolution", "tree_support_with_infill",
"tree_support_collision_resolution", "tree_support_with_infill",
"max_volumetric_speed", "max_print_speed",
"support_closing_radius",
"remove_freq_sweep", "remove_bed_leveling", "remove_extrusion_calibration",

View file

@ -101,7 +101,7 @@ enum SupportMaterialPattern {
};
enum SupportMaterialStyle {
smsDefault, smsGrid, smsSnug, smsTreeSlim, smsTreeStrong, smsTreeHybrid
smsDefault, smsGrid, smsSnug, smsTreeSlim, smsTreeStrong, smsTreeHybrid, smsOrganic,
};
enum SupportMaterialInterfacePattern {
@ -699,9 +699,14 @@ PRINT_CONFIG_CLASS_DEFINE(
((ConfigOptionBool, flush_into_infill))
((ConfigOptionBool, flush_into_support))
// BBS
((ConfigOptionPercent, tree_support_top_rate))
((ConfigOptionFloat, tree_support_branch_distance))
((ConfigOptionFloat, tree_support_tip_diameter))
((ConfigOptionFloat, tree_support_branch_diameter))
((ConfigOptionFloat, tree_support_branch_diameter_angle))
((ConfigOptionFloat, tree_support_branch_diameter_double_wall))
((ConfigOptionFloat, tree_support_branch_angle))
((ConfigOptionFloat, tree_support_angle_slow))
((ConfigOptionInt, tree_support_wall_count))
((ConfigOptionBool, tree_support_adaptive_layer_height))
((ConfigOptionBool, tree_support_auto_brim))

View file

@ -8,6 +8,7 @@
#include "Layer.hpp"
#include "MutablePolygon.hpp"
#include "SupportMaterial.hpp"
#include "Support/TreeSupport.hpp"
#include "Surface.hpp"
#include "Slicing.hpp"
#include "Tesselate.hpp"
@ -813,9 +814,14 @@ bool PrintObject::invalidate_state_by_config_options(
|| opt_key == "tree_support_adaptive_layer_height"
|| opt_key == "tree_support_auto_brim"
|| opt_key == "tree_support_brim_width"
|| opt_key == "tree_support_top_rate"
|| opt_key == "tree_support_branch_distance"
|| opt_key == "tree_support_tip_diameter"
|| opt_key == "tree_support_branch_diameter"
|| opt_key == "tree_support_branch_diameter_angle"
|| opt_key == "tree_support_branch_diameter_double_wall"
|| opt_key == "tree_support_branch_angle"
|| opt_key == "tree_support_angle_slow"
|| opt_key == "tree_support_wall_count") {
steps.emplace_back(posSupportMaterial);
} else if (
@ -2494,8 +2500,14 @@ void PrintObject::_generate_support_material()
PrintObjectSupportMaterial support_material(this, m_slicing_params);
support_material.generate(*this);
TreeSupport tree_support(*this, m_slicing_params);
tree_support.generate();
if (this->config().enable_support.value && is_tree(this->config().support_type.value)) {
if (this->config().support_style.value == smsOrganic) {
fff_tree_support_generate(*this, std::function<void()>([this]() { this->throw_if_canceled(); }));
} else {
TreeSupport tree_support(*this, m_slicing_params);
tree_support.generate();
}
}
}
// BBS

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,43 @@
///|/ Copyright (c) Prusa Research 2023 Vojtěch Bubník @bubnikv
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#ifndef slic3r_OrganicSupport_hpp
#define slic3r_OrganicSupport_hpp
#include "SupportCommon.hpp"
#include "TreeSupport.hpp"
namespace Slic3r
{
class PrintObject;
namespace FFFTreeSupport
{
class TreeModelVolumes;
// Organic specific: Smooth branches and produce one cummulative mesh to be sliced.
void organic_draw_branches(
PrintObject &print_object,
TreeModelVolumes &volumes,
const TreeSupportSettings &config,
std::vector<SupportElements> &move_bounds,
// I/O:
SupportGeneratorLayersPtr &bottom_contacts,
SupportGeneratorLayersPtr &top_contacts,
InterfacePlacer &interface_placer,
// Output:
SupportGeneratorLayersPtr &intermediate_layers,
SupportGeneratorLayerStorage &layer_storage,
std::function<void()> throw_on_cancel);
} // namespace FFFTreeSupport
} // namespace Slic3r
#endif // slic3r_OrganicSupport_hpp

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,161 @@
///|/ Copyright (c) Prusa Research 2023 Vojtěch Bubník @bubnikv
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#ifndef slic3r_SupportCommon_hpp_
#define slic3r_SupportCommon_hpp_
#include "../Layer.hpp"
#include "../Polygon.hpp"
#include "../Print.hpp"
#include "SupportLayer.hpp"
#include "SupportParameters.hpp"
namespace Slic3r {
class PrintObject;
class SupportLayer;
namespace FFFSupport {
// Remove bridges from support contact areas.
// To be called if PrintObjectConfig::dont_support_bridges.
void remove_bridges_from_contacts(
const PrintConfig &print_config,
const Layer &lower_layer,
const LayerRegion &layerm,
float fw,
Polygons &contact_polygons);
// Turn some of the base layers into base interface layers.
// For soluble interfaces with non-soluble bases, print maximum two first interface layers with the base
// extruder to improve adhesion of the soluble filament to the base.
// For Organic supports, merge top_interface_layers & top_base_interface_layers with the interfaces
// produced by this function.
std::pair<SupportGeneratorLayersPtr, SupportGeneratorLayersPtr> generate_interface_layers(
const PrintObjectConfig &config,
const SupportParameters &support_params,
const SupportGeneratorLayersPtr &bottom_contacts,
const SupportGeneratorLayersPtr &top_contacts,
// Input / output, will be merged with output
SupportGeneratorLayersPtr &top_interface_layers,
SupportGeneratorLayersPtr &top_base_interface_layers,
// Input, will be trimmed with the newly created interface layers.
SupportGeneratorLayersPtr &intermediate_layers,
SupportGeneratorLayerStorage &layer_storage);
// Generate raft layers, also expand the 1st support layer
// in case there is no raft layer to improve support adhesion.
SupportGeneratorLayersPtr generate_raft_base(
const PrintObject &object,
const SupportParameters &support_params,
const SlicingParameters &slicing_params,
const SupportGeneratorLayersPtr &top_contacts,
const SupportGeneratorLayersPtr &interface_layers,
const SupportGeneratorLayersPtr &base_interface_layers,
const SupportGeneratorLayersPtr &base_layers,
SupportGeneratorLayerStorage &layer_storage);
// returns sorted layers
SupportGeneratorLayersPtr generate_support_layers(
PrintObject &object,
const SupportGeneratorLayersPtr &raft_layers,
const SupportGeneratorLayersPtr &bottom_contacts,
const SupportGeneratorLayersPtr &top_contacts,
const SupportGeneratorLayersPtr &intermediate_layers,
const SupportGeneratorLayersPtr &interface_layers,
const SupportGeneratorLayersPtr &base_interface_layers);
// Produce the support G-code.
// Used by both classic and tree supports.
void generate_support_toolpaths(
SupportLayerPtrs &support_layers,
const PrintObjectConfig &config,
const SupportParameters &support_params,
const SlicingParameters &slicing_params,
const SupportGeneratorLayersPtr &raft_layers,
const SupportGeneratorLayersPtr &bottom_contacts,
const SupportGeneratorLayersPtr &top_contacts,
const SupportGeneratorLayersPtr &intermediate_layers,
const SupportGeneratorLayersPtr &interface_layers,
const SupportGeneratorLayersPtr &base_interface_layers);
// FN_HIGHER_EQUAL: the provided object pointer has a Z value >= of an internal threshold.
// Find the first item with Z value >= of an internal threshold of fn_higher_equal.
// If no vec item with Z value >= of an internal threshold of fn_higher_equal is found, return vec.size()
// If the initial idx is size_t(-1), then use binary search.
// Otherwise search linearly upwards.
template<typename IteratorType, typename IndexType, typename FN_HIGHER_EQUAL>
IndexType idx_higher_or_equal(IteratorType begin, IteratorType end, IndexType idx, FN_HIGHER_EQUAL fn_higher_equal)
{
auto size = int(end - begin);
if (size == 0) {
idx = 0;
} else if (idx == IndexType(-1)) {
// First of the batch of layers per thread pool invocation. Use binary search.
int idx_low = 0;
int idx_high = std::max(0, size - 1);
while (idx_low + 1 < idx_high) {
int idx_mid = (idx_low + idx_high) / 2;
if (fn_higher_equal(begin[idx_mid]))
idx_high = idx_mid;
else
idx_low = idx_mid;
}
idx = fn_higher_equal(begin[idx_low]) ? idx_low :
(fn_higher_equal(begin[idx_high]) ? idx_high : size);
} else {
// For the other layers of this batch of layers, search incrementally, which is cheaper than the binary search.
while (int(idx) < size && ! fn_higher_equal(begin[idx]))
++ idx;
}
return idx;
}
template<typename T, typename IndexType, typename FN_HIGHER_EQUAL>
IndexType idx_higher_or_equal(const std::vector<T>& vec, IndexType idx, FN_HIGHER_EQUAL fn_higher_equal)
{
return idx_higher_or_equal(vec.begin(), vec.end(), idx, fn_higher_equal);
}
// FN_LOWER_EQUAL: the provided object pointer has a Z value <= of an internal threshold.
// Find the first item with Z value <= of an internal threshold of fn_lower_equal.
// If no vec item with Z value <= of an internal threshold of fn_lower_equal is found, return -1.
// If the initial idx is < -1, then use binary search.
// Otherwise search linearly downwards.
template<typename IT, typename FN_LOWER_EQUAL>
int idx_lower_or_equal(IT begin, IT end, int idx, FN_LOWER_EQUAL fn_lower_equal)
{
auto size = int(end - begin);
if (size == 0) {
idx = -1;
} else if (idx < -1) {
// First of the batch of layers per thread pool invocation. Use binary search.
int idx_low = 0;
int idx_high = std::max(0, size - 1);
while (idx_low + 1 < idx_high) {
int idx_mid = (idx_low + idx_high) / 2;
if (fn_lower_equal(begin[idx_mid]))
idx_low = idx_mid;
else
idx_high = idx_mid;
}
idx = fn_lower_equal(begin[idx_high]) ? idx_high :
(fn_lower_equal(begin[idx_low ]) ? idx_low : -1);
} else {
// For the other layers of this batch of layers, search incrementally, which is cheaper than the binary search.
while (idx >= 0 && ! fn_lower_equal(begin[idx]))
-- idx;
}
return idx;
}
template<typename T, typename FN_LOWER_EQUAL>
int idx_lower_or_equal(const std::vector<T*> &vec, int idx, FN_LOWER_EQUAL fn_lower_equal)
{
return idx_lower_or_equal(vec.begin(), vec.end(), idx, fn_lower_equal);
}
} // namespace FFFSupport
} // namespace Slic3r
#endif /* slic3r_SupportCommon_hpp_ */

View file

@ -0,0 +1,112 @@
///|/ Copyright (c) Prusa Research 2023 Vojtěch Bubník @bubnikv
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#if 1 //#ifdef SLIC3R_DEBUG
#include "../ClipperUtils.hpp"
#include "../SVG.hpp"
#include "../Layer.hpp"
#include "SupportLayer.hpp"
namespace Slic3r::FFFSupport {
const char* support_surface_type_to_color_name(const SupporLayerType surface_type)
{
switch (surface_type) {
case SupporLayerType::TopContact: return "rgb(255,0,0)"; // "red";
case SupporLayerType::TopInterface: return "rgb(0,255,0)"; // "green";
case SupporLayerType::Base: return "rgb(0,0,255)"; // "blue";
case SupporLayerType::BottomInterface:return "rgb(255,255,128)"; // yellow
case SupporLayerType::BottomContact: return "rgb(255,0,255)"; // magenta
case SupporLayerType::RaftInterface: return "rgb(0,255,255)";
case SupporLayerType::RaftBase: return "rgb(128,128,128)";
case SupporLayerType::Unknown: return "rgb(128,0,0)"; // maroon
default: return "rgb(64,64,64)";
};
}
Point export_support_surface_type_legend_to_svg_box_size()
{
return Point(scale_(1.+10.*8.), scale_(3.));
}
void export_support_surface_type_legend_to_svg(SVG &svg, const Point &pos)
{
// 1st row
coord_t pos_x0 = pos(0) + scale_(1.);
coord_t pos_x = pos_x0;
coord_t pos_y = pos(1) + scale_(1.5);
coord_t step_x = scale_(10.);
svg.draw_legend(Point(pos_x, pos_y), "top contact" , support_surface_type_to_color_name(SupporLayerType::TopContact));
pos_x += step_x;
svg.draw_legend(Point(pos_x, pos_y), "top iface" , support_surface_type_to_color_name(SupporLayerType::TopInterface));
pos_x += step_x;
svg.draw_legend(Point(pos_x, pos_y), "base" , support_surface_type_to_color_name(SupporLayerType::Base));
pos_x += step_x;
svg.draw_legend(Point(pos_x, pos_y), "bottom iface" , support_surface_type_to_color_name(SupporLayerType::BottomInterface));
pos_x += step_x;
svg.draw_legend(Point(pos_x, pos_y), "bottom contact" , support_surface_type_to_color_name(SupporLayerType::BottomContact));
// 2nd row
pos_x = pos_x0;
pos_y = pos(1)+scale_(2.8);
svg.draw_legend(Point(pos_x, pos_y), "raft interface" , support_surface_type_to_color_name(SupporLayerType::RaftInterface));
pos_x += step_x;
svg.draw_legend(Point(pos_x, pos_y), "raft base" , support_surface_type_to_color_name(SupporLayerType::RaftBase));
pos_x += step_x;
svg.draw_legend(Point(pos_x, pos_y), "unknown" , support_surface_type_to_color_name(SupporLayerType::Unknown));
pos_x += step_x;
svg.draw_legend(Point(pos_x, pos_y), "intermediate" , support_surface_type_to_color_name(SupporLayerType::Intermediate));
}
void export_print_z_polygons_to_svg(const char *path, SupportGeneratorLayer ** const layers, int n_layers)
{
BoundingBox bbox;
for (int i = 0; i < n_layers; ++ i)
bbox.merge(get_extents(layers[i]->polygons));
Point legend_size = export_support_surface_type_legend_to_svg_box_size();
Point legend_pos(bbox.min(0), bbox.max(1));
bbox.merge(Point(std::max(bbox.min(0) + legend_size(0), bbox.max(0)), bbox.max(1) + legend_size(1)));
SVG svg(path, bbox);
const float transparency = 0.5f;
for (int i = 0; i < n_layers; ++ i)
svg.draw(union_ex(layers[i]->polygons), support_surface_type_to_color_name(layers[i]->layer_type), transparency);
for (int i = 0; i < n_layers; ++ i)
svg.draw(to_polylines(layers[i]->polygons), support_surface_type_to_color_name(layers[i]->layer_type));
export_support_surface_type_legend_to_svg(svg, legend_pos);
svg.Close();
}
void export_print_z_polygons_and_extrusions_to_svg(
const char *path,
SupportGeneratorLayer ** const layers,
int n_layers,
SupportLayer &support_layer)
{
BoundingBox bbox;
for (int i = 0; i < n_layers; ++ i)
bbox.merge(get_extents(layers[i]->polygons));
Point legend_size = export_support_surface_type_legend_to_svg_box_size();
Point legend_pos(bbox.min(0), bbox.max(1));
bbox.merge(Point(std::max(bbox.min(0) + legend_size(0), bbox.max(0)), bbox.max(1) + legend_size(1)));
SVG svg(path, bbox);
const float transparency = 0.5f;
for (int i = 0; i < n_layers; ++ i)
svg.draw(union_ex(layers[i]->polygons), support_surface_type_to_color_name(layers[i]->layer_type), transparency);
for (int i = 0; i < n_layers; ++ i)
svg.draw(to_polylines(layers[i]->polygons), support_surface_type_to_color_name(layers[i]->layer_type));
Polygons polygons_support, polygons_interface;
support_layer.support_fills.polygons_covered_by_width(polygons_support, float(SCALED_EPSILON));
// support_layer.support_interface_fills.polygons_covered_by_width(polygons_interface, SCALED_EPSILON);
svg.draw(union_ex(polygons_support), "brown");
svg.draw(union_ex(polygons_interface), "black");
export_support_surface_type_legend_to_svg(svg, legend_pos);
svg.Close();
}
} // namespace Slic3r
#endif /* SLIC3R_DEBUG */

View file

@ -0,0 +1,22 @@
///|/ Copyright (c) Prusa Research 2023 Vojtěch Bubník @bubnikv
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#ifndef slic3r_SupportCommon_hpp_
#define slic3r_SupportCommon_hpp_
namespace Slic3r {
class SupportGeneratorLayer;
class SupportLayer;
namespace FFFSupport {
void export_print_z_polygons_to_svg(const char *path, SupportGeneratorLayer ** const layers, size_t n_layers);
void export_print_z_polygons_and_extrusions_to_svg(const char *path, SupportGeneratorLayer ** const layers, size_t n_layers, SupportLayer& support_layer);
} // namespace FFFSupport
} // namespace Slic3r
#endif /* slic3r_SupportCommon_hpp_ */

View file

@ -0,0 +1,150 @@
///|/ Copyright (c) Prusa Research 2023 Vojtěch Bubník @bubnikv, Pavel Mikuš @Godrak
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#ifndef slic3r_SupportLayer_hpp_
#define slic3r_SupportLayer_hpp_
#include <oneapi/tbb/scalable_allocator.h>
#include <oneapi/tbb/spin_mutex.h>
// for Slic3r::deque
#include "../libslic3r.h"
#include "../ClipperUtils.hpp"
#include "../Polygon.hpp"
namespace Slic3r::FFFSupport {
// Support layer type to be used by SupportGeneratorLayer. This type carries a much more detailed information
// about the support layer type than the final support layers stored in a PrintObject.
enum class SupporLayerType {
Unknown = 0,
// Ratft base layer, to be printed with the support material.
RaftBase,
// Raft interface layer, to be printed with the support interface material.
RaftInterface,
// Bottom contact layer placed over a top surface of an object. To be printed with a support interface material.
BottomContact,
// Dense interface layer, to be printed with the support interface material.
// This layer is separated from an object by an BottomContact layer.
BottomInterface,
// Sparse base support layer, to be printed with a support material.
Base,
// Dense interface layer, to be printed with the support interface material.
// This layer is separated from an object with TopContact layer.
TopInterface,
// Top contact layer directly supporting an overhang. To be printed with a support interface material.
TopContact,
// Some undecided type yet. It will turn into Base first, then it may turn into BottomInterface or TopInterface.
Intermediate,
};
// A support layer type used internally by the SupportMaterial class. This class carries a much more detailed
// information about the support layer than the layers stored in the PrintObject, mainly
// the SupportGeneratorLayer is aware of the bridging flow and the interface gaps between the object and the support.
class SupportGeneratorLayer
{
public:
void reset() {
*this = SupportGeneratorLayer();
}
bool operator==(const SupportGeneratorLayer &layer2) const {
return print_z == layer2.print_z && height == layer2.height && bridging == layer2.bridging;
}
// Order the layers by lexicographically by an increasing print_z and a decreasing layer height.
bool operator<(const SupportGeneratorLayer &layer2) const {
if (print_z < layer2.print_z) {
return true;
} else if (print_z == layer2.print_z) {
if (height > layer2.height)
return true;
else if (height == layer2.height) {
// Bridging layers first.
return bridging && ! layer2.bridging;
} else
return false;
} else
return false;
}
void merge(SupportGeneratorLayer &&rhs) {
// The union_() does not support move semantic yet, but maybe one day it will.
this->polygons = union_(this->polygons, std::move(rhs.polygons));
auto merge = [](std::unique_ptr<Polygons> &dst, std::unique_ptr<Polygons> &src) {
if (! dst || dst->empty())
dst = std::move(src);
else if (src && ! src->empty())
*dst = union_(*dst, std::move(*src));
};
merge(this->contact_polygons, rhs.contact_polygons);
merge(this->overhang_polygons, rhs.overhang_polygons);
merge(this->enforcer_polygons, rhs.enforcer_polygons);
rhs.reset();
}
// For the bridging flow, bottom_print_z will be above bottom_z to account for the vertical separation.
// For the non-bridging flow, bottom_print_z will be equal to bottom_z.
coordf_t bottom_print_z() const { return print_z - height; }
// To sort the extremes of top / bottom interface layers.
coordf_t extreme_z() const { return (this->layer_type == SupporLayerType::TopContact) ? this->bottom_z : this->print_z; }
SupporLayerType layer_type { SupporLayerType::Unknown };
// Z used for printing, in unscaled coordinates.
coordf_t print_z { 0 };
// Bottom Z of this layer. For soluble layers, bottom_z + height = print_z,
// otherwise bottom_z + gap + height = print_z.
coordf_t bottom_z { 0 };
// Layer height in unscaled coordinates.
coordf_t height { 0 };
// Index of a PrintObject layer_id supported by this layer. This will be set for top contact layers.
// If this is not a contact layer, it will be set to size_t(-1).
size_t idx_object_layer_above { size_t(-1) };
// Index of a PrintObject layer_id, which supports this layer. This will be set for bottom contact layers.
// If this is not a contact layer, it will be set to size_t(-1).
size_t idx_object_layer_below { size_t(-1) };
// Use a bridging flow when printing this support layer.
bool bridging { false };
// Polygons to be filled by the support pattern.
Polygons polygons;
// Currently for the contact layers only.
std::unique_ptr<Polygons> contact_polygons;
std::unique_ptr<Polygons> overhang_polygons;
// Enforcers need to be propagated independently in case the "support on build plate only" option is enabled.
std::unique_ptr<Polygons> enforcer_polygons;
};
// Layers are allocated and owned by a deque. Once a layer is allocated, it is maintained
// up to the end of a generate() method. The layer storage may be replaced by an allocator class in the future,
// which would allocate layers by multiple chunks.
class SupportGeneratorLayerStorage {
public:
SupportGeneratorLayer& allocate_unguarded(SupporLayerType layer_type) {
m_storage.emplace_back();
m_storage.back().layer_type = layer_type;
return m_storage.back();
}
SupportGeneratorLayer& allocate(SupporLayerType layer_type)
{
m_mutex.lock();
m_storage.emplace_back();
SupportGeneratorLayer *layer_new = &m_storage.back();
m_mutex.unlock();
layer_new->layer_type = layer_type;
return *layer_new;
}
private:
template<typename BaseType>
using Allocator = tbb::scalable_allocator<BaseType>;
Slic3r::deque<SupportGeneratorLayer, Allocator<SupportGeneratorLayer>> m_storage;
tbb::spin_mutex m_mutex;
};
using SupportGeneratorLayersPtr = std::vector<SupportGeneratorLayer*>;
} // namespace Slic3r
#endif /* slic3r_SupportLayer_hpp_ */

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,106 @@
///|/ Copyright (c) Prusa Research 2016 - 2023 Vojtěch Bubník @bubnikv, Lukáš Matěna @lukasmatena
///|/ Copyright (c) Slic3r 2014 Alessandro Ranellucci @alranel
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#ifndef slic3r_SupportMaterial_hpp_
#define slic3r_SupportMaterial_hpp_
#include "../Flow.hpp"
#include "../PrintConfig.hpp"
#include "../Slicing.hpp"
#include "SupportLayer.hpp"
#include "SupportParameters.hpp"
namespace Slic3r {
class PrintObject;
// This class manages raft and supports for a single PrintObject.
// Instantiated by Slic3r::Print::Object->_support_material()
// This class is instantiated before the slicing starts as Object.pm will query
// the parameters of the raft to determine the 1st layer height and thickness.
class PrintObjectSupportMaterial
{
public:
PrintObjectSupportMaterial(const PrintObject *object, const SlicingParameters &slicing_params);
// Is raft enabled?
bool has_raft() const { return m_slicing_params.has_raft(); }
// Has any support?
bool has_support() const { return m_object_config->enable_support.value || m_object_config->enforce_support_layers; }
bool build_plate_only() const { return this->has_support() && m_object_config->support_on_build_plate_only.value; }
bool synchronize_layers() const { return m_slicing_params.soluble_interface && m_print_config->independent_support_layer_height.value; }
bool has_contact_loops() const { return m_object_config->support_interface_loop_pattern.value; }
// Generate support material for the object.
// New support layers will be added to the object,
// with extrusion paths and islands filled in for each support layer.
void generate(PrintObject &object);
private:
using SupportGeneratorLayersPtr = FFFSupport::SupportGeneratorLayersPtr;
using SupportGeneratorLayerStorage = FFFSupport::SupportGeneratorLayerStorage;
using SupportParameters = FFFSupport::SupportParameters;
std::vector<Polygons> buildplate_covered(const PrintObject &object) const;
// Generate top contact layers supporting overhangs.
// For a soluble interface material synchronize the layer heights with the object, otherwise leave the layer height undefined.
// If supports over bed surface only are requested, don't generate contact layers over an object.
SupportGeneratorLayersPtr top_contact_layers(const PrintObject &object, const std::vector<Polygons> &buildplate_covered, SupportGeneratorLayerStorage &layer_storage) const;
// Generate bottom contact layers supporting the top contact layers.
// For a soluble interface material synchronize the layer heights with the object,
// otherwise set the layer height to a bridging flow of a support interface nozzle.
SupportGeneratorLayersPtr bottom_contact_layers_and_layer_support_areas(
const PrintObject &object, const SupportGeneratorLayersPtr &top_contacts, std::vector<Polygons> &buildplate_covered,
SupportGeneratorLayerStorage &layer_storage, std::vector<Polygons> &layer_support_areas) const;
// Trim the top_contacts layers with the bottom_contacts layers if they overlap, so there would not be enough vertical space for both of them.
void trim_top_contacts_by_bottom_contacts(const PrintObject &object, const SupportGeneratorLayersPtr &bottom_contacts, SupportGeneratorLayersPtr &top_contacts) const;
// Generate raft layers and the intermediate support layers between the bottom contact and top contact surfaces.
SupportGeneratorLayersPtr raft_and_intermediate_support_layers(
const PrintObject &object,
const SupportGeneratorLayersPtr &bottom_contacts,
const SupportGeneratorLayersPtr &top_contacts,
SupportGeneratorLayerStorage &layer_storage) const;
// Fill in the base layers with polygons.
void generate_base_layers(
const PrintObject &object,
const SupportGeneratorLayersPtr &bottom_contacts,
const SupportGeneratorLayersPtr &top_contacts,
SupportGeneratorLayersPtr &intermediate_layers,
const std::vector<Polygons> &layer_support_areas) const;
// Trim support layers by an object to leave a defined gap between
// the support volume and the object.
void trim_support_layers_by_object(
const PrintObject &object,
SupportGeneratorLayersPtr &support_layers,
const coordf_t gap_extra_above,
const coordf_t gap_extra_below,
const coordf_t gap_xy) const;
/*
void generate_pillars_shape();
void clip_with_shape();
*/
// Following objects are not owned by SupportMaterial class.
const PrintConfig *m_print_config;
const PrintObjectConfig *m_object_config;
// Pre-calculated parameters shared between the object slicer and the support generator,
// carrying information on a raft, 1st layer height, 1st object layer height, gap between the raft and object etc.
SlicingParameters m_slicing_params;
// Various precomputed support parameters to be shared with external functions.
SupportParameters m_support_params;
};
} // namespace Slic3r
#endif /* slic3r_SupportMaterial_hpp_ */

View file

@ -0,0 +1,148 @@
///|/ Copyright (c) Prusa Research 2023 Vojtěch Bubník @bubnikv
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#include "../Print.hpp"
#include "../PrintConfig.hpp"
#include "../Slicing.hpp"
#include "SupportParameters.hpp"
namespace Slic3r::FFFSupport {
SupportParameters::SupportParameters(const PrintObject &object)
{
const PrintConfig &print_config = object.print()->config();
const PrintObjectConfig &object_config = object.config();
const SlicingParameters &slicing_params = object.slicing_parameters();
this->soluble_interface = slicing_params.soluble_interface;
this->soluble_interface_non_soluble_base =
// Zero z-gap between the overhangs and the support interface.
slicing_params.soluble_interface &&
// Interface extruder soluble.
object_config.support_interface_filament.value > 0 && print_config.filament_soluble.get_at(object_config.support_interface_filament.value - 1) &&
// Base extruder: Either "print with active extruder" not soluble.
(object_config.support_filament.value == 0 || ! print_config.filament_soluble.get_at(object_config.support_filament.value - 1));
{
int num_top_interface_layers = std::max(0, object_config.support_interface_top_layers.value);
int num_bottom_interface_layers = object_config.support_interface_bottom_layers < 0 ?
num_top_interface_layers : object_config.support_interface_bottom_layers;
this->has_top_contacts = num_top_interface_layers > 0;
this->has_bottom_contacts = num_bottom_interface_layers > 0;
this->num_top_interface_layers = this->has_top_contacts ? size_t(num_top_interface_layers - 1) : 0;
this->num_bottom_interface_layers = this->has_bottom_contacts ? size_t(num_bottom_interface_layers - 1) : 0;
if (this->soluble_interface_non_soluble_base) {
// Try to support soluble dense interfaces with non-soluble dense interfaces.
this->num_top_base_interface_layers = size_t(std::min(num_top_interface_layers / 2, 2));
this->num_bottom_base_interface_layers = size_t(std::min(num_bottom_interface_layers / 2, 2));
} else {
this->num_top_base_interface_layers = 0;
this->num_bottom_base_interface_layers = 0;
}
}
this->first_layer_flow = Slic3r::support_material_1st_layer_flow(&object, float(slicing_params.first_print_layer_height));
this->support_material_flow = Slic3r::support_material_flow(&object, float(slicing_params.layer_height));
this->support_material_interface_flow = Slic3r::support_material_interface_flow(&object, float(slicing_params.layer_height));
this->raft_interface_flow = support_material_interface_flow;
// Calculate a minimum support layer height as a minimum over all extruders, but not smaller than 10um.
this->support_layer_height_min = scaled<coord_t>(0.01);
for (auto lh : print_config.min_layer_height.values)
this->support_layer_height_min = std::min(this->support_layer_height_min, std::max(0.01, lh));
for (auto layer : object.layers())
this->support_layer_height_min = std::min(this->support_layer_height_min, std::max(0.01, layer->height));
if (object_config.support_interface_top_layers.value == 0) {
// No interface layers allowed, print everything with the base support pattern.
this->support_material_interface_flow = this->support_material_flow;
}
// Evaluate the XY gap between the object outer perimeters and the support structures.
// Evaluate the XY gap between the object outer perimeters and the support structures.
coordf_t external_perimeter_width = 0.;
coordf_t bridge_flow_ratio = 0;
for (size_t region_id = 0; region_id < object.num_printing_regions(); ++ region_id) {
const PrintRegion &region = object.printing_region(region_id);
external_perimeter_width = std::max(external_perimeter_width, coordf_t(region.flow(object, frExternalPerimeter, slicing_params.layer_height).width()));
bridge_flow_ratio += region.config().bridge_flow;
}
this->gap_xy = object_config.support_object_xy_distance;//.get_abs_value(external_perimeter_width);
bridge_flow_ratio /= object.num_printing_regions();
this->support_material_bottom_interface_flow = slicing_params.soluble_interface || ! object_config.thick_bridges ?
this->support_material_interface_flow.with_flow_ratio(bridge_flow_ratio) :
Flow::bridging_flow(bridge_flow_ratio * this->support_material_interface_flow.nozzle_diameter(), this->support_material_interface_flow.nozzle_diameter());
this->can_merge_support_regions = object_config.support_filament.value == object_config.support_interface_filament.value;
if (!this->can_merge_support_regions && (object_config.support_filament.value == 0 || object_config.support_interface_filament.value == 0)) {
// One of the support extruders is of "don't care" type.
auto object_extruders = object.object_extruders();
if (object_extruders.size() == 1 &&
*object_extruders.begin() == std::max<unsigned int>(object_config.support_filament.value, object_config.support_interface_filament.value))
// Object is printed with the same extruder as the support.
this->can_merge_support_regions = true;
}
double interface_spacing = object_config.support_interface_spacing.value + this->support_material_interface_flow.spacing();
this->interface_density = std::min(1., this->support_material_interface_flow.spacing() / interface_spacing);
double raft_interface_spacing = object_config.support_interface_spacing.value + this->raft_interface_flow.spacing();
this->raft_interface_density = std::min(1., this->raft_interface_flow.spacing() / raft_interface_spacing);
double support_spacing = object_config.support_base_pattern_spacing.value + this->support_material_flow.spacing();
this->support_density = std::min(1., this->support_material_flow.spacing() / support_spacing);
if (object_config.support_interface_top_layers.value == 0) {
// No interface layers allowed, print everything with the base support pattern.
this->interface_density = this->support_density;
}
SupportMaterialPattern support_pattern = object_config.support_base_pattern;
this->with_sheath = false;//object_config.support_material_with_sheath;
this->base_fill_pattern =
support_pattern == smpHoneycomb ? ipHoneycomb :
this->support_density > 0.95 || this->with_sheath ? ipRectilinear : ipSupportBase;
this->interface_fill_pattern = (this->interface_density > 0.95 ? ipRectilinear : ipSupportBase);
this->raft_interface_fill_pattern = this->raft_interface_density > 0.95 ? ipRectilinear : ipSupportBase;
this->contact_fill_pattern =
(object_config.support_interface_pattern == smipAuto && slicing_params.soluble_interface) ||
object_config.support_interface_pattern == smipConcentric ?
ipConcentric :
(this->interface_density > 0.95 ? ipRectilinear : ipSupportBase);
this->base_angle = Geometry::deg2rad(float(object_config.support_angle.value));
this->interface_angle = Geometry::deg2rad(float(object_config.support_angle.value + 90.));
this->raft_angle_1st_layer = 0.f;
this->raft_angle_base = 0.f;
this->raft_angle_interface = 0.f;
if (slicing_params.base_raft_layers > 1) {
assert(slicing_params.raft_layers() >= 4);
// There are all raft layer types (1st layer, base, interface & contact layers) available.
this->raft_angle_1st_layer = this->interface_angle;
this->raft_angle_base = this->base_angle;
this->raft_angle_interface = this->interface_angle;
if ((slicing_params.interface_raft_layers & 1) == 0)
// Allign the 1st raft interface layer so that the object 1st layer is hatched perpendicularly to the raft contact interface.
this->raft_angle_interface += float(0.5 * M_PI);
} else if (slicing_params.base_raft_layers == 1 || slicing_params.interface_raft_layers > 1) {
assert(slicing_params.raft_layers() == 2 || slicing_params.raft_layers() == 3);
// 1st layer, interface & contact layers available.
this->raft_angle_1st_layer = this->base_angle;
this->raft_angle_interface = this->interface_angle + 0.5 * M_PI;
} else if (slicing_params.interface_raft_layers == 1) {
// Only the contact raft layer is non-empty, which will be printed as the 1st layer.
assert(slicing_params.base_raft_layers == 0);
assert(slicing_params.interface_raft_layers == 1);
assert(slicing_params.raft_layers() == 1);
this->raft_angle_1st_layer = float(0.5 * M_PI);
this->raft_angle_interface = this->raft_angle_1st_layer;
} else {
// No raft.
assert(slicing_params.base_raft_layers == 0);
assert(slicing_params.interface_raft_layers == 0);
assert(slicing_params.raft_layers() == 0);
}
this->tree_branch_diameter_double_wall_area_scaled = 0.25 * sqr(scaled<double>(object_config.tree_support_branch_diameter_double_wall.value)) * M_PI;
}
} // namespace Slic3r

View file

@ -0,0 +1,100 @@
///|/ Copyright (c) Prusa Research 2023 Vojtěch Bubník @bubnikv
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#ifndef slic3r_SupportParameters_hpp_
#define slic3r_SupportParameters_hpp_
#include "../libslic3r.h"
#include "../Flow.hpp"
namespace Slic3r {
class PrintObject;
enum InfillPattern : int;
namespace FFFSupport {
struct SupportParameters {
SupportParameters(const PrintObject &object);
// Both top / bottom contacts and interfaces are soluble.
bool soluble_interface;
// Support contact & interface are soluble, but support base is non-soluble.
bool soluble_interface_non_soluble_base;
// Is there at least a top contact layer extruded above support base?
bool has_top_contacts;
// Is there at least a bottom contact layer extruded below support base?
bool has_bottom_contacts;
// Number of top interface layers without counting the contact layer.
size_t num_top_interface_layers;
// Number of bottom interface layers without counting the contact layer.
size_t num_bottom_interface_layers;
// Number of top base interface layers. Zero if not soluble_interface_non_soluble_base.
size_t num_top_base_interface_layers;
// Number of bottom base interface layers. Zero if not soluble_interface_non_soluble_base.
size_t num_bottom_base_interface_layers;
bool has_contacts() const { return this->has_top_contacts || this->has_bottom_contacts; }
bool has_interfaces() const { return this->num_top_interface_layers + this->num_bottom_interface_layers > 0; }
bool has_base_interfaces() const { return this->num_top_base_interface_layers + this->num_bottom_base_interface_layers > 0; }
size_t num_top_interface_layers_only() const { return this->num_top_interface_layers - this->num_top_base_interface_layers; }
size_t num_bottom_interface_layers_only() const { return this->num_bottom_interface_layers - this->num_bottom_base_interface_layers; }
// Flow at the 1st print layer.
Flow first_layer_flow;
// Flow at the support base (neither top, nor bottom interface).
// Also flow at the raft base with the exception of raft interface and contact layers.
Flow support_material_flow;
// Flow at the top interface and contact layers.
Flow support_material_interface_flow;
// Flow at the bottom interfaces and contacts.
Flow support_material_bottom_interface_flow;
// Flow at raft inteface & contact layers.
Flow raft_interface_flow;
// Is merging of regions allowed? Could the interface & base support regions be printed with the same extruder?
bool can_merge_support_regions;
coordf_t support_layer_height_min;
// coordf_t support_layer_height_max;
coordf_t gap_xy;
float base_angle;
float interface_angle;
// Density of the top / bottom interface and contact layers.
coordf_t interface_density;
// Density of the raft interface and contact layers.
coordf_t raft_interface_density;
// Density of the base support layers.
coordf_t support_density;
// Pattern of the sparse infill including sparse raft layers.
InfillPattern base_fill_pattern;
// Pattern of the top / bottom interface and contact layers.
InfillPattern interface_fill_pattern;
// Pattern of the raft interface and contact layers.
InfillPattern raft_interface_fill_pattern;
// Pattern of the contact layers.
InfillPattern contact_fill_pattern;
// Shall the sparse (base) layers be printed with a single perimeter line (sheath) for robustness?
bool with_sheath;
// Branches of organic supports with area larger than this threshold will be extruded with double lines.
double tree_branch_diameter_double_wall_area_scaled;
float raft_angle_1st_layer;
float raft_angle_base;
float raft_angle_interface;
// Produce a raft interface angle for a given SupportLayer::interface_id()
float raft_interface_angle(size_t interface_id) const
{ return this->raft_angle_interface + ((interface_id & 1) ? float(- M_PI / 4.) : float(+ M_PI / 4.)); }
};
} // namespace FFFSupport
} // namespace Slic3r
#endif /* slic3r_SupportParameters_hpp_ */

View file

@ -0,0 +1,878 @@
///|/ Copyright (c) Prusa Research 2022 - 2023 Vojtěch Bubník @bubnikv, Pavel Mikuš @Godrak
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
// Tree supports by Thomas Rahm, losely based on Tree Supports by CuraEngine.
// Original source of Thomas Rahm's tree supports:
// https://github.com/ThomasRahm/CuraEngine
//
// Original CuraEngine copyright:
// Copyright (c) 2021 Ultimaker B.V.
// CuraEngine is released under the terms of the AGPLv3 or higher.
#include "TreeModelVolumes.hpp"
#include "TreeSupportCommon.hpp"
#include "../BuildVolume.hpp"
#include "../ClipperUtils.hpp"
#include "../Flow.hpp"
#include "../Layer.hpp"
#include "../Point.hpp"
#include "../Print.hpp"
#include "../PrintConfig.hpp"
#include "../Utils.hpp"
#include "../format.hpp"
#include <string_view>
#include <boost/log/trivial.hpp>
#include <tbb/parallel_for.h>
#include <tbb/task_group.h>
namespace Slic3r::FFFTreeSupport
{
using namespace std::literals;
// or warning
// had to use a define beacuse the macro processing inside macro BOOST_LOG_TRIVIAL()
#define error_level_not_in_cache error
//FIXME Machine border is currently ignored.
static Polygons calculateMachineBorderCollision(Polygon machine_border)
{
// Put a border of 1m around the print volume so that we don't collide.
#if 1
//FIXME just returning no border will let tree support legs collide with print bed boundary
return {};
#else
//FIXME offsetting by 1000mm easily overflows int32_tr coordinate.
Polygons out = offset(machine_border, scaled<float>(1000.), jtMiter, 1.2);
machine_border.reverse(); // Makes the polygon negative so that we subtract the actual volume from the collision area.
out.emplace_back(std::move(machine_border));
return out;
#endif
}
TreeModelVolumes::TreeModelVolumes(
const PrintObject &print_object,
const BuildVolume &build_volume,
const coord_t max_move, const coord_t max_move_slow, size_t current_mesh_idx,
#ifdef SLIC3R_TREESUPPORTS_PROGRESS
double progress_multiplier, double progress_offset,
#endif // SLIC3R_TREESUPPORTS_PROGRESS
const std::vector<Polygons>& additional_excluded_areas) :
// -2 to avoid rounding errors
m_max_move{ std::max<coord_t>(max_move - 2, 0) }, m_max_move_slow{ std::max<coord_t>(max_move_slow - 2, 0) },
#ifdef SLIC3R_TREESUPPORTS_PROGRESS
m_progress_multiplier{ progress_multiplier }, m_progress_offset{ progress_offset },
#endif // SLIC3R_TREESUPPORTS_PROGRESS
m_machine_border{ calculateMachineBorderCollision(build_volume.polygon()) }
{
#if 0
std::unordered_map<size_t, size_t> mesh_to_layeroutline_idx;
for (size_t mesh_idx = 0; mesh_idx < storage.meshes.size(); ++ mesh_idx) {
SliceMeshStorage mesh = storage.meshes[mesh_idx];
bool added = false;
for (size_t idx = 0; idx < m_layer_outlines.size(); ++ idx)
if (TreeSupport::TreeSupportSettings(m_layer_outlines[idx].first) == TreeSupport::TreeSupportSettings(mesh.settings)) {
added = true;
mesh_to_layeroutline_idx[mesh_idx] = idx;
}
if (! added) {
mesh_to_layeroutline_idx[mesh_idx] = m_layer_outlines.size();
m_layer_outlines.emplace_back(mesh.settings, std::vector<Polygons>(storage.support.supportLayers.size(), Polygons()));
}
}
for (size_t idx = 0; idx < m_layer_outlines.size(); ++ idx) {
tbb::parallel_for(tbb::blocked_range<size_t>(0, m_layer_outlines[idx].second.size()),
[&](const tbb::blocked_range<size_t> &range) {
for (const size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx)
m_layer_outlines[idx].second[layer_idx] = union_(m_layer_outlines[idx].second[layer_idx]);
});
}
m_current_outline_idx = mesh_to_layeroutline_idx[current_mesh_idx];
#else
{
m_anti_overhang = print_object.slice_support_blockers();
TreeSupportMeshGroupSettings mesh_settings(print_object);
const TreeSupportSettings config{ mesh_settings, print_object.slicing_parameters() };
m_current_min_xy_dist = config.xy_min_distance;
m_current_min_xy_dist_delta = config.xy_distance - m_current_min_xy_dist;
assert(m_current_min_xy_dist_delta >= 0);
m_increase_until_radius = config.increase_radius_until_radius;
m_radius_0 = config.getRadius(0);
m_raft_layers = config.raft_layers;
m_current_outline_idx = 0;
m_layer_outlines.emplace_back(mesh_settings, std::vector<Polygons>{});
std::vector<Polygons> &outlines = m_layer_outlines.front().second;
size_t num_raft_layers = m_raft_layers.size();
size_t num_layers = print_object.layer_count() + num_raft_layers;
outlines.assign(num_layers, Polygons{});
tbb::parallel_for(tbb::blocked_range<size_t>(num_raft_layers, num_layers, std::min<size_t>(1, std::max<size_t>(16, num_layers / (8 * tbb::this_task_arena::max_concurrency())))),
[&](const tbb::blocked_range<size_t> &range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx)
outlines[layer_idx] = polygons_simplify(to_polygons(print_object.get_layer(layer_idx - num_raft_layers)->lslices), mesh_settings.resolution, polygons_strictly_simple);
});
}
#endif
m_support_rests_on_model = false;
m_min_resolution = std::numeric_limits<coord_t>::max();
for (auto data_pair : m_layer_outlines) {
m_support_rests_on_model |= ! data_pair.first.support_material_buildplate_only;
m_min_resolution = std::min(m_min_resolution, data_pair.first.resolution);
}
#if 0
for (size_t mesh_idx = 0; mesh_idx < storage.meshes.size(); mesh_idx++) {
SliceMeshStorage mesh = storage.meshes[mesh_idx];
tbb::parallel_for(tbb::blocked_range<size_t>(0, m_layer_outlines[mesh_to_layeroutline_idx[mesh_idx]].second.size()),
[&](const tbb::blocked_range<size_t> &range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx)
if (layer_idx < mesh.layer_nr_max_filled_layer) {
Polygons outline = extractOutlineFromMesh(mesh, layer_idx);
append(m_layer_outlines[mesh_to_layeroutline_idx[mesh_idx]].second[layer_idx], outline);
}
});
}
if (! additional_excluded_areas.empty()) {
tbb::parallel_for(tbb::blocked_range<size_t>(0, m_anti_overhang.size()),
[&](const tbb::blocked_range<size_t> &range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
if (layer_idx < coord_t(additional_excluded_areas.size()))
append(m_anti_overhang[layer_idx], additional_excluded_areas[layer_idx]);
// if (SUPPORT_TREE_AVOID_SUPPORT_BLOCKER)
// append(m_anti_overhang[layer_idx], storage.support.supportLayers[layer_idx].anti_overhang);
//FIXME block wipe tower
// if (storage.primeTower.enabled)
// append(m_anti_overhang[layer_idx], layer_idx == 0 ? storage.primeTower.outer_poly_first_layer : storage.primeTower.outer_poly);
m_anti_overhang[layer_idx] = union_(m_anti_overhang[layer_idx]);
}
});
}
#endif
}
void TreeModelVolumes::precalculate(const PrintObject& print_object, const coord_t max_layer, std::function<void()> throw_on_cancel)
{
auto t_start = std::chrono::high_resolution_clock::now();
m_precalculated = true;
// Get the config corresponding to one mesh that is in the current group. Which one has to be irrelevant.
// Not the prettiest way to do this, but it ensures some calculations that may be a bit more complex
// like inital layer diameter are only done in once.
TreeSupportSettings config(m_layer_outlines[m_current_outline_idx].first, print_object.slicing_parameters());
{
// calculate which radius each layer in the tip may have.
std::vector<coord_t> possible_tip_radiis;
for (size_t distance_to_top = 0; distance_to_top <= config.tip_layers; ++ distance_to_top) {
possible_tip_radiis.emplace_back(ceilRadius(config.getRadius(distance_to_top)));
possible_tip_radiis.emplace_back(ceilRadius(config.getRadius(distance_to_top) + m_current_min_xy_dist_delta));
}
sort_remove_duplicates(possible_tip_radiis);
// It theoretically may happen in the tip, that the radius can change so much in-between 2 layers,
// that a ceil step is skipped (as in there is a radius r so that ceilRadius(radius(dtt))<ceilRadius(r)<ceilRadius(radius(dtt+1))).
// As such a radius will not reasonable happen in the tree and it will most likely not be requested,
// there is no need to calculate them. So just skip these.
for (coord_t radius_eval = m_radius_0; radius_eval <= config.branch_radius; radius_eval = ceilRadius(radius_eval + 1))
if (! std::binary_search(possible_tip_radiis.begin(), possible_tip_radiis.end(), radius_eval))
m_ignorable_radii.emplace_back(radius_eval);
}
throw_on_cancel();
// it may seem that the required avoidance can be of a smaller radius when going to model (no initial layer diameter for to model branches)
// but as for every branch going towards the bp, the to model avoidance is required to check for possible merges with to model branches, this assumption is in-fact wrong.
std::unordered_map<coord_t, LayerIndex> radius_until_layer;
// while it is possible to calculate, up to which layer the avoidance should be calculated, this simulation is easier to understand, and does not need to be adjusted if something of the radius calculation is changed.
// Overhead with an assumed worst case of 6600 layers was about 2ms
for (LayerIndex distance_to_top = 0; distance_to_top <= max_layer; ++ distance_to_top) {
const LayerIndex current_layer = max_layer - distance_to_top;
auto update_radius_until_layer = [&radius_until_layer, current_layer](coord_t r) {
auto it = radius_until_layer.find(r);
if (it == radius_until_layer.end())
radius_until_layer.emplace_hint(it, r, current_layer);
else
assert(it->second >= current_layer);
};
// regular radius
update_radius_until_layer(ceilRadius(config.getRadius(distance_to_top, 0) + m_current_min_xy_dist_delta));
// the maximum radius that the radius with the min_xy_dist can achieve
update_radius_until_layer(ceilRadius(config.getRadius(distance_to_top, 0)));
update_radius_until_layer(ceilRadius(config.recommendedMinRadius(current_layer) + m_current_min_xy_dist_delta));
}
throw_on_cancel();
// Copy to deque to use in parallel for later.
std::vector<RadiusLayerPair> relevant_avoidance_radiis{ radius_until_layer.begin(), radius_until_layer.end() };
// Append additional radiis needed for collision.
// To calculate collision holefree for every radius, the collision of radius m_increase_until_radius will be required.
radius_until_layer[ceilRadius(m_increase_until_radius + m_current_min_xy_dist_delta)] = max_layer;
// Collision for radius 0 needs to be calculated everywhere, as it will be used to ensure valid xy_distance in drawAreas.
radius_until_layer[0] = max_layer;
if (m_current_min_xy_dist_delta != 0)
radius_until_layer[m_current_min_xy_dist_delta] = max_layer;
// Now that required_avoidance_limit contains the maximum of ild and regular required radius just copy.
std::vector<RadiusLayerPair> relevant_collision_radiis{ radius_until_layer.begin(), radius_until_layer.end() };
// Calculate the relevant collisions
calculateCollision(relevant_collision_radiis, throw_on_cancel);
// calculate a separate Collisions with all holes removed. These are relevant for some avoidances that try to avoid holes (called safe)
std::vector<RadiusLayerPair> relevant_hole_collision_radiis;
for (RadiusLayerPair key : relevant_avoidance_radiis)
if (key.first < m_increase_until_radius + m_current_min_xy_dist_delta)
relevant_hole_collision_radiis.emplace_back(key);
// Calculate collisions without holes, built from regular collision
calculateCollisionHolefree(relevant_hole_collision_radiis, throw_on_cancel);
// Let placables be calculated from calculateAvoidance() for better parallelization.
if (m_support_rests_on_model)
calculatePlaceables(relevant_avoidance_radiis, throw_on_cancel);
auto t_coll = std::chrono::high_resolution_clock::now();
// Calculate the relevant avoidances in parallel as far as possible
{
tbb::task_group task_group;
task_group.run([this, relevant_avoidance_radiis, throw_on_cancel]{ calculateAvoidance(relevant_avoidance_radiis, true, m_support_rests_on_model, throw_on_cancel); });
task_group.run([this, relevant_avoidance_radiis, throw_on_cancel]{ calculateWallRestrictions(relevant_avoidance_radiis, throw_on_cancel); });
task_group.wait();
}
auto t_end = std::chrono::high_resolution_clock::now();
auto dur_col = 0.001 * std::chrono::duration_cast<std::chrono::microseconds>(t_coll - t_start).count();
auto dur_avo = 0.001 * std::chrono::duration_cast<std::chrono::microseconds>(t_end - t_coll).count();
// m_precalculated = true;
BOOST_LOG_TRIVIAL(info) << "Precalculating collision took" << dur_col << " ms. Precalculating avoidance took " << dur_avo << " ms.";
#if 0
// Paint caches into SVGs:
auto paint_cache_into_SVGs = [this](const RadiusLayerPolygonCache &cache, std::string_view name) {
const std::vector<std::pair<RadiusLayerPair, std::reference_wrapper<const Polygons>>> sorted = cache.sorted();
static constexpr const std::string_view colors[] = {
"red", "green", "blue", "magenta", "orange"
};
static constexpr const size_t num_colors = sizeof(colors) / sizeof(colors[0]);
for (size_t i = 0; i < sorted.size();) {
// Find range of cache items with the same layer index.
size_t j = i;
for (++ j; j < sorted.size() && sorted[i].first.second == sorted[j].first.second; ++ j) ;
// Collect expolygons in reverse order (largest to smallest).
std::vector<std::pair<Slic3r::ExPolygons, SVG::ExPolygonAttributes>> expolygons_with_attributes;
for (int k = int(j - 1); k >= int(i); -- k) {
std::string legend = format("radius-%1%", unscaled<float>(sorted[k].first.first));
expolygons_with_attributes.push_back({ union_ex(sorted[k].second), SVG::ExPolygonAttributes(legend, std::string(colors[(k - int(i)) % num_colors]), 1.) });
SVG::export_expolygons(debug_out_path("treesupport_cache-%s-%d-%s.svg", name.data(), sorted[i].first.second, legend.c_str()), { expolygons_with_attributes.back() });
}
// Render the range of per radius collision polygons into a common SVG.
SVG::export_expolygons(debug_out_path("treesupport_cache-%s-%d.svg", name.data(), sorted[i].first.second), expolygons_with_attributes);
i = j;
}
};
paint_cache_into_SVGs(m_collision_cache, "collision_cache");
paint_cache_into_SVGs(m_collision_cache_holefree, "collision_cache_holefree");
paint_cache_into_SVGs(m_avoidance_cache, "avoidance_cache");
paint_cache_into_SVGs(m_avoidance_cache_slow, "avoidance_cache_slow");
paint_cache_into_SVGs(m_avoidance_cache_to_model, "avoidance_cache_to_model");
paint_cache_into_SVGs(m_avoidance_cache_to_model_slow, "avoidance_cache_to_model_slow");
paint_cache_into_SVGs(m_placeable_areas_cache, "placable_areas_cache");
paint_cache_into_SVGs(m_avoidance_cache_holefree, "avoidance_cache_holefree");
paint_cache_into_SVGs(m_avoidance_cache_holefree_to_model, "avoidance_cache_holefree_to_model");
paint_cache_into_SVGs(m_wall_restrictions_cache, "wall_restrictions_cache");
paint_cache_into_SVGs(m_wall_restrictions_cache_min, "wall_restrictions_cache_min");
#endif
}
const Polygons& TreeModelVolumes::getCollision(const coord_t orig_radius, LayerIndex layer_idx, bool min_xy_dist) const
{
const coord_t radius = this->ceilRadius(orig_radius, min_xy_dist);
if (std::optional<std::reference_wrapper<const Polygons>> result = m_collision_cache.getArea({ radius, layer_idx }); result)
return (*result).get();
if (m_precalculated) {
BOOST_LOG_TRIVIAL(error_level_not_in_cache) << "Had to calculate collision at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!";
tree_supports_show_error("Not precalculated Collision requested."sv, false);
}
const_cast<TreeModelVolumes*>(this)->calculateCollision(radius, layer_idx, []{});
return getCollision(orig_radius, layer_idx, min_xy_dist);
}
// Get a collision area at a given layer for a radius that is a lower or equial to the key radius.
// It is expected that the collision area is precalculated for a given layer at least for the radius zero.
// Used for pushing tree supports away from object during the final Organic optimization step.
std::optional<std::pair<coord_t, std::reference_wrapper<const Polygons>>> TreeModelVolumes::get_collision_lower_bound_area(LayerIndex layer_id, coord_t max_radius) const
{
return m_collision_cache.get_lower_bound_area({ max_radius, layer_id });
}
// Private. Only called internally by calculateAvoidance() and calculateAvoidanceToModel(), radius is already snapped to grid.
const Polygons& TreeModelVolumes::getCollisionHolefree(coord_t radius, LayerIndex layer_idx) const
{
assert(radius == this->ceilRadius(radius));
assert(radius < m_increase_until_radius + m_current_min_xy_dist_delta);
if (std::optional<std::reference_wrapper<const Polygons>> result = m_collision_cache_holefree.getArea({ radius, layer_idx }); result)
return (*result).get();
if (m_precalculated) {
BOOST_LOG_TRIVIAL(error_level_not_in_cache) << "Had to calculate collision holefree at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!";
tree_supports_show_error("Not precalculated Holefree Collision requested."sv, false);
}
const_cast<TreeModelVolumes*>(this)->calculateCollisionHolefree({ radius, layer_idx });
return getCollisionHolefree(radius, layer_idx);
}
const Polygons& TreeModelVolumes::getAvoidance(const coord_t orig_radius, LayerIndex layer_idx, AvoidanceType type, bool to_model, bool min_xy_dist) const
{
if (layer_idx == 0) // What on the layer directly above buildplate do i have to avoid to reach the buildplate ...
return getCollision(orig_radius, layer_idx, min_xy_dist);
const coord_t radius = this->ceilRadius(orig_radius, min_xy_dist);
if (type == AvoidanceType::FastSafe && radius >= m_increase_until_radius + m_current_min_xy_dist_delta)
// no holes anymore by definition at this request
type = AvoidanceType::Fast;
if (std::optional<std::reference_wrapper<const Polygons>> result =
this->avoidance_cache(type, to_model).getArea({ radius, layer_idx });
result)
return (*result).get();
if (m_precalculated) {
if (to_model) {
BOOST_LOG_TRIVIAL(error_level_not_in_cache) << "Had to calculate Avoidance to model at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!";
tree_supports_show_error("Not precalculated Avoidance(to model) requested."sv, false);
} else {
BOOST_LOG_TRIVIAL(error_level_not_in_cache) << "Had to calculate Avoidance at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!";
tree_supports_show_error("Not precalculated Avoidance(to buildplate) requested."sv, false);
}
}
const_cast<TreeModelVolumes*>(this)->calculateAvoidance({ radius, layer_idx }, ! to_model, to_model);
// Retrive failed and correct result was calculated. Now it has to be retrived.
return getAvoidance(orig_radius, layer_idx, type, to_model, min_xy_dist);
}
const Polygons& TreeModelVolumes::getPlaceableAreas(const coord_t orig_radius, LayerIndex layer_idx, std::function<void()> throw_on_cancel) const
{
const coord_t radius = ceilRadius(orig_radius);
if (std::optional<std::reference_wrapper<const Polygons>> result = m_placeable_areas_cache.getArea({ radius, layer_idx }); result)
return (*result).get();
if (m_precalculated) {
BOOST_LOG_TRIVIAL(error_level_not_in_cache) << "Had to calculate Placeable Areas at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!";
tree_supports_show_error(format("Not precalculated Placeable areas requested, radius %1%, layer %2%", radius, layer_idx), false);
}
if (orig_radius == 0)
// Placable areas for radius 0 are calculated in the general collision code.
return this->getCollision(0, layer_idx, true);
else
const_cast<TreeModelVolumes*>(this)->calculatePlaceables(radius, layer_idx, throw_on_cancel);
return getPlaceableAreas(orig_radius, layer_idx, throw_on_cancel);
}
const Polygons& TreeModelVolumes::getWallRestriction(const coord_t orig_radius, LayerIndex layer_idx, bool min_xy_dist) const
{
assert(layer_idx > 0);
if (layer_idx == 0)
// Should never be requested as there will be no going below layer 0 ...,
// but just to be sure some semi-sane catch. Alternative would be empty Polygon.
return getCollision(orig_radius, layer_idx, min_xy_dist);
min_xy_dist &= m_current_min_xy_dist_delta > 0;
const coord_t radius = ceilRadius(orig_radius);
if (std::optional<std::reference_wrapper<const Polygons>> result =
(min_xy_dist ? m_wall_restrictions_cache_min : m_wall_restrictions_cache).getArea({ radius, layer_idx });
result)
return (*result).get();
if (m_precalculated) {
BOOST_LOG_TRIVIAL(error_level_not_in_cache) << "Had to calculate Wall restricions at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!";
tree_supports_show_error(
min_xy_dist ?
"Not precalculated Wall restriction of minimum xy distance requested )." :
"Not precalculated Wall restriction requested )."sv
, false);
}
const_cast<TreeModelVolumes*>(this)->calculateWallRestrictions({ radius, layer_idx });
return getWallRestriction(orig_radius, layer_idx, min_xy_dist); // Retrieve failed and correct result was calculated. Now it has to be retrieved.
}
void TreeModelVolumes::calculateCollision(const std::vector<RadiusLayerPair> &keys, std::function<void()> throw_on_cancel)
{
tbb::parallel_for(tbb::blocked_range<size_t>(0, keys.size()),
[&](const tbb::blocked_range<size_t> &range) {
for (size_t ikey = range.begin(); ikey != range.end(); ++ ikey) {
const LayerIndex radius = keys[ikey].first;
const size_t max_layer_idx = keys[ikey].second;
// recursive call to parallel_for.
calculateCollision(radius, max_layer_idx, throw_on_cancel);
}
});
}
// Calculate collisions and placable areas for radius and for layer 0 to max_layer_idx inclusive.
void TreeModelVolumes::calculateCollision(const coord_t radius, const LayerIndex max_layer_idx, std::function<void()> throw_on_cancel)
{
// assert(radius == this->ceilRadius(radius));
// Process the outlines from least layers to most layers so that the final union will run over the longest vector.
std::vector<size_t> layer_outline_indices(m_layer_outlines.size(), 0);
std::iota(layer_outline_indices.begin(), layer_outline_indices.end(), 0);
std::sort(layer_outline_indices.begin(), layer_outline_indices.end(),
[this](size_t i, size_t j) { return m_layer_outlines[i].second.size() < m_layer_outlines[j].second.size(); });
// Layer range for which the collisions will be calculated.
LayerPolygonCache data;
data.allocate(m_collision_cache.getMaxCalculatedLayer(radius) + 1, max_layer_idx + 1);
const bool calculate_placable = m_support_rests_on_model && radius == 0;
LayerPolygonCache data_placeable;
if (calculate_placable)
data_placeable.allocate(data.begin(), data.end());
for (size_t outline_idx : layer_outline_indices)
if (const std::vector<Polygons> &outlines = m_layer_outlines[outline_idx].second; ! outlines.empty()) {
const TreeSupportMeshGroupSettings &settings = m_layer_outlines[outline_idx].first;
const coord_t layer_height = settings.layer_height;
const int z_distance_bottom_layers = int(round(double(settings.support_bottom_distance) / double(layer_height)));
const int z_distance_top_layers = int(round(double(settings.support_top_distance) / double(layer_height)));
const coord_t xy_distance = outline_idx == m_current_outline_idx ? m_current_min_xy_dist :
// technically this causes collision for the normal xy_distance to be larger by m_current_min_xy_dist_delta for all
// not currently processing meshes as this delta will be added at request time.
// avoiding this would require saving each collision for each outline_idx separately.
// and later for each avoidance... But avoidance calculation has to be for the whole scene and can NOT be done for each outline_idx separately and combined later.
// so avoiding this inaccuracy seems infeasible as it would require 2x the avoidance calculations => 0.5x the performance.
//FIXME support_xy_distance is not corrected for "soluble" flag, see TreeSupportSettings constructor.
settings.support_xy_distance;
// 1) Calculate offsets of collision areas in parallel.
LayerPolygonCache collision_areas_offsetted;
collision_areas_offsetted.allocate(
std::max<LayerIndex>(0, data.begin() - z_distance_bottom_layers),
std::min<LayerIndex>(outlines.size(), data.end() + z_distance_top_layers));
tbb::parallel_for(tbb::blocked_range<LayerIndex>(collision_areas_offsetted.begin(), collision_areas_offsetted.end()),
[&outlines, &machine_border = std::as_const(m_machine_border), offset_value = radius + xy_distance, &collision_areas_offsetted, &throw_on_cancel]
(const tbb::blocked_range<LayerIndex> &range) {
for (LayerIndex layer_idx = range.begin(); layer_idx != range.end(); ++ layer_idx) {
Polygons collision_areas = machine_border;
append(collision_areas, outlines[layer_idx]);
// jtRound is not needed here, as the overshoot can not cause errors in the algorithm, because no assumptions are made about the model.
// if a key does not exist when it is accessed it is added!
collision_areas_offsetted[layer_idx] = offset_value == 0 ?
union_(collision_areas) :
offset(union_ex(collision_areas), offset_value, ClipperLib::jtMiter, 1.2);
throw_on_cancel();
}
});
// 2) Sum over top / bottom ranges.
const bool processing_last_mesh = outline_idx == layer_outline_indices.size();
tbb::parallel_for(tbb::blocked_range<LayerIndex>(data.begin(), data.end()),
[&collision_areas_offsetted, &outlines, &machine_border = m_machine_border, &anti_overhang = m_anti_overhang, radius,
xy_distance, z_distance_bottom_layers, z_distance_top_layers, min_resolution = m_min_resolution, &data, processing_last_mesh, &throw_on_cancel]
(const tbb::blocked_range<LayerIndex>& range) {
for (LayerIndex layer_idx = range.begin(); layer_idx != range.end(); ++ layer_idx) {
Polygons collisions;
for (int i = - z_distance_bottom_layers; i <= 0; ++ i)
if (int j = layer_idx + i; collision_areas_offsetted.has(j))
append(collisions, collision_areas_offsetted[j]);
for (int i = 1; i <= z_distance_top_layers; ++ i)
if (int j = layer_idx + i; j < int(outlines.size())) {
Polygons collision_areas_original = machine_border;
append(collision_areas_original, outlines[j]);
// If just the collision (including the xy distance) of the layers above is accumulated, it leads to the
// following issue:
// Example: assuming the z distance is 2 layer
// + = xy_distance
// - = model
// o = overhang of the area two layers above that should result in tips on this layer
//
// +-----+
// +-----+
// +-----+
// o +-----+
// If just the collision above is accumulated the overhang will get overwritten by the xy_distance of the
// layer below the overhang...
//
// This only causes issues if the overhang area is thinner than xy_distance
// Just accumulating areas of the model above without the xy distance is also problematic, as then support
// may get closer to the model (on the diagonal downwards) than the user intended. Example (s = support):
// +-----+
// +-----+
// +-----+
// s+-----+
// technically the calculation below is off by one layer, as the actual distance between plastic one layer
// down is 0 not layer height, as this layer is filled with said plastic. But otherwise a part of the
// overhang that is expected to be supported is overwritten by the remaining part of the xy distance of the
// layer below the to be supported area.
coord_t required_range_x =
(xy_distance - ((i - (z_distance_top_layers == 1 ? 0.5 : 0)) * xy_distance / z_distance_top_layers));
// the conditional -0.5 ensures that plastic can never touch on the diagonal
// downward when the z_distance_top_layers = 1. It is assumed to be better to
// not support an overhang<90 degree than to risk fusing to it.
append(collisions, offset(union_ex(collision_areas_original), radius + required_range_x, ClipperLib::jtMiter, 1.2));
}
collisions = processing_last_mesh && layer_idx < int(anti_overhang.size()) ?
union_(collisions, offset(union_ex(anti_overhang[layer_idx]), radius, ClipperLib::jtMiter, 1.2)) :
union_(collisions);
auto &dst = data[layer_idx];
if (processing_last_mesh) {
if (! dst.empty())
collisions = union_(collisions, dst);
dst = polygons_simplify(collisions, min_resolution, polygons_strictly_simple);
} else
append(dst, std::move(collisions));
throw_on_cancel();
}
});
// 3) Optionally calculate placables.
if (calculate_placable) {
// Now calculate the placable areas.
tbb::parallel_for(tbb::blocked_range<LayerIndex>(std::max(z_distance_bottom_layers + 1, data.begin()), data.end()),
[&collision_areas_offsetted, &outlines, &anti_overhang = m_anti_overhang, processing_last_mesh,
min_resolution = m_min_resolution, z_distance_bottom_layers, xy_distance, &data_placeable, &throw_on_cancel]
(const tbb::blocked_range<LayerIndex>& range) {
for (LayerIndex layer_idx = range.begin(); layer_idx != range.end(); ++ layer_idx) {
LayerIndex layer_idx_below = layer_idx - z_distance_bottom_layers - 1;
assert(layer_idx_below >= 0);
const Polygons &current = collision_areas_offsetted[layer_idx];
const Polygons &below = outlines[layer_idx_below];
Polygons placable = diff(
// Inflate the surface to sit on by the separation distance to increase chance of a support being placed on a sloped surface.
offset(below, xy_distance),
layer_idx_below < int(anti_overhang.size()) ? union_(current, anti_overhang[layer_idx_below]) : current);
auto &dst = data_placeable[layer_idx];
if (processing_last_mesh) {
if (! dst.empty())
placable = union_(placable, dst);
dst = polygons_simplify(placable, min_resolution, polygons_strictly_simple);
} else
append(dst, placable);
throw_on_cancel();
}
});
} else {
// Calculating just the collision areas.
}
}
#ifdef SLIC3R_TREESUPPORTS_PROGRESS
{
std::lock_guard<std::mutex> critical_section(*m_critical_progress);
if (m_precalculated && m_precalculation_progress < TREE_PROGRESS_PRECALC_COLL) {
m_precalculation_progress += TREE_PROGRESS_PRECALC_COLL / keys.size();
Progress::messageProgress(Progress::Stage::SUPPORT, m_precalculation_progress * m_progress_multiplier + m_progress_offset, TREE_PROGRESS_TOTAL);
}
}
#endif
throw_on_cancel();
m_collision_cache.insert(std::move(data), radius);
if (calculate_placable)
m_placeable_areas_cache.insert(std::move(data_placeable), radius);
}
void TreeModelVolumes::calculateCollisionHolefree(const std::vector<RadiusLayerPair> &keys, std::function<void()> throw_on_cancel)
{
LayerIndex max_layer = 0;
for (long long unsigned int i = 0; i < keys.size(); i++)
max_layer = std::max(max_layer, keys[i].second);
tbb::parallel_for(tbb::blocked_range<LayerIndex>(0, max_layer + 1, keys.size()),
[&](const tbb::blocked_range<LayerIndex> &range) {
std::vector<std::pair<RadiusLayerPair, Polygons>> data;
data.reserve(range.size() * keys.size());
for (LayerIndex layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
for (RadiusLayerPair key : keys)
if (layer_idx <= key.second) {
// Logically increase the collision by m_increase_until_radius
coord_t radius = key.first;
assert(radius == this->ceilRadius(radius));
assert(radius < m_increase_until_radius + m_current_min_xy_dist_delta);
coord_t increase_radius_ceil = ceilRadius(m_increase_until_radius, false) - radius;
assert(increase_radius_ceil > 0);
// this union is important as otherwise holes(in form of lines that will increase to holes in a later step) can get unioned onto the area.
data.emplace_back(RadiusLayerPair(radius, layer_idx), polygons_simplify(
offset(union_ex(this->getCollision(m_increase_until_radius, layer_idx, false)),
5 - increase_radius_ceil, ClipperLib::jtRound, m_min_resolution),
m_min_resolution, polygons_strictly_simple));
throw_on_cancel();
}
}
m_collision_cache_holefree.insert(std::move(data));
});
}
void TreeModelVolumes::calculateAvoidance(const std::vector<RadiusLayerPair> &keys, bool to_build_plate, bool to_model, std::function<void()> throw_on_cancel)
{
// For every RadiusLayer pair there are 3 avoidances that have to be calculated.
// Prepare tasks for parallelization.
struct AvoidanceTask {
AvoidanceType type;
coord_t radius;
LayerIndex max_required_layer;
bool to_model;
LayerIndex start_layer;
bool slow() const { return this->type == AvoidanceType::Slow; }
bool holefree() const { return this->type == AvoidanceType::FastSafe; }
};
std::vector<AvoidanceTask> avoidance_tasks;
avoidance_tasks.reserve((int(to_build_plate) + int(to_model)) * keys.size() * size_t(AvoidanceType::Count));
for (int iter_idx = 0; iter_idx < 2 * int(keys.size()) * int(AvoidanceType::Count); ++ iter_idx) {
AvoidanceTask task{
AvoidanceType(iter_idx % int(AvoidanceType::Count)),
keys[iter_idx / 6].first, // radius
keys[iter_idx / 6].second, // max_layer
((iter_idx / 3) & 1) != 0 // to_model
};
// Ensure start_layer is at least 1 as if no avoidance was calculated yet getMaxCalculatedLayer() returns -1.
task.start_layer = std::max<LayerIndex>(1, 1 + avoidance_cache(task.type, task.to_model).getMaxCalculatedLayer(task.radius));
if (task.start_layer > task.max_required_layer) {
BOOST_LOG_TRIVIAL(debug) << "Calculation requested for value already calculated?";
continue;
}
if ((task.to_model ? to_model : to_build_plate) &&
(! task.holefree() || task.radius < m_increase_until_radius + m_current_min_xy_dist_delta))
avoidance_tasks.emplace_back(task);
}
throw_on_cancel();
tbb::parallel_for(tbb::blocked_range<size_t>(0, avoidance_tasks.size(), 1),
[this, &avoidance_tasks, &throw_on_cancel](const tbb::blocked_range<size_t> &range) {
for (size_t task_idx = range.begin(); task_idx < range.end(); ++ task_idx) {
const AvoidanceTask &task = avoidance_tasks[task_idx];
assert(! task.holefree() || task.radius < m_increase_until_radius + m_current_min_xy_dist_delta);
if (task.to_model)
// ensuring Placeableareas are calculated
//FIXME pass throw_on_cancel
getPlaceableAreas(task.radius, task.max_required_layer, throw_on_cancel);
// The following loop propagating avoidance regions bottom up is inherently serial.
const bool collision_holefree = (task.slow() || task.holefree()) && task.radius < m_increase_until_radius + m_current_min_xy_dist_delta;
const float max_move = task.slow() ? m_max_move_slow : m_max_move;
// Limiting the offset step so that unioning the shrunk latest_avoidance with the current layer collisions
// will not create gaps in the resulting avoidance region letting a tree support branch tunneling through an object wall.
float move_step = 1.9 * std::max(task.radius, m_current_min_xy_dist);
int move_steps = round_up_divide<int>(max_move, move_step);
assert(move_steps > 0);
float last_move_step = max_move - (move_steps - 1) * move_step;
if (last_move_step < scaled<float>(0.05)) {
assert(move_steps > 1);
if (move_steps > 1) {
// Avoid taking a very short last step, stretch the other steps a bit instead.
move_step = max_move / (-- move_steps);
last_move_step = move_step;
}
}
// minDist as the delta was already added, also avoidance for layer 0 will return the collision.
Polygons latest_avoidance = getAvoidance(task.radius, task.start_layer - 1, task.type, task.to_model, true);
std::vector<std::pair<RadiusLayerPair, Polygons>> data;
data.reserve(task.max_required_layer + 1 - task.start_layer);
for (LayerIndex layer_idx = task.start_layer; layer_idx <= task.max_required_layer; ++ layer_idx) {
// Merge current layer collisions with shrunk last_avoidance.
const Polygons &current_layer_collisions = collision_holefree ? getCollisionHolefree(task.radius, layer_idx) : getCollision(task.radius, layer_idx, true);
// For mildly steep branch angles only one step will be taken.
for (int istep = 0; istep < move_steps; ++ istep)
latest_avoidance = union_(current_layer_collisions,
offset(latest_avoidance,
istep + 1 == move_steps ? - last_move_step : - move_step,
ClipperLib::jtRound, m_min_resolution));
if (task.to_model)
latest_avoidance = diff(latest_avoidance, getPlaceableAreas(task.radius, layer_idx, throw_on_cancel));
latest_avoidance = polygons_simplify(latest_avoidance, m_min_resolution, polygons_strictly_simple);
data.emplace_back(RadiusLayerPair{task.radius, layer_idx}, latest_avoidance);
throw_on_cancel();
}
#ifdef SLIC3R_TREESUPPORTS_PROGRESS
{
std::lock_guard<std::mutex> critical_section(*m_critical_progress);
if (m_precalculated && m_precalculation_progress < TREE_PROGRESS_PRECALC_COLL + TREE_PROGRESS_PRECALC_AVO) {
m_precalculation_progress += to_model ?
0.4 * TREE_PROGRESS_PRECALC_AVO / (keys.size() * 3) :
m_support_rests_on_model ? 0.4 : 1 * TREE_PROGRESS_PRECALC_AVO / (keys.size() * 3);
Progress::messageProgress(Progress::Stage::SUPPORT, m_precalculation_progress * m_progress_multiplier + m_progress_offset, TREE_PROGRESS_TOTAL);
}
}
#endif
avoidance_cache(task.type, task.to_model).insert(std::move(data));
}
});
}
void TreeModelVolumes::calculatePlaceables(const std::vector<RadiusLayerPair> &keys, std::function<void()> throw_on_cancel)
{
tbb::parallel_for(tbb::blocked_range<size_t>(0, keys.size()),
[&, keys](const tbb::blocked_range<size_t>& range) {
for (size_t key_idx = range.begin(); key_idx < range.end(); ++ key_idx)
this->calculatePlaceables(keys[key_idx].first, keys[key_idx].second, throw_on_cancel);
});
}
void TreeModelVolumes::calculatePlaceables(const coord_t radius, const LayerIndex max_required_layer, std::function<void()> throw_on_cancel)
{
LayerIndex start_layer = 1 + m_placeable_areas_cache.getMaxCalculatedLayer(radius);
if (start_layer > max_required_layer) {
BOOST_LOG_TRIVIAL(debug) << "Requested calculation for value already calculated ?";
return;
}
std::vector<Polygons> data(max_required_layer + 1 - start_layer, Polygons{});
if (start_layer == 0)
data[0] = diff(m_machine_border, getCollision(radius, 0, true));
tbb::parallel_for(tbb::blocked_range<LayerIndex>(std::max(1, start_layer), max_required_layer + 1),
[this, &data, radius, start_layer, &throw_on_cancel](const tbb::blocked_range<LayerIndex>& range) {
for (LayerIndex layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
data[layer_idx - start_layer] = offset(
union_ex(getPlaceableAreas(0, layer_idx, throw_on_cancel)),
// As a placeable area is calculated by (collision of the layer below) - (collision of the current layer) and the collision is offset by xy_distance,
// it can happen that a small line is considered a flat area to place something onto, even though it is mostly
// xy_distance that cant support it. Making the area smaller by xy_distance fixes this.
- (radius + m_current_min_xy_dist + m_current_min_xy_dist_delta),
jtMiter, 1.2);
throw_on_cancel();
}
});
#ifdef SLIC3R_TREESUPPORTS_PROGRESS
{
std::lock_guard<std::mutex> critical_section(*m_critical_progress);
if (m_precalculated && m_precalculation_progress < TREE_PROGRESS_PRECALC_COLL + TREE_PROGRESS_PRECALC_AVO) {
m_precalculation_progress += 0.2 * TREE_PROGRESS_PRECALC_AVO / (keys.size());
Progress::messageProgress(Progress::Stage::SUPPORT, m_precalculation_progress * m_progress_multiplier + m_progress_offset, TREE_PROGRESS_TOTAL);
}
}
#endif
m_placeable_areas_cache.insert(std::move(data), start_layer, radius);
}
void TreeModelVolumes::calculateWallRestrictions(const std::vector<RadiusLayerPair> &keys, std::function<void()> throw_on_cancel)
{
// Wall restrictions are mainly important when they represent actual walls that are printed, and not "just" the configured z_distance, because technically valid placement is no excuse for moving through a wall.
// As they exist to prevent accidentially moving though a wall at high speed between layers like thie (x = wall,i = influence area,o= empty space,d = blocked area because of z distance) Assume maximum movement distance is two characters and maximum safe movement distance of one character
/* Potential issue addressed by the wall restrictions: Influence area may lag through a wall
* layer z+1:iiiiiiiiiiioooo
* layer z+0:xxxxxiiiiiiiooo
* layer z-1:ooooixxxxxxxxxx
*/
// The radius for the upper collission has to be 0 as otherwise one may not enter areas that may be forbidden on layer_idx but not one below (c = not an influence area even though it should ):
/*
* layer z+1:xxxxxiiiiiioo
* layer z+0:dddddiiiiiiio
* layer z-1:dddocdddddddd
*/
// Also there can not just the collision of the lower layer be used because if it were:
/*
* layer z+1:dddddiiiiiiiiiio
* layer z+0:xxxxxddddddddddc
* layer z-1:dddddxxxxxxxxxxc
*/
// Or of the upper layer be used because if it were:
/*
* layer z+1:dddddiiiiiiiiiio
* layer z+0:xxxxcddddddddddc
* layer z-1:ddddcxxxxxxxxxxc
*/
// And just offseting with maximum movement distance (and not in multiple steps) could cause:
/*
* layer z: oxiiiiiiiiioo
* layer z-1: ixiiiiiiiiiii
*/
tbb::parallel_for(tbb::blocked_range<size_t>(0, keys.size()),
[&, keys](const tbb::blocked_range<size_t> &range) {
for (size_t key_idx = range.begin(); key_idx < range.end(); ++ key_idx) {
const coord_t radius = keys[key_idx].first;
const LayerIndex max_required_layer = keys[key_idx].second;
const coord_t min_layer_bottom = std::max(1, m_wall_restrictions_cache.getMaxCalculatedLayer(radius));
const size_t buffer_size = max_required_layer + 1 - min_layer_bottom;
std::vector<Polygons> data(buffer_size, Polygons{});
std::vector<Polygons> data_min;
if (m_current_min_xy_dist_delta > 0)
data_min.assign(buffer_size, Polygons{});
tbb::parallel_for(tbb::blocked_range<LayerIndex>(min_layer_bottom, max_required_layer + 1),
[this, &data, &data_min, radius, min_layer_bottom, &throw_on_cancel](const tbb::blocked_range<LayerIndex> &range) {
for (LayerIndex layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
data[layer_idx - min_layer_bottom] = polygons_simplify(
// radius contains m_current_min_xy_dist_delta already if required
intersection(getCollision(0, layer_idx, false), getCollision(radius, layer_idx - 1, true)),
m_min_resolution, polygons_strictly_simple);
if (! data_min.empty())
data_min[layer_idx - min_layer_bottom] =
polygons_simplify(
intersection(getCollision(0, layer_idx, true), getCollision(radius, layer_idx - 1, true)),
m_min_resolution, polygons_strictly_simple);
throw_on_cancel();
}
});
m_wall_restrictions_cache.insert(std::move(data), min_layer_bottom, radius);
if (! data_min.empty())
m_wall_restrictions_cache_min.insert(std::move(data_min), min_layer_bottom, radius);
}
});
}
coord_t TreeModelVolumes::ceilRadius(const coord_t radius) const
{
if (radius == 0)
return 0;
coord_t out = m_radius_0;
if (radius > m_radius_0) {
// generate SUPPORT_TREE_PRE_EXPONENTIAL_STEPS of radiis before starting to exponentially increase them.
coord_t initial_radius_delta = SUPPORT_TREE_EXPONENTIAL_THRESHOLD - m_radius_0;
auto ignore = [this](coord_t r) { return std::binary_search(m_ignorable_radii.begin(), m_ignorable_radii.end(), r); };
if (initial_radius_delta > SUPPORT_TREE_COLLISION_RESOLUTION) {
const int num_steps = round_up_divide(initial_radius_delta, SUPPORT_TREE_EXPONENTIAL_THRESHOLD);
const int stepsize = initial_radius_delta / num_steps;
out += stepsize;
for (auto step = 0; step < num_steps; ++ step) {
if (out >= radius && ! ignore(out))
return out;
out += stepsize;
}
} else
out += SUPPORT_TREE_COLLISION_RESOLUTION;
while (out < radius || ignore(out)) {
assert(out * SUPPORT_TREE_EXPONENTIAL_FACTOR > out + SUPPORT_TREE_COLLISION_RESOLUTION);
out = out * SUPPORT_TREE_EXPONENTIAL_FACTOR;
}
}
return out;
}
void TreeModelVolumes::RadiusLayerPolygonCache::allocate_layers(size_t num_layers)
{
if (num_layers > m_data.size()) {
if (num_layers > m_data.capacity())
reserve_power_of_2(m_data, num_layers);
m_data.resize(num_layers, {});
}
}
// For debugging purposes, sorted by layer index, then by radius.
std::vector<std::pair<TreeModelVolumes::RadiusLayerPair, std::reference_wrapper<const Polygons>>> TreeModelVolumes::RadiusLayerPolygonCache::sorted() const
{
std::vector<std::pair<RadiusLayerPair, std::reference_wrapper<const Polygons>>> out;
for (auto &layer : m_data) {
auto layer_idx = LayerIndex(&layer - m_data.data());
for (auto &radius_polygons : layer)
out.emplace_back(std::make_pair(radius_polygons.first, layer_idx), radius_polygons.second);
}
assert(std::is_sorted(out.begin(), out.end(), [](auto &l, auto &r){ return l.first.second < r.first.second || (l.first.second == r.first.second) && l.first.first < r.first.first; }));
return out;
}
} // namespace Slic3r::FFFTreeSupport

View file

@ -0,0 +1,558 @@
///|/ Copyright (c) Prusa Research 2022 - 2023 Vojtěch Bubník @bubnikv, Oleksandra Iushchenko @YuSanka
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
// Tree supports by Thomas Rahm, losely based on Tree Supports by CuraEngine.
// Original source of Thomas Rahm's tree supports:
// https://github.com/ThomasRahm/CuraEngine
//
// Original CuraEngine copyright:
// Copyright (c) 2021 Ultimaker B.V.
// CuraEngine is released under the terms of the AGPLv3 or higher.
#ifndef slic3r_TreeModelVolumes_hpp
#define slic3r_TreeModelVolumes_hpp
#include <mutex>
#include <unordered_map>
#include <boost/functional/hash.hpp>
#include "TreeSupportCommon.hpp"
#include "../Point.hpp"
#include "../Polygon.hpp"
#include "../PrintConfig.hpp"
namespace Slic3r
{
class BuildVolume;
class PrintObject;
namespace FFFTreeSupport
{
static constexpr const double SUPPORT_TREE_EXPONENTIAL_FACTOR = 1.5;
static constexpr const coord_t SUPPORT_TREE_EXPONENTIAL_THRESHOLD = scaled<coord_t>(1. * SUPPORT_TREE_EXPONENTIAL_FACTOR);
static constexpr const coord_t SUPPORT_TREE_COLLISION_RESOLUTION = scaled<coord_t>(0.5);
static constexpr const bool SUPPORT_TREE_AVOID_SUPPORT_BLOCKER = true;
class TreeModelVolumes
{
public:
TreeModelVolumes() = default;
explicit TreeModelVolumes(const PrintObject &print_object, const BuildVolume &build_volume,
coord_t max_move, coord_t max_move_slow, size_t current_mesh_idx,
#ifdef SLIC3R_TREESUPPORTS_PROGRESS
double progress_multiplier,
double progress_offset,
#endif // SLIC3R_TREESUPPORTS_PROGRESS
const std::vector<Polygons> &additional_excluded_areas = {});
TreeModelVolumes(TreeModelVolumes&&) = default;
TreeModelVolumes& operator=(TreeModelVolumes&&) = default;
TreeModelVolumes(const TreeModelVolumes&) = delete;
TreeModelVolumes& operator=(const TreeModelVolumes&) = delete;
void clear() {
this->clear_all_but_object_collision();
m_collision_cache.clear();
m_placeable_areas_cache.clear();
}
void clear_all_but_object_collision() {
//m_collision_cache.clear_all_but_radius0();
m_collision_cache_holefree.clear();
m_avoidance_cache.clear();
m_avoidance_cache_slow.clear();
m_avoidance_cache_to_model.clear();
m_avoidance_cache_to_model_slow.clear();
m_placeable_areas_cache.clear_all_but_radius0();
m_avoidance_cache_holefree.clear();
m_avoidance_cache_holefree_to_model.clear();
m_wall_restrictions_cache.clear();
m_wall_restrictions_cache_min.clear();
}
enum class AvoidanceType : int8_t
{
Slow,
FastSafe,
Fast,
Count
};
/*!
* \brief Precalculate avoidances and collisions up to max_layer.
*
* Knowledge about branch angle is used to only calculate avoidances and collisions that may actually be needed.
* Not calling precalculate() will cause the class to lazily calculate avoidances and collisions as needed, which will be a lot slower on systems with more then one or two cores!
*/
void precalculate(const PrintObject& print_object, const coord_t max_layer, std::function<void()> throw_on_cancel);
/*!
* \brief Provides the areas that have to be avoided by the tree's branches to prevent collision with the model on this layer.
*
* The result is a 2D area that would cause nodes of radius \p radius to
* collide with the model.
*
* \param radius The radius of the node of interest
* \param layer_idx The layer of interest
* \param min_xy_dist Is the minimum xy distance used.
* \return Polygons object
*/
const Polygons& getCollision(const coord_t radius, LayerIndex layer_idx, bool min_xy_dist) const;
// Get a collision area at a given layer for a radius that is a lower or equial to the key radius.
// It is expected that the collision area is precalculated for a given layer at least for the radius zero.
// Used for pushing tree supports away from object during the final Organic optimization step.
std::optional<std::pair<coord_t, std::reference_wrapper<const Polygons>>> get_collision_lower_bound_area(LayerIndex layer_id, coord_t max_radius) const;
/*!
* \brief Provides the areas that have to be avoided by the tree's branches
* in order to reach the build plate.
*
* The result is a 2D area that would cause nodes of radius \p radius to
* collide with the model or be unable to reach the build platform.
*
* The input collision areas are inset by the maximum move distance and
* propagated upwards.
*
* \param radius The radius of the node of interest
* \param layer_idx The layer of interest
* \param type Is the propagation with the maximum move distance slow required.
* \param to_model Does the avoidance allow good connections with the model.
* \param min_xy_dist is the minimum xy distance used.
* \return Polygons object
*/
const Polygons& getAvoidance(coord_t radius, LayerIndex layer_idx, AvoidanceType type, bool to_model, bool min_xy_dist) const;
/*!
* \brief Provides the area represents all areas on the model where the branch does completely fit on the given layer.
* \param radius The radius of the node of interest
* \param layer_idx The layer of interest
* \return Polygons object
*/
const Polygons& getPlaceableAreas(coord_t radius, LayerIndex layer_idx, std::function<void()> throw_on_cancel) const;
/*!
* \brief Provides the area that represents the walls, as in the printed area, of the model. This is an abstract representation not equal with the outline. See calculateWallRestrictions for better description.
* \param radius The radius of the node of interest.
* \param layer_idx The layer of interest.
* \param min_xy_dist is the minimum xy distance used.
* \return Polygons object
*/
const Polygons& getWallRestriction(coord_t radius, LayerIndex layer_idx, bool min_xy_dist) const;
/*!
* \brief Round \p radius upwards to either a multiple of m_radius_sample_resolution or a exponentially increasing value
*
* It also adds the difference between the minimum xy distance and the regular one.
*
* \param radius The radius of the node of interest
* \param min_xy_dist is the minimum xy distance used.
* \return The rounded radius
*/
coord_t ceilRadius(const coord_t radius, const bool min_xy_dist) const {
assert(radius >= 0);
return min_xy_dist ?
this->ceilRadius(radius) :
// special case as if a radius 0 is requested it could be to ensure correct xy distance. As such it is beneficial if the collision is as close to the configured values as possible.
radius > 0 ? this->ceilRadius(radius + m_current_min_xy_dist_delta) : m_current_min_xy_dist_delta;
}
/*!
* \brief Round \p radius upwards to the maximum that would still round up to the same value as the provided one.
*
* \param radius The radius of the node of interest
* \param min_xy_dist is the minimum xy distance used.
* \return The maximum radius, resulting in the same rounding.
*/
coord_t getRadiusNextCeil(coord_t radius, bool min_xy_dist) const {
assert(radius > 0);
return min_xy_dist ?
this->ceilRadius(radius) :
this->ceilRadius(radius + m_current_min_xy_dist_delta) - m_current_min_xy_dist_delta;
}
private:
// Caching polygons for a range of layers.
class LayerPolygonCache {
public:
void allocate(LayerIndex aidx_begin, LayerIndex aidx_end) {
m_idx_begin = aidx_begin;
m_idx_end = aidx_end;
m_polygons.assign(aidx_end - aidx_begin, {});
}
LayerIndex begin() const { return m_idx_begin; }
LayerIndex end() const { return m_idx_end; }
size_t size() const { return m_polygons.size(); }
bool has(LayerIndex idx) const { return idx >= m_idx_begin && idx < m_idx_end; }
Polygons& operator[](LayerIndex idx) { assert(idx >= m_idx_begin && idx < m_idx_end); return m_polygons[idx - m_idx_begin]; }
std::vector<Polygons>& polygons_mutable() { return m_polygons; }
private:
std::vector<Polygons> m_polygons;
LayerIndex m_idx_begin;
LayerIndex m_idx_end;
};
/*!
* \brief Convenience typedef for the keys to the caches
*/
using RadiusLayerPair = std::pair<coord_t, LayerIndex>;
class RadiusLayerPolygonCache {
// Map from radius to Polygons. Cache of one layer collision regions.
using LayerData = std::map<coord_t, Polygons>;
// Vector of layers, at each layer map of radius to Polygons.
// Reference to Polygons returned shall be stable to insertion.
using Layers = std::vector<LayerData>;
public:
RadiusLayerPolygonCache() = default;
RadiusLayerPolygonCache(RadiusLayerPolygonCache &&rhs) : m_data(std::move(rhs.m_data)) {}
RadiusLayerPolygonCache& operator=(RadiusLayerPolygonCache &&rhs) { m_data = std::move(rhs.m_data); return *this; }
RadiusLayerPolygonCache(const RadiusLayerPolygonCache&) = delete;
RadiusLayerPolygonCache& operator=(const RadiusLayerPolygonCache&) = delete;
void insert(std::vector<std::pair<RadiusLayerPair, Polygons>> &&in) {
std::lock_guard<std::mutex> guard(m_mutex);
for (auto &d : in)
this->get_allocate_layer_data(d.first.second).emplace(d.first.first, std::move(d.second));
}
// by layer
void insert(std::vector<std::pair<coord_t, Polygons>> &&in, coord_t radius) {
std::lock_guard<std::mutex> guard(m_mutex);
for (auto &d : in)
this->get_allocate_layer_data(d.first).emplace(radius, std::move(d.second));
}
void insert(std::vector<Polygons> &&in, coord_t first_layer_idx, coord_t radius) {
std::lock_guard<std::mutex> guard(m_mutex);
allocate_layers(first_layer_idx + in.size());
for (auto &d : in)
m_data[first_layer_idx ++].emplace(radius, std::move(d));
}
void insert(LayerPolygonCache &&in, coord_t radius) {
std::lock_guard<std::mutex> guard(m_mutex);
LayerIndex i = in.begin();
allocate_layers(i + LayerIndex(in.size()));
for (auto &d : in.polygons_mutable())
m_data[i ++].emplace(radius, std::move(d));
}
/*!
* \brief Checks a cache for a given RadiusLayerPair and returns it if it is found
* \param key RadiusLayerPair of the requested areas. The radius will be calculated up to the provided layer.
* \return A wrapped optional reference of the requested area (if it was found, an empty optional if nothing was found)
*/
std::optional<std::reference_wrapper<const Polygons>> getArea(const TreeModelVolumes::RadiusLayerPair &key) const {
std::lock_guard<std::mutex> guard(m_mutex);
if (key.second >= LayerIndex(m_data.size()))
return std::optional<std::reference_wrapper<const Polygons>>{};
const auto &layer = m_data[key.second];
auto it = layer.find(key.first);
return it == layer.end() ?
std::optional<std::reference_wrapper<const Polygons>>{} : std::optional<std::reference_wrapper<const Polygons>>{ it->second };
}
// Get a collision area at a given layer for a radius that is a lower or equial to the key radius.
std::optional<std::pair<coord_t, std::reference_wrapper<const Polygons>>> get_lower_bound_area(const TreeModelVolumes::RadiusLayerPair &key) const {
std::lock_guard<std::mutex> guard(m_mutex);
if (key.second >= LayerIndex(m_data.size()))
return {};
const auto &layer = m_data[key.second];
if (layer.empty())
return {};
auto it = layer.lower_bound(key.first);
if (it == layer.end() || it->first != key.first) {
if (it == layer.begin())
return {};
-- it;
}
return std::make_pair(it->first, std::reference_wrapper<const Polygons>(it->second));
}
/*!
* \brief Get the highest already calculated layer in the cache.
* \param radius The radius for which the highest already calculated layer has to be found.
* \param map The cache in which the lookup is performed.
*
* \return A wrapped optional reference of the requested area (if it was found, an empty optional if nothing was found)
*/
LayerIndex getMaxCalculatedLayer(coord_t radius) const {
std::lock_guard<std::mutex> guard(m_mutex);
auto layer_idx = LayerIndex(m_data.size()) - 1;
for (; layer_idx > 0; -- layer_idx)
if (const auto &layer = m_data[layer_idx]; layer.find(radius) != layer.end())
break;
// The placeable on model areas do not exist on layer 0, as there can not be model below it. As such it may be possible that layer 1 is available, but layer 0 does not exist.
return layer_idx == 0 ? -1 : layer_idx;
}
// For debugging purposes, sorted by layer index, then by radius.
[[nodiscard]] std::vector<std::pair<RadiusLayerPair, std::reference_wrapper<const Polygons>>> sorted() const;
void clear() { m_data.clear(); }
void clear_all_but_radius0() {
for (LayerData &l : m_data) {
auto begin = l.begin();
auto end = l.end();
if (begin != end && ++ begin != end)
l.erase(begin, end);
}
}
private:
LayerData& get_allocate_layer_data(LayerIndex layer_idx) {
allocate_layers(layer_idx + 1);
return m_data[layer_idx];
}
void allocate_layers(size_t num_layers);
Layers m_data;
mutable std::mutex m_mutex;
};
/*!
* \brief Provides the areas that have to be avoided by the tree's branches to prevent collision with the model on this layer. Holes are removed.
*
* The result is a 2D area that would cause nodes of given radius to
* collide with the model or be inside a hole.
* A Hole is defined as an area, in which a branch with m_increase_until_radius radius would collide with the wall.
* minimum xy distance is always used.
* \param radius The radius of the node of interest
* \param layer_idx The layer of interest
* \param min_xy_dist Is the minimum xy distance used.
* \return Polygons object
*/
const Polygons& getCollisionHolefree(coord_t radius, LayerIndex layer_idx) const;
/*!
* \brief Round \p radius upwards to either a multiple of m_radius_sample_resolution or a exponentially increasing value
*
* \param radius The radius of the node of interest
*/
coord_t ceilRadius(const coord_t radius) const;
/*!
* \brief Creates the areas that have to be avoided by the tree's branches to prevent collision with the model on this layer.
*
* The result is a 2D area that would cause nodes of given radius to
* collide with the model. Result is saved in the cache.
* \param keys RadiusLayerPairs of all requested areas. Every radius will be calculated up to the provided layer.
*/
void calculateCollision(const std::vector<RadiusLayerPair> &keys, std::function<void()> throw_on_cancel);
void calculateCollision(const coord_t radius, const LayerIndex max_layer_idx, std::function<void()> throw_on_cancel);
/*!
* \brief Creates the areas that have to be avoided by the tree's branches to prevent collision with the model on this layer. Holes are removed.
*
* The result is a 2D area that would cause nodes of given radius to
* collide with the model or be inside a hole. Result is saved in the cache.
* A Hole is defined as an area, in which a branch with m_increase_until_radius radius would collide with the wall.
* \param keys RadiusLayerPairs of all requested areas. Every radius will be calculated up to the provided layer.
*/
void calculateCollisionHolefree(const std::vector<RadiusLayerPair> &keys, std::function<void()> throw_on_cancel);
/*!
* \brief Creates the areas that have to be avoided by the tree's branches to prevent collision with the model on this layer. Holes are removed.
*
* The result is a 2D area that would cause nodes of given radius to
* collide with the model or be inside a hole. Result is saved in the cache.
* A Hole is defined as an area, in which a branch with m_increase_until_radius radius would collide with the wall.
* \param key RadiusLayerPairs the requested areas. The radius will be calculated up to the provided layer.
*/
void calculateCollisionHolefree(RadiusLayerPair key)
{
calculateCollisionHolefree(std::vector<RadiusLayerPair>{ RadiusLayerPair(key) }, []{});
}
/*!
* \brief Creates the areas that have to be avoided by the tree's branches to prevent collision with the model.
*
* The result is a 2D area that would cause nodes of radius \p radius to
* collide with the model. Result is saved in the cache.
* \param keys RadiusLayerPairs of all requested areas. Every radius will be calculated up to the provided layer.
*/
void calculateAvoidance(const std::vector<RadiusLayerPair> &keys, bool to_build_plate, bool to_model, std::function<void()> throw_on_cancel);
/*!
* \brief Creates the areas that have to be avoided by the tree's branches to prevent collision with the model.
*
* The result is a 2D area that would cause nodes of radius \p radius to
* collide with the model. Result is saved in the cache.
* \param key RadiusLayerPair of the requested areas. It will be calculated up to the provided layer.
*/
void calculateAvoidance(RadiusLayerPair key, bool to_build_plate, bool to_model)
{
calculateAvoidance(std::vector<RadiusLayerPair>{ RadiusLayerPair(key) }, to_build_plate, to_model, []{});
}
/*!
* \brief Creates the areas where a branch of a given radius can be place on the model.
* Result is saved in the cache.
* \param key RadiusLayerPair of the requested areas. It will be calculated up to the provided layer.
*/
void calculatePlaceables(const coord_t radius, const LayerIndex max_required_layer, std::function<void()> throw_on_cancel);
/*!
* \brief Creates the areas where a branch of a given radius can be placed on the model.
* Result is saved in the cache.
* \param keys RadiusLayerPair of the requested areas. The radius will be calculated up to the provided layer.
*/
void calculatePlaceables(const std::vector<RadiusLayerPair> &keys, std::function<void()> throw_on_cancel);
/*!
* \brief Creates the areas that can not be passed when expanding an area downwards. As such these areas are an somewhat abstract representation of a wall (as in a printed object).
*
* These areas are at least xy_min_dist wide. When calculating it is always assumed that every wall is printed on top of another (as in has an overlap with the wall a layer below). Result is saved in the corresponding cache.
*
* \param keys RadiusLayerPairs of all requested areas. Every radius will be calculated up to the provided layer.
*/
void calculateWallRestrictions(const std::vector<RadiusLayerPair> &keys, std::function<void()> throw_on_cancel);
/*!
* \brief Creates the areas that can not be passed when expanding an area downwards. As such these areas are an somewhat abstract representation of a wall (as in a printed object).
* These areas are at least xy_min_dist wide. When calculating it is always assumed that every wall is printed on top of another (as in has an overlap with the wall a layer below). Result is saved in the corresponding cache.
* \param key RadiusLayerPair of the requested area. It well be will be calculated up to the provided layer.
*/
void calculateWallRestrictions(RadiusLayerPair key)
{
calculateWallRestrictions(std::vector<RadiusLayerPair>{ RadiusLayerPair(key) }, []{});
}
/*!
* \brief The maximum distance that the center point of a tree branch may move in consecutive layers if it has to avoid the model.
*/
coord_t m_max_move;
/*!
* \brief The maximum distance that the centre-point of a tree branch may
* move in consecutive layers if it does not have to avoid the model
*/
coord_t m_max_move_slow;
/*!
* \brief The smallest maximum resolution for simplify
*/
coord_t m_min_resolution;
bool m_precalculated = false;
/*!
* \brief The index to access the outline corresponding with the currently processing mesh
*/
size_t m_current_outline_idx;
/*!
* \brief The minimum required clearance between the model and the tree branches
*/
coord_t m_current_min_xy_dist;
/*!
* \brief The difference between the minimum required clearance between the model and the tree branches and the regular one.
*/
coord_t m_current_min_xy_dist_delta;
/*!
* \brief Does at least one mesh allow support to rest on a model.
*/
bool m_support_rests_on_model;
#ifdef SLIC3R_TREESUPPORTS_PROGRESS
/*!
* \brief The progress of the precalculate function for communicating it to the progress bar.
*/
coord_t m_precalculation_progress = 0;
/*!
* \brief The progress multiplier of all values added progress bar.
* Required for the progress bar the behave as expected when areas have to be calculated multiple times
*/
double m_progress_multiplier;
/*!
* \brief The progress offset added to all values communicated to the progress bar.
* Required for the progress bar the behave as expected when areas have to be calculated multiple times
*/
double m_progress_offset;
#endif // SLIC3R_TREESUPPORTS_PROGRESS
/*!
* \brief Increase radius in the resulting drawn branches, even if the avoidance does not allow it. Will be cut later to still fit.
*/
coord_t m_increase_until_radius;
/*!
* \brief Polygons representing the limits of the printable area of the
* machine
*/
Polygons m_machine_border;
/*!
* \brief Storage for layer outlines and the corresponding settings of the meshes grouped by meshes with identical setting.
*/
std::vector<std::pair<TreeSupportMeshGroupSettings, std::vector<Polygons>>> m_layer_outlines;
/*!
* \brief Storage for areas that should be avoided, like support blocker or previous generated trees.
*/
std::vector<Polygons> m_anti_overhang;
/*!
* \brief Radii that can be ignored by ceilRadius as they will never be requested, sorted.
*/
std::vector<coord_t> m_ignorable_radii;
/*!
* \brief Smallest radius a branch can have. This is the radius of a SupportElement with DTT=0.
*/
coord_t m_radius_0;
// Z heights of the raft layers (additional layers below the object, last raft layer aligned with the bottom of the first object layer).
std::vector<double> m_raft_layers;
/*!
* \brief Caches for the collision, avoidance and areas on the model where support can be placed safely
* at given radius and layer indices.
*/
RadiusLayerPolygonCache m_collision_cache;
RadiusLayerPolygonCache m_collision_cache_holefree;
RadiusLayerPolygonCache m_avoidance_cache;
RadiusLayerPolygonCache m_avoidance_cache_slow;
RadiusLayerPolygonCache m_avoidance_cache_to_model;
RadiusLayerPolygonCache m_avoidance_cache_to_model_slow;
RadiusLayerPolygonCache m_placeable_areas_cache;
/*!
* \brief Caches to avoid holes smaller than the radius until which the radius is always increased, as they are free of holes.
* Also called safe avoidances, as they are safe regarding not running into holes.
*/
RadiusLayerPolygonCache m_avoidance_cache_holefree;
RadiusLayerPolygonCache m_avoidance_cache_holefree_to_model;
RadiusLayerPolygonCache& avoidance_cache(const AvoidanceType type, const bool to_model) {
if (to_model) {
switch (type) {
case AvoidanceType::Fast: return m_avoidance_cache_to_model;
case AvoidanceType::Slow: return m_avoidance_cache_to_model_slow;
case AvoidanceType::Count: assert(false);
case AvoidanceType::FastSafe: return m_avoidance_cache_holefree_to_model;
}
} else {
switch (type) {
case AvoidanceType::Fast: return m_avoidance_cache;
case AvoidanceType::Slow: return m_avoidance_cache_slow;
case AvoidanceType::Count: assert(false);
case AvoidanceType::FastSafe: return m_avoidance_cache_holefree;
}
}
assert(false);
return m_avoidance_cache;
}
const RadiusLayerPolygonCache& avoidance_cache(const AvoidanceType type, const bool to_model) const {
return const_cast<TreeModelVolumes*>(this)->avoidance_cache(type, to_model);
}
/*!
* \brief Caches to represent walls not allowed to be passed over.
*/
RadiusLayerPolygonCache m_wall_restrictions_cache;
// A different cache for min_xy_dist as the maximal safe distance an influence area can be increased(guaranteed overlap of two walls in consecutive layer)
// is much smaller when min_xy_dist is used. This causes the area of the wall restriction to be thinner and as such just using the min_xy_dist wall
// restriction would be slower.
RadiusLayerPolygonCache m_wall_restrictions_cache_min;
#ifdef SLIC3R_TREESUPPORTS_PROGRESS
std::unique_ptr<std::mutex> m_critical_progress { std::make_unique<std::mutex>() };
#endif // SLIC3R_TREESUPPORTS_PROGRESS
};
} // namespace FFFTreeSupport
} // namespace Slic3r
#endif //slic3r_TreeModelVolumes_hpp

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,303 @@
///|/ Copyright (c) Prusa Research 2022 - 2023 Vojtěch Bubník @bubnikv
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
// Tree supports by Thomas Rahm, losely based on Tree Supports by CuraEngine.
// Original source of Thomas Rahm's tree supports:
// https://github.com/ThomasRahm/CuraEngine
//
// Original CuraEngine copyright:
// Copyright (c) 2021 Ultimaker B.V.
// CuraEngine is released under the terms of the AGPLv3 or higher.
#ifndef slic3r_TreeSupport_hpp
#define slic3r_TreeSupport_hpp
#include "SupportLayer.hpp"
#include "TreeModelVolumes.hpp"
#include "TreeSupportCommon.hpp"
#include "../BoundingBox.hpp"
#include "../Point.hpp"
#include "../Utils.hpp"
#include <boost/container/small_vector.hpp>
// #define TREE_SUPPORT_SHOW_ERRORS
#ifdef SLIC3R_TREESUPPORTS_PROGRESS
// The various stages of the process can be weighted differently in the progress bar.
// These weights are obtained experimentally using a small sample size. Sensible weights can differ drastically based on the assumed default settings and model.
#define TREE_PROGRESS_TOTAL 10000
#define TREE_PROGRESS_PRECALC_COLL TREE_PROGRESS_TOTAL * 0.1
#define TREE_PROGRESS_PRECALC_AVO TREE_PROGRESS_TOTAL * 0.4
#define TREE_PROGRESS_GENERATE_NODES TREE_PROGRESS_TOTAL * 0.1
#define TREE_PROGRESS_AREA_CALC TREE_PROGRESS_TOTAL * 0.3
#define TREE_PROGRESS_DRAW_AREAS TREE_PROGRESS_TOTAL * 0.1
#define TREE_PROGRESS_GENERATE_BRANCH_AREAS TREE_PROGRESS_DRAW_AREAS / 3
#define TREE_PROGRESS_SMOOTH_BRANCH_AREAS TREE_PROGRESS_DRAW_AREAS / 3
#define TREE_PROGRESS_FINALIZE_BRANCH_AREAS TREE_PROGRESS_DRAW_AREAS / 3
#endif // SLIC3R_TREESUPPORTS_PROGRESS
namespace Slic3r
{
// Forward declarations
class Print;
class PrintObject;
struct SlicingParameters;
namespace FFFTreeSupport
{
// The number of vertices in each circle.
static constexpr const size_t SUPPORT_TREE_CIRCLE_RESOLUTION = 25;
struct AreaIncreaseSettings
{
AreaIncreaseSettings(
TreeModelVolumes::AvoidanceType type = TreeModelVolumes::AvoidanceType::Fast, coord_t increase_speed = 0,
bool increase_radius = false, bool no_error = false, bool use_min_distance = false, bool move = false) :
increase_speed{ increase_speed }, type{ type }, increase_radius{ increase_radius }, no_error{ no_error }, use_min_distance{ use_min_distance }, move{ move } {}
coord_t increase_speed;
// Packing for smaller memory footprint of SupportElementState && SupportElementMerging
TreeModelVolumes::AvoidanceType type;
bool increase_radius : 1;
bool no_error : 1;
bool use_min_distance : 1;
bool move : 1;
bool operator==(const AreaIncreaseSettings& other) const
{
return type == other.type &&
increase_speed == other.increase_speed &&
increase_radius == other.increase_radius &&
no_error == other.no_error &&
use_min_distance == other.use_min_distance &&
move == other.move;
}
};
#define TREE_SUPPORTS_TRACK_LOST
// C++17 does not support in place initializers of bit values, thus a constructor zeroing the bits is provided.
struct SupportElementStateBits {
SupportElementStateBits() :
to_buildplate(false),
to_model_gracious(false),
use_min_xy_dist(false),
supports_roof(false),
can_use_safe_radius(false),
skip_ovalisation(false),
#ifdef TREE_SUPPORTS_TRACK_LOST
lost(false),
verylost(false),
#endif // TREE_SUPPORTS_TRACK_LOST
deleted(false),
marked(false)
{}
/*!
* \brief The element trys to reach the buildplate
*/
bool to_buildplate : 1;
/*!
* \brief Will the branch be able to rest completely on a flat surface, be it buildplate or model ?
*/
bool to_model_gracious : 1;
/*!
* \brief Whether the min_xy_distance can be used to get avoidance or similar. Will only be true if support_xy_overrides_z=Z overrides X/Y.
*/
bool use_min_xy_dist : 1;
/*!
* \brief True if this Element or any parent (element above) provides support to a support roof.
*/
bool supports_roof : 1;
/*!
* \brief An influence area is considered safe when it can use the holefree avoidance <=> It will not have to encounter holes on its way downward.
*/
bool can_use_safe_radius : 1;
/*!
* \brief Skip the ovalisation to parent and children when generating the final circles.
*/
bool skip_ovalisation : 1;
#ifdef TREE_SUPPORTS_TRACK_LOST
// Likely a lost branch, debugging information.
bool lost : 1;
bool verylost : 1;
#endif // TREE_SUPPORTS_TRACK_LOST
// Not valid anymore, to be deleted.
bool deleted : 1;
// General purpose flag marking a visited element.
bool marked : 1;
};
struct SupportElementState : public SupportElementStateBits
{
/*!
* \brief The layer this support elements wants reach
*/
LayerIndex target_height;
/*!
* \brief The position this support elements wants to support on layer=target_height
*/
Point target_position;
/*!
* \brief The next position this support elements wants to reach. NOTE: This is mainly a suggestion regarding direction inside the influence area.
*/
Point next_position;
/*!
* \brief The next height this support elements wants to reach
*/
LayerIndex layer_idx;
/*!
* \brief The Effective distance to top of this element regarding radius increases and collision calculations.
*/
uint32_t effective_radius_height;
/*!
* \brief The amount of layers this element is below the topmost layer of this branch.
*/
uint32_t distance_to_top;
/*!
* \brief The resulting center point around which a circle will be drawn later.
* Will be set by setPointsOnAreas
*/
Point result_on_layer { std::numeric_limits<coord_t>::max(), std::numeric_limits<coord_t>::max() };
bool result_on_layer_is_set() const { return this->result_on_layer != Point{ std::numeric_limits<coord_t>::max(), std::numeric_limits<coord_t>::max() }; }
void result_on_layer_reset() { this->result_on_layer = Point{ std::numeric_limits<coord_t>::max(), std::numeric_limits<coord_t>::max() }; }
/*!
* \brief The amount of extra radius we got from merging branches that could have reached the buildplate, but merged with ones that can not.
*/
coord_t increased_to_model_radius; // how much to model we increased only relevant for merging
/*!
* \brief Counter about the times the elephant foot was increased. Can be fractions for merge reasons.
*/
double elephant_foot_increases;
/*!
* \brief The element tries to not move until this dtt is reached, is set to 0 if the element had to move.
*/
uint32_t dont_move_until;
/*!
* \brief Settings used to increase the influence area to its current state.
*/
AreaIncreaseSettings last_area_increase;
/*!
* \brief Amount of roof layers that were not yet added, because the branch needed to move.
*/
uint32_t missing_roof_layers;
// called by increase_single_area() and increaseAreas()
[[nodiscard]] static SupportElementState propagate_down(const SupportElementState &src)
{
SupportElementState dst{ src };
++ dst.distance_to_top;
-- dst.layer_idx;
// set to invalid as we are a new node on a new layer
dst.result_on_layer_reset();
dst.skip_ovalisation = false;
return dst;
}
[[nodiscard]] bool locked() const { return this->distance_to_top < this->dont_move_until; }
};
/*!
* \brief Get the Distance to top regarding the real radius this part will have. This is different from distance_to_top, which is can be used to calculate the top most layer of the branch.
* \param elem[in] The SupportElement one wants to know the effectiveDTT
* \return The Effective DTT.
*/
[[nodiscard]] inline size_t getEffectiveDTT(const TreeSupportSettings &settings, const SupportElementState &elem)
{
return elem.effective_radius_height < settings.increase_radius_until_layer ?
(elem.distance_to_top < settings.increase_radius_until_layer ? elem.distance_to_top : settings.increase_radius_until_layer) :
elem.effective_radius_height;
}
/*!
* \brief Get the Radius, that this element will have.
* \param elem[in] The Element.
* \return The radius the element has.
*/
[[nodiscard]] inline coord_t support_element_radius(const TreeSupportSettings &settings, const SupportElementState &elem)
{
return settings.getRadius(getEffectiveDTT(settings, elem), elem.elephant_foot_increases);
}
/*!
* \brief Get the collision Radius of this Element. This can be smaller then the actual radius, as the drawAreas will cut off areas that may collide with the model.
* \param elem[in] The Element.
* \return The collision radius the element has.
*/
[[nodiscard]] inline coord_t support_element_collision_radius(const TreeSupportSettings &settings, const SupportElementState &elem)
{
return settings.getRadius(elem.effective_radius_height, elem.elephant_foot_increases);
}
struct SupportElement
{
using ParentIndices =
#ifdef NDEBUG
// To reduce memory allocation in release mode.
boost::container::small_vector<int32_t, 4>;
#else // NDEBUG
// To ease debugging.
std::vector<int32_t>;
#endif // NDEBUG
// SupportElement(const SupportElementState &state) : SupportElementState(state) {}
SupportElement(const SupportElementState &state, Polygons &&influence_area) : state(state), influence_area(std::move(influence_area)) {}
SupportElement(const SupportElementState &state, ParentIndices &&parents, Polygons &&influence_area) :
state(state), parents(std::move(parents)), influence_area(std::move(influence_area)) {}
SupportElementState state;
/*!
* \brief All elements in the layer above the current one that are supported by this element
*/
ParentIndices parents;
/*!
* \brief The resulting influence area.
* Will only be set in the results of createLayerPathing, and will be nullptr inside!
*/
Polygons influence_area;
};
using SupportElements = std::deque<SupportElement>;
[[nodiscard]] inline coord_t support_element_radius(const TreeSupportSettings &settings, const SupportElement &elem)
{
return support_element_radius(settings, elem.state);
}
[[nodiscard]] inline coord_t support_element_collision_radius(const TreeSupportSettings &settings, const SupportElement &elem)
{
return support_element_collision_radius(settings, elem.state);
}
} // namespace FFFTreeSupport
void fff_tree_support_generate(PrintObject &print_object, std::function<void()> throw_on_cancel = []{});
} // namespace Slic3r
#endif /* slic3r_TreeSupport_hpp */

View file

@ -0,0 +1,196 @@
///|/ Copyright (c) Prusa Research 2023 Vojtěch Bubník @bubnikv
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
// Tree supports by Thomas Rahm, losely based on Tree Supports by CuraEngine.
// Original source of Thomas Rahm's tree supports:
// https://github.com/ThomasRahm/CuraEngine
//
// Original CuraEngine copyright:
// Copyright (c) 2021 Ultimaker B.V.
// CuraEngine is released under the terms of the AGPLv3 or higher.
#include "TreeSupportCommon.hpp"
namespace Slic3r::FFFTreeSupport {
TreeSupportMeshGroupSettings::TreeSupportMeshGroupSettings(const PrintObject &print_object)
{
const PrintConfig &print_config = print_object.print()->config();
const PrintObjectConfig &config = print_object.config();
const SlicingParameters &slicing_params = print_object.slicing_parameters();
// const std::vector<unsigned int> printing_extruders = print_object.object_extruders();
// Support must be enabled and set to Tree style.
assert(config.support_material || config.support_material_enforce_layers > 0);
assert(config.support_material_style == smsTree || config.support_material_style == smsOrganic);
// Calculate maximum external perimeter width over all printing regions, taking into account the default layer height.
coordf_t external_perimeter_width = 0.;
for (size_t region_id = 0; region_id < print_object.num_printing_regions(); ++ region_id) {
const PrintRegion &region = print_object.printing_region(region_id);
external_perimeter_width = std::max<coordf_t>(external_perimeter_width, region.flow(print_object, frExternalPerimeter, config.layer_height).width());
}
this->layer_height = scaled<coord_t>(config.layer_height.value);
this->resolution = scaled<coord_t>(print_config.resolution.value);
// Arache feature
this->min_feature_size = scaled<coord_t>(config.min_feature_size.value);
// +1 makes the threshold inclusive
this->support_angle = 0.5 * M_PI - std::clamp<double>((config.support_threshold_angle + 1) * M_PI / 180., 0., 0.5 * M_PI);
this->support_line_width = support_material_flow(&print_object, config.layer_height).scaled_width();
this->support_roof_line_width = support_material_interface_flow(&print_object, config.layer_height).scaled_width();
//FIXME add it to SlicingParameters and reuse in both tree and normal supports?
this->support_bottom_enable = config.support_interface_top_layers.value > 0 && config.support_interface_bottom_layers.value != 0;
this->support_bottom_height = this->support_bottom_enable ?
(config.support_interface_bottom_layers.value > 0 ?
config.support_interface_bottom_layers.value :
config.support_interface_top_layers.value) * this->layer_height :
0;
this->support_material_buildplate_only = config.support_on_build_plate_only;
this->support_xy_distance = scaled<coord_t>(config.support_object_xy_distance.value);
// Separation of interfaces, it is likely smaller than support_xy_distance.
this->support_xy_distance_overhang = std::min(this->support_xy_distance, scaled<coord_t>(0.5 * external_perimeter_width));
this->support_top_distance = scaled<coord_t>(slicing_params.gap_support_object);
this->support_bottom_distance = scaled<coord_t>(slicing_params.gap_object_support);
// this->support_interface_skip_height =
// this->support_infill_angles =
this->support_roof_enable = config.support_interface_top_layers.value > 0;
this->support_roof_layers = this->support_roof_enable ? config.support_interface_top_layers.value : 0;
this->support_floor_enable = config.support_interface_top_layers.value > 0 && config.support_interface_bottom_layers.value > 0;
this->support_floor_layers = this->support_floor_enable ? config.support_interface_bottom_layers.value : 0;
// this->minimum_roof_area =
// this->support_roof_angles =
this->support_roof_pattern = config.support_interface_pattern;
this->support_pattern = config.support_base_pattern;
this->support_line_spacing = scaled<coord_t>(config.support_base_pattern_spacing.value);
// this->support_bottom_offset =
// this->support_wall_count = config.support_material_with_sheath ? 1 : 0;
this->support_wall_count = 1;
this->support_roof_line_distance = scaled<coord_t>(config.support_interface_spacing.value) + this->support_roof_line_width;
// this->minimum_support_area =
// this->minimum_bottom_area =
// this->support_offset =
this->support_tree_branch_distance = scaled<coord_t>(config.tree_support_branch_distance.value);
this->support_tree_angle = std::clamp<double>(config.tree_support_branch_angle * M_PI / 180., 0., 0.5 * M_PI - EPSILON);
this->support_tree_angle_slow = std::clamp<double>(config.tree_support_angle_slow * M_PI / 180., 0., this->support_tree_angle - EPSILON);
this->support_tree_branch_diameter = scaled<coord_t>(config.tree_support_branch_diameter.value);
this->support_tree_branch_diameter_angle = std::clamp<double>(config.tree_support_branch_diameter_angle * M_PI / 180., 0., 0.5 * M_PI - EPSILON);
this->support_tree_top_rate = config.tree_support_top_rate.value; // percent
// this->support_tree_tip_diameter = this->support_line_width;
this->support_tree_tip_diameter = std::clamp(scaled<coord_t>(config.tree_support_tip_diameter.value), 0, this->support_tree_branch_diameter);
}
TreeSupportSettings::TreeSupportSettings(const TreeSupportMeshGroupSettings &mesh_group_settings, const SlicingParameters &slicing_params)
: support_line_width(mesh_group_settings.support_line_width),
layer_height(mesh_group_settings.layer_height),
branch_radius(mesh_group_settings.support_tree_branch_diameter / 2),
min_radius(mesh_group_settings.support_tree_tip_diameter / 2), // The actual radius is 50 microns larger as the resulting branches will be increased by 50 microns to avoid rounding errors effectively increasing the xydistance
maximum_move_distance((mesh_group_settings.support_tree_angle < M_PI / 2.) ? (coord_t)(tan(mesh_group_settings.support_tree_angle) * layer_height) : std::numeric_limits<coord_t>::max()),
maximum_move_distance_slow((mesh_group_settings.support_tree_angle_slow < M_PI / 2.) ? (coord_t)(tan(mesh_group_settings.support_tree_angle_slow) * layer_height) : std::numeric_limits<coord_t>::max()),
support_bottom_layers(mesh_group_settings.support_bottom_enable ? (mesh_group_settings.support_bottom_height + layer_height / 2) / layer_height : 0),
// Ensure lines always stack nicely even if layer height is large.
tip_layers(std::max((branch_radius - min_radius) / (support_line_width / 3), branch_radius / layer_height)),
branch_radius_increase_per_layer(tan(mesh_group_settings.support_tree_branch_diameter_angle) * layer_height),
max_to_model_radius_increase(mesh_group_settings.support_tree_max_diameter_increase_by_merges_when_support_to_model / 2),
min_dtt_to_model(round_up_divide(mesh_group_settings.support_tree_min_height_to_model, layer_height)),
increase_radius_until_radius(mesh_group_settings.support_tree_branch_diameter / 2),
increase_radius_until_layer(increase_radius_until_radius <= branch_radius ? tip_layers * (increase_radius_until_radius / branch_radius) : (increase_radius_until_radius - branch_radius) / branch_radius_increase_per_layer),
support_rests_on_model(! mesh_group_settings.support_material_buildplate_only),
xy_distance(mesh_group_settings.support_xy_distance),
xy_min_distance(std::min(mesh_group_settings.support_xy_distance, mesh_group_settings.support_xy_distance_overhang)),
bp_radius(mesh_group_settings.support_tree_bp_diameter / 2),
// Increase by half a line overlap, but not faster than 40 degrees angle (0 degrees means zero increase in radius).
bp_radius_increase_per_layer(std::min(tan(0.7) * layer_height, 0.5 * support_line_width)),
z_distance_bottom_layers(size_t(round(double(mesh_group_settings.support_bottom_distance) / double(layer_height)))),
z_distance_top_layers(size_t(round(double(mesh_group_settings.support_top_distance) / double(layer_height)))),
// support_infill_angles(mesh_group_settings.support_infill_angles),
support_roof_angles(mesh_group_settings.support_roof_angles),
roof_pattern(mesh_group_settings.support_roof_pattern),
support_pattern(mesh_group_settings.support_pattern),
support_roof_line_width(mesh_group_settings.support_roof_line_width),
support_line_spacing(mesh_group_settings.support_line_spacing),
support_bottom_offset(mesh_group_settings.support_bottom_offset),
support_wall_count(mesh_group_settings.support_wall_count),
resolution(mesh_group_settings.resolution),
support_roof_line_distance(mesh_group_settings.support_roof_line_distance), // in the end the actual infill has to be calculated to subtract interface from support areas according to interface_preference.
settings(mesh_group_settings),
min_feature_size(mesh_group_settings.min_feature_size)
{
// At least one tip layer must be defined.
assert(tip_layers > 0);
layer_start_bp_radius = (bp_radius - branch_radius) / bp_radius_increase_per_layer;
if (TreeSupportSettings::soluble) {
// safeOffsetInc can only work in steps of the size xy_min_distance in the worst case => xy_min_distance has to be a bit larger than 0 in this worst case and should be large enough for performance to not suffer extremely
// When for all meshes the z bottom and top distance is more than one layer though the worst case is xy_min_distance + min_feature_size
// This is not the best solution, but the only one to ensure areas can not lag though walls at high maximum_move_distance.
xy_min_distance = std::max(xy_min_distance, scaled<coord_t>(0.1));
xy_distance = std::max(xy_distance, xy_min_distance);
}
// const std::unordered_map<std::string, InterfacePreference> interface_map = { { "support_area_overwrite_interface_area", InterfacePreference::SupportAreaOverwritesInterface }, { "interface_area_overwrite_support_area", InterfacePreference::InterfaceAreaOverwritesSupport }, { "support_lines_overwrite_interface_area", InterfacePreference::SupportLinesOverwriteInterface }, { "interface_lines_overwrite_support_area", InterfacePreference::InterfaceLinesOverwriteSupport }, { "nothing", InterfacePreference::Nothing } };
// interface_preference = interface_map.at(mesh_group_settings.get<std::string>("support_interface_priority"));
//FIXME this was the default
// interface_preference = InterfacePreference::SupportLinesOverwriteInterface;
//interface_preference = InterfacePreference::SupportAreaOverwritesInterface;
interface_preference = InterfacePreference::InterfaceAreaOverwritesSupport;
if (slicing_params.raft_layers() > 0) {
// Fill in raft_layers with the heights of the layers below the first object layer.
// First layer
double z = slicing_params.first_print_layer_height;
this->raft_layers.emplace_back(z);
// Raft base layers
for (size_t i = 1; i < slicing_params.base_raft_layers; ++ i) {
z += slicing_params.base_raft_layer_height;
this->raft_layers.emplace_back(z);
}
// Raft interface layers
for (size_t i = 0; i + 1 < slicing_params.interface_raft_layers; ++ i) {
z += slicing_params.interface_raft_layer_height;
this->raft_layers.emplace_back(z);
}
// Raft contact layer
if (slicing_params.raft_layers() > 1) {
z = slicing_params.raft_contact_top_z;
this->raft_layers.emplace_back(z);
}
if (double dist_to_go = slicing_params.object_print_z_min - z; dist_to_go > EPSILON) {
// Layers between the raft contacts and bottom of the object.
auto nsteps = int(ceil(dist_to_go / slicing_params.max_suport_layer_height));
double step = dist_to_go / nsteps;
for (int i = 0; i < nsteps; ++ i) {
z += step;
this->raft_layers.emplace_back(z);
}
}
}
}
#if defined(TREE_SUPPORT_SHOW_ERRORS) && defined(_WIN32)
#define TREE_SUPPORT_SHOW_ERRORS_WIN32
#include <windows.h>
#endif
// Shared with generate_support_areas()
bool g_showed_critical_error = false;
bool g_showed_performance_warning = false;
void tree_supports_show_error(std::string_view message, bool critical)
{ // todo Remove! ONLY FOR PUBLIC BETA!!
printf("Error: %s, critical: %d\n", message.data(), int(critical));
#ifdef TREE_SUPPORT_SHOW_ERRORS_WIN32
static bool showed_critical = false;
static bool showed_performance = false;
auto bugtype = std::string(critical ? " This is a critical bug. It may cause missing or malformed branches.\n" : "This bug should only decrease performance.\n");
bool show = (critical && !g_showed_critical_error) || (!critical && !g_showed_performance_warning);
(critical ? g_showed_critical_error : g_showed_performance_warning) = true;
if (show)
MessageBoxA(nullptr, std::string("TreeSupport_2 MOD detected an error while generating the tree support.\nPlease report this back to me with profile and model.\nRevision 5.0\n" + std::string(message) + "\n" + bugtype).c_str(),
"Bug detected!", MB_OK | MB_SYSTEMMODAL | MB_SETFOREGROUND | MB_ICONWARNING);
#endif // TREE_SUPPORT_SHOW_ERRORS_WIN32
}
} // namespace Slic3r::FFFTreeSupport

View file

@ -0,0 +1,595 @@
///|/ Copyright (c) Prusa Research 2023 Vojtěch Bubník @bubnikv
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
// Tree supports by Thomas Rahm, losely based on Tree Supports by CuraEngine.
// Original source of Thomas Rahm's tree supports:
// https://github.com/ThomasRahm/CuraEngine
//
// Original CuraEngine copyright:
// Copyright (c) 2021 Ultimaker B.V.
// CuraEngine is released under the terms of the AGPLv3 or higher.
#ifndef slic3r_TreeSupportCommon_hpp
#define slic3r_TreeSupportCommon_hpp
#include "../libslic3r.h"
#include "../Polygon.hpp"
#include "SupportCommon.hpp"
#include <string_view>
using namespace Slic3r::FFFSupport;
namespace Slic3r
{
namespace FFFTreeSupport
{
using LayerIndex = int;
enum class InterfacePreference
{
InterfaceAreaOverwritesSupport,
SupportAreaOverwritesInterface,
InterfaceLinesOverwriteSupport,
SupportLinesOverwriteInterface,
Nothing
};
struct TreeSupportMeshGroupSettings {
TreeSupportMeshGroupSettings() = default;
explicit TreeSupportMeshGroupSettings(const PrintObject &print_object);
/*********************************************************************/
/* Print parameters, not support specific: */
/*********************************************************************/
coord_t layer_height { scaled<coord_t>(0.15) };
// Maximum Deviation (meshfix_maximum_deviation)
// The maximum deviation allowed when reducing the resolution for the Maximum Resolution setting. If you increase this,
// the print will be less accurate, but the g-code will be smaller. Maximum Deviation is a limit for Maximum Resolution,
// so if the two conflict the Maximum Deviation will always be held true.
coord_t resolution { scaled<coord_t>(0.025) };
// Minimum Feature Size (aka minimum line width) - Arachne specific
// Minimum thickness of thin features. Model features that are thinner than this value will not be printed, while features thicker
// than the Minimum Feature Size will be widened to the Minimum Wall Line Width.
coord_t min_feature_size { scaled<coord_t>(0.1) };
/*********************************************************************/
/* General support parameters: */
/*********************************************************************/
// Support Overhang Angle
// The minimum angle of overhangs for which support is added. At a value of 0° all overhangs are supported, 90° will not provide any support.
double support_angle { 50. * M_PI / 180. };
// Support Line Width
// Width of a single support structure line.
coord_t support_line_width { scaled<coord_t>(0.4) };
// Support Roof Line Width: Width of a single support roof line.
coord_t support_roof_line_width { scaled<coord_t>(0.4) };
// Enable Support Floor (aka bottom interfaces)
// Generate a dense slab of material between the bottom of the support and the model. This will create a skin between the model and support.
bool support_bottom_enable { false };
// Support Floor Thickness
// The thickness of the support floors. This controls the number of dense layers that are printed on top of places of a model on which support rests.
coord_t support_bottom_height { scaled<coord_t>(1.) };
bool support_material_buildplate_only { false };
// Support X/Y Distance
// Distance of the support structure from the print in the X/Y directions.
// minimum: 0, maximum warning: 1.5 * machine_nozzle_tip_outer_diameter
coord_t support_xy_distance { scaled<coord_t>(0.7) };
// Minimum Support X/Y Distance
// Distance of the support structure from the overhang in the X/Y directions.
// minimum_value: 0, minimum warning": support_xy_distance - support_line_width * 2, maximum warning: support_xy_distance
coord_t support_xy_distance_overhang { scaled<coord_t>(0.2) };
// Support Top Distance
// Distance from the top of the support to the print.
coord_t support_top_distance { scaled<coord_t>(0.1) };
// Support Bottom Distance
// Distance from the print to the bottom of the support.
coord_t support_bottom_distance { scaled<coord_t>(0.1) };
//FIXME likely not needed, optimization for clipping of interface layers
// When checking where there's model above and below the support, take steps of the given height. Lower values will slice slower, while higher values
// may cause normal support to be printed in some places where there should have been support interface.
coord_t support_interface_skip_height { scaled<coord_t>(0.3) };
// Support Infill Line Directions
// A list of integer line directions to use. Elements from the list are used sequentially as the layers progress and when the end
// of the list is reached, it starts at the beginning again. The list items are separated by commas and the whole list is contained
// in square brackets. Default is an empty list which means use the default angle 0 degrees.
// std::vector<double> support_infill_angles {};
// Enable Support Roof
// Generate a dense slab of material between the top of support and the model. This will create a skin between the model and support.
bool support_roof_enable { false };
// Support Roof Thickness
// The thickness of the support roofs. This controls the amount of dense layers at the top of the support on which the model rests.
coord_t support_roof_layers { 2 };
bool support_floor_enable { false };
coord_t support_floor_layers { 2 };
// Minimum Support Roof Area
// Minimum area size for the roofs of the support. Polygons which have an area smaller than this value will be printed as normal support.
double minimum_roof_area { scaled<double>(scaled<double>(1.)) };
// A list of integer line directions to use. Elements from the list are used sequentially as the layers progress
// and when the end of the list is reached, it starts at the beginning again. The list items are separated
// by commas and the whole list is contained in square brackets. Default is an empty list which means
// use the default angles (alternates between 45 and 135 degrees if interfaces are quite thick or 90 degrees).
std::vector<double> support_roof_angles {};
// Support Roof Pattern (aka top interface)
// The pattern with which the roofs of the support are printed.
SupportMaterialInterfacePattern support_roof_pattern { smipAuto };
// Support Pattern
// The pattern of the support structures of the print. The different options available result in sturdy or easy to remove support.
SupportMaterialPattern support_pattern { smpRectilinear };
// Support Line Distance
// Distance between the printed support structure lines. This setting is calculated by the support density.
coord_t support_line_spacing { scaled<coord_t>(2.66 - 0.4) };
// Support Floor Horizontal Expansion
// Amount of offset applied to the floors of the support.
coord_t support_bottom_offset { scaled<coord_t>(0.) };
// Support Wall Line Count
// The number of walls with which to surround support infill. Adding a wall can make support print more reliably
// and can support overhangs better, but increases print time and material used.
// tree: 1, zig-zag: 0, concentric: 1
int support_wall_count { 1 };
// Support Roof Line Distance
// Distance between the printed support roof lines. This setting is calculated by the Support Roof Density, but can be adjusted separately.
coord_t support_roof_line_distance { scaled<coord_t>(0.4) };
// Minimum Support Area
// Minimum area size for support polygons. Polygons which have an area smaller than this value will not be generated.
coord_t minimum_support_area { scaled<coord_t>(0.) };
// Minimum Support Floor Area
// Minimum area size for the floors of the support. Polygons which have an area smaller than this value will be printed as normal support.
coord_t minimum_bottom_area { scaled<coord_t>(1.0) };
// Support Horizontal Expansion
// Amount of offset applied to all support polygons in each layer. Positive values can smooth out the support areas and result in more sturdy support.
coord_t support_offset { scaled<coord_t>(0.) };
/*********************************************************************/
/* Parameters for the Cura tree supports implementation: */
/*********************************************************************/
// Tree Support Maximum Branch Angle
// The maximum angle of the branches, when the branches have to avoid the model. Use a lower angle to make them more vertical and more stable. Use a higher angle to be able to have more reach.
// minimum: 0, minimum warning: 20, maximum: 89, maximum warning": 85
double support_tree_angle { 60. * M_PI / 180. };
// Tree Support Branch Diameter Angle
// The angle of the branches' diameter as they gradually become thicker towards the bottom. An angle of 0 will cause the branches to have uniform thickness over their length.
// A bit of an angle can increase stability of the tree support.
// minimum: 0, maximum: 89.9999, maximum warning: 15
double support_tree_branch_diameter_angle { 5. * M_PI / 180. };
// Tree Support Branch Distance
// How far apart the branches need to be when they touch the model. Making this distance small will cause
// the tree support to touch the model at more points, causing better overhang but making support harder to remove.
coord_t support_tree_branch_distance { scaled<coord_t>(1.) };
// Tree Support Branch Diameter
// The diameter of the thinnest branches of tree support. Thicker branches are more sturdy. Branches towards the base will be thicker than this.
// minimum: 0.001, minimum warning: support_line_width * 2
coord_t support_tree_branch_diameter { scaled<coord_t>(2.) };
/*********************************************************************/
/* Parameters new to the Thomas Rahm's tree supports implementation: */
/*********************************************************************/
// Tree Support Preferred Branch Angle
// The preferred angle of the branches, when they do not have to avoid the model. Use a lower angle to make them more vertical and more stable. Use a higher angle for branches to merge faster.
// minimum: 0, minimum warning: 10, maximum: support_tree_angle, maximum warning: support_tree_angle-1
double support_tree_angle_slow { 50. * M_PI / 180. };
// Tree Support Diameter Increase To Model
// The most the diameter of a branch that has to connect to the model may increase by merging with branches that could reach the buildplate.
// Increasing this reduces print time, but increases the area of support that rests on model
// minimum: 0
coord_t support_tree_max_diameter_increase_by_merges_when_support_to_model { scaled<coord_t>(1.0) };
// Tree Support Minimum Height To Model
// How tall a branch has to be if it is placed on the model. Prevents small blobs of support. This setting is ignored when a branch is supporting a support roof.
// minimum: 0, maximum warning: 5
coord_t support_tree_min_height_to_model { scaled<coord_t>(1.0) };
// Tree Support Inital Layer Diameter
// Diameter every branch tries to achieve when reaching the buildplate. Improves bed adhesion.
// minimum: 0, maximum warning: 20
coord_t support_tree_bp_diameter { scaled<coord_t>(7.5) };
// Tree Support Branch Density
// Adjusts the density of the support structure used to generate the tips of the branches. A higher value results in better overhangs,
// but the supports are harder to remove. Use Support Roof for very high values or ensure support density is similarly high at the top.
// ->
// Adjusts the density of the support structure used to generate the tips of the branches.
// A higher value results in better overhangs but the supports are harder to remove, thus it is recommended to enable top support interfaces
// instead of a high branch density value if dense interfaces are needed.
// 5%-35%
double support_tree_top_rate { 15. };
// Tree Support Tip Diameter
// The diameter of the top of the tip of the branches of tree support.
// minimum: min_wall_line_width, minimum warning: min_wall_line_width+0.05, maximum_value: support_tree_branch_diameter, value: support_line_width
coord_t support_tree_tip_diameter { scaled<coord_t>(0.4) };
// Support Interface Priority
// How support interface and support will interact when they overlap. Currently only implemented for support roof.
//enum support_interface_priority { support_lines_overwrite_interface_area };
};
/*!
* \brief This struct contains settings used in the tree support. Thanks to this most functions do not need to know of meshes etc. Also makes the code shorter.
*/
struct TreeSupportSettings
{
public:
TreeSupportSettings() = default; // required for the definition of the config variable in the TreeSupportGenerator class.
explicit TreeSupportSettings(const TreeSupportMeshGroupSettings &mesh_group_settings, const SlicingParameters &slicing_params);
// some static variables dependent on other meshes that are not currently processed.
// Has to be static because TreeSupportConfig will be used in TreeModelVolumes as this reduces redundancy.
inline static bool soluble = false;
/*!
* \brief Width of a single line of support.
*/
coord_t support_line_width;
/*!
* \brief Height of a single layer
*/
coord_t layer_height;
/*!
* \brief Radius of a branch when it has left the tip.
*/
coord_t branch_radius;
/*!
* \brief smallest allowed radius, required to ensure that even at DTT 0 every circle will still be printed
*/
coord_t min_radius;
/*!
* \brief How far an influence area may move outward every layer at most.
*/
coord_t maximum_move_distance;
/*!
* \brief How far every influence area will move outward every layer if possible.
*/
coord_t maximum_move_distance_slow;
/*!
* \brief Amount of bottom layers. 0 if disabled.
*/
size_t support_bottom_layers;
/*!
* \brief Amount of effectiveDTT increases are required to reach branch radius.
*/
size_t tip_layers;
/*!
* \brief How much a branch radius increases with each layer to guarantee the prescribed tree widening.
*/
double branch_radius_increase_per_layer;
/*!
* \brief How much a branch resting on the model may grow in radius by merging with branches that can reach the buildplate.
*/
coord_t max_to_model_radius_increase;
/*!
* \brief If smaller (in layers) than that, all branches to model will be deleted
*/
size_t min_dtt_to_model;
/*!
* \brief Increase radius in the resulting drawn branches, even if the avoidance does not allow it. Will be cut later to still fit.
*/
coord_t increase_radius_until_radius;
/*!
* \brief Same as increase_radius_until_radius, but contains the DTT at which the radius will be reached.
*/
size_t increase_radius_until_layer;
/*!
* \brief True if the branches may connect to the model.
*/
bool support_rests_on_model;
/*!
* \brief How far should support be from the model.
*/
coord_t xy_distance;
/*!
* \brief A minimum radius a tree trunk should expand to at the buildplate if possible.
*/
coord_t bp_radius;
/*!
* \brief The layer index at which an increase in radius may be required to reach the bp_radius.
*/
LayerIndex layer_start_bp_radius;
/*!
* \brief How much one is allowed to increase the tree branch radius close to print bed to reach the required bp_radius at layer 0.
* Note that this radius increase will not happen in the tip, to ensure the tip is structurally sound.
*/
double bp_radius_increase_per_layer;
/*!
* \brief minimum xy_distance. Only relevant when Z overrides XY, otherwise equal to xy_distance-
*/
coord_t xy_min_distance;
/*!
* \brief Amount of layers distance required the top of the support to the model
*/
size_t z_distance_top_layers;
/*!
* \brief Amount of layers distance required from the top of the model to the bottom of a support structure.
*/
size_t z_distance_bottom_layers;
/*!
* \brief User specified angles for the support infill.
*/
// std::vector<double> support_infill_angles;
/*!
* \brief User specified angles for the support roof infill.
*/
std::vector<double> support_roof_angles;
/*!
* \brief Pattern used in the support roof. May contain non relevant data if support roof is disabled.
*/
SupportMaterialInterfacePattern roof_pattern;
/*!
* \brief Pattern used in the support infill.
*/
SupportMaterialPattern support_pattern;
/*!
* \brief Line width of the support roof.
*/
coord_t support_roof_line_width;
/*!
* \brief Distance between support infill lines.
*/
coord_t support_line_spacing;
/*!
* \brief Offset applied to the support floor area.
*/
coord_t support_bottom_offset;
/*
* \brief Amount of walls the support area will have.
*/
int support_wall_count;
/*
* \brief Maximum allowed deviation when simplifying.
*/
coord_t resolution;
/*
* \brief Distance between the lines of the roof.
*/
coord_t support_roof_line_distance;
/*
* \brief How overlaps of an interface area with a support area should be handled.
*/
InterfacePreference interface_preference;
/*
* \brief The infill class wants a settings object. This one will be the correct one for all settings it uses.
*/
TreeSupportMeshGroupSettings settings;
/*
* \brief Minimum thickness of any model features.
*/
coord_t min_feature_size;
// Extra raft layers below the object.
std::vector<coordf_t> raft_layers;
public:
bool operator==(const TreeSupportSettings& other) const
{
return branch_radius == other.branch_radius && tip_layers == other.tip_layers && branch_radius_increase_per_layer == other.branch_radius_increase_per_layer && layer_start_bp_radius == other.layer_start_bp_radius && bp_radius == other.bp_radius &&
// as a recalculation of the collision areas is required to set a new min_radius.
bp_radius_increase_per_layer == other.bp_radius_increase_per_layer && min_radius == other.min_radius && xy_min_distance == other.xy_min_distance &&
xy_distance - xy_min_distance == other.xy_distance - other.xy_min_distance && // if the delta of xy_min_distance and xy_distance is different the collision areas have to be recalculated.
support_rests_on_model == other.support_rests_on_model && increase_radius_until_layer == other.increase_radius_until_layer && min_dtt_to_model == other.min_dtt_to_model && max_to_model_radius_increase == other.max_to_model_radius_increase && maximum_move_distance == other.maximum_move_distance && maximum_move_distance_slow == other.maximum_move_distance_slow && z_distance_bottom_layers == other.z_distance_bottom_layers && support_line_width == other.support_line_width &&
support_line_spacing == other.support_line_spacing && support_roof_line_width == other.support_roof_line_width && // can not be set on a per-mesh basis currently, so code to enable processing different roof line width in the same iteration seems useless.
support_bottom_offset == other.support_bottom_offset && support_wall_count == other.support_wall_count && support_pattern == other.support_pattern && roof_pattern == other.roof_pattern && // can not be set on a per-mesh basis currently, so code to enable processing different roof patterns in the same iteration seems useless.
support_roof_angles == other.support_roof_angles &&
//support_infill_angles == other.support_infill_angles &&
increase_radius_until_radius == other.increase_radius_until_radius && support_bottom_layers == other.support_bottom_layers && layer_height == other.layer_height && z_distance_top_layers == other.z_distance_top_layers && resolution == other.resolution && // Infill generation depends on deviation and resolution.
support_roof_line_distance == other.support_roof_line_distance && interface_preference == other.interface_preference
&& min_feature_size == other.min_feature_size // interface_preference should be identical to ensure the tree will correctly interact with the roof.
// The infill class now wants the settings object and reads a lot of settings, and as the infill class is used to calculate support roof lines for interface-preference. Not all of these may be required to be identical, but as I am not sure, better safe than sorry
#if 0
&& (interface_preference == InterfacePreference::InterfaceAreaOverwritesSupport || interface_preference == InterfacePreference::SupportAreaOverwritesInterface
// Perimeter generator parameters
||
(settings.get<bool>("fill_outline_gaps") == other.settings.get<bool>("fill_outline_gaps") &&
settings.get<coord_t>("min_bead_width") == other.settings.get<coord_t>("min_bead_width") &&
settings.get<double>("wall_transition_angle") == other.settings.get<double>("wall_transition_angle") &&
settings.get<coord_t>("wall_transition_length") == other.settings.get<coord_t>("wall_transition_length") &&
settings.get<Ratio>("wall_split_middle_threshold") == other.settings.get<Ratio>("wall_split_middle_threshold") &&
settings.get<Ratio>("wall_add_middle_threshold") == other.settings.get<Ratio>("wall_add_middle_threshold") &&
settings.get<int>("wall_distribution_count") == other.settings.get<int>("wall_distribution_count") &&
settings.get<coord_t>("wall_transition_filter_distance") == other.settings.get<coord_t>("wall_transition_filter_distance") &&
settings.get<coord_t>("wall_transition_filter_deviation") == other.settings.get<coord_t>("wall_transition_filter_deviation") &&
settings.get<coord_t>("wall_line_width_x") == other.settings.get<coord_t>("wall_line_width_x") &&
settings.get<int>("meshfix_maximum_extrusion_area_deviation") == other.settings.get<int>("meshfix_maximum_extrusion_area_deviation"))
)
#endif
&& raft_layers == other.raft_layers
;
}
/*!
* \brief Get the Radius part will have based on numeric values.
* \param distance_to_top[in] The effective distance_to_top of the element
* \param elephant_foot_increases[in] The elephant_foot_increases of the element.
* \return The radius an element with these attributes would have.
*/
[[nodiscard]] inline coord_t getRadius(size_t distance_to_top, const double elephant_foot_increases = 0) const
{
return (distance_to_top <= tip_layers ? min_radius + (branch_radius - min_radius) * distance_to_top / tip_layers : // tip
branch_radius + // base
(distance_to_top - tip_layers) * branch_radius_increase_per_layer)
+ // gradual increase
elephant_foot_increases * (std::max(bp_radius_increase_per_layer - branch_radius_increase_per_layer, 0.0));
}
/*!
* \brief Get the Radius an element should at least have at a given layer.
* \param layer_idx[in] The layer.
* \return The radius every element should aim to achieve.
*/
[[nodiscard]] inline coord_t recommendedMinRadius(LayerIndex layer_idx) const
{
double num_layers_widened = layer_start_bp_radius - layer_idx;
return num_layers_widened > 0 ? branch_radius + num_layers_widened * bp_radius_increase_per_layer : 0;
}
#if 0
/*!
* \brief Return on which z in microns the layer will be printed. Used only for support infill line generation.
* \param layer_idx[in] The layer.
* \return The radius every element should aim to achieve.
*/
[[nodiscard]] inline coord_t getActualZ(LayerIndex layer_idx)
{
return layer_idx < coord_t(known_z.size()) ? known_z[layer_idx] : (layer_idx - known_z.size()) * layer_height + known_z.size() ? known_z.back() : 0;
}
/*!
* \brief Set the z every Layer is printed at. Required for getActualZ to work
* \param z[in] The z every LayerIndex is printed. Vector is used as a map<LayerIndex,coord_t> with the index of each element being the corresponding LayerIndex
* \return The radius every element should aim to achieve.
*/
void setActualZ(std::vector<coord_t>& z)
{
known_z = z;
}
#endif
private:
// std::vector<coord_t> known_z;
};
static constexpr const bool polygons_strictly_simple = false;
static constexpr const auto tiny_area_threshold = sqr(scaled<double>(0.001));
void tree_supports_show_error(std::string_view message, bool critical);
inline double layer_z(const SlicingParameters &slicing_params, const TreeSupportSettings &config, const size_t layer_idx)
{
return layer_idx >= config.raft_layers.size() ?
slicing_params.object_print_z_min + slicing_params.first_object_layer_height + (layer_idx - config.raft_layers.size()) * slicing_params.layer_height :
config.raft_layers[layer_idx];
}
// Lowest collision layer
inline LayerIndex layer_idx_ceil(const SlicingParameters &slicing_params, const TreeSupportSettings &config, const double z)
{
return
LayerIndex(config.raft_layers.size()) +
std::max<LayerIndex>(0, ceil((z - slicing_params.object_print_z_min - slicing_params.first_object_layer_height) / slicing_params.layer_height));
}
// Highest collision layer
inline LayerIndex layer_idx_floor(const SlicingParameters &slicing_params, const TreeSupportSettings &config, const double z)
{
return
LayerIndex(config.raft_layers.size()) +
std::max<LayerIndex>(0, floor((z - slicing_params.object_print_z_min - slicing_params.first_object_layer_height) / slicing_params.layer_height));
}
inline SupportGeneratorLayer& layer_initialize(
SupportGeneratorLayer &layer_new,
const SlicingParameters &slicing_params,
const TreeSupportSettings &config,
const size_t layer_idx)
{
layer_new.print_z = layer_z(slicing_params, config, layer_idx);
layer_new.bottom_z = layer_idx > 0 ? layer_z(slicing_params, config, layer_idx - 1) : 0;
layer_new.height = layer_new.print_z - layer_new.bottom_z;
return layer_new;
}
// Using the std::deque as an allocator.
inline SupportGeneratorLayer& layer_allocate_unguarded(
SupportGeneratorLayerStorage &layer_storage,
SupporLayerType layer_type,
const SlicingParameters &slicing_params,
const TreeSupportSettings &config,
size_t layer_idx)
{
SupportGeneratorLayer &layer = layer_storage.allocate_unguarded(layer_type);
return layer_initialize(layer, slicing_params, config, layer_idx);
}
inline SupportGeneratorLayer& layer_allocate(
SupportGeneratorLayerStorage &layer_storage,
SupporLayerType layer_type,
const SlicingParameters &slicing_params,
const TreeSupportSettings &config,
size_t layer_idx)
{
SupportGeneratorLayer &layer = layer_storage.allocate(layer_type);
return layer_initialize(layer, slicing_params, config, layer_idx);
}
// Used by generate_initial_areas() in parallel by multiple layers.
class InterfacePlacer {
public:
InterfacePlacer(
const SlicingParameters &slicing_parameters,
const SupportParameters &support_parameters,
const TreeSupportSettings &config,
SupportGeneratorLayerStorage &layer_storage,
SupportGeneratorLayersPtr &top_contacts,
SupportGeneratorLayersPtr &top_interfaces,
SupportGeneratorLayersPtr &top_base_interfaces)
:
slicing_parameters(slicing_parameters), support_parameters(support_parameters), config(config),
layer_storage(layer_storage), top_contacts(top_contacts), top_interfaces(top_interfaces), top_base_interfaces(top_base_interfaces)
{}
InterfacePlacer(const InterfacePlacer& rhs) :
slicing_parameters(rhs.slicing_parameters), support_parameters(rhs.support_parameters), config(rhs.config),
layer_storage(rhs.layer_storage), top_contacts(rhs.top_contacts), top_interfaces(rhs.top_interfaces), top_base_interfaces(rhs.top_base_interfaces)
{}
const SlicingParameters &slicing_parameters;
const SupportParameters &support_parameters;
const TreeSupportSettings &config;
SupportGeneratorLayersPtr& top_contacts_mutable() { return this->top_contacts; }
public:
// Insert the contact layer and some of the inteface and base interface layers below.
void add_roofs(std::vector<Polygons> &&new_roofs, const size_t insert_layer_idx)
{
if (! new_roofs.empty()) {
std::lock_guard<std::mutex> lock(m_mutex_layer_storage);
for (size_t idx = 0; idx < new_roofs.size(); ++ idx)
if (! new_roofs[idx].empty())
add_roof_unguarded(std::move(new_roofs[idx]), insert_layer_idx - idx, idx);
}
}
void add_roof(Polygons &&new_roof, const size_t insert_layer_idx, const size_t dtt_tip)
{
std::lock_guard<std::mutex> lock(m_mutex_layer_storage);
add_roof_unguarded(std::move(new_roof), insert_layer_idx, dtt_tip);
}
// called by sample_overhang_area()
void add_roof_build_plate(Polygons &&overhang_areas, size_t dtt_roof)
{
std::lock_guard<std::mutex> lock(m_mutex_layer_storage);
this->add_roof_unguarded(std::move(overhang_areas), 0, std::min(dtt_roof, this->support_parameters.num_top_interface_layers));
}
void add_roof_unguarded(Polygons &&new_roofs, const size_t insert_layer_idx, const size_t dtt_roof)
{
assert(support_parameters.has_top_contacts);
assert(dtt_roof <= support_parameters.num_top_interface_layers);
SupportGeneratorLayersPtr &layers =
dtt_roof == 0 ? this->top_contacts :
dtt_roof <= support_parameters.num_top_interface_layers_only() ? this->top_interfaces : this->top_base_interfaces;
SupportGeneratorLayer*& l = layers[insert_layer_idx];
if (l == nullptr)
l = &layer_allocate_unguarded(layer_storage, dtt_roof == 0 ? SupporLayerType::TopContact : SupporLayerType::TopInterface,
slicing_parameters, config, insert_layer_idx);
// will be unioned in finalize_interface_and_support_areas()
append(l->polygons, std::move(new_roofs));
}
private:
// Outputs
SupportGeneratorLayerStorage &layer_storage;
SupportGeneratorLayersPtr &top_contacts;
SupportGeneratorLayersPtr &top_interfaces;
SupportGeneratorLayersPtr &top_base_interfaces;
// Mutexes, guards
std::mutex m_mutex_layer_storage;
};
} // namespace FFFTreeSupport
} // namespace Slic3r
#endif // slic3r_TreeSupportCommon_hpp

View file

@ -282,6 +282,10 @@ template<class T> size_t next_highest_power_of_2(T v,
return next_highest_power_of_2(uint32_t(v));
}
template <class VectorType> void reserve_power_of_2(VectorType &vector, size_t n) {
vector.reserve(next_highest_power_of_2(n));
}
template<typename INDEX_TYPE>
inline INDEX_TYPE prev_idx_modulo(INDEX_TYPE idx, const INDEX_TYPE count)
{
@ -298,6 +302,14 @@ inline INDEX_TYPE next_idx_modulo(INDEX_TYPE idx, const INDEX_TYPE count)
return idx;
}
// Return dividend divided by divisor rounded to the nearest integer
template<typename INDEX_TYPE>
inline INDEX_TYPE round_up_divide(const INDEX_TYPE dividend, const INDEX_TYPE divisor)
{
return (dividend + divisor - 1) / divisor;
}
template<typename CONTAINER_TYPE>
inline typename CONTAINER_TYPE::size_type prev_idx_modulo(typename CONTAINER_TYPE::size_type idx, const CONTAINER_TYPE &container)
{

View file

@ -404,7 +404,7 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con
auto support_type = config->opt_enum<SupportType>("support_type");
auto support_style = config->opt_enum<SupportMaterialStyle>("support_style");
std::set<int> enum_set_normal = {0, 1, 2};
std::set<int> enum_set_tree = {0, 3, 4, 5};
std::set<int> enum_set_tree = {0, 3, 4, 5, 6};
auto & set = is_tree(support_type) ? enum_set_tree : enum_set_normal;
if (set.find(support_style) == set.end()) {
DynamicPrintConfig new_conf = *config;
@ -595,18 +595,22 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co
//toggle_field("support_closing_radius", have_support_material && support_style == smsSnug);
bool support_is_tree = config->opt_bool("enable_support") && is_tree(support_type);
for (auto el : {"tree_support_branch_angle", "tree_support_wall_count", "tree_support_branch_distance",
"tree_support_branch_diameter", "tree_support_adaptive_layer_height", "tree_support_auto_brim", "tree_support_brim_width"})
toggle_field(el, support_is_tree);
// hide tree support settings when normal is selected
for (auto el : {"tree_support_branch_angle", "tree_support_wall_count", "tree_support_branch_distance",
"tree_support_branch_diameter", "max_bridge_length", "tree_support_adaptive_layer_height", "tree_support_auto_brim", "tree_support_brim_width"})
bool support_is_normal_tree = support_is_tree && support_style != smsOrganic;
bool support_is_organic = support_is_tree && !support_is_normal_tree;
// settings shared by normal and organic trees
for (auto el : {"tree_support_branch_angle", "tree_support_branch_distance", "tree_support_branch_diameter" })
toggle_line(el, support_is_tree);
// settings specific to normal trees
for (auto el : {"tree_support_wall_count", "tree_support_auto_brim", "tree_support_brim_width", "tree_support_adaptive_layer_height"})
toggle_line(el, support_is_normal_tree);
// settings specific to organic trees
for (auto el : {"tree_support_angle_slow","tree_support_tip_diameter", "tree_support_top_rate", "tree_support_branch_diameter_angle", "tree_support_branch_diameter_double_wall"})
toggle_line(el, support_is_organic);
toggle_field("tree_support_brim_width", support_is_tree && !config->opt_bool("tree_support_auto_brim"));
// tree support use max_bridge_length instead of bridge_no_support
toggle_line("bridge_no_support", !support_is_tree);
// non-organic tree support use max_bridge_length instead of bridge_no_support
toggle_line("max_bridge_length", support_is_normal_tree);
toggle_line("bridge_no_support", !support_is_normal_tree);
for (auto el : { "support_interface_spacing", "support_interface_filament",
"support_interface_loop_pattern", "support_bottom_interface_spacing" })
@ -625,8 +629,10 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co
toggle_field("support_filament", have_support_material || have_skirt);
toggle_line("raft_contact_distance", have_raft && !have_support_soluble);
// Orca: Raft, grid, snug and organic supports use these two parameters to control the size & density of the "brim"/flange
for (auto el : { "raft_first_layer_expansion", "raft_first_layer_density"})
toggle_line(el, have_raft);
toggle_field(el, have_support_material && !support_is_normal_tree);
bool has_ironing = (config->opt_enum<IroningType>("ironing_type") != IroningType::NoIroning);
for (auto el : { "ironing_flow", "ironing_spacing", "ironing_speed" })

View file

@ -1420,7 +1420,8 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value)
}
// BBS set support style to default when support type changes
if (opt_key == "support_type") {
// Orca: do this only in simple mode
if (opt_key == "support_type" && m_mode == comSimple) {
DynamicPrintConfig new_conf = *m_config;
new_conf.set_key_value("support_style", new ConfigOptionEnum<SupportMaterialStyle>(smsDefault));
m_config_manipulation.apply(m_config, &new_conf);
@ -2003,6 +2004,8 @@ void TabPrint::build()
optgroup->append_single_option_line("support_type", "support#support-types");
optgroup->append_single_option_line("support_style", "support#support-styles");
optgroup->append_single_option_line("support_threshold_angle", "support#threshold-angle");
optgroup->append_single_option_line("raft_first_layer_density");
optgroup->append_single_option_line("raft_first_layer_expansion");
optgroup->append_single_option_line("support_on_build_plate_only");
optgroup->append_single_option_line("support_critical_regions_only");
optgroup->append_single_option_line("support_remove_small_overhang");
@ -2011,8 +2014,6 @@ void TabPrint::build()
optgroup = page->new_optgroup(L("Raft"), L"param_raft");
optgroup->append_single_option_line("raft_layers");
optgroup->append_single_option_line("raft_contact_distance");
optgroup->append_single_option_line("raft_first_layer_density");
optgroup->append_single_option_line("raft_first_layer_expansion");
optgroup = page->new_optgroup(L("Support filament"), L"param_support_filament");
optgroup->append_single_option_line("support_filament", "support#support-filament");
@ -2022,9 +2023,14 @@ void TabPrint::build()
//BBS
optgroup = page->new_optgroup(L("Advanced"), L"param_advanced");
optgroup->append_single_option_line("tree_support_tip_diameter");
optgroup->append_single_option_line("tree_support_branch_distance", "support#tree-support-only-options");
optgroup->append_single_option_line("tree_support_top_rate");
optgroup->append_single_option_line("tree_support_branch_diameter", "support#tree-support-only-options");
optgroup->append_single_option_line("tree_support_branch_diameter_angle");
optgroup->append_single_option_line("tree_support_branch_angle", "support#tree-support-only-options");
optgroup->append_single_option_line("tree_support_angle_slow");
optgroup->append_single_option_line("tree_support_branch_diameter_double_wall");
optgroup->append_single_option_line("tree_support_wall_count");
optgroup->append_single_option_line("tree_support_adaptive_layer_height");
optgroup->append_single_option_line("tree_support_auto_brim");
@ -2165,7 +2171,7 @@ void TabPrint::toggle_options()
if (auto choice = dynamic_cast<Choice*>(field)) {
auto def = print_config_def.get("support_style");
std::vector<int> enum_set_normal = {0, 1, 2};
std::vector<int> enum_set_tree = {0, 3, 4, 5};
std::vector<int> enum_set_tree = {0, 3, 4, 5, 6};
auto & set = is_tree(support_type) ? enum_set_tree : enum_set_normal;
auto & opt = const_cast<ConfigOptionDef &>(field->m_opt);
auto cb = dynamic_cast<ComboBox *>(choice->window);