From a236351fd3043e6a8a007830aaabc423387b1c6c Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Fri, 5 May 2023 12:59:01 +0200 Subject: [PATCH] Supports refactoring: Split FFF supports into multiple files, enclosed into namespaces. --- src/libslic3r/CMakeLists.txt | 5 + src/libslic3r/Support/SupportCommon.cpp | 1807 +++++++++++++++++ src/libslic3r/Support/SupportCommon.hpp | 138 ++ src/libslic3r/Support/SupportDebug.cpp | 108 + src/libslic3r/Support/SupportDebug.hpp | 18 + src/libslic3r/Support/SupportLayer.hpp | 26 +- src/libslic3r/Support/SupportParameters.cpp | 116 ++ src/libslic3r/Support/SupportParameters.hpp | 4 + src/libslic3r/SupportMaterial.cpp | 2013 +------------------ src/libslic3r/SupportMaterial.hpp | 52 +- src/libslic3r/TreeSupport.cpp | 44 +- 11 files changed, 2259 insertions(+), 2072 deletions(-) create mode 100644 src/libslic3r/Support/SupportCommon.cpp create mode 100644 src/libslic3r/Support/SupportCommon.hpp create mode 100644 src/libslic3r/Support/SupportDebug.cpp create mode 100644 src/libslic3r/Support/SupportDebug.hpp create mode 100644 src/libslic3r/Support/SupportParameters.cpp diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 32d44cb5c..11f7adf76 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -276,7 +276,12 @@ set(SLIC3R_SOURCES SlicingAdaptive.hpp Subdivide.cpp Subdivide.hpp + Support/SupportCommon.cpp + Support/SupportCommon.hpp + Support/SupportDebug.cpp + Support/SupportDebug.hpp Support/SupportLayer.hpp + Support/SupportParameters.cpp Support/SupportParameters.hpp SupportMaterial.cpp SupportMaterial.hpp diff --git a/src/libslic3r/Support/SupportCommon.cpp b/src/libslic3r/Support/SupportCommon.cpp new file mode 100644 index 000000000..8825d8dd2 --- /dev/null +++ b/src/libslic3r/Support/SupportCommon.cpp @@ -0,0 +1,1807 @@ +#include "../ClipperUtils.hpp" +#include "../ExtrusionEntityCollection.hpp" +#include "../Layer.hpp" +#include "../Print.hpp" +#include "../Fill/FillBase.hpp" +#include "../Geometry.hpp" +#include "../Point.hpp" + +#include +#include + +#include + +#include "SupportCommon.hpp" +#include "SupportLayer.hpp" +#include "SupportParameters.hpp" + +// #define SLIC3R_DEBUG + +// Make assert active if SLIC3R_DEBUG +#ifdef SLIC3R_DEBUG + #define DEBUG + #define _DEBUG + #undef NDEBUG + #include "utils.hpp" + #include "SVG.hpp" +#endif + +#include + +namespace Slic3r::FFFSupport { + +// how much we extend support around the actual contact area +//FIXME this should be dependent on the nozzle diameter! +#define SUPPORT_MATERIAL_MARGIN 1.5 + +//#define SUPPORT_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 3. +//#define SUPPORT_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 1.5 +#define SUPPORT_SURFACES_OFFSET_PARAMETERS ClipperLib::jtSquare, 0. + +void remove_bridges_from_contacts( + const PrintConfig &print_config, + const Layer &lower_layer, + const LayerRegion &layerm, + float fw, + Polygons &contact_polygons) +{ + // compute the area of bridging perimeters + Polygons bridges; + { + // Surface supporting this layer, expanded by 0.5 * nozzle_diameter, as we consider this kind of overhang to be sufficiently supported. + Polygons lower_grown_slices = expand(lower_layer.lslices, + //FIXME to mimic the decision in the perimeter generator, we should use half the external perimeter width. + 0.5f * float(scale_(print_config.nozzle_diameter.get_at(layerm.region().config().perimeter_extruder-1))), + SUPPORT_SURFACES_OFFSET_PARAMETERS); + // Collect perimeters of this layer. + //FIXME split_at_first_point() could split a bridge mid-way + #if 0 + Polylines overhang_perimeters = layerm.perimeters.as_polylines(); + // workaround for Clipper bug, see Slic3r::Polygon::clip_as_polyline() + for (Polyline &polyline : overhang_perimeters) + polyline.points[0].x += 1; + // Trim the perimeters of this layer by the lower layer to get the unsupported pieces of perimeters. + overhang_perimeters = diff_pl(overhang_perimeters, lower_grown_slices); + #else + Polylines overhang_perimeters = diff_pl(layerm.perimeters().as_polylines(), lower_grown_slices); + #endif + + // only consider straight overhangs + // only consider overhangs having endpoints inside layer's slices + // convert bridging polylines into polygons by inflating them with their thickness + // since we're dealing with bridges, we can't assume width is larger than spacing, + // so we take the largest value and also apply safety offset to be ensure no gaps + // are left in between + Flow perimeter_bridge_flow = layerm.bridging_flow(frPerimeter); + //FIXME one may want to use a maximum of bridging flow width and normal flow width, as the perimeters are calculated using the normal flow + // and then turned to bridging flow, thus their centerlines are derived from non-bridging flow and expanding them by a bridging flow + // may not expand them to the edge of their respective islands. + const float w = float(0.5 * std::max(perimeter_bridge_flow.scaled_width(), perimeter_bridge_flow.scaled_spacing())) + scaled(0.001); + for (Polyline &polyline : overhang_perimeters) + if (polyline.is_straight()) { + // This is a bridge + polyline.extend_start(fw); + polyline.extend_end(fw); + // Is the straight perimeter segment supported at both sides? + Point pts[2] = { polyline.first_point(), polyline.last_point() }; + bool supported[2] = { false, false }; + for (size_t i = 0; i < lower_layer.lslices.size() && ! (supported[0] && supported[1]); ++ i) + for (int j = 0; j < 2; ++ j) + if (! supported[j] && lower_layer.lslices_ex[i].bbox.contains(pts[j]) && lower_layer.lslices[i].contains(pts[j])) + supported[j] = true; + if (supported[0] && supported[1]) + // Offset a polyline into a thick line. + polygons_append(bridges, offset(polyline, w)); + } + bridges = union_(bridges); + } + // remove the entire bridges and only support the unsupported edges + //FIXME the brided regions are already collected as layerm.bridged. Use it? + for (const Surface &surface : layerm.fill_surfaces()) + if (surface.surface_type == stBottomBridge && surface.bridge_angle >= 0.0) + polygons_append(bridges, surface.expolygon); + //FIXME add the gap filled areas. Extrude the gaps with a bridge flow? + // Remove the unsupported ends of the bridges from the bridged areas. + //FIXME add supports at regular intervals to support long bridges! + bridges = diff(bridges, + // Offset unsupported edges into polygons. + offset(layerm.unsupported_bridge_edges(), scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS)); + // Remove bridged areas from the supported areas. + contact_polygons = diff(contact_polygons, bridges, ApplySafetyOffset::Yes); + + #ifdef SLIC3R_DEBUG + static int iRun = 0; + SVG::export_expolygons(debug_out_path("support-top-contacts-remove-bridges-run%d.svg", iRun ++), + { { { union_ex(offset(layerm.unsupported_bridge_edges(), scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS)) }, { "unsupported_bridge_edges", "orange", 0.5f } }, + { { union_ex(contact_polygons) }, { "contact_polygons", "blue", 0.5f } }, + { { union_ex(bridges) }, { "bridges", "red", "black", "", scaled(0.1f), 0.5f } } }); + #endif /* SLIC3R_DEBUG */ +} + +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) +{ + // If there is brim to be generated, calculate the trimming regions. + Polygons brim; + if (object.has_brim()) { + // The object does not have a raft. + // Calculate the area covered by the brim. + const BrimType brim_type = object.config().brim_type; + const bool brim_outer = brim_type == btOuterOnly || brim_type == btOuterAndInner; + const bool brim_inner = brim_type == btInnerOnly || brim_type == btOuterAndInner; + const auto brim_separation = scaled(object.config().brim_separation.value + object.config().brim_width.value); + for (const ExPolygon &ex : object.layers().front()->lslices) { + if (brim_outer && brim_inner) + polygons_append(brim, offset(ex, brim_separation)); + else { + if (brim_outer) + polygons_append(brim, offset(ex.contour, brim_separation, ClipperLib::jtRound, float(scale_(0.1)))); + else + brim.emplace_back(ex.contour); + if (brim_inner) { + Polygons holes = ex.holes; + polygons_reverse(holes); + holes = shrink(holes, brim_separation, ClipperLib::jtRound, float(scale_(0.1))); + polygons_reverse(holes); + polygons_append(brim, std::move(holes)); + } else + polygons_append(brim, ex.holes); + } + } + brim = union_(brim); + } + + // How much to inflate the support columns to be stable. This also applies to the 1st layer, if no raft layers are to be printed. + const float inflate_factor_fine = float(scale_((slicing_params.raft_layers() > 1) ? 0.5 : EPSILON)); + const float inflate_factor_1st_layer = std::max(0.f, float(scale_(object.config().raft_first_layer_expansion)) - inflate_factor_fine); + SupportGeneratorLayer *contacts = top_contacts .empty() ? nullptr : top_contacts .front(); + SupportGeneratorLayer *interfaces = interface_layers .empty() ? nullptr : interface_layers .front(); + SupportGeneratorLayer *base_interfaces = base_interface_layers.empty() ? nullptr : base_interface_layers.front(); + SupportGeneratorLayer *columns_base = base_layers .empty() ? nullptr : base_layers .front(); + if (contacts != nullptr && contacts->print_z > std::max(slicing_params.first_print_layer_height, slicing_params.raft_contact_top_z) + EPSILON) + // This is not the raft contact layer. + contacts = nullptr; + if (interfaces != nullptr && interfaces->bottom_print_z() > slicing_params.raft_interface_top_z + EPSILON) + // This is not the raft column base layer. + interfaces = nullptr; + if (base_interfaces != nullptr && base_interfaces->bottom_print_z() > slicing_params.raft_interface_top_z + EPSILON) + // This is not the raft column base layer. + base_interfaces = nullptr; + if (columns_base != nullptr && columns_base->bottom_print_z() > slicing_params.raft_interface_top_z + EPSILON) + // This is not the raft interface layer. + columns_base = nullptr; + + Polygons interface_polygons; + if (contacts != nullptr && ! contacts->polygons.empty()) + polygons_append(interface_polygons, expand(contacts->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS)); + if (interfaces != nullptr && ! interfaces->polygons.empty()) + polygons_append(interface_polygons, expand(interfaces->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS)); + if (base_interfaces != nullptr && ! base_interfaces->polygons.empty()) + polygons_append(interface_polygons, expand(base_interfaces->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS)); + + // Output vector. + SupportGeneratorLayersPtr raft_layers; + + if (slicing_params.raft_layers() > 1) { + Polygons base; + Polygons columns; + Polygons first_layer; + if (columns_base != nullptr) { + if (columns_base->bottom_print_z() > slicing_params.raft_interface_top_z - EPSILON) { + // Classic supports with colums above the raft interface. + base = columns_base->polygons; + columns = base; + if (! interface_polygons.empty()) + // Trim the 1st layer columns with the inflated interface polygons. + columns = diff(columns, interface_polygons); + } else { + // Organic supports with raft on print bed. + assert(is_approx(columns_base->print_z, slicing_params.first_print_layer_height)); + first_layer = columns_base->polygons; + } + } + if (! interface_polygons.empty()) { + // Merge the untrimmed columns base with the expanded raft interface, to be used for the support base and interface. + base = union_(base, interface_polygons); + } + // Do not add the raft contact layer, only add the raft layers below the contact layer. + // Insert the 1st layer. + { + SupportGeneratorLayer &new_layer = layer_storage.allocate_unguarded(slicing_params.base_raft_layers > 0 ? SupporLayerType::RaftBase : SupporLayerType::RaftInterface); + raft_layers.push_back(&new_layer); + new_layer.print_z = slicing_params.first_print_layer_height; + new_layer.height = slicing_params.first_print_layer_height; + new_layer.bottom_z = 0.; + first_layer = union_(std::move(first_layer), base); + new_layer.polygons = inflate_factor_1st_layer > 0 ? expand(first_layer, inflate_factor_1st_layer) : first_layer; + } + // Insert the base layers. + for (size_t i = 1; i < slicing_params.base_raft_layers; ++ i) { + coordf_t print_z = raft_layers.back()->print_z; + SupportGeneratorLayer &new_layer = layer_storage.allocate_unguarded(SupporLayerType::RaftBase); + raft_layers.push_back(&new_layer); + new_layer.print_z = print_z + slicing_params.base_raft_layer_height; + new_layer.height = slicing_params.base_raft_layer_height; + new_layer.bottom_z = print_z; + new_layer.polygons = base; + } + // Insert the interface layers. + for (size_t i = 1; i < slicing_params.interface_raft_layers; ++ i) { + coordf_t print_z = raft_layers.back()->print_z; + SupportGeneratorLayer &new_layer = layer_storage.allocate_unguarded(SupporLayerType::RaftInterface); + raft_layers.push_back(&new_layer); + new_layer.print_z = print_z + slicing_params.interface_raft_layer_height; + new_layer.height = slicing_params.interface_raft_layer_height; + new_layer.bottom_z = print_z; + new_layer.polygons = interface_polygons; + //FIXME misusing contact_polygons for support columns. + new_layer.contact_polygons = std::make_unique(columns); + } + } else { + if (columns_base != nullptr) { + // Expand the bases of the support columns in the 1st layer. + Polygons &raft = columns_base->polygons; + Polygons trimming = offset(object.layers().front()->lslices, (float)scale_(support_params.gap_xy), SUPPORT_SURFACES_OFFSET_PARAMETERS); + if (inflate_factor_1st_layer > SCALED_EPSILON) { + // Inflate in multiple steps to avoid leaking of the support 1st layer through object walls. + auto nsteps = std::max(5, int(ceil(inflate_factor_1st_layer / support_params.first_layer_flow.scaled_width()))); + float step = inflate_factor_1st_layer / nsteps; + for (int i = 0; i < nsteps; ++ i) + raft = diff(expand(raft, step), trimming); + } else + raft = diff(raft, trimming); + if (! interface_polygons.empty()) + columns_base->polygons = diff(columns_base->polygons, interface_polygons); + } + if (! brim.empty()) { + if (columns_base) + columns_base->polygons = diff(columns_base->polygons, brim); + if (contacts) + contacts->polygons = diff(contacts->polygons, brim); + if (interfaces) + interfaces->polygons = diff(interfaces->polygons, brim); + if (base_interfaces) + base_interfaces->polygons = diff(base_interfaces->polygons, brim); + } + } + + return raft_layers; +} + +static inline void fill_expolygon_generate_paths( + ExtrusionEntitiesPtr &dst, + ExPolygon &&expolygon, + Fill *filler, + const FillParams &fill_params, + float density, + ExtrusionRole role, + const Flow &flow) +{ + Surface surface(stInternal, std::move(expolygon)); + Polylines polylines; + try { + assert(!fill_params.use_arachne); + polylines = filler->fill_surface(&surface, fill_params); + } catch (InfillFailedException &) { + } + extrusion_entities_append_paths( + dst, + std::move(polylines), + role, + flow.mm3_per_mm(), flow.width(), flow.height()); +} + +static inline void fill_expolygons_generate_paths( + ExtrusionEntitiesPtr &dst, + ExPolygons &&expolygons, + Fill *filler, + const FillParams &fill_params, + float density, + ExtrusionRole role, + const Flow &flow) +{ + for (ExPolygon &expoly : expolygons) + fill_expolygon_generate_paths(dst, std::move(expoly), filler, fill_params, density, role, flow); +} + +static inline void fill_expolygons_generate_paths( + ExtrusionEntitiesPtr &dst, + ExPolygons &&expolygons, + Fill *filler, + float density, + ExtrusionRole role, + const Flow &flow) +{ + FillParams fill_params; + fill_params.density = density; + fill_params.dont_adjust = true; + fill_expolygons_generate_paths(dst, std::move(expolygons), filler, fill_params, density, role, flow); +} + +static Polylines draw_perimeters(const ExPolygon &expoly, double clip_length) +{ + // Draw the perimeters. + Polylines polylines; + polylines.reserve(expoly.holes.size() + 1); + for (size_t i = 0; i <= expoly.holes.size(); ++ i) { + Polyline pl(i == 0 ? expoly.contour.points : expoly.holes[i - 1].points); + pl.points.emplace_back(pl.points.front()); + if (i > 0) + // It is a hole, reverse it. + pl.reverse(); + // so that all contours are CCW oriented. + pl.clip_end(clip_length); + polylines.emplace_back(std::move(pl)); + } + return polylines; +} + +static inline void tree_supports_generate_paths( + ExtrusionEntitiesPtr &dst, + const Polygons &polygons, + const Flow &flow) +{ + // Offset expolygon inside, returns number of expolygons collected (0 or 1). + // Vertices of output paths are marked with Z = source contour index of the expoly. + // Vertices at the intersection of source contours are marked with Z = -1. + auto shrink_expolygon_with_contour_idx = [](const Slic3r::ExPolygon &expoly, const float delta, ClipperLib::JoinType joinType, double miterLimit, ClipperLib_Z::Paths &out) -> int + { + assert(delta > 0); + auto append_paths_with_z = [](ClipperLib::Paths &src, coord_t contour_idx, ClipperLib_Z::Paths &dst) { + dst.reserve(next_highest_power_of_2(dst.size() + src.size())); + for (const ClipperLib::Path &contour : src) { + ClipperLib_Z::Path tmp; + tmp.reserve(contour.size()); + for (const Point &p : contour) + tmp.emplace_back(p.x(), p.y(), contour_idx); + dst.emplace_back(std::move(tmp)); + } + }; + + // 1) Offset the outer contour. + ClipperLib_Z::Paths contours; + { + ClipperLib::ClipperOffset co; + if (joinType == jtRound) + co.ArcTolerance = miterLimit; + else + co.MiterLimit = miterLimit; + co.ShortestEdgeLength = double(delta * 0.005); + co.AddPath(expoly.contour.points, joinType, ClipperLib::etClosedPolygon); + ClipperLib::Paths contours_raw; + co.Execute(contours_raw, - delta); + if (contours_raw.empty()) + // No need to try to offset the holes. + return 0; + append_paths_with_z(contours_raw, 0, contours); + } + + if (expoly.holes.empty()) { + // No need to subtract holes from the offsetted expolygon, we are done. + append(out, std::move(contours)); + } else { + // 2) Offset the holes one by one, collect the offsetted holes. + ClipperLib_Z::Paths holes; + { + for (const Polygon &hole : expoly.holes) { + ClipperLib::ClipperOffset co; + if (joinType == jtRound) + co.ArcTolerance = miterLimit; + else + co.MiterLimit = miterLimit; + co.ShortestEdgeLength = double(delta * 0.005); + co.AddPath(hole.points, joinType, ClipperLib::etClosedPolygon); + ClipperLib::Paths out2; + // Execute reorients the contours so that the outer most contour has a positive area. Thus the output + // contours will be CCW oriented even though the input paths are CW oriented. + // Offset is applied after contour reorientation, thus the signum of the offset value is reversed. + co.Execute(out2, delta); + append_paths_with_z(out2, 1 + (&hole - expoly.holes.data()), holes); + } + } + + // 3) Subtract holes from the contours. + if (holes.empty()) { + // No hole remaining after an offset. Just copy the outer contour. + append(out, std::move(contours)); + } else { + // Negative offset. There is a chance, that the offsetted hole intersects the outer contour. + // Subtract the offsetted holes from the offsetted contours. + ClipperLib_Z::Clipper clipper; + clipper.ZFillFunction([](const ClipperLib_Z::IntPoint &e1bot, const ClipperLib_Z::IntPoint &e1top, const ClipperLib_Z::IntPoint &e2bot, const ClipperLib_Z::IntPoint &e2top, ClipperLib_Z::IntPoint &pt) { + //pt.z() = std::max(std::max(e1bot.z(), e1top.z()), std::max(e2bot.z(), e2top.z())); + // Just mark the intersection. + pt.z() = -1; + }); + clipper.AddPaths(contours, ClipperLib_Z::ptSubject, true); + clipper.AddPaths(holes, ClipperLib_Z::ptClip, true); + ClipperLib_Z::Paths output; + clipper.Execute(ClipperLib_Z::ctDifference, output, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero); + if (! output.empty()) { + append(out, std::move(output)); + } else { + // The offsetted holes have eaten up the offsetted outer contour. + return 0; + } + } + } + + return 1; + }; + + const double spacing = flow.scaled_spacing(); + // Clip the sheath path to avoid the extruder to get exactly on the first point of the loop. + const double clip_length = spacing * 0.15; + const double anchor_length = spacing * 6.; + ClipperLib_Z::Paths anchor_candidates; + for (ExPolygon& expoly : closing_ex(polygons, float(SCALED_EPSILON), float(SCALED_EPSILON + 0.5 * flow.scaled_width()))) { + std::unique_ptr eec; + double area = expoly.area(); + if (area > sqr(scaled(5.))) { + eec = std::make_unique(); + // Don't reoder internal / external loops of the same island, always start with the internal loop. + eec->no_sort = true; + // Make the tree branch stable by adding another perimeter. + ExPolygons level2 = offset2_ex({ expoly }, -1.5 * flow.scaled_width(), 0.5 * flow.scaled_width()); + if (level2.size() == 1) { + Polylines polylines; + extrusion_entities_append_paths(eec->entities, draw_perimeters(expoly, clip_length), ExtrusionRole::SupportMaterial, flow.mm3_per_mm(), flow.width(), flow.height(), + // Disable reversal of the path, always start with the anchor, always print CCW. + false); + expoly = level2.front(); + } + } + + // Try to produce one more perimeter to place the seam anchor. + // First genrate a 2nd perimeter loop as a source for anchor candidates. + // The anchor candidate points are annotated with an index of the source contour or with -1 if on intersection. + anchor_candidates.clear(); + shrink_expolygon_with_contour_idx(expoly, flow.scaled_width(), DefaultJoinType, 1.2, anchor_candidates); + // Orient all contours CW. + for (auto &path : anchor_candidates) + if (ClipperLib_Z::Area(path) > 0) + std::reverse(path.begin(), path.end()); + + // Draw the perimeters. + Polylines polylines; + polylines.reserve(expoly.holes.size() + 1); + for (size_t idx_loop = 0; idx_loop < expoly.num_contours(); ++ idx_loop) { + // Open the loop with a seam. + const Polygon &loop = expoly.contour_or_hole(idx_loop); + Polyline pl(loop.points); + // Orient all contours CW, because the anchor will be added to the end of polyline while we want to start a loop with the anchor. + if (idx_loop == 0) + // It is an outer contour. + pl.reverse(); + pl.points.emplace_back(pl.points.front()); + pl.clip_end(clip_length); + if (pl.size() < 2) + continue; + // Find the foot of the seam point on anchor_candidates. Only pick an anchor point that was created by offsetting the source contour. + ClipperLib_Z::Path *closest_contour = nullptr; + Vec2d closest_point; + int closest_point_idx = -1; + double closest_point_t; + double d2min = std::numeric_limits::max(); + Vec2d seam_pt = pl.back().cast(); + for (ClipperLib_Z::Path &path : anchor_candidates) + for (int i = 0; i < path.size(); ++ i) { + int j = next_idx_modulo(i, path); + if (path[i].z() == idx_loop || path[j].z() == idx_loop) { + Vec2d pi(path[i].x(), path[i].y()); + Vec2d pj(path[j].x(), path[j].y()); + Vec2d v = pj - pi; + Vec2d w = seam_pt - pi; + auto l2 = v.squaredNorm(); + auto t = std::clamp((l2 == 0) ? 0 : v.dot(w) / l2, 0., 1.); + if ((path[i].z() == idx_loop || t > EPSILON) && (path[j].z() == idx_loop || t < 1. - EPSILON)) { + // Closest point. + Vec2d fp = pi + v * t; + double d2 = (fp - seam_pt).squaredNorm(); + if (d2 < d2min) { + d2min = d2; + closest_contour = &path; + closest_point = fp; + closest_point_idx = i; + closest_point_t = t; + } + } + } + } + if (d2min < sqr(flow.scaled_width() * 3.)) { + // Try to cut an anchor from the closest_contour. + // Both closest_contour and pl are CW oriented. + pl.points.emplace_back(closest_point.cast()); + const ClipperLib_Z::Path &path = *closest_contour; + double remaining_length = anchor_length - (seam_pt - closest_point).norm(); + int i = closest_point_idx; + int j = next_idx_modulo(i, *closest_contour); + Vec2d pi(path[i].x(), path[i].y()); + Vec2d pj(path[j].x(), path[j].y()); + Vec2d v = pj - pi; + double l = v.norm(); + if (remaining_length < (1. - closest_point_t) * l) { + // Just trim the current line. + pl.points.emplace_back((closest_point + v * (remaining_length / l)).cast()); + } else { + // Take the rest of the current line, continue with the other lines. + pl.points.emplace_back(path[j].x(), path[j].y()); + pi = pj; + for (i = j; path[i].z() == idx_loop && remaining_length > 0; i = j, pi = pj) { + j = next_idx_modulo(i, path); + pj = Vec2d(path[j].x(), path[j].y()); + v = pj - pi; + l = v.norm(); + if (i == closest_point_idx) { + // Back at the first segment. Most likely this should not happen and we may end the anchor. + break; + } + if (remaining_length <= l) { + pl.points.emplace_back((pi + v * (remaining_length / l)).cast()); + break; + } + pl.points.emplace_back(path[j].x(), path[j].y()); + remaining_length -= l; + } + } + } + // Start with the anchor. + pl.reverse(); + polylines.emplace_back(std::move(pl)); + } + + ExtrusionEntitiesPtr &out = eec ? eec->entities : dst; + extrusion_entities_append_paths(out, std::move(polylines), ExtrusionRole::SupportMaterial, flow.mm3_per_mm(), flow.width(), flow.height(), + // Disable reversal of the path, always start with the anchor, always print CCW. + false); + if (eec) { + std::reverse(eec->entities.begin(), eec->entities.end()); + dst.emplace_back(eec.release()); + } + } +} + +static inline void fill_expolygons_with_sheath_generate_paths( + ExtrusionEntitiesPtr &dst, + const Polygons &polygons, + Fill *filler, + float density, + ExtrusionRole role, + const Flow &flow, + bool with_sheath, + bool no_sort) +{ + if (polygons.empty()) + return; + + if (! with_sheath) { + fill_expolygons_generate_paths(dst, closing_ex(polygons, float(SCALED_EPSILON)), filler, density, role, flow); + return; + } + + FillParams fill_params; + fill_params.density = density; + fill_params.dont_adjust = true; + + const double spacing = flow.scaled_spacing(); + // Clip the sheath path to avoid the extruder to get exactly on the first point of the loop. + const double clip_length = spacing * 0.15; + + for (ExPolygon &expoly : closing_ex(polygons, float(SCALED_EPSILON), float(SCALED_EPSILON + 0.5*flow.scaled_width()))) { + // Don't reorder the skirt and its infills. + std::unique_ptr eec; + if (no_sort) { + eec = std::make_unique(); + eec->no_sort = true; + } + ExtrusionEntitiesPtr &out = no_sort ? eec->entities : dst; + extrusion_entities_append_paths(out, draw_perimeters(expoly, clip_length), ExtrusionRole::SupportMaterial, flow.mm3_per_mm(), flow.width(), flow.height()); + // Fill in the rest. + fill_expolygons_generate_paths(out, offset_ex(expoly, float(-0.4 * spacing)), filler, fill_params, density, role, flow); + if (no_sort && ! eec->empty()) + dst.emplace_back(eec.release()); + } +} + +// Support layers, partially processed. +struct SupportGeneratorLayerExtruded +{ + SupportGeneratorLayerExtruded& operator=(SupportGeneratorLayerExtruded &&rhs) { + this->layer = rhs.layer; + this->extrusions = std::move(rhs.extrusions); + m_polygons_to_extrude = std::move(rhs.m_polygons_to_extrude); + rhs.layer = nullptr; + return *this; + } + + bool empty() const { + return layer == nullptr || layer->polygons.empty(); + } + + void set_polygons_to_extrude(Polygons &&polygons) { + if (m_polygons_to_extrude == nullptr) + m_polygons_to_extrude = std::make_unique(std::move(polygons)); + else + *m_polygons_to_extrude = std::move(polygons); + } + Polygons& polygons_to_extrude() { return (m_polygons_to_extrude == nullptr) ? layer->polygons : *m_polygons_to_extrude; } + const Polygons& polygons_to_extrude() const { return (m_polygons_to_extrude == nullptr) ? layer->polygons : *m_polygons_to_extrude; } + + bool could_merge(const SupportGeneratorLayerExtruded &other) const { + return ! this->empty() && ! other.empty() && + std::abs(this->layer->height - other.layer->height) < EPSILON && + this->layer->bridging == other.layer->bridging; + } + + // Merge regions, perform boolean union over the merged polygons. + void merge(SupportGeneratorLayerExtruded &&other) { + assert(this->could_merge(other)); + // 1) Merge the rest polygons to extrude, if there are any. + if (other.m_polygons_to_extrude != nullptr) { + if (m_polygons_to_extrude == nullptr) { + // This layer has no extrusions generated yet, if it has no m_polygons_to_extrude (its area to extrude was not reduced yet). + assert(this->extrusions.empty()); + m_polygons_to_extrude = std::make_unique(this->layer->polygons); + } + Slic3r::polygons_append(*m_polygons_to_extrude, std::move(*other.m_polygons_to_extrude)); + *m_polygons_to_extrude = union_safety_offset(*m_polygons_to_extrude); + other.m_polygons_to_extrude.reset(); + } else if (m_polygons_to_extrude != nullptr) { + assert(other.m_polygons_to_extrude == nullptr); + // The other layer has no extrusions generated yet, if it has no m_polygons_to_extrude (its area to extrude was not reduced yet). + assert(other.extrusions.empty()); + Slic3r::polygons_append(*m_polygons_to_extrude, other.layer->polygons); + *m_polygons_to_extrude = union_safety_offset(*m_polygons_to_extrude); + } + // 2) Merge the extrusions. + this->extrusions.insert(this->extrusions.end(), other.extrusions.begin(), other.extrusions.end()); + other.extrusions.clear(); + // 3) Merge the infill polygons. + Slic3r::polygons_append(this->layer->polygons, std::move(other.layer->polygons)); + this->layer->polygons = union_safety_offset(this->layer->polygons); + other.layer->polygons.clear(); + } + + void polygons_append(Polygons &dst) const { + if (layer != NULL && ! layer->polygons.empty()) + Slic3r::polygons_append(dst, layer->polygons); + } + + // The source layer. It carries the height and extrusion type (bridging / non bridging, extrusion height). + SupportGeneratorLayer *layer { nullptr }; + // Collect extrusions. They will be exported sorted by the bottom height. + ExtrusionEntitiesPtr extrusions; + +private: + // In case the extrusions are non-empty, m_polygons_to_extrude may contain the rest areas yet to be filled by additional support. + // This is useful mainly for the loop interfaces, which are generated before the zig-zag infills. + std::unique_ptr m_polygons_to_extrude; +}; + +typedef std::vector SupportGeneratorLayerExtrudedPtrs; + +struct LoopInterfaceProcessor +{ + LoopInterfaceProcessor(coordf_t circle_r) : + n_contact_loops(0), + circle_radius(circle_r), + circle_distance(circle_r * 3.) + { + // Shape of the top contact area. + circle.points.reserve(6); + for (size_t i = 0; i < 6; ++ i) { + double angle = double(i) * M_PI / 3.; + circle.points.push_back(Point(circle_radius * cos(angle), circle_radius * sin(angle))); + } + } + + // Generate loop contacts at the top_contact_layer, + // trim the top_contact_layer->polygons with the areas covered by the loops. + void generate(SupportGeneratorLayerExtruded &top_contact_layer, const Flow &interface_flow_src) const; + + int n_contact_loops; + coordf_t circle_radius; + coordf_t circle_distance; + Polygon circle; +}; + +void LoopInterfaceProcessor::generate(SupportGeneratorLayerExtruded &top_contact_layer, const Flow &interface_flow_src) const +{ + if (n_contact_loops == 0 || top_contact_layer.empty()) + return; + + Flow flow = interface_flow_src.with_height(top_contact_layer.layer->height); + + Polygons overhang_polygons; + if (top_contact_layer.layer->overhang_polygons != nullptr) + overhang_polygons = std::move(*top_contact_layer.layer->overhang_polygons); + + // Generate the outermost loop. + // Find centerline of the external loop (or any other kind of extrusions should the loop be skipped) + ExPolygons top_contact_expolygons = offset_ex(union_ex(top_contact_layer.layer->polygons), - 0.5f * flow.scaled_width()); + + // Grid size and bit shifts for quick and exact to/from grid coordinates manipulation. + coord_t circle_grid_resolution = 1; + coord_t circle_grid_powerof2 = 0; + { + // epsilon to account for rounding errors + coord_t circle_grid_resolution_non_powerof2 = coord_t(2. * circle_distance + 3.); + while (circle_grid_resolution < circle_grid_resolution_non_powerof2) { + circle_grid_resolution <<= 1; + ++ circle_grid_powerof2; + } + } + + struct PointAccessor { + const Point* operator()(const Point &pt) const { return &pt; } + }; + typedef ClosestPointInRadiusLookup ClosestPointLookupType; + + Polygons loops0; + { + // find centerline of the external loop of the contours + // Only consider the loops facing the overhang. + Polygons external_loops; + // Holes in the external loops. + Polygons circles; + Polygons overhang_with_margin = offset(union_ex(overhang_polygons), 0.5f * flow.scaled_width()); + for (ExPolygons::iterator it_contact_expoly = top_contact_expolygons.begin(); it_contact_expoly != top_contact_expolygons.end(); ++ it_contact_expoly) { + // Store the circle centers placed for an expolygon into a regular grid, hashed by the circle centers. + ClosestPointLookupType circle_centers_lookup(coord_t(circle_distance - SCALED_EPSILON)); + Points circle_centers; + Point center_last; + // For each contour of the expolygon, start with the outer contour, continue with the holes. + for (size_t i_contour = 0; i_contour <= it_contact_expoly->holes.size(); ++ i_contour) { + Polygon &contour = (i_contour == 0) ? it_contact_expoly->contour : it_contact_expoly->holes[i_contour - 1]; + const Point *seg_current_pt = nullptr; + coordf_t seg_current_t = 0.; + if (! intersection_pl(contour.split_at_first_point(), overhang_with_margin).empty()) { + // The contour is below the overhang at least to some extent. + //FIXME ideally one would place the circles below the overhang only. + // Walk around the contour and place circles so their centers are not closer than circle_distance from each other. + if (circle_centers.empty()) { + // Place the first circle. + seg_current_pt = &contour.points.front(); + seg_current_t = 0.; + center_last = *seg_current_pt; + circle_centers_lookup.insert(center_last); + circle_centers.push_back(center_last); + } + for (Points::const_iterator it = contour.points.begin() + 1; it != contour.points.end(); ++it) { + // Is it possible to place a circle on this segment? Is it not too close to any of the circles already placed on this contour? + const Point &p1 = *(it-1); + const Point &p2 = *it; + // Intersection of a ray (p1, p2) with a circle placed at center_last, with radius of circle_distance. + const Vec2d v_seg(coordf_t(p2(0)) - coordf_t(p1(0)), coordf_t(p2(1)) - coordf_t(p1(1))); + const Vec2d v_cntr(coordf_t(p1(0) - center_last(0)), coordf_t(p1(1) - center_last(1))); + coordf_t a = v_seg.squaredNorm(); + coordf_t b = 2. * v_seg.dot(v_cntr); + coordf_t c = v_cntr.squaredNorm() - circle_distance * circle_distance; + coordf_t disc = b * b - 4. * a * c; + if (disc > 0.) { + // The circle intersects a ray. Avoid the parts of the segment inside the circle. + coordf_t t1 = (-b - sqrt(disc)) / (2. * a); + coordf_t t2 = (-b + sqrt(disc)) / (2. * a); + coordf_t t0 = (seg_current_pt == &p1) ? seg_current_t : 0.; + // Take the lowest t in , excluding . + coordf_t t; + if (t0 <= t1) + t = t0; + else if (t2 <= 1.) + t = t2; + else { + // Try the following segment. + seg_current_pt = nullptr; + continue; + } + seg_current_pt = &p1; + seg_current_t = t; + center_last = Point(p1(0) + coord_t(v_seg(0) * t), p1(1) + coord_t(v_seg(1) * t)); + // It has been verified that the new point is far enough from center_last. + // Ensure, that it is far enough from all the centers. + std::pair circle_closest = circle_centers_lookup.find(center_last); + if (circle_closest.first != nullptr) { + -- it; + continue; + } + } else { + // All of the segment is outside the circle. Take the first point. + seg_current_pt = &p1; + seg_current_t = 0.; + center_last = p1; + } + // Place the first circle. + circle_centers_lookup.insert(center_last); + circle_centers.push_back(center_last); + } + external_loops.push_back(std::move(contour)); + for (const Point ¢er : circle_centers) { + circles.push_back(circle); + circles.back().translate(center); + } + } + } + } + // Apply a pattern to the external loops. + loops0 = diff(external_loops, circles); + } + + Polylines loop_lines; + { + // make more loops + Polygons loop_polygons = loops0; + for (int i = 1; i < n_contact_loops; ++ i) + polygons_append(loop_polygons, + opening( + loops0, + i * flow.scaled_spacing() + 0.5f * flow.scaled_spacing(), + 0.5f * flow.scaled_spacing())); + // Clip such loops to the side oriented towards the object. + // Collect split points, so they will be recognized after the clipping. + // At the split points the clipped pieces will be stitched back together. + loop_lines.reserve(loop_polygons.size()); + std::unordered_map map_split_points; + for (Polygons::const_iterator it = loop_polygons.begin(); it != loop_polygons.end(); ++ it) { + assert(map_split_points.find(it->first_point()) == map_split_points.end()); + map_split_points[it->first_point()] = -1; + loop_lines.push_back(it->split_at_first_point()); + } + loop_lines = intersection_pl(loop_lines, expand(overhang_polygons, scale_(SUPPORT_MATERIAL_MARGIN))); + // Because a closed loop has been split to a line, loop_lines may contain continuous segments split to 2 pieces. + // Try to connect them. + for (int i_line = 0; i_line < int(loop_lines.size()); ++ i_line) { + Polyline &polyline = loop_lines[i_line]; + auto it = map_split_points.find(polyline.first_point()); + if (it != map_split_points.end()) { + // This is a stitching point. + // If this assert triggers, multiple source polygons likely intersected at this point. + assert(it->second != -2); + if (it->second < 0) { + // First occurence. + it->second = i_line; + } else { + // Second occurence. Join the lines. + Polyline &polyline_1st = loop_lines[it->second]; + assert(polyline_1st.first_point() == it->first || polyline_1st.last_point() == it->first); + if (polyline_1st.first_point() == it->first) + polyline_1st.reverse(); + polyline_1st.append(std::move(polyline)); + it->second = -2; + } + continue; + } + it = map_split_points.find(polyline.last_point()); + if (it != map_split_points.end()) { + // This is a stitching point. + // If this assert triggers, multiple source polygons likely intersected at this point. + assert(it->second != -2); + if (it->second < 0) { + // First occurence. + it->second = i_line; + } else { + // Second occurence. Join the lines. + Polyline &polyline_1st = loop_lines[it->second]; + assert(polyline_1st.first_point() == it->first || polyline_1st.last_point() == it->first); + if (polyline_1st.first_point() == it->first) + polyline_1st.reverse(); + polyline.reverse(); + polyline_1st.append(std::move(polyline)); + it->second = -2; + } + } + } + // Remove empty lines. + remove_degenerate(loop_lines); + } + + // add the contact infill area to the interface area + // note that growing loops by $circle_radius ensures no tiny + // extrusions are left inside the circles; however it creates + // a very large gap between loops and contact_infill_polygons, so maybe another + // solution should be found to achieve both goals + // Store the trimmed polygons into a separate polygon set, so the original infill area remains intact for + // "modulate by layer thickness". + top_contact_layer.set_polygons_to_extrude(diff(top_contact_layer.layer->polygons, offset(loop_lines, float(circle_radius * 1.1)))); + + // Transform loops into ExtrusionPath objects. + extrusion_entities_append_paths( + top_contact_layer.extrusions, + std::move(loop_lines), + ExtrusionRole::SupportMaterialInterface, flow.mm3_per_mm(), flow.width(), flow.height()); +} + +#ifdef SLIC3R_DEBUG +static std::string dbg_index_to_color(int idx) +{ + if (idx < 0) + return "yellow"; + idx = idx % 3; + switch (idx) { + case 0: return "red"; + case 1: return "green"; + default: return "blue"; + } +} +#endif /* SLIC3R_DEBUG */ + +// When extruding a bottom interface layer over an object, the bottom interface layer is extruded in a thin air, therefore +// it is being extruded with a bridging flow to not shrink excessively (the die swell effect). +// Tiny extrusions are better avoided and it is always better to anchor the thread to an existing support structure if possible. +// Therefore the bottom interface spots are expanded a bit. The expanded regions may overlap with another bottom interface layers, +// leading to over extrusion, where they overlap. The over extrusion is better avoided as it often makes the interface layers +// to stick too firmly to the object. +// +// Modulate thickness (increase bottom_z) of extrusions_in_out generated for this_layer +// if they overlap with overlapping_layers, whose print_z is above this_layer.bottom_z() and below this_layer.print_z. +static void modulate_extrusion_by_overlapping_layers( + // Extrusions generated for this_layer. + ExtrusionEntitiesPtr &extrusions_in_out, + const SupportGeneratorLayer &this_layer, + // Multiple layers overlapping with this_layer, sorted bottom up. + const SupportGeneratorLayersPtr &overlapping_layers) +{ + size_t n_overlapping_layers = overlapping_layers.size(); + if (n_overlapping_layers == 0 || extrusions_in_out.empty()) + // The extrusions do not overlap with any other extrusion. + return; + + // Get the initial extrusion parameters. + ExtrusionPath *extrusion_path_template = dynamic_cast(extrusions_in_out.front()); + assert(extrusion_path_template != nullptr); + ExtrusionRole extrusion_role = extrusion_path_template->role(); + float extrusion_width = extrusion_path_template->width; + + struct ExtrusionPathFragment + { + ExtrusionPathFragment() : mm3_per_mm(-1), width(-1), height(-1) {}; + ExtrusionPathFragment(double mm3_per_mm, float width, float height) : mm3_per_mm(mm3_per_mm), width(width), height(height) {}; + + Polylines polylines; + double mm3_per_mm; + float width; + float height; + }; + + // Split the extrusions by the overlapping layers, reduce their extrusion rate. + // The last path_fragment is from this_layer. + std::vector path_fragments( + n_overlapping_layers + 1, + ExtrusionPathFragment(extrusion_path_template->mm3_per_mm, extrusion_path_template->width, extrusion_path_template->height)); + // Don't use it, it will be released. + extrusion_path_template = nullptr; + +#ifdef SLIC3R_DEBUG + static int iRun = 0; + ++ iRun; + BoundingBox bbox; + for (size_t i_overlapping_layer = 0; i_overlapping_layer < n_overlapping_layers; ++ i_overlapping_layer) { + const SupportGeneratorLayer &overlapping_layer = *overlapping_layers[i_overlapping_layer]; + bbox.merge(get_extents(overlapping_layer.polygons)); + } + for (ExtrusionEntitiesPtr::const_iterator it = extrusions_in_out.begin(); it != extrusions_in_out.end(); ++ it) { + ExtrusionPath *path = dynamic_cast(*it); + assert(path != nullptr); + bbox.merge(get_extents(path->polyline)); + } + SVG svg(debug_out_path("support-fragments-%d-%lf.svg", iRun, this_layer.print_z).c_str(), bbox); + const float transparency = 0.5f; + // Filled polygons for the overlapping regions. + svg.draw(union_ex(this_layer.polygons), dbg_index_to_color(-1), transparency); + for (size_t i_overlapping_layer = 0; i_overlapping_layer < n_overlapping_layers; ++ i_overlapping_layer) { + const SupportGeneratorLayer &overlapping_layer = *overlapping_layers[i_overlapping_layer]; + svg.draw(union_ex(overlapping_layer.polygons), dbg_index_to_color(int(i_overlapping_layer)), transparency); + } + // Contours of the overlapping regions. + svg.draw(to_polylines(this_layer.polygons), dbg_index_to_color(-1), scale_(0.2)); + for (size_t i_overlapping_layer = 0; i_overlapping_layer < n_overlapping_layers; ++ i_overlapping_layer) { + const SupportGeneratorLayer &overlapping_layer = *overlapping_layers[i_overlapping_layer]; + svg.draw(to_polylines(overlapping_layer.polygons), dbg_index_to_color(int(i_overlapping_layer)), scale_(0.1)); + } + // Fill extrusion, the source. + for (ExtrusionEntitiesPtr::const_iterator it = extrusions_in_out.begin(); it != extrusions_in_out.end(); ++ it) { + ExtrusionPath *path = dynamic_cast(*it); + std::string color_name; + switch ((it - extrusions_in_out.begin()) % 9) { + case 0: color_name = "magenta"; break; + case 1: color_name = "deepskyblue"; break; + case 2: color_name = "coral"; break; + case 3: color_name = "goldenrod"; break; + case 4: color_name = "orange"; break; + case 5: color_name = "olivedrab"; break; + case 6: color_name = "blueviolet"; break; + case 7: color_name = "brown"; break; + default: color_name = "orchid"; break; + } + svg.draw(path->polyline, color_name, scale_(0.2)); + } +#endif /* SLIC3R_DEBUG */ + + // End points of the original paths. + std::vector> path_ends; + // Collect the paths of this_layer. + { + Polylines &polylines = path_fragments.back().polylines; + for (ExtrusionEntity *ee : extrusions_in_out) { + ExtrusionPath *path = dynamic_cast(ee); + assert(path != nullptr); + polylines.emplace_back(Polyline(std::move(path->polyline))); + path_ends.emplace_back(std::pair(polylines.back().points.front(), polylines.back().points.back())); + delete path; + } + } + // Destroy the original extrusion paths, their polylines were moved to path_fragments already. + // This will be the destination for the new paths. + extrusions_in_out.clear(); + + // Fragment the path segments by overlapping layers. The overlapping layers are sorted by an increasing print_z. + // Trim by the highest overlapping layer first. + for (int i_overlapping_layer = int(n_overlapping_layers) - 1; i_overlapping_layer >= 0; -- i_overlapping_layer) { + const SupportGeneratorLayer &overlapping_layer = *overlapping_layers[i_overlapping_layer]; + ExtrusionPathFragment &frag = path_fragments[i_overlapping_layer]; + Polygons polygons_trimming = offset(union_ex(overlapping_layer.polygons), float(scale_(0.5*extrusion_width))); + frag.polylines = intersection_pl(path_fragments.back().polylines, polygons_trimming); + path_fragments.back().polylines = diff_pl(path_fragments.back().polylines, polygons_trimming); + // Adjust the extrusion parameters for a reduced layer height and a non-bridging flow (nozzle_dmr = -1, does not matter). + assert(this_layer.print_z > overlapping_layer.print_z); + frag.height = float(this_layer.print_z - overlapping_layer.print_z); + frag.mm3_per_mm = Flow(frag.width, frag.height, -1.f).mm3_per_mm(); +#ifdef SLIC3R_DEBUG + svg.draw(frag.polylines, dbg_index_to_color(i_overlapping_layer), scale_(0.1)); +#endif /* SLIC3R_DEBUG */ + } + +#ifdef SLIC3R_DEBUG + svg.draw(path_fragments.back().polylines, dbg_index_to_color(-1), scale_(0.1)); + svg.Close(); +#endif /* SLIC3R_DEBUG */ + + // Now chain the split segments using hashing and a nearly exact match, maintaining the order of segments. + // Create a single ExtrusionPath or ExtrusionEntityCollection per source ExtrusionPath. + // Map of fragment start/end points to a pair of + // Because a non-exact matching is used for the end points, a multi-map is used. + // As the clipper library may reverse the order of some clipped paths, store both ends into the map. + struct ExtrusionPathFragmentEnd + { + ExtrusionPathFragmentEnd(size_t alayer_idx, size_t apolyline_idx, bool ais_start) : + layer_idx(alayer_idx), polyline_idx(apolyline_idx), is_start(ais_start) {} + size_t layer_idx; + size_t polyline_idx; + bool is_start; + }; + class ExtrusionPathFragmentEndPointAccessor { + public: + ExtrusionPathFragmentEndPointAccessor(const std::vector &path_fragments) : m_path_fragments(path_fragments) {} + // Return an end point of a fragment, or nullptr if the fragment has been consumed already. + const Point* operator()(const ExtrusionPathFragmentEnd &fragment_end) const { + const Polyline &polyline = m_path_fragments[fragment_end.layer_idx].polylines[fragment_end.polyline_idx]; + return polyline.points.empty() ? nullptr : + (fragment_end.is_start ? &polyline.points.front() : &polyline.points.back()); + } + private: + ExtrusionPathFragmentEndPointAccessor& operator=(const ExtrusionPathFragmentEndPointAccessor&) { + return *this; + } + + const std::vector &m_path_fragments; + }; + const coord_t search_radius = 7; + ClosestPointInRadiusLookup map_fragment_starts( + search_radius, ExtrusionPathFragmentEndPointAccessor(path_fragments)); + for (size_t i_overlapping_layer = 0; i_overlapping_layer <= n_overlapping_layers; ++ i_overlapping_layer) { + const Polylines &polylines = path_fragments[i_overlapping_layer].polylines; + for (size_t i_polyline = 0; i_polyline < polylines.size(); ++ i_polyline) { + // Map a starting point of a polyline to a pair of + if (polylines[i_polyline].points.size() >= 2) { + map_fragment_starts.insert(ExtrusionPathFragmentEnd(i_overlapping_layer, i_polyline, true)); + map_fragment_starts.insert(ExtrusionPathFragmentEnd(i_overlapping_layer, i_polyline, false)); + } + } + } + + // For each source path: + for (size_t i_path = 0; i_path < path_ends.size(); ++ i_path) { + const Point &pt_start = path_ends[i_path].first; + const Point &pt_end = path_ends[i_path].second; + Point pt_current = pt_start; + // Find a chain of fragments with the original / reduced print height. + ExtrusionMultiPath multipath; + for (;;) { + // Find a closest end point to pt_current. + std::pair end_and_dist2 = map_fragment_starts.find(pt_current); + // There may be a bug in Clipper flipping the order of two last points in a fragment? + // assert(end_and_dist2.first != nullptr); + assert(end_and_dist2.first == nullptr || end_and_dist2.second < search_radius * search_radius); + if (end_and_dist2.first == nullptr) { + // New fragment connecting to pt_current was not found. + // Verify that the last point found is close to the original end point of the unfragmented path. + //const double d2 = (pt_end - pt_current).cast.squaredNorm(); + //assert(d2 < coordf_t(search_radius * search_radius)); + // End of the path. + break; + } + const ExtrusionPathFragmentEnd &fragment_end_min = *end_and_dist2.first; + // Fragment to consume. + ExtrusionPathFragment &frag = path_fragments[fragment_end_min.layer_idx]; + Polyline &frag_polyline = frag.polylines[fragment_end_min.polyline_idx]; + // Path to append the fragment to. + ExtrusionPath *path = multipath.paths.empty() ? nullptr : &multipath.paths.back(); + if (path != nullptr) { + // Verify whether the path is compatible with the current fragment. + assert(this_layer.layer_type == SupporLayerType::BottomContact || path->height != frag.height || path->mm3_per_mm != frag.mm3_per_mm); + if (path->height != frag.height || path->mm3_per_mm != frag.mm3_per_mm) { + path = nullptr; + } + // Merging with the previous path. This can only happen if the current layer was reduced by a base layer, which was split into a base and interface layer. + } + if (path == nullptr) { + // Allocate a new path. + multipath.paths.push_back(ExtrusionPath(extrusion_role, frag.mm3_per_mm, frag.width, frag.height)); + path = &multipath.paths.back(); + } + // The Clipper library may flip the order of the clipped polylines arbitrarily. + // Reverse the source polyline, if connecting to the end. + if (! fragment_end_min.is_start) + frag_polyline.reverse(); + // Enforce exact overlap of the end points of successive fragments. + assert(frag_polyline.points.front() == pt_current); + frag_polyline.points.front() = pt_current; + // Don't repeat the first point. + if (! path->polyline.points.empty()) + path->polyline.points.pop_back(); + // Consume the fragment's polyline, remove it from the input fragments, so it will be ignored the next time. + path->polyline.append(std::move(frag_polyline)); + frag_polyline.points.clear(); + pt_current = path->polyline.points.back(); + if (pt_current == pt_end) { + // End of the path. + break; + } + } + if (!multipath.paths.empty()) { + if (multipath.paths.size() == 1) { + // This path was not fragmented. + extrusions_in_out.push_back(new ExtrusionPath(std::move(multipath.paths.front()))); + } else { + // This path was fragmented. Copy the collection as a whole object, so the order inside the collection will not be changed + // during the chaining of extrusions_in_out. + extrusions_in_out.push_back(new ExtrusionMultiPath(std::move(multipath))); + } + } + } + // If there are any non-consumed fragments, add them separately. + //FIXME this shall not happen, if the Clipper works as expected and all paths split to fragments could be re-connected. + for (auto it_fragment = path_fragments.begin(); it_fragment != path_fragments.end(); ++ it_fragment) + extrusion_entities_append_paths(extrusions_in_out, std::move(it_fragment->polylines), extrusion_role, it_fragment->mm3_per_mm, it_fragment->width, it_fragment->height); +} + +// Support layer that is covered by some form of dense interface. +static constexpr const std::initializer_list support_types_interface{ + SupporLayerType::RaftInterface, SupporLayerType::BottomContact, SupporLayerType::BottomInterface, SupporLayerType::TopContact, SupporLayerType::TopInterface +}; + +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) +{ + // Install support layers into the object. + // A support layer installed on a PrintObject has a unique print_z. + SupportGeneratorLayersPtr layers_sorted; + layers_sorted.reserve(raft_layers.size() + bottom_contacts.size() + top_contacts.size() + intermediate_layers.size() + interface_layers.size() + base_interface_layers.size()); + append(layers_sorted, raft_layers); + append(layers_sorted, bottom_contacts); + append(layers_sorted, top_contacts); + append(layers_sorted, intermediate_layers); + append(layers_sorted, interface_layers); + append(layers_sorted, base_interface_layers); + // Sort the layers lexicographically by a raising print_z and a decreasing height. + std::sort(layers_sorted.begin(), layers_sorted.end(), [](auto *l1, auto *l2) { return *l1 < *l2; }); + int layer_id = 0; + int layer_id_interface = 0; + assert(object.support_layers().empty()); + for (size_t i = 0; i < layers_sorted.size();) { + // Find the last layer with roughly the same print_z, find the minimum layer height of all. + // Due to the floating point inaccuracies, the print_z may not be the same even if in theory they should. + size_t j = i + 1; + coordf_t zmax = layers_sorted[i]->print_z + EPSILON; + for (; j < layers_sorted.size() && layers_sorted[j]->print_z <= zmax; ++j) ; + // Assign an average print_z to the set of layers with nearly equal print_z. + coordf_t zavg = 0.5 * (layers_sorted[i]->print_z + layers_sorted[j - 1]->print_z); + coordf_t height_min = layers_sorted[i]->height; + bool empty = true; + // For snug supports, layers where the direction of the support interface shall change are accounted for. + size_t num_interfaces = 0; + size_t num_top_contacts = 0; + double top_contact_bottom_z = 0; + for (size_t u = i; u < j; ++u) { + SupportGeneratorLayer &layer = *layers_sorted[u]; + if (! layer.polygons.empty()) { + empty = false; + num_interfaces += one_of(layer.layer_type, support_types_interface); + if (layer.layer_type == SupporLayerType::TopContact) { + ++ num_top_contacts; + assert(num_top_contacts <= 1); + // All top contact layers sharing this print_z shall also share bottom_z. + //assert(num_top_contacts == 1 || (top_contact_bottom_z - layer.bottom_z) < EPSILON); + top_contact_bottom_z = layer.bottom_z; + } + } + layer.print_z = zavg; + height_min = std::min(height_min, layer.height); + } + if (! empty) { + // Here the upper_layer and lower_layer pointers are left to null at the support layers, + // as they are never used. These pointers are candidates for removal. + bool this_layer_contacts_only = num_top_contacts > 0 && num_top_contacts == num_interfaces; + size_t this_layer_id_interface = layer_id_interface; + if (this_layer_contacts_only) { + // Find a supporting layer for its interface ID. + for (auto it = object.support_layers().rbegin(); it != object.support_layers().rend(); ++ it) + if (const SupportLayer &other_layer = **it; std::abs(other_layer.print_z - top_contact_bottom_z) < EPSILON) { + // other_layer supports this top contact layer. Assign a different support interface direction to this layer + // from the layer that supports it. + this_layer_id_interface = other_layer.interface_id() + 1; + } + } + object.add_support_layer(layer_id ++, this_layer_id_interface, height_min, zavg); + if (num_interfaces && ! this_layer_contacts_only) + ++ layer_id_interface; + } + i = j; + } + return layers_sorted; +} + +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) +{ + // loop_interface_processor with a given circle radius. + LoopInterfaceProcessor loop_interface_processor(1.5 * support_params.support_material_interface_flow.scaled_width()); + loop_interface_processor.n_contact_loops = config.support_material_interface_contact_loops ? 1 : 0; + + std::vector angles { support_params.base_angle }; + if (config.support_material_pattern == smpRectilinearGrid) + angles.push_back(support_params.interface_angle); + + BoundingBox bbox_object(Point(-scale_(1.), -scale_(1.0)), Point(scale_(1.), scale_(1.))); + +// const coordf_t link_max_length_factor = 3.; + const coordf_t link_max_length_factor = 0.; + + // Insert the raft base layers. + auto n_raft_layers = std::min(support_layers.size(), std::max(0, int(slicing_params.raft_layers()) - 1)); + + tbb::parallel_for(tbb::blocked_range(0, n_raft_layers), + [&support_layers, &raft_layers, &intermediate_layers, &config, &support_params, &slicing_params, + &bbox_object, link_max_length_factor] + (const tbb::blocked_range& range) { + for (size_t support_layer_id = range.begin(); support_layer_id < range.end(); ++ support_layer_id) + { + assert(support_layer_id < raft_layers.size()); + SupportLayer &support_layer = *support_layers[support_layer_id]; + assert(support_layer.support_fills.entities.empty()); + SupportGeneratorLayer &raft_layer = *raft_layers[support_layer_id]; + + std::unique_ptr filler_interface = std::unique_ptr(Fill::new_from_type(support_params.raft_interface_fill_pattern)); + std::unique_ptr filler_support = std::unique_ptr(Fill::new_from_type(support_params.base_fill_pattern)); + filler_interface->set_bounding_box(bbox_object); + filler_support->set_bounding_box(bbox_object); + + // Print the tree supports cutting through the raft with the exception of the 1st layer, where a full support layer will be printed below + // both the raft and the trees. + // Trim the raft layers with the tree polygons. + const Polygons &tree_polygons = + support_layer_id > 0 && support_layer_id < intermediate_layers.size() && is_approx(intermediate_layers[support_layer_id]->print_z, support_layer.print_z) ? + intermediate_layers[support_layer_id]->polygons : Polygons(); + + // Print the support base below the support columns, or the support base for the support columns plus the contacts. + if (support_layer_id > 0) { + const Polygons &to_infill_polygons = (support_layer_id < slicing_params.base_raft_layers) ? + raft_layer.polygons : + //FIXME misusing contact_polygons for support columns. + ((raft_layer.contact_polygons == nullptr) ? Polygons() : *raft_layer.contact_polygons); + // Trees may cut through the raft layers down to a print bed. + Flow flow(float(support_params.support_material_flow.width()), float(raft_layer.height), support_params.support_material_flow.nozzle_diameter()); + assert(!raft_layer.bridging); + if (! to_infill_polygons.empty()) { + Fill *filler = filler_support.get(); + filler->angle = support_params.raft_angle_base; + filler->spacing = support_params.support_material_flow.spacing(); + filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_params.support_density)); + fill_expolygons_with_sheath_generate_paths( + // Destination + support_layer.support_fills.entities, + // Regions to fill + tree_polygons.empty() ? to_infill_polygons : diff(to_infill_polygons, tree_polygons), + // Filler and its parameters + filler, float(support_params.support_density), + // Extrusion parameters + ExtrusionRole::SupportMaterial, flow, + support_params.with_sheath, false); + } + if (! tree_polygons.empty()) + tree_supports_generate_paths(support_layer.support_fills.entities, tree_polygons, flow); + } + + Fill *filler = filler_interface.get(); + Flow flow = support_params.first_layer_flow; + float density = 0.f; + if (support_layer_id == 0) { + // Base flange. + filler->angle = support_params.raft_angle_1st_layer; + filler->spacing = support_params.first_layer_flow.spacing(); + density = float(config.raft_first_layer_density.value * 0.01); + } else if (support_layer_id >= slicing_params.base_raft_layers) { + filler->angle = support_params.raft_interface_angle(support_layer.interface_id()); + // We don't use $base_flow->spacing because we need a constant spacing + // value that guarantees that all layers are correctly aligned. + filler->spacing = support_params.support_material_flow.spacing(); + assert(! raft_layer.bridging); + flow = Flow(float(support_params.raft_interface_flow.width()), float(raft_layer.height), support_params.raft_interface_flow.nozzle_diameter()); + density = float(support_params.raft_interface_density); + } else + continue; + filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / density)); + fill_expolygons_with_sheath_generate_paths( + // Destination + support_layer.support_fills.entities, + // Regions to fill + tree_polygons.empty() ? raft_layer.polygons : diff(raft_layer.polygons, tree_polygons), + // Filler and its parameters + filler, density, + // Extrusion parameters + (support_layer_id < slicing_params.base_raft_layers) ? ExtrusionRole::SupportMaterial : ExtrusionRole::SupportMaterialInterface, flow, + // sheath at first layer + support_layer_id == 0, support_layer_id == 0); + } + }); + + struct LayerCacheItem { + LayerCacheItem(SupportGeneratorLayerExtruded *layer_extruded = nullptr) : layer_extruded(layer_extruded) {} + SupportGeneratorLayerExtruded *layer_extruded; + std::vector overlapping; + }; + struct LayerCache { + SupportGeneratorLayerExtruded bottom_contact_layer; + SupportGeneratorLayerExtruded top_contact_layer; + SupportGeneratorLayerExtruded base_layer; + SupportGeneratorLayerExtruded interface_layer; + SupportGeneratorLayerExtruded base_interface_layer; + boost::container::static_vector nonempty; + + void add_nonempty_and_sort() { + for (SupportGeneratorLayerExtruded *item : { &bottom_contact_layer, &top_contact_layer, &interface_layer, &base_interface_layer, &base_layer }) + if (! item->empty()) + this->nonempty.emplace_back(item); + // Sort the layers with the same print_z coordinate by their heights, thickest first. + std::stable_sort(this->nonempty.begin(), this->nonempty.end(), [](const LayerCacheItem &lc1, const LayerCacheItem &lc2) { return lc1.layer_extruded->layer->height > lc2.layer_extruded->layer->height; }); + } + }; + std::vector layer_caches(support_layers.size()); + + tbb::parallel_for(tbb::blocked_range(n_raft_layers, support_layers.size()), + [&config, &slicing_params, &support_params, &support_layers, &bottom_contacts, &top_contacts, &intermediate_layers, &interface_layers, &base_interface_layers, &layer_caches, &loop_interface_processor, + &bbox_object, &angles, n_raft_layers, link_max_length_factor] + (const tbb::blocked_range& range) { + // Indices of the 1st layer in their respective container at the support layer height. + size_t idx_layer_bottom_contact = size_t(-1); + size_t idx_layer_top_contact = size_t(-1); + size_t idx_layer_intermediate = size_t(-1); + size_t idx_layer_interface = size_t(-1); + size_t idx_layer_base_interface = size_t(-1); + const auto fill_type_first_layer = ipRectilinear; + auto filler_interface = std::unique_ptr(Fill::new_from_type(support_params.contact_fill_pattern)); + // Filler for the 1st layer interface, if different from filler_interface. + auto filler_first_layer_ptr = std::unique_ptr(range.begin() == 0 && support_params.contact_fill_pattern != fill_type_first_layer ? Fill::new_from_type(fill_type_first_layer) : nullptr); + // Pointer to the 1st layer interface filler. + auto filler_first_layer = filler_first_layer_ptr ? filler_first_layer_ptr.get() : filler_interface.get(); + // Filler for the 1st layer interface, if different from filler_interface. + auto filler_raft_contact_ptr = std::unique_ptr(range.begin() == n_raft_layers && config.support_material_interface_layers.value == 0 ? + Fill::new_from_type(support_params.raft_interface_fill_pattern) : nullptr); + // Pointer to the 1st layer interface filler. + auto filler_raft_contact = filler_raft_contact_ptr ? filler_raft_contact_ptr.get() : filler_interface.get(); + // Filler for the base interface (to be used for soluble interface / non soluble base, to produce non soluble interface layer below soluble interface layer). + auto filler_base_interface = std::unique_ptr(base_interface_layers.empty() ? nullptr : + Fill::new_from_type(support_params.interface_density > 0.95 || support_params.with_sheath ? ipRectilinear : ipSupportBase)); + auto filler_support = std::unique_ptr(Fill::new_from_type(support_params.base_fill_pattern)); + filler_interface->set_bounding_box(bbox_object); + if (filler_first_layer_ptr) + filler_first_layer_ptr->set_bounding_box(bbox_object); + if (filler_raft_contact_ptr) + filler_raft_contact_ptr->set_bounding_box(bbox_object); + if (filler_base_interface) + filler_base_interface->set_bounding_box(bbox_object); + filler_support->set_bounding_box(bbox_object); + for (size_t support_layer_id = range.begin(); support_layer_id < range.end(); ++ support_layer_id) + { + SupportLayer &support_layer = *support_layers[support_layer_id]; + LayerCache &layer_cache = layer_caches[support_layer_id]; + const float support_interface_angle = config.support_material_style.value == smsGrid ? + support_params.interface_angle : support_params.raft_interface_angle(support_layer.interface_id()); + + // Find polygons with the same print_z. + SupportGeneratorLayerExtruded &bottom_contact_layer = layer_cache.bottom_contact_layer; + SupportGeneratorLayerExtruded &top_contact_layer = layer_cache.top_contact_layer; + SupportGeneratorLayerExtruded &base_layer = layer_cache.base_layer; + SupportGeneratorLayerExtruded &interface_layer = layer_cache.interface_layer; + SupportGeneratorLayerExtruded &base_interface_layer = layer_cache.base_interface_layer; + // Increment the layer indices to find a layer at support_layer.print_z. + { + auto fun = [&support_layer](const SupportGeneratorLayer *l){ return l->print_z >= support_layer.print_z - EPSILON; }; + idx_layer_bottom_contact = idx_higher_or_equal(bottom_contacts, idx_layer_bottom_contact, fun); + idx_layer_top_contact = idx_higher_or_equal(top_contacts, idx_layer_top_contact, fun); + idx_layer_intermediate = idx_higher_or_equal(intermediate_layers, idx_layer_intermediate, fun); + idx_layer_interface = idx_higher_or_equal(interface_layers, idx_layer_interface, fun); + idx_layer_base_interface = idx_higher_or_equal(base_interface_layers, idx_layer_base_interface,fun); + } + // Copy polygons from the layers. + if (idx_layer_bottom_contact < bottom_contacts.size() && bottom_contacts[idx_layer_bottom_contact]->print_z < support_layer.print_z + EPSILON) + bottom_contact_layer.layer = bottom_contacts[idx_layer_bottom_contact]; + if (idx_layer_top_contact < top_contacts.size() && top_contacts[idx_layer_top_contact]->print_z < support_layer.print_z + EPSILON) + top_contact_layer.layer = top_contacts[idx_layer_top_contact]; + if (idx_layer_interface < interface_layers.size() && interface_layers[idx_layer_interface]->print_z < support_layer.print_z + EPSILON) + interface_layer.layer = interface_layers[idx_layer_interface]; + if (idx_layer_base_interface < base_interface_layers.size() && base_interface_layers[idx_layer_base_interface]->print_z < support_layer.print_z + EPSILON) + base_interface_layer.layer = base_interface_layers[idx_layer_base_interface]; + if (idx_layer_intermediate < intermediate_layers.size() && intermediate_layers[idx_layer_intermediate]->print_z < support_layer.print_z + EPSILON) + base_layer.layer = intermediate_layers[idx_layer_intermediate]; + + // This layer is a raft contact layer. Any contact polygons at this layer are raft contacts. + bool raft_layer = slicing_params.interface_raft_layers && top_contact_layer.layer && is_approx(top_contact_layer.layer->print_z, slicing_params.raft_contact_top_z); + if (config.support_material_interface_layers == 0) { + // If no top interface layers were requested, we treat the contact layer exactly as a generic base layer. + // Don't merge the raft contact layer though. + if (support_params.can_merge_support_regions && ! raft_layer) { + if (base_layer.could_merge(top_contact_layer)) + base_layer.merge(std::move(top_contact_layer)); + else if (base_layer.empty()) + base_layer = std::move(top_contact_layer); + } + } else { + loop_interface_processor.generate(top_contact_layer, support_params.support_material_interface_flow); + // If no loops are allowed, we treat the contact layer exactly as a generic interface layer. + // Merge interface_layer into top_contact_layer, as the top_contact_layer is not synchronized and therefore it will be used + // to trim other layers. + if (top_contact_layer.could_merge(interface_layer) && ! raft_layer) + top_contact_layer.merge(std::move(interface_layer)); + } + if ((config.support_material_interface_layers == 0 || config.support_material_bottom_interface_layers == 0) && support_params.can_merge_support_regions) { + if (base_layer.could_merge(bottom_contact_layer)) + base_layer.merge(std::move(bottom_contact_layer)); + else if (base_layer.empty() && ! bottom_contact_layer.empty() && ! bottom_contact_layer.layer->bridging) + base_layer = std::move(bottom_contact_layer); + } else if (bottom_contact_layer.could_merge(top_contact_layer) && ! raft_layer) + top_contact_layer.merge(std::move(bottom_contact_layer)); + else if (bottom_contact_layer.could_merge(interface_layer)) + bottom_contact_layer.merge(std::move(interface_layer)); + +#if 0 + if ( ! interface_layer.empty() && ! base_layer.empty()) { + // turn base support into interface when it's contained in our holes + // (this way we get wider interface anchoring) + //FIXME The intention of the code below is unclear. One likely wanted to just merge small islands of base layers filling in the holes + // inside interface layers, but the code below fills just too much, see GH #4570 + Polygons islands = top_level_islands(interface_layer.layer->polygons); + polygons_append(interface_layer.layer->polygons, intersection(base_layer.layer->polygons, islands)); + base_layer.layer->polygons = diff(base_layer.layer->polygons, islands); + } +#endif + + // Top and bottom contacts, interface layers. + enum class InterfaceLayerType { TopContact, BottomContact, RaftContact, Interface, InterfaceAsBase }; + auto extrude_interface = [&](SupportGeneratorLayerExtruded &layer_ex, InterfaceLayerType interface_layer_type) { + if (! layer_ex.empty() && ! layer_ex.polygons_to_extrude().empty()) { + bool interface_as_base = interface_layer_type == InterfaceLayerType::InterfaceAsBase; + bool raft_contact = interface_layer_type == InterfaceLayerType::RaftContact; + //FIXME Bottom interfaces are extruded with the briding flow. Some bridging layers have its height slightly reduced, therefore + // the bridging flow does not quite apply. Reduce the flow to area of an ellipse? (A = pi * a * b) + auto *filler = raft_contact ? filler_raft_contact : filler_interface.get(); + auto interface_flow = layer_ex.layer->bridging ? + Flow::bridging_flow(layer_ex.layer->height, support_params.support_material_bottom_interface_flow.nozzle_diameter()) : + (raft_contact ? &support_params.raft_interface_flow : + interface_as_base ? &support_params.support_material_flow : &support_params.support_material_interface_flow) + ->with_height(float(layer_ex.layer->height)); + filler->angle = interface_as_base ? + // If zero interface layers are configured, use the same angle as for the base layers. + angles[support_layer_id % angles.size()] : + // Use interface angle for the interface layers. + raft_contact ? + support_params.raft_interface_angle(support_layer.interface_id()) : + support_interface_angle; + double density = raft_contact ? support_params.raft_interface_density : interface_as_base ? support_params.support_density : support_params.interface_density; + filler->spacing = raft_contact ? support_params.raft_interface_flow.spacing() : + interface_as_base ? support_params.support_material_flow.spacing() : support_params.support_material_interface_flow.spacing(); + filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / density)); + fill_expolygons_generate_paths( + // Destination + layer_ex.extrusions, + // Regions to fill + union_safety_offset_ex(layer_ex.polygons_to_extrude()), + // Filler and its parameters + filler, float(density), + // Extrusion parameters + ExtrusionRole::SupportMaterialInterface, interface_flow); + } + }; + const bool top_interfaces = config.support_material_interface_layers.value != 0; + const bool bottom_interfaces = top_interfaces && config.support_material_bottom_interface_layers != 0; + extrude_interface(top_contact_layer, raft_layer ? InterfaceLayerType::RaftContact : top_interfaces ? InterfaceLayerType::TopContact : InterfaceLayerType::InterfaceAsBase); + extrude_interface(bottom_contact_layer, bottom_interfaces ? InterfaceLayerType::BottomContact : InterfaceLayerType::InterfaceAsBase); + extrude_interface(interface_layer, top_interfaces ? InterfaceLayerType::Interface : InterfaceLayerType::InterfaceAsBase); + + // Base interface layers under soluble interfaces + if ( ! base_interface_layer.empty() && ! base_interface_layer.polygons_to_extrude().empty()) { + Fill *filler = filler_base_interface.get(); + //FIXME Bottom interfaces are extruded with the briding flow. Some bridging layers have its height slightly reduced, therefore + // the bridging flow does not quite apply. Reduce the flow to area of an ellipse? (A = pi * a * b) + assert(! base_interface_layer.layer->bridging); + Flow interface_flow = support_params.support_material_flow.with_height(float(base_interface_layer.layer->height)); + filler->angle = support_interface_angle; + filler->spacing = support_params.support_material_interface_flow.spacing(); + filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_params.interface_density)); + fill_expolygons_generate_paths( + // Destination + base_interface_layer.extrusions, + //base_layer_interface.extrusions, + // Regions to fill + union_safety_offset_ex(base_interface_layer.polygons_to_extrude()), + // Filler and its parameters + filler, float(support_params.interface_density), + // Extrusion parameters + ExtrusionRole::SupportMaterial, interface_flow); + } + + // Base support or flange. + if (! base_layer.empty() && ! base_layer.polygons_to_extrude().empty()) { + Fill *filler = filler_support.get(); + filler->angle = angles[support_layer_id % angles.size()]; + // We don't use $base_flow->spacing because we need a constant spacing + // value that guarantees that all layers are correctly aligned. + assert(! base_layer.layer->bridging); + auto flow = support_params.support_material_flow.with_height(float(base_layer.layer->height)); + filler->spacing = support_params.support_material_flow.spacing(); + filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_params.support_density)); + float density = float(support_params.support_density); + bool sheath = support_params.with_sheath; + bool no_sort = false; + bool done = false; + if (base_layer.layer->bottom_z < EPSILON) { + // Base flange (the 1st layer). + filler = filler_first_layer; + filler->angle = Geometry::deg2rad(float(config.support_material_angle.value + 90.)); + density = float(config.raft_first_layer_density.value * 0.01); + flow = support_params.first_layer_flow; + // use the proper spacing for first layer as we don't need to align + // its pattern to the other layers + //FIXME When paralellizing, each thread shall have its own copy of the fillers. + filler->spacing = flow.spacing(); + filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / density)); + sheath = true; + no_sort = true; + } else if (config.support_material_style == SupportMaterialStyle::smsOrganic) { + tree_supports_generate_paths(base_layer.extrusions, base_layer.polygons_to_extrude(), flow); + done = true; + } + if (! done) + fill_expolygons_with_sheath_generate_paths( + // Destination + base_layer.extrusions, + // Regions to fill + base_layer.polygons_to_extrude(), + // Filler and its parameters + filler, density, + // Extrusion parameters + ExtrusionRole::SupportMaterial, flow, + sheath, no_sort); + } + + // Merge base_interface_layers to base_layers to avoid unneccessary retractions + if (! base_layer.empty() && ! base_interface_layer.empty() && ! base_layer.polygons_to_extrude().empty() && ! base_interface_layer.polygons_to_extrude().empty() && + base_layer.could_merge(base_interface_layer)) + base_layer.merge(std::move(base_interface_layer)); + + layer_cache.add_nonempty_and_sort(); + + // Collect the support areas with this print_z into islands, as there is no need + // for retraction over these islands. + Polygons polys; + // Collect the extrusions, sorted by the bottom extrusion height. + for (LayerCacheItem &layer_cache_item : layer_cache.nonempty) { + // Collect islands to polys. + layer_cache_item.layer_extruded->polygons_append(polys); + // The print_z of the top contact surfaces and bottom_z of the bottom contact surfaces are "free" + // in a sense that they are not synchronized with other support layers. As the top and bottom contact surfaces + // are inflated to achieve a better anchoring, it may happen, that these surfaces will at least partially + // overlap in Z with another support layers, leading to over-extrusion. + // Mitigate the over-extrusion by modulating the extrusion rate over these regions. + // The print head will follow the same print_z, but the layer thickness will be reduced + // where it overlaps with another support layer. + //FIXME When printing a briging path, what is an equivalent height of the squished extrudate of the same width? + // Collect overlapping top/bottom surfaces. + layer_cache_item.overlapping.reserve(20); + coordf_t bottom_z = layer_cache_item.layer_extruded->layer->bottom_print_z() + EPSILON; + auto add_overlapping = [&layer_cache_item, bottom_z](const SupportGeneratorLayersPtr &layers, size_t idx_top) { + for (int i = int(idx_top) - 1; i >= 0 && layers[i]->print_z > bottom_z; -- i) + layer_cache_item.overlapping.push_back(layers[i]); + }; + add_overlapping(top_contacts, idx_layer_top_contact); + if (layer_cache_item.layer_extruded->layer->layer_type == SupporLayerType::BottomContact) { + // Bottom contact layer may overlap with a base layer, which may be changed to interface layer. + add_overlapping(intermediate_layers, idx_layer_intermediate); + add_overlapping(interface_layers, idx_layer_interface); + add_overlapping(base_interface_layers, idx_layer_base_interface); + } + // Order the layers by lexicographically by an increasing print_z and a decreasing layer height. + std::stable_sort(layer_cache_item.overlapping.begin(), layer_cache_item.overlapping.end(), [](auto *l1, auto *l2) { return *l1 < *l2; }); + } + assert(support_layer.support_islands.empty()); + if (! polys.empty()) { + support_layer.support_islands = union_ex(polys); + support_layer.support_islands_bboxes.reserve(support_layer.support_islands.size()); + for (const ExPolygon &expoly : support_layer.support_islands) + support_layer.support_islands_bboxes.emplace_back(get_extents(expoly).inflated(SCALED_EPSILON)); + } + } // for each support_layer_id + }); + + // Now modulate the support layer height in parallel. + tbb::parallel_for(tbb::blocked_range(n_raft_layers, support_layers.size()), + [&support_layers, &layer_caches] + (const tbb::blocked_range& range) { + for (size_t support_layer_id = range.begin(); support_layer_id < range.end(); ++ support_layer_id) { + SupportLayer &support_layer = *support_layers[support_layer_id]; + LayerCache &layer_cache = layer_caches[support_layer_id]; + // For all extrusion types at this print_z, ordered by decreasing layer height: + for (LayerCacheItem &layer_cache_item : layer_cache.nonempty) { + // Trim the extrusion height from the bottom by the overlapping layers. + modulate_extrusion_by_overlapping_layers(layer_cache_item.layer_extruded->extrusions, *layer_cache_item.layer_extruded->layer, layer_cache_item.overlapping); + support_layer.support_fills.append(std::move(layer_cache_item.layer_extruded->extrusions)); + } + } + }); + +#ifndef NDEBUG + struct Test { + static bool verify_nonempty(const ExtrusionEntityCollection *collection) { + for (const ExtrusionEntity *ee : collection->entities) { + if (const ExtrusionPath *path = dynamic_cast(ee)) + assert(! path->empty()); + else if (const ExtrusionMultiPath *multipath = dynamic_cast(ee)) + assert(! multipath->empty()); + else if (const ExtrusionEntityCollection *eecol = dynamic_cast(ee)) { + assert(! eecol->empty()); + return verify_nonempty(eecol); + } else + assert(false); + } + return true; + } + }; + for (const SupportLayer *support_layer : support_layers) + assert(Test::verify_nonempty(&support_layer->support_fills)); +#endif // NDEBUG +} + +/* +void PrintObjectSupportMaterial::clip_by_pillars( + const PrintObject &object, + LayersPtr &bottom_contacts, + LayersPtr &top_contacts, + LayersPtr &intermediate_contacts); + +{ + // this prevents supplying an empty point set to BoundingBox constructor + if (top_contacts.empty()) + return; + + coord_t pillar_size = scale_(PILLAR_SIZE); + coord_t pillar_spacing = scale_(PILLAR_SPACING); + + // A regular grid of pillars, filling the 2D bounding box. + Polygons grid; + { + // Rectangle with a side of 2.5x2.5mm. + Polygon pillar; + pillar.points.push_back(Point(0, 0)); + pillar.points.push_back(Point(pillar_size, 0)); + pillar.points.push_back(Point(pillar_size, pillar_size)); + pillar.points.push_back(Point(0, pillar_size)); + + // 2D bounding box of the projection of all contact polygons. + BoundingBox bbox; + for (LayersPtr::const_iterator it = top_contacts.begin(); it != top_contacts.end(); ++ it) + bbox.merge(get_extents((*it)->polygons)); + grid.reserve(size_t(ceil(bb.size()(0) / pillar_spacing)) * size_t(ceil(bb.size()(1) / pillar_spacing))); + for (coord_t x = bb.min(0); x <= bb.max(0) - pillar_size; x += pillar_spacing) { + for (coord_t y = bb.min(1); y <= bb.max(1) - pillar_size; y += pillar_spacing) { + grid.push_back(pillar); + for (size_t i = 0; i < pillar.points.size(); ++ i) + grid.back().points[i].translate(Point(x, y)); + } + } + } + + // add pillars to every layer + for my $i (0..n_support_z) { + $shape->[$i] = [ @$grid ]; + } + + // build capitals + for my $i (0..n_support_z) { + my $z = $support_z->[$i]; + + my $capitals = intersection( + $grid, + $contact->{$z} // [], + ); + + // work on one pillar at time (if any) to prevent the capitals from being merged + // but store the contact area supported by the capital because we need to make + // sure nothing is left + my $contact_supported_by_capitals = []; + foreach my $capital (@$capitals) { + // enlarge capital tops + $capital = offset([$capital], +($pillar_spacing - $pillar_size)/2); + push @$contact_supported_by_capitals, @$capital; + + for (my $j = $i-1; $j >= 0; $j--) { + my $jz = $support_z->[$j]; + $capital = offset($capital, -$self->interface_flow->scaled_width/2); + last if !@$capitals; + push @{ $shape->[$j] }, @$capital; + } + } + + // Capitals will not generally cover the whole contact area because there will be + // remainders. For now we handle this situation by projecting such unsupported + // areas to the ground, just like we would do with a normal support. + my $contact_not_supported_by_capitals = diff( + $contact->{$z} // [], + $contact_supported_by_capitals, + ); + if (@$contact_not_supported_by_capitals) { + for (my $j = $i-1; $j >= 0; $j--) { + push @{ $shape->[$j] }, @$contact_not_supported_by_capitals; + } + } + } +} + +sub clip_with_shape { + my ($self, $support, $shape) = @_; + + foreach my $i (keys %$support) { + // don't clip bottom layer with shape so that we + // can generate a continuous base flange + // also don't clip raft layers + next if $i == 0; + next if $i < $self->object_config->raft_layers; + $support->{$i} = intersection( + $support->{$i}, + $shape->[$i], + ); + } +} +*/ + +} // namespace Slic3r diff --git a/src/libslic3r/Support/SupportCommon.hpp b/src/libslic3r/Support/SupportCommon.hpp new file mode 100644 index 000000000..4eabce772 --- /dev/null +++ b/src/libslic3r/Support/SupportCommon.hpp @@ -0,0 +1,138 @@ +#ifndef slic3r_SupportCommon_hpp_ +#define slic3r_SupportCommon_hpp_ + +#include "../Polygon.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); + +// 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 +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 +IndexType idx_higher_or_equal(const std::vector& 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 +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 +int idx_lower_or_equal(const std::vector &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_ */ diff --git a/src/libslic3r/Support/SupportDebug.cpp b/src/libslic3r/Support/SupportDebug.cpp new file mode 100644 index 000000000..8cec806c1 --- /dev/null +++ b/src/libslic3r/Support/SupportDebug.cpp @@ -0,0 +1,108 @@ +#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 */ diff --git a/src/libslic3r/Support/SupportDebug.hpp b/src/libslic3r/Support/SupportDebug.hpp new file mode 100644 index 000000000..22a43bc4e --- /dev/null +++ b/src/libslic3r/Support/SupportDebug.hpp @@ -0,0 +1,18 @@ +#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_ */ diff --git a/src/libslic3r/Support/SupportLayer.hpp b/src/libslic3r/Support/SupportLayer.hpp index 913e28136..82265881c 100644 --- a/src/libslic3r/Support/SupportLayer.hpp +++ b/src/libslic3r/Support/SupportLayer.hpp @@ -3,8 +3,10 @@ #include #include +// for Slic3r::deque +#include "../libslic3r.h" -namespace Slic3r { +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. @@ -111,18 +113,30 @@ public: // 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. -#if 0 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 using Allocator = tbb::scalable_allocator; - Slic3r::deque> m_data; + Slic3r::deque> m_storage; tbb::spin_mutex m_mutex; }; -#else -#endif -using SupportGeneratorLayerStorage = std::deque; using SupportGeneratorLayersPtr = std::vector; } // namespace Slic3r diff --git a/src/libslic3r/Support/SupportParameters.cpp b/src/libslic3r/Support/SupportParameters.cpp new file mode 100644 index 000000000..1c7f860b8 --- /dev/null +++ b/src/libslic3r/Support/SupportParameters.cpp @@ -0,0 +1,116 @@ +#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->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(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_material_interface_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 ®ion = 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_ratio; + } + this->gap_xy = object_config.support_material_xy_spacing.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_material_extruder.value == object_config.support_material_interface_extruder.value; + if (!this->can_merge_support_regions && (object_config.support_material_extruder.value == 0 || object_config.support_material_interface_extruder.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(object_config.support_material_extruder.value, object_config.support_material_interface_extruder.value)) + // Object is printed with the same extruder as the support. + this->can_merge_support_regions = true; + } + + + double interface_spacing = object_config.support_material_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_material_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_material_spacing.value + this->support_material_flow.spacing(); + this->support_density = std::min(1., this->support_material_flow.spacing() / support_spacing); + if (object_config.support_material_interface_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_material_pattern; + this->with_sheath = 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_material_interface_pattern == smipAuto && slicing_params.soluble_interface) || + object_config.support_material_interface_pattern == smipConcentric ? + ipConcentric : + (this->interface_density > 0.95 ? ipRectilinear : ipSupportBase); + + this->base_angle = Geometry::deg2rad(float(object_config.support_material_angle.value)); + this->interface_angle = Geometry::deg2rad(float(object_config.support_material_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); + } +} + +} // namespace Slic3r diff --git a/src/libslic3r/Support/SupportParameters.hpp b/src/libslic3r/Support/SupportParameters.hpp index fd4f1f8b7..904e8ffe2 100644 --- a/src/libslic3r/Support/SupportParameters.hpp +++ b/src/libslic3r/Support/SupportParameters.hpp @@ -9,6 +9,8 @@ namespace Slic3r { class PrintObject; enum InfillPattern : int; +namespace FFFSupport { + struct SupportParameters { SupportParameters(const PrintObject &object); @@ -61,6 +63,8 @@ struct SupportParameters { { return this->raft_angle_interface + ((interface_id & 1) ? float(- M_PI / 4.) : float(+ M_PI / 4.)); } }; +} // namespace FFFSupport + } // namespace Slic3r #endif /* slic3r_SupportParameters_hpp_ */ diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index 224216466..40221ec2a 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -8,6 +8,8 @@ #include "Point.hpp" #include "MutablePolygon.hpp" +#include "Support/SupportCommon.hpp" + #include #include @@ -16,7 +18,6 @@ #include #include -#include #include #define SUPPORT_USE_AGG_RASTERIZER @@ -43,12 +44,10 @@ #include "SVG.hpp" #endif -#pragma message ("TODO: Wrap svg usages in DEBUG ifdef and remove the following include") -#include "SVG.hpp" - -// #undef NDEBUG #include +using namespace Slic3r::FFFSupport; + namespace Slic3r { // how much we extend support around the actual contact area @@ -66,103 +65,6 @@ namespace Slic3r { //#define SUPPORT_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 1.5 #define SUPPORT_SURFACES_OFFSET_PARAMETERS ClipperLib::jtSquare, 0. -#if 1 //#ifdef SLIC3R_DEBUG -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(); -} -#endif /* SLIC3R_DEBUG */ - #ifdef SUPPORT_USE_AGG_RASTERIZER static std::vector rasterize_polygons(const Vec2i &grid_size, const double pixel_size, const Point &left_bottom, const Polygons &polygons) { @@ -326,114 +228,6 @@ static Polygons contours_simplified(const Vec2i &grid_size, const double pixel_s } #endif // SUPPORT_USE_AGG_RASTERIZER -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->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(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_material_interface_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 ®ion = 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_ratio; - } - this->gap_xy = object_config.support_material_xy_spacing.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_material_extruder.value == object_config.support_material_interface_extruder.value; - if (!this->can_merge_support_regions && (object_config.support_material_extruder.value == 0 || object_config.support_material_interface_extruder.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(object_config.support_material_extruder.value, object_config.support_material_interface_extruder.value)) - // Object is printed with the same extruder as the support. - this->can_merge_support_regions = true; - } - - - double interface_spacing = object_config.support_material_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_material_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_material_spacing.value + this->support_material_flow.spacing(); - this->support_density = std::min(1., this->support_material_flow.spacing() / support_spacing); - if (object_config.support_material_interface_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_material_pattern; - this->with_sheath = 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_material_interface_pattern == smipAuto && slicing_params.soluble_interface) || - object_config.support_material_interface_pattern == smipConcentric ? - ipConcentric : - (this->interface_density > 0.95 ? ipRectilinear : ipSupportBase); - - this->base_angle = Geometry::deg2rad(float(object_config.support_material_angle.value)); - this->interface_angle = Geometry::deg2rad(float(object_config.support_material_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); - } -} - PrintObjectSupportMaterial::PrintObjectSupportMaterial(const PrintObject *object, const SlicingParameters &slicing_params) : m_print_config (&object->print()->config()), m_object_config (&object->config()), @@ -442,39 +236,6 @@ PrintObjectSupportMaterial::PrintObjectSupportMaterial(const PrintObject *object { } -// Using the std::deque as an allocator. -inline SupportGeneratorLayer& layer_allocate( - std::deque &layer_storage, - SupporLayerType layer_type) -{ - layer_storage.push_back(SupportGeneratorLayer()); - layer_storage.back().layer_type = layer_type; - return layer_storage.back(); -} - -inline SupportGeneratorLayer& layer_allocate( - std::deque &layer_storage, - tbb::spin_mutex &layer_storage_mutex, - SupporLayerType layer_type) -{ - layer_storage_mutex.lock(); - layer_storage.push_back(SupportGeneratorLayer()); - SupportGeneratorLayer *layer_new = &layer_storage.back(); - layer_storage_mutex.unlock(); - layer_new->layer_type = layer_type; - return *layer_new; -} - -inline void layers_append(SupportGeneratorLayersPtr &dst, const SupportGeneratorLayersPtr &src) -{ - dst.insert(dst.end(), src.begin(), src.end()); -} - -// Support layer that is covered by some form of dense interface. -static constexpr const std::initializer_list support_types_interface { - SupporLayerType::RaftInterface, SupporLayerType::BottomContact, SupporLayerType::BottomInterface, SupporLayerType::TopContact, SupporLayerType::TopInterface -}; - void PrintObjectSupportMaterial::generate(PrintObject &object) { BOOST_LOG_TRIVIAL(info) << "Support generator - Start"; @@ -1296,86 +1057,6 @@ namespace SupportMaterialInternal { } } -void remove_bridges_from_contacts( - const PrintConfig &print_config, - const Layer &lower_layer, - const LayerRegion &layerm, - float fw, - Polygons &contact_polygons) -{ - // compute the area of bridging perimeters - Polygons bridges; - { - // Surface supporting this layer, expanded by 0.5 * nozzle_diameter, as we consider this kind of overhang to be sufficiently supported. - Polygons lower_grown_slices = expand(lower_layer.lslices, - //FIXME to mimic the decision in the perimeter generator, we should use half the external perimeter width. - 0.5f * float(scale_(print_config.nozzle_diameter.get_at(layerm.region().config().perimeter_extruder-1))), - SUPPORT_SURFACES_OFFSET_PARAMETERS); - // Collect perimeters of this layer. - //FIXME split_at_first_point() could split a bridge mid-way - #if 0 - Polylines overhang_perimeters = layerm.perimeters.as_polylines(); - // workaround for Clipper bug, see Slic3r::Polygon::clip_as_polyline() - for (Polyline &polyline : overhang_perimeters) - polyline.points[0].x += 1; - // Trim the perimeters of this layer by the lower layer to get the unsupported pieces of perimeters. - overhang_perimeters = diff_pl(overhang_perimeters, lower_grown_slices); - #else - Polylines overhang_perimeters = diff_pl(layerm.perimeters().as_polylines(), lower_grown_slices); - #endif - - // only consider straight overhangs - // only consider overhangs having endpoints inside layer's slices - // convert bridging polylines into polygons by inflating them with their thickness - // since we're dealing with bridges, we can't assume width is larger than spacing, - // so we take the largest value and also apply safety offset to be ensure no gaps - // are left in between - Flow perimeter_bridge_flow = layerm.bridging_flow(frPerimeter); - //FIXME one may want to use a maximum of bridging flow width and normal flow width, as the perimeters are calculated using the normal flow - // and then turned to bridging flow, thus their centerlines are derived from non-bridging flow and expanding them by a bridging flow - // may not expand them to the edge of their respective islands. - const float w = float(0.5 * std::max(perimeter_bridge_flow.scaled_width(), perimeter_bridge_flow.scaled_spacing())) + scaled(0.001); - for (Polyline &polyline : overhang_perimeters) - if (polyline.is_straight()) { - // This is a bridge - polyline.extend_start(fw); - polyline.extend_end(fw); - // Is the straight perimeter segment supported at both sides? - Point pts[2] = { polyline.first_point(), polyline.last_point() }; - bool supported[2] = { false, false }; - for (size_t i = 0; i < lower_layer.lslices.size() && ! (supported[0] && supported[1]); ++ i) - for (int j = 0; j < 2; ++ j) - if (! supported[j] && lower_layer.lslices_ex[i].bbox.contains(pts[j]) && lower_layer.lslices[i].contains(pts[j])) - supported[j] = true; - if (supported[0] && supported[1]) - // Offset a polyline into a thick line. - polygons_append(bridges, offset(polyline, w)); - } - bridges = union_(bridges); - } - // remove the entire bridges and only support the unsupported edges - //FIXME the brided regions are already collected as layerm.bridged. Use it? - for (const Surface &surface : layerm.fill_surfaces()) - if (surface.surface_type == stBottomBridge && surface.bridge_angle >= 0.0) - polygons_append(bridges, surface.expolygon); - //FIXME add the gap filled areas. Extrude the gaps with a bridge flow? - // Remove the unsupported ends of the bridges from the bridged areas. - //FIXME add supports at regular intervals to support long bridges! - bridges = diff(bridges, - // Offset unsupported edges into polygons. - offset(layerm.unsupported_bridge_edges(), scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS)); - // Remove bridged areas from the supported areas. - contact_polygons = diff(contact_polygons, bridges, ApplySafetyOffset::Yes); - - #ifdef SLIC3R_DEBUG - static int iRun = 0; - SVG::export_expolygons(debug_out_path("support-top-contacts-remove-bridges-run%d.svg", iRun ++), - { { { union_ex(offset(layerm.unsupported_bridge_edges(), scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS)) }, { "unsupported_bridge_edges", "orange", 0.5f } }, - { { union_ex(contact_polygons) }, { "contact_polygons", "blue", 0.5f } }, - { { union_ex(bridges) }, { "bridges", "red", "black", "", scaled(0.1f), 0.5f } } }); - #endif /* SLIC3R_DEBUG */ -} - std::vector PrintObjectSupportMaterial::buildplate_covered(const PrintObject &object) const { // Build support on a build plate only? If so, then collect and union all the surfaces below the current layer. @@ -1674,8 +1355,7 @@ static inline std::pair new_cont const SlicingParameters &slicing_params, const coordf_t support_layer_height_min, const Layer &layer, - std::deque &layer_storage, - tbb::spin_mutex &layer_storage_mutex) + SupportGeneratorLayerStorage &layer_storage) { double print_z, bottom_z, height; SupportGeneratorLayer* bridging_layer = nullptr; @@ -1735,7 +1415,7 @@ static inline std::pair new_cont } if (bridging_print_z < print_z - EPSILON) { // Allocate the new layer. - bridging_layer = &layer_allocate(layer_storage, layer_storage_mutex, SupporLayerType::TopContact); + bridging_layer = &layer_storage.allocate(SupporLayerType::TopContact); bridging_layer->idx_object_layer_above = layer_id; bridging_layer->print_z = bridging_print_z; if (bridging_print_z == slicing_params.first_print_layer_height) { @@ -1751,7 +1431,7 @@ static inline std::pair new_cont } } - SupportGeneratorLayer &new_layer = layer_allocate(layer_storage, layer_storage_mutex, SupporLayerType::TopContact); + SupportGeneratorLayer &new_layer = layer_storage.allocate(SupporLayerType::TopContact); new_layer.idx_object_layer_above = layer_id; new_layer.print_z = print_z; new_layer.bottom_z = bottom_z; @@ -1983,9 +1663,8 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::top_contact_layers( // For each overhang layer, two supporting layers may be generated: One for the overhangs extruded with a bridging flow, // and the other for the overhangs extruded with a normal flow. contact_out.assign(num_layers * 2, nullptr); - tbb::spin_mutex layer_storage_mutex; tbb::parallel_for(tbb::blocked_range(this->has_raft() ? 0 : 1, num_layers), - [this, &object, &annotations, &layer_storage, &layer_storage_mutex, &contact_out] + [this, &object, &annotations, &layer_storage, &contact_out] (const tbb::blocked_range& range) { for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id) { @@ -2003,7 +1682,7 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::top_contact_layers( // Now apply the contact areas to the layer where they need to be made. if (! contact_polygons.empty() || ! overhang_polygons.empty()) { // Allocate the two empty layers. - auto [new_layer, bridging_layer] = new_contact_layer(*m_print_config, *m_object_config, m_slicing_params, m_support_params.support_layer_height_min, layer, layer_storage, layer_storage_mutex); + auto [new_layer, bridging_layer] = new_contact_layer(*m_print_config, *m_object_config, m_slicing_params, m_support_params.support_layer_height_min, layer, layer_storage); if (new_layer) { // Fill the non-bridging layer with polygons. fill_contact_layer(*new_layer, layer_id, m_slicing_params, @@ -2053,7 +1732,7 @@ static inline SupportGeneratorLayer* detect_bottom_contacts( // First top contact layer index overlapping with this new bottom interface layer. size_t contact_idx, // To allocate a new layer from. - std::deque &layer_storage, + SupportGeneratorLayerStorage &layer_storage, // To trim the support areas above this bottom interface layer with this newly created bottom interface layer. std::vector &layer_support_areas, // Support areas projected from top to bottom, starting with top support interfaces. @@ -2088,7 +1767,7 @@ static inline SupportGeneratorLayer* detect_bottom_contacts( size_t layer_id = layer.id() - slicing_params.raft_layers(); // Allocate a new bottom contact layer. - SupportGeneratorLayer &layer_new = layer_allocate(layer_storage, SupporLayerType::BottomContact); + SupportGeneratorLayer &layer_new = layer_storage.allocate_unguarded(SupporLayerType::BottomContact); // Grow top surfaces so that interface and support generation are generated // with some spacing from object - it looks we don't need the actual // top shapes so this can be done here @@ -2392,80 +2071,6 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::bottom_contact_layers_and_ return bottom_contacts; } -// 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 -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 -IndexType idx_higher_or_equal(const std::vector& 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 -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 -int idx_lower_or_equal(const std::vector &vec, int idx, FN_LOWER_EQUAL fn_lower_equal) -{ - return idx_lower_or_equal(vec.begin(), vec.end(), idx, fn_lower_equal); -} - // 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 PrintObjectSupportMaterial::trim_top_contacts_by_bottom_contacts( const PrintObject &object, const SupportGeneratorLayersPtr &bottom_contacts, SupportGeneratorLayersPtr &top_contacts) const @@ -2561,7 +2166,7 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::raft_and_intermediate_supp assert(extr2->bottom_z == m_slicing_params.first_print_layer_height); assert(extr2->print_z >= m_slicing_params.first_print_layer_height + m_support_params.support_layer_height_min - EPSILON); if (intermediate_layers.empty() || intermediate_layers.back()->print_z < m_slicing_params.first_print_layer_height) { - SupportGeneratorLayer &layer_new = layer_allocate(layer_storage, SupporLayerType::Intermediate); + SupportGeneratorLayer &layer_new = layer_storage.allocate_unguarded(SupporLayerType::Intermediate); layer_new.bottom_z = 0.; layer_new.print_z = m_slicing_params.first_print_layer_height; layer_new.height = m_slicing_params.first_print_layer_height; @@ -2583,7 +2188,7 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::raft_and_intermediate_supp // At this point only layers above first_print_layer_heigth + EPSILON are expected as the other cases were captured earlier. assert(extr2z >= m_slicing_params.first_print_layer_height + EPSILON); // Generate a new intermediate layer. - SupportGeneratorLayer &layer_new = layer_allocate(layer_storage, SupporLayerType::Intermediate); + SupportGeneratorLayer &layer_new = layer_storage.allocate_unguarded(SupporLayerType::Intermediate); layer_new.bottom_z = 0.; layer_new.print_z = extr1z = m_slicing_params.first_print_layer_height; layer_new.height = extr1z; @@ -2603,7 +2208,7 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::raft_and_intermediate_supp ++ idx_layer_object; if (idx_layer_object == 0 && extr1z == m_slicing_params.raft_interface_top_z) { // Insert one base support layer below the object. - SupportGeneratorLayer &layer_new = layer_allocate(layer_storage, SupporLayerType::Intermediate); + SupportGeneratorLayer &layer_new = layer_storage.allocate_unguarded(SupporLayerType::Intermediate); layer_new.print_z = m_slicing_params.object_print_z_min; layer_new.bottom_z = m_slicing_params.raft_interface_top_z; layer_new.height = layer_new.print_z - layer_new.bottom_z; @@ -2611,7 +2216,7 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::raft_and_intermediate_supp } // Emit all intermediate support layers synchronized with object layers up to extr2z. for (; idx_layer_object < object.layers().size() && object.layers()[idx_layer_object]->print_z < extr2z + EPSILON; ++ idx_layer_object) { - SupportGeneratorLayer &layer_new = layer_allocate(layer_storage, SupporLayerType::Intermediate); + SupportGeneratorLayer &layer_new = layer_storage.allocate_unguarded(SupporLayerType::Intermediate); layer_new.print_z = object.layers()[idx_layer_object]->print_z; layer_new.height = object.layers()[idx_layer_object]->height; layer_new.bottom_z = (idx_layer_object > 0) ? object.layers()[idx_layer_object - 1]->print_z : (layer_new.print_z - layer_new.height); @@ -2629,7 +2234,7 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::raft_and_intermediate_supp // between the 1st intermediate layer print_z and extr1->print_z is not too small. assert(extr1->bottom_z + m_support_params.support_layer_height_min < extr1->print_z + EPSILON); // Generate the first intermediate layer. - SupportGeneratorLayer &layer_new = layer_allocate(layer_storage, SupporLayerType::Intermediate); + SupportGeneratorLayer &layer_new = layer_storage.allocate_unguarded(SupporLayerType::Intermediate); layer_new.bottom_z = extr1->bottom_z; layer_new.print_z = extr1z = extr1->print_z; layer_new.height = extr1->height; @@ -2653,7 +2258,7 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::raft_and_intermediate_supp coordf_t extr2z_large_steps = extr2z; // Take the largest allowed step in the Z axis until extr2z_large_steps is reached. for (size_t i = 0; i < n_layers_extra; ++ i) { - SupportGeneratorLayer &layer_new = layer_allocate(layer_storage, SupporLayerType::Intermediate); + SupportGeneratorLayer &layer_new = layer_storage.allocate_unguarded(SupporLayerType::Intermediate); if (i + 1 == n_layers_extra) { // Last intermediate layer added. Align the last entered layer with extr2z_large_steps exactly. layer_new.bottom_z = (i == 0) ? extr1z : intermediate_layers.back()->print_z; @@ -2909,163 +2514,6 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object( BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::trim_support_layers_by_object() in parallel - end"; } -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) -{ - // If there is brim to be generated, calculate the trimming regions. - Polygons brim; - if (object.has_brim()) { - // The object does not have a raft. - // Calculate the area covered by the brim. - const BrimType brim_type = object.config().brim_type; - const bool brim_outer = brim_type == btOuterOnly || brim_type == btOuterAndInner; - const bool brim_inner = brim_type == btInnerOnly || brim_type == btOuterAndInner; - const auto brim_separation = scaled(object.config().brim_separation.value + object.config().brim_width.value); - for (const ExPolygon &ex : object.layers().front()->lslices) { - if (brim_outer && brim_inner) - polygons_append(brim, offset(ex, brim_separation)); - else { - if (brim_outer) - polygons_append(brim, offset(ex.contour, brim_separation, ClipperLib::jtRound, float(scale_(0.1)))); - else - brim.emplace_back(ex.contour); - if (brim_inner) { - Polygons holes = ex.holes; - polygons_reverse(holes); - holes = shrink(holes, brim_separation, ClipperLib::jtRound, float(scale_(0.1))); - polygons_reverse(holes); - polygons_append(brim, std::move(holes)); - } else - polygons_append(brim, ex.holes); - } - } - brim = union_(brim); - } - - // How much to inflate the support columns to be stable. This also applies to the 1st layer, if no raft layers are to be printed. - const float inflate_factor_fine = float(scale_((slicing_params.raft_layers() > 1) ? 0.5 : EPSILON)); - const float inflate_factor_1st_layer = std::max(0.f, float(scale_(object.config().raft_first_layer_expansion)) - inflate_factor_fine); - SupportGeneratorLayer *contacts = top_contacts .empty() ? nullptr : top_contacts .front(); - SupportGeneratorLayer *interfaces = interface_layers .empty() ? nullptr : interface_layers .front(); - SupportGeneratorLayer *base_interfaces = base_interface_layers.empty() ? nullptr : base_interface_layers.front(); - SupportGeneratorLayer *columns_base = base_layers .empty() ? nullptr : base_layers .front(); - if (contacts != nullptr && contacts->print_z > std::max(slicing_params.first_print_layer_height, slicing_params.raft_contact_top_z) + EPSILON) - // This is not the raft contact layer. - contacts = nullptr; - if (interfaces != nullptr && interfaces->bottom_print_z() > slicing_params.raft_interface_top_z + EPSILON) - // This is not the raft column base layer. - interfaces = nullptr; - if (base_interfaces != nullptr && base_interfaces->bottom_print_z() > slicing_params.raft_interface_top_z + EPSILON) - // This is not the raft column base layer. - base_interfaces = nullptr; - if (columns_base != nullptr && columns_base->bottom_print_z() > slicing_params.raft_interface_top_z + EPSILON) - // This is not the raft interface layer. - columns_base = nullptr; - - Polygons interface_polygons; - if (contacts != nullptr && ! contacts->polygons.empty()) - polygons_append(interface_polygons, expand(contacts->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS)); - if (interfaces != nullptr && ! interfaces->polygons.empty()) - polygons_append(interface_polygons, expand(interfaces->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS)); - if (base_interfaces != nullptr && ! base_interfaces->polygons.empty()) - polygons_append(interface_polygons, expand(base_interfaces->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS)); - - // Output vector. - SupportGeneratorLayersPtr raft_layers; - - if (slicing_params.raft_layers() > 1) { - Polygons base; - Polygons columns; - Polygons first_layer; - if (columns_base != nullptr) { - if (columns_base->bottom_print_z() > slicing_params.raft_interface_top_z - EPSILON) { - // Classic supports with colums above the raft interface. - base = columns_base->polygons; - columns = base; - if (! interface_polygons.empty()) - // Trim the 1st layer columns with the inflated interface polygons. - columns = diff(columns, interface_polygons); - } else { - // Organic supports with raft on print bed. - assert(is_approx(columns_base->print_z, slicing_params.first_print_layer_height)); - first_layer = columns_base->polygons; - } - } - if (! interface_polygons.empty()) { - // Merge the untrimmed columns base with the expanded raft interface, to be used for the support base and interface. - base = union_(base, interface_polygons); - } - // Do not add the raft contact layer, only add the raft layers below the contact layer. - // Insert the 1st layer. - { - SupportGeneratorLayer &new_layer = layer_allocate(layer_storage, (slicing_params.base_raft_layers > 0) ? SupporLayerType::RaftBase : SupporLayerType::RaftInterface); - raft_layers.push_back(&new_layer); - new_layer.print_z = slicing_params.first_print_layer_height; - new_layer.height = slicing_params.first_print_layer_height; - new_layer.bottom_z = 0.; - first_layer = union_(std::move(first_layer), base); - new_layer.polygons = inflate_factor_1st_layer > 0 ? expand(first_layer, inflate_factor_1st_layer) : first_layer; - } - // Insert the base layers. - for (size_t i = 1; i < slicing_params.base_raft_layers; ++ i) { - coordf_t print_z = raft_layers.back()->print_z; - SupportGeneratorLayer &new_layer = layer_allocate(layer_storage, SupporLayerType::RaftBase); - raft_layers.push_back(&new_layer); - new_layer.print_z = print_z + slicing_params.base_raft_layer_height; - new_layer.height = slicing_params.base_raft_layer_height; - new_layer.bottom_z = print_z; - new_layer.polygons = base; - } - // Insert the interface layers. - for (size_t i = 1; i < slicing_params.interface_raft_layers; ++ i) { - coordf_t print_z = raft_layers.back()->print_z; - SupportGeneratorLayer &new_layer = layer_allocate(layer_storage, SupporLayerType::RaftInterface); - raft_layers.push_back(&new_layer); - new_layer.print_z = print_z + slicing_params.interface_raft_layer_height; - new_layer.height = slicing_params.interface_raft_layer_height; - new_layer.bottom_z = print_z; - new_layer.polygons = interface_polygons; - //FIXME misusing contact_polygons for support columns. - new_layer.contact_polygons = std::make_unique(columns); - } - } else { - if (columns_base != nullptr) { - // Expand the bases of the support columns in the 1st layer. - Polygons &raft = columns_base->polygons; - Polygons trimming = offset(object.layers().front()->lslices, (float)scale_(support_params.gap_xy), SUPPORT_SURFACES_OFFSET_PARAMETERS); - if (inflate_factor_1st_layer > SCALED_EPSILON) { - // Inflate in multiple steps to avoid leaking of the support 1st layer through object walls. - auto nsteps = std::max(5, int(ceil(inflate_factor_1st_layer / support_params.first_layer_flow.scaled_width()))); - float step = inflate_factor_1st_layer / nsteps; - for (int i = 0; i < nsteps; ++ i) - raft = diff(expand(raft, step), trimming); - } else - raft = diff(raft, trimming); - if (! interface_polygons.empty()) - columns_base->polygons = diff(columns_base->polygons, interface_polygons); - } - if (! brim.empty()) { - if (columns_base) - columns_base->polygons = diff(columns_base->polygons, brim); - if (contacts) - contacts->polygons = diff(contacts->polygons, brim); - if (interfaces) - interfaces->polygons = diff(interfaces->polygons, brim); - if (base_interfaces) - base_interfaces->polygons = diff(base_interfaces->polygons, brim); - } - } - - return raft_layers; -} - // Convert some of the intermediate layers into top/bottom interface layers as well as base interface layers. std::pair PrintObjectSupportMaterial::generate_interface_layers( const SupportGeneratorLayersPtr &bottom_contacts, @@ -3111,9 +2559,8 @@ std::pair PrintObjectSuppo auto smoothing_distance = m_support_params.support_material_interface_flow.scaled_spacing() * 1.5; auto minimum_island_radius = m_support_params.support_material_interface_flow.scaled_spacing() / m_support_params.interface_density; auto closing_distance = smoothing_distance; // scaled(m_object_config->support_material_closing_radius.value); - tbb::spin_mutex layer_storage_mutex; // Insert a new layer into base_interface_layers, if intersection with base exists. - auto insert_layer = [&layer_storage, &layer_storage_mutex, snug_supports, closing_distance, smoothing_distance, minimum_island_radius]( + auto insert_layer = [&layer_storage, snug_supports, closing_distance, smoothing_distance, minimum_island_radius]( SupportGeneratorLayer &intermediate_layer, Polygons &bottom, Polygons &&top, const Polygons *subtract, SupporLayerType type) -> SupportGeneratorLayer* { assert(! bottom.empty() || ! top.empty()); // Merge top into bottom, unite them with a safety offset. @@ -3128,7 +2575,7 @@ std::pair PrintObjectSuppo //FIXME Remove non-printable tiny islands, let them be printed using the base support. //bottom = opening(std::move(bottom), minimum_island_radius); if (! bottom.empty()) { - SupportGeneratorLayer &layer_new = layer_allocate(layer_storage, layer_storage_mutex, type); + SupportGeneratorLayer &layer_new = layer_storage.allocate(type); layer_new.polygons = std::move(bottom); layer_new.print_z = intermediate_layer.print_z; layer_new.bottom_z = intermediate_layer.bottom_z; @@ -3230,1428 +2677,6 @@ std::pair PrintObjectSuppo return base_and_interface_layers; } -static inline void fill_expolygon_generate_paths( - ExtrusionEntitiesPtr &dst, - ExPolygon &&expolygon, - Fill *filler, - const FillParams &fill_params, - float density, - ExtrusionRole role, - const Flow &flow) -{ - Surface surface(stInternal, std::move(expolygon)); - Polylines polylines; - try { - assert(!fill_params.use_arachne); - polylines = filler->fill_surface(&surface, fill_params); - } catch (InfillFailedException &) { - } - extrusion_entities_append_paths( - dst, - std::move(polylines), - role, - flow.mm3_per_mm(), flow.width(), flow.height()); -} - -static inline void fill_expolygons_generate_paths( - ExtrusionEntitiesPtr &dst, - ExPolygons &&expolygons, - Fill *filler, - const FillParams &fill_params, - float density, - ExtrusionRole role, - const Flow &flow) -{ - for (ExPolygon &expoly : expolygons) - fill_expolygon_generate_paths(dst, std::move(expoly), filler, fill_params, density, role, flow); -} - -static inline void fill_expolygons_generate_paths( - ExtrusionEntitiesPtr &dst, - ExPolygons &&expolygons, - Fill *filler, - float density, - ExtrusionRole role, - const Flow &flow) -{ - FillParams fill_params; - fill_params.density = density; - fill_params.dont_adjust = true; - fill_expolygons_generate_paths(dst, std::move(expolygons), filler, fill_params, density, role, flow); -} - -static Polylines draw_perimeters(const ExPolygon &expoly, double clip_length) -{ - // Draw the perimeters. - Polylines polylines; - polylines.reserve(expoly.holes.size() + 1); - for (size_t i = 0; i <= expoly.holes.size(); ++ i) { - Polyline pl(i == 0 ? expoly.contour.points : expoly.holes[i - 1].points); - pl.points.emplace_back(pl.points.front()); - if (i > 0) - // It is a hole, reverse it. - pl.reverse(); - // so that all contours are CCW oriented. - pl.clip_end(clip_length); - polylines.emplace_back(std::move(pl)); - } - return polylines; -} - -static inline void tree_supports_generate_paths( - ExtrusionEntitiesPtr &dst, - const Polygons &polygons, - const Flow &flow) -{ - // Offset expolygon inside, returns number of expolygons collected (0 or 1). - // Vertices of output paths are marked with Z = source contour index of the expoly. - // Vertices at the intersection of source contours are marked with Z = -1. - auto shrink_expolygon_with_contour_idx = [](const Slic3r::ExPolygon &expoly, const float delta, ClipperLib::JoinType joinType, double miterLimit, ClipperLib_Z::Paths &out) -> int - { - assert(delta > 0); - auto append_paths_with_z = [](ClipperLib::Paths &src, coord_t contour_idx, ClipperLib_Z::Paths &dst) { - dst.reserve(next_highest_power_of_2(dst.size() + src.size())); - for (const ClipperLib::Path &contour : src) { - ClipperLib_Z::Path tmp; - tmp.reserve(contour.size()); - for (const Point &p : contour) - tmp.emplace_back(p.x(), p.y(), contour_idx); - dst.emplace_back(std::move(tmp)); - } - }; - - // 1) Offset the outer contour. - ClipperLib_Z::Paths contours; - { - ClipperLib::ClipperOffset co; - if (joinType == jtRound) - co.ArcTolerance = miterLimit; - else - co.MiterLimit = miterLimit; - co.ShortestEdgeLength = double(delta * 0.005); - co.AddPath(expoly.contour.points, joinType, ClipperLib::etClosedPolygon); - ClipperLib::Paths contours_raw; - co.Execute(contours_raw, - delta); - if (contours_raw.empty()) - // No need to try to offset the holes. - return 0; - append_paths_with_z(contours_raw, 0, contours); - } - - if (expoly.holes.empty()) { - // No need to subtract holes from the offsetted expolygon, we are done. - append(out, std::move(contours)); - } else { - // 2) Offset the holes one by one, collect the offsetted holes. - ClipperLib_Z::Paths holes; - { - for (const Polygon &hole : expoly.holes) { - ClipperLib::ClipperOffset co; - if (joinType == jtRound) - co.ArcTolerance = miterLimit; - else - co.MiterLimit = miterLimit; - co.ShortestEdgeLength = double(delta * 0.005); - co.AddPath(hole.points, joinType, ClipperLib::etClosedPolygon); - ClipperLib::Paths out2; - // Execute reorients the contours so that the outer most contour has a positive area. Thus the output - // contours will be CCW oriented even though the input paths are CW oriented. - // Offset is applied after contour reorientation, thus the signum of the offset value is reversed. - co.Execute(out2, delta); - append_paths_with_z(out2, 1 + (&hole - expoly.holes.data()), holes); - } - } - - // 3) Subtract holes from the contours. - if (holes.empty()) { - // No hole remaining after an offset. Just copy the outer contour. - append(out, std::move(contours)); - } else { - // Negative offset. There is a chance, that the offsetted hole intersects the outer contour. - // Subtract the offsetted holes from the offsetted contours. - ClipperLib_Z::Clipper clipper; - clipper.ZFillFunction([](const ClipperLib_Z::IntPoint &e1bot, const ClipperLib_Z::IntPoint &e1top, const ClipperLib_Z::IntPoint &e2bot, const ClipperLib_Z::IntPoint &e2top, ClipperLib_Z::IntPoint &pt) { - //pt.z() = std::max(std::max(e1bot.z(), e1top.z()), std::max(e2bot.z(), e2top.z())); - // Just mark the intersection. - pt.z() = -1; - }); - clipper.AddPaths(contours, ClipperLib_Z::ptSubject, true); - clipper.AddPaths(holes, ClipperLib_Z::ptClip, true); - ClipperLib_Z::Paths output; - clipper.Execute(ClipperLib_Z::ctDifference, output, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero); - if (! output.empty()) { - append(out, std::move(output)); - } else { - // The offsetted holes have eaten up the offsetted outer contour. - return 0; - } - } - } - - return 1; - }; - - const double spacing = flow.scaled_spacing(); - // Clip the sheath path to avoid the extruder to get exactly on the first point of the loop. - const double clip_length = spacing * 0.15; - const double anchor_length = spacing * 6.; - ClipperLib_Z::Paths anchor_candidates; - for (ExPolygon& expoly : closing_ex(polygons, float(SCALED_EPSILON), float(SCALED_EPSILON + 0.5 * flow.scaled_width()))) { - std::unique_ptr eec; - double area = expoly.area(); - if (area > sqr(scaled(5.))) { - eec = std::make_unique(); - // Don't reoder internal / external loops of the same island, always start with the internal loop. - eec->no_sort = true; - // Make the tree branch stable by adding another perimeter. - ExPolygons level2 = offset2_ex({ expoly }, -1.5 * flow.scaled_width(), 0.5 * flow.scaled_width()); - if (level2.size() == 1) { - Polylines polylines; - extrusion_entities_append_paths(eec->entities, draw_perimeters(expoly, clip_length), ExtrusionRole::SupportMaterial, flow.mm3_per_mm(), flow.width(), flow.height(), - // Disable reversal of the path, always start with the anchor, always print CCW. - false); - expoly = level2.front(); - } - } - - // Try to produce one more perimeter to place the seam anchor. - // First genrate a 2nd perimeter loop as a source for anchor candidates. - // The anchor candidate points are annotated with an index of the source contour or with -1 if on intersection. - anchor_candidates.clear(); - shrink_expolygon_with_contour_idx(expoly, flow.scaled_width(), DefaultJoinType, 1.2, anchor_candidates); - // Orient all contours CW. - for (auto &path : anchor_candidates) - if (ClipperLib_Z::Area(path) > 0) - std::reverse(path.begin(), path.end()); - - // Draw the perimeters. - Polylines polylines; - polylines.reserve(expoly.holes.size() + 1); - for (size_t idx_loop = 0; idx_loop < expoly.num_contours(); ++ idx_loop) { - // Open the loop with a seam. - const Polygon &loop = expoly.contour_or_hole(idx_loop); - Polyline pl(loop.points); - // Orient all contours CW, because the anchor will be added to the end of polyline while we want to start a loop with the anchor. - if (idx_loop == 0) - // It is an outer contour. - pl.reverse(); - pl.points.emplace_back(pl.points.front()); - pl.clip_end(clip_length); - if (pl.size() < 2) - continue; - // Find the foot of the seam point on anchor_candidates. Only pick an anchor point that was created by offsetting the source contour. - ClipperLib_Z::Path *closest_contour = nullptr; - Vec2d closest_point; - int closest_point_idx = -1; - double closest_point_t; - double d2min = std::numeric_limits::max(); - Vec2d seam_pt = pl.back().cast(); - for (ClipperLib_Z::Path &path : anchor_candidates) - for (int i = 0; i < path.size(); ++ i) { - int j = next_idx_modulo(i, path); - if (path[i].z() == idx_loop || path[j].z() == idx_loop) { - Vec2d pi(path[i].x(), path[i].y()); - Vec2d pj(path[j].x(), path[j].y()); - Vec2d v = pj - pi; - Vec2d w = seam_pt - pi; - auto l2 = v.squaredNorm(); - auto t = std::clamp((l2 == 0) ? 0 : v.dot(w) / l2, 0., 1.); - if ((path[i].z() == idx_loop || t > EPSILON) && (path[j].z() == idx_loop || t < 1. - EPSILON)) { - // Closest point. - Vec2d fp = pi + v * t; - double d2 = (fp - seam_pt).squaredNorm(); - if (d2 < d2min) { - d2min = d2; - closest_contour = &path; - closest_point = fp; - closest_point_idx = i; - closest_point_t = t; - } - } - } - } - if (d2min < sqr(flow.scaled_width() * 3.)) { - // Try to cut an anchor from the closest_contour. - // Both closest_contour and pl are CW oriented. - pl.points.emplace_back(closest_point.cast()); - const ClipperLib_Z::Path &path = *closest_contour; - double remaining_length = anchor_length - (seam_pt - closest_point).norm(); - int i = closest_point_idx; - int j = next_idx_modulo(i, *closest_contour); - Vec2d pi(path[i].x(), path[i].y()); - Vec2d pj(path[j].x(), path[j].y()); - Vec2d v = pj - pi; - double l = v.norm(); - if (remaining_length < (1. - closest_point_t) * l) { - // Just trim the current line. - pl.points.emplace_back((closest_point + v * (remaining_length / l)).cast()); - } else { - // Take the rest of the current line, continue with the other lines. - pl.points.emplace_back(path[j].x(), path[j].y()); - pi = pj; - for (i = j; path[i].z() == idx_loop && remaining_length > 0; i = j, pi = pj) { - j = next_idx_modulo(i, path); - pj = Vec2d(path[j].x(), path[j].y()); - v = pj - pi; - l = v.norm(); - if (i == closest_point_idx) { - // Back at the first segment. Most likely this should not happen and we may end the anchor. - break; - } - if (remaining_length <= l) { - pl.points.emplace_back((pi + v * (remaining_length / l)).cast()); - break; - } - pl.points.emplace_back(path[j].x(), path[j].y()); - remaining_length -= l; - } - } - } - // Start with the anchor. - pl.reverse(); - polylines.emplace_back(std::move(pl)); - } - - ExtrusionEntitiesPtr &out = eec ? eec->entities : dst; - extrusion_entities_append_paths(out, std::move(polylines), ExtrusionRole::SupportMaterial, flow.mm3_per_mm(), flow.width(), flow.height(), - // Disable reversal of the path, always start with the anchor, always print CCW. - false); - if (eec) { - std::reverse(eec->entities.begin(), eec->entities.end()); - dst.emplace_back(eec.release()); - } - } -} - -static inline void fill_expolygons_with_sheath_generate_paths( - ExtrusionEntitiesPtr &dst, - const Polygons &polygons, - Fill *filler, - float density, - ExtrusionRole role, - const Flow &flow, - bool with_sheath, - bool no_sort) -{ - if (polygons.empty()) - return; - - if (! with_sheath) { - fill_expolygons_generate_paths(dst, closing_ex(polygons, float(SCALED_EPSILON)), filler, density, role, flow); - return; - } - - FillParams fill_params; - fill_params.density = density; - fill_params.dont_adjust = true; - - const double spacing = flow.scaled_spacing(); - // Clip the sheath path to avoid the extruder to get exactly on the first point of the loop. - const double clip_length = spacing * 0.15; - - for (ExPolygon &expoly : closing_ex(polygons, float(SCALED_EPSILON), float(SCALED_EPSILON + 0.5*flow.scaled_width()))) { - // Don't reorder the skirt and its infills. - std::unique_ptr eec; - if (no_sort) { - eec = std::make_unique(); - eec->no_sort = true; - } - ExtrusionEntitiesPtr &out = no_sort ? eec->entities : dst; - extrusion_entities_append_paths(out, draw_perimeters(expoly, clip_length), ExtrusionRole::SupportMaterial, flow.mm3_per_mm(), flow.width(), flow.height()); - // Fill in the rest. - fill_expolygons_generate_paths(out, offset_ex(expoly, float(-0.4 * spacing)), filler, fill_params, density, role, flow); - if (no_sort && ! eec->empty()) - dst.emplace_back(eec.release()); - } -} - -// Support layers, partially processed. -struct SupportGeneratorLayerExtruded -{ - SupportGeneratorLayerExtruded& operator=(SupportGeneratorLayerExtruded &&rhs) { - this->layer = rhs.layer; - this->extrusions = std::move(rhs.extrusions); - m_polygons_to_extrude = std::move(rhs.m_polygons_to_extrude); - rhs.layer = nullptr; - return *this; - } - - bool empty() const { - return layer == nullptr || layer->polygons.empty(); - } - - void set_polygons_to_extrude(Polygons &&polygons) { - if (m_polygons_to_extrude == nullptr) - m_polygons_to_extrude = std::make_unique(std::move(polygons)); - else - *m_polygons_to_extrude = std::move(polygons); - } - Polygons& polygons_to_extrude() { return (m_polygons_to_extrude == nullptr) ? layer->polygons : *m_polygons_to_extrude; } - const Polygons& polygons_to_extrude() const { return (m_polygons_to_extrude == nullptr) ? layer->polygons : *m_polygons_to_extrude; } - - bool could_merge(const SupportGeneratorLayerExtruded &other) const { - return ! this->empty() && ! other.empty() && - std::abs(this->layer->height - other.layer->height) < EPSILON && - this->layer->bridging == other.layer->bridging; - } - - // Merge regions, perform boolean union over the merged polygons. - void merge(SupportGeneratorLayerExtruded &&other) { - assert(this->could_merge(other)); - // 1) Merge the rest polygons to extrude, if there are any. - if (other.m_polygons_to_extrude != nullptr) { - if (m_polygons_to_extrude == nullptr) { - // This layer has no extrusions generated yet, if it has no m_polygons_to_extrude (its area to extrude was not reduced yet). - assert(this->extrusions.empty()); - m_polygons_to_extrude = std::make_unique(this->layer->polygons); - } - Slic3r::polygons_append(*m_polygons_to_extrude, std::move(*other.m_polygons_to_extrude)); - *m_polygons_to_extrude = union_safety_offset(*m_polygons_to_extrude); - other.m_polygons_to_extrude.reset(); - } else if (m_polygons_to_extrude != nullptr) { - assert(other.m_polygons_to_extrude == nullptr); - // The other layer has no extrusions generated yet, if it has no m_polygons_to_extrude (its area to extrude was not reduced yet). - assert(other.extrusions.empty()); - Slic3r::polygons_append(*m_polygons_to_extrude, other.layer->polygons); - *m_polygons_to_extrude = union_safety_offset(*m_polygons_to_extrude); - } - // 2) Merge the extrusions. - this->extrusions.insert(this->extrusions.end(), other.extrusions.begin(), other.extrusions.end()); - other.extrusions.clear(); - // 3) Merge the infill polygons. - Slic3r::polygons_append(this->layer->polygons, std::move(other.layer->polygons)); - this->layer->polygons = union_safety_offset(this->layer->polygons); - other.layer->polygons.clear(); - } - - void polygons_append(Polygons &dst) const { - if (layer != NULL && ! layer->polygons.empty()) - Slic3r::polygons_append(dst, layer->polygons); - } - - // The source layer. It carries the height and extrusion type (bridging / non bridging, extrusion height). - SupportGeneratorLayer *layer { nullptr }; - // Collect extrusions. They will be exported sorted by the bottom height. - ExtrusionEntitiesPtr extrusions; - -private: - // In case the extrusions are non-empty, m_polygons_to_extrude may contain the rest areas yet to be filled by additional support. - // This is useful mainly for the loop interfaces, which are generated before the zig-zag infills. - std::unique_ptr m_polygons_to_extrude; -}; - -typedef std::vector SupportGeneratorLayerExtrudedPtrs; - -struct LoopInterfaceProcessor -{ - LoopInterfaceProcessor(coordf_t circle_r) : - n_contact_loops(0), - circle_radius(circle_r), - circle_distance(circle_r * 3.) - { - // Shape of the top contact area. - circle.points.reserve(6); - for (size_t i = 0; i < 6; ++ i) { - double angle = double(i) * M_PI / 3.; - circle.points.push_back(Point(circle_radius * cos(angle), circle_radius * sin(angle))); - } - } - - // Generate loop contacts at the top_contact_layer, - // trim the top_contact_layer->polygons with the areas covered by the loops. - void generate(SupportGeneratorLayerExtruded &top_contact_layer, const Flow &interface_flow_src) const; - - int n_contact_loops; - coordf_t circle_radius; - coordf_t circle_distance; - Polygon circle; -}; - -void LoopInterfaceProcessor::generate(SupportGeneratorLayerExtruded &top_contact_layer, const Flow &interface_flow_src) const -{ - if (n_contact_loops == 0 || top_contact_layer.empty()) - return; - - Flow flow = interface_flow_src.with_height(top_contact_layer.layer->height); - - Polygons overhang_polygons; - if (top_contact_layer.layer->overhang_polygons != nullptr) - overhang_polygons = std::move(*top_contact_layer.layer->overhang_polygons); - - // Generate the outermost loop. - // Find centerline of the external loop (or any other kind of extrusions should the loop be skipped) - ExPolygons top_contact_expolygons = offset_ex(union_ex(top_contact_layer.layer->polygons), - 0.5f * flow.scaled_width()); - - // Grid size and bit shifts for quick and exact to/from grid coordinates manipulation. - coord_t circle_grid_resolution = 1; - coord_t circle_grid_powerof2 = 0; - { - // epsilon to account for rounding errors - coord_t circle_grid_resolution_non_powerof2 = coord_t(2. * circle_distance + 3.); - while (circle_grid_resolution < circle_grid_resolution_non_powerof2) { - circle_grid_resolution <<= 1; - ++ circle_grid_powerof2; - } - } - - struct PointAccessor { - const Point* operator()(const Point &pt) const { return &pt; } - }; - typedef ClosestPointInRadiusLookup ClosestPointLookupType; - - Polygons loops0; - { - // find centerline of the external loop of the contours - // Only consider the loops facing the overhang. - Polygons external_loops; - // Holes in the external loops. - Polygons circles; - Polygons overhang_with_margin = offset(union_ex(overhang_polygons), 0.5f * flow.scaled_width()); - for (ExPolygons::iterator it_contact_expoly = top_contact_expolygons.begin(); it_contact_expoly != top_contact_expolygons.end(); ++ it_contact_expoly) { - // Store the circle centers placed for an expolygon into a regular grid, hashed by the circle centers. - ClosestPointLookupType circle_centers_lookup(coord_t(circle_distance - SCALED_EPSILON)); - Points circle_centers; - Point center_last; - // For each contour of the expolygon, start with the outer contour, continue with the holes. - for (size_t i_contour = 0; i_contour <= it_contact_expoly->holes.size(); ++ i_contour) { - Polygon &contour = (i_contour == 0) ? it_contact_expoly->contour : it_contact_expoly->holes[i_contour - 1]; - const Point *seg_current_pt = nullptr; - coordf_t seg_current_t = 0.; - if (! intersection_pl(contour.split_at_first_point(), overhang_with_margin).empty()) { - // The contour is below the overhang at least to some extent. - //FIXME ideally one would place the circles below the overhang only. - // Walk around the contour and place circles so their centers are not closer than circle_distance from each other. - if (circle_centers.empty()) { - // Place the first circle. - seg_current_pt = &contour.points.front(); - seg_current_t = 0.; - center_last = *seg_current_pt; - circle_centers_lookup.insert(center_last); - circle_centers.push_back(center_last); - } - for (Points::const_iterator it = contour.points.begin() + 1; it != contour.points.end(); ++it) { - // Is it possible to place a circle on this segment? Is it not too close to any of the circles already placed on this contour? - const Point &p1 = *(it-1); - const Point &p2 = *it; - // Intersection of a ray (p1, p2) with a circle placed at center_last, with radius of circle_distance. - const Vec2d v_seg(coordf_t(p2(0)) - coordf_t(p1(0)), coordf_t(p2(1)) - coordf_t(p1(1))); - const Vec2d v_cntr(coordf_t(p1(0) - center_last(0)), coordf_t(p1(1) - center_last(1))); - coordf_t a = v_seg.squaredNorm(); - coordf_t b = 2. * v_seg.dot(v_cntr); - coordf_t c = v_cntr.squaredNorm() - circle_distance * circle_distance; - coordf_t disc = b * b - 4. * a * c; - if (disc > 0.) { - // The circle intersects a ray. Avoid the parts of the segment inside the circle. - coordf_t t1 = (-b - sqrt(disc)) / (2. * a); - coordf_t t2 = (-b + sqrt(disc)) / (2. * a); - coordf_t t0 = (seg_current_pt == &p1) ? seg_current_t : 0.; - // Take the lowest t in , excluding . - coordf_t t; - if (t0 <= t1) - t = t0; - else if (t2 <= 1.) - t = t2; - else { - // Try the following segment. - seg_current_pt = nullptr; - continue; - } - seg_current_pt = &p1; - seg_current_t = t; - center_last = Point(p1(0) + coord_t(v_seg(0) * t), p1(1) + coord_t(v_seg(1) * t)); - // It has been verified that the new point is far enough from center_last. - // Ensure, that it is far enough from all the centers. - std::pair circle_closest = circle_centers_lookup.find(center_last); - if (circle_closest.first != nullptr) { - -- it; - continue; - } - } else { - // All of the segment is outside the circle. Take the first point. - seg_current_pt = &p1; - seg_current_t = 0.; - center_last = p1; - } - // Place the first circle. - circle_centers_lookup.insert(center_last); - circle_centers.push_back(center_last); - } - external_loops.push_back(std::move(contour)); - for (const Point ¢er : circle_centers) { - circles.push_back(circle); - circles.back().translate(center); - } - } - } - } - // Apply a pattern to the external loops. - loops0 = diff(external_loops, circles); - } - - Polylines loop_lines; - { - // make more loops - Polygons loop_polygons = loops0; - for (int i = 1; i < n_contact_loops; ++ i) - polygons_append(loop_polygons, - opening( - loops0, - i * flow.scaled_spacing() + 0.5f * flow.scaled_spacing(), - 0.5f * flow.scaled_spacing())); - // Clip such loops to the side oriented towards the object. - // Collect split points, so they will be recognized after the clipping. - // At the split points the clipped pieces will be stitched back together. - loop_lines.reserve(loop_polygons.size()); - std::unordered_map map_split_points; - for (Polygons::const_iterator it = loop_polygons.begin(); it != loop_polygons.end(); ++ it) { - assert(map_split_points.find(it->first_point()) == map_split_points.end()); - map_split_points[it->first_point()] = -1; - loop_lines.push_back(it->split_at_first_point()); - } - loop_lines = intersection_pl(loop_lines, expand(overhang_polygons, scale_(SUPPORT_MATERIAL_MARGIN))); - // Because a closed loop has been split to a line, loop_lines may contain continuous segments split to 2 pieces. - // Try to connect them. - for (int i_line = 0; i_line < int(loop_lines.size()); ++ i_line) { - Polyline &polyline = loop_lines[i_line]; - auto it = map_split_points.find(polyline.first_point()); - if (it != map_split_points.end()) { - // This is a stitching point. - // If this assert triggers, multiple source polygons likely intersected at this point. - assert(it->second != -2); - if (it->second < 0) { - // First occurence. - it->second = i_line; - } else { - // Second occurence. Join the lines. - Polyline &polyline_1st = loop_lines[it->second]; - assert(polyline_1st.first_point() == it->first || polyline_1st.last_point() == it->first); - if (polyline_1st.first_point() == it->first) - polyline_1st.reverse(); - polyline_1st.append(std::move(polyline)); - it->second = -2; - } - continue; - } - it = map_split_points.find(polyline.last_point()); - if (it != map_split_points.end()) { - // This is a stitching point. - // If this assert triggers, multiple source polygons likely intersected at this point. - assert(it->second != -2); - if (it->second < 0) { - // First occurence. - it->second = i_line; - } else { - // Second occurence. Join the lines. - Polyline &polyline_1st = loop_lines[it->second]; - assert(polyline_1st.first_point() == it->first || polyline_1st.last_point() == it->first); - if (polyline_1st.first_point() == it->first) - polyline_1st.reverse(); - polyline.reverse(); - polyline_1st.append(std::move(polyline)); - it->second = -2; - } - } - } - // Remove empty lines. - remove_degenerate(loop_lines); - } - - // add the contact infill area to the interface area - // note that growing loops by $circle_radius ensures no tiny - // extrusions are left inside the circles; however it creates - // a very large gap between loops and contact_infill_polygons, so maybe another - // solution should be found to achieve both goals - // Store the trimmed polygons into a separate polygon set, so the original infill area remains intact for - // "modulate by layer thickness". - top_contact_layer.set_polygons_to_extrude(diff(top_contact_layer.layer->polygons, offset(loop_lines, float(circle_radius * 1.1)))); - - // Transform loops into ExtrusionPath objects. - extrusion_entities_append_paths( - top_contact_layer.extrusions, - std::move(loop_lines), - ExtrusionRole::SupportMaterialInterface, flow.mm3_per_mm(), flow.width(), flow.height()); -} - -#ifdef SLIC3R_DEBUG -static std::string dbg_index_to_color(int idx) -{ - if (idx < 0) - return "yellow"; - idx = idx % 3; - switch (idx) { - case 0: return "red"; - case 1: return "green"; - default: return "blue"; - } -} -#endif /* SLIC3R_DEBUG */ - -// When extruding a bottom interface layer over an object, the bottom interface layer is extruded in a thin air, therefore -// it is being extruded with a bridging flow to not shrink excessively (the die swell effect). -// Tiny extrusions are better avoided and it is always better to anchor the thread to an existing support structure if possible. -// Therefore the bottom interface spots are expanded a bit. The expanded regions may overlap with another bottom interface layers, -// leading to over extrusion, where they overlap. The over extrusion is better avoided as it often makes the interface layers -// to stick too firmly to the object. -// -// Modulate thickness (increase bottom_z) of extrusions_in_out generated for this_layer -// if they overlap with overlapping_layers, whose print_z is above this_layer.bottom_z() and below this_layer.print_z. -void modulate_extrusion_by_overlapping_layers( - // Extrusions generated for this_layer. - ExtrusionEntitiesPtr &extrusions_in_out, - const SupportGeneratorLayer &this_layer, - // Multiple layers overlapping with this_layer, sorted bottom up. - const SupportGeneratorLayersPtr &overlapping_layers) -{ - size_t n_overlapping_layers = overlapping_layers.size(); - if (n_overlapping_layers == 0 || extrusions_in_out.empty()) - // The extrusions do not overlap with any other extrusion. - return; - - // Get the initial extrusion parameters. - ExtrusionPath *extrusion_path_template = dynamic_cast(extrusions_in_out.front()); - assert(extrusion_path_template != nullptr); - ExtrusionRole extrusion_role = extrusion_path_template->role(); - float extrusion_width = extrusion_path_template->width; - - struct ExtrusionPathFragment - { - ExtrusionPathFragment() : mm3_per_mm(-1), width(-1), height(-1) {}; - ExtrusionPathFragment(double mm3_per_mm, float width, float height) : mm3_per_mm(mm3_per_mm), width(width), height(height) {}; - - Polylines polylines; - double mm3_per_mm; - float width; - float height; - }; - - // Split the extrusions by the overlapping layers, reduce their extrusion rate. - // The last path_fragment is from this_layer. - std::vector path_fragments( - n_overlapping_layers + 1, - ExtrusionPathFragment(extrusion_path_template->mm3_per_mm, extrusion_path_template->width, extrusion_path_template->height)); - // Don't use it, it will be released. - extrusion_path_template = nullptr; - -#ifdef SLIC3R_DEBUG - static int iRun = 0; - ++ iRun; - BoundingBox bbox; - for (size_t i_overlapping_layer = 0; i_overlapping_layer < n_overlapping_layers; ++ i_overlapping_layer) { - const SupportGeneratorLayer &overlapping_layer = *overlapping_layers[i_overlapping_layer]; - bbox.merge(get_extents(overlapping_layer.polygons)); - } - for (ExtrusionEntitiesPtr::const_iterator it = extrusions_in_out.begin(); it != extrusions_in_out.end(); ++ it) { - ExtrusionPath *path = dynamic_cast(*it); - assert(path != nullptr); - bbox.merge(get_extents(path->polyline)); - } - SVG svg(debug_out_path("support-fragments-%d-%lf.svg", iRun, this_layer.print_z).c_str(), bbox); - const float transparency = 0.5f; - // Filled polygons for the overlapping regions. - svg.draw(union_ex(this_layer.polygons), dbg_index_to_color(-1), transparency); - for (size_t i_overlapping_layer = 0; i_overlapping_layer < n_overlapping_layers; ++ i_overlapping_layer) { - const SupportGeneratorLayer &overlapping_layer = *overlapping_layers[i_overlapping_layer]; - svg.draw(union_ex(overlapping_layer.polygons), dbg_index_to_color(int(i_overlapping_layer)), transparency); - } - // Contours of the overlapping regions. - svg.draw(to_polylines(this_layer.polygons), dbg_index_to_color(-1), scale_(0.2)); - for (size_t i_overlapping_layer = 0; i_overlapping_layer < n_overlapping_layers; ++ i_overlapping_layer) { - const SupportGeneratorLayer &overlapping_layer = *overlapping_layers[i_overlapping_layer]; - svg.draw(to_polylines(overlapping_layer.polygons), dbg_index_to_color(int(i_overlapping_layer)), scale_(0.1)); - } - // Fill extrusion, the source. - for (ExtrusionEntitiesPtr::const_iterator it = extrusions_in_out.begin(); it != extrusions_in_out.end(); ++ it) { - ExtrusionPath *path = dynamic_cast(*it); - std::string color_name; - switch ((it - extrusions_in_out.begin()) % 9) { - case 0: color_name = "magenta"; break; - case 1: color_name = "deepskyblue"; break; - case 2: color_name = "coral"; break; - case 3: color_name = "goldenrod"; break; - case 4: color_name = "orange"; break; - case 5: color_name = "olivedrab"; break; - case 6: color_name = "blueviolet"; break; - case 7: color_name = "brown"; break; - default: color_name = "orchid"; break; - } - svg.draw(path->polyline, color_name, scale_(0.2)); - } -#endif /* SLIC3R_DEBUG */ - - // End points of the original paths. - std::vector> path_ends; - // Collect the paths of this_layer. - { - Polylines &polylines = path_fragments.back().polylines; - for (ExtrusionEntity *ee : extrusions_in_out) { - ExtrusionPath *path = dynamic_cast(ee); - assert(path != nullptr); - polylines.emplace_back(Polyline(std::move(path->polyline))); - path_ends.emplace_back(std::pair(polylines.back().points.front(), polylines.back().points.back())); - delete path; - } - } - // Destroy the original extrusion paths, their polylines were moved to path_fragments already. - // This will be the destination for the new paths. - extrusions_in_out.clear(); - - // Fragment the path segments by overlapping layers. The overlapping layers are sorted by an increasing print_z. - // Trim by the highest overlapping layer first. - for (int i_overlapping_layer = int(n_overlapping_layers) - 1; i_overlapping_layer >= 0; -- i_overlapping_layer) { - const SupportGeneratorLayer &overlapping_layer = *overlapping_layers[i_overlapping_layer]; - ExtrusionPathFragment &frag = path_fragments[i_overlapping_layer]; - Polygons polygons_trimming = offset(union_ex(overlapping_layer.polygons), float(scale_(0.5*extrusion_width))); - frag.polylines = intersection_pl(path_fragments.back().polylines, polygons_trimming); - path_fragments.back().polylines = diff_pl(path_fragments.back().polylines, polygons_trimming); - // Adjust the extrusion parameters for a reduced layer height and a non-bridging flow (nozzle_dmr = -1, does not matter). - assert(this_layer.print_z > overlapping_layer.print_z); - frag.height = float(this_layer.print_z - overlapping_layer.print_z); - frag.mm3_per_mm = Flow(frag.width, frag.height, -1.f).mm3_per_mm(); -#ifdef SLIC3R_DEBUG - svg.draw(frag.polylines, dbg_index_to_color(i_overlapping_layer), scale_(0.1)); -#endif /* SLIC3R_DEBUG */ - } - -#ifdef SLIC3R_DEBUG - svg.draw(path_fragments.back().polylines, dbg_index_to_color(-1), scale_(0.1)); - svg.Close(); -#endif /* SLIC3R_DEBUG */ - - // Now chain the split segments using hashing and a nearly exact match, maintaining the order of segments. - // Create a single ExtrusionPath or ExtrusionEntityCollection per source ExtrusionPath. - // Map of fragment start/end points to a pair of - // Because a non-exact matching is used for the end points, a multi-map is used. - // As the clipper library may reverse the order of some clipped paths, store both ends into the map. - struct ExtrusionPathFragmentEnd - { - ExtrusionPathFragmentEnd(size_t alayer_idx, size_t apolyline_idx, bool ais_start) : - layer_idx(alayer_idx), polyline_idx(apolyline_idx), is_start(ais_start) {} - size_t layer_idx; - size_t polyline_idx; - bool is_start; - }; - class ExtrusionPathFragmentEndPointAccessor { - public: - ExtrusionPathFragmentEndPointAccessor(const std::vector &path_fragments) : m_path_fragments(path_fragments) {} - // Return an end point of a fragment, or nullptr if the fragment has been consumed already. - const Point* operator()(const ExtrusionPathFragmentEnd &fragment_end) const { - const Polyline &polyline = m_path_fragments[fragment_end.layer_idx].polylines[fragment_end.polyline_idx]; - return polyline.points.empty() ? nullptr : - (fragment_end.is_start ? &polyline.points.front() : &polyline.points.back()); - } - private: - ExtrusionPathFragmentEndPointAccessor& operator=(const ExtrusionPathFragmentEndPointAccessor&) { - return *this; - } - - const std::vector &m_path_fragments; - }; - const coord_t search_radius = 7; - ClosestPointInRadiusLookup map_fragment_starts( - search_radius, ExtrusionPathFragmentEndPointAccessor(path_fragments)); - for (size_t i_overlapping_layer = 0; i_overlapping_layer <= n_overlapping_layers; ++ i_overlapping_layer) { - const Polylines &polylines = path_fragments[i_overlapping_layer].polylines; - for (size_t i_polyline = 0; i_polyline < polylines.size(); ++ i_polyline) { - // Map a starting point of a polyline to a pair of - if (polylines[i_polyline].points.size() >= 2) { - map_fragment_starts.insert(ExtrusionPathFragmentEnd(i_overlapping_layer, i_polyline, true)); - map_fragment_starts.insert(ExtrusionPathFragmentEnd(i_overlapping_layer, i_polyline, false)); - } - } - } - - // For each source path: - for (size_t i_path = 0; i_path < path_ends.size(); ++ i_path) { - const Point &pt_start = path_ends[i_path].first; - const Point &pt_end = path_ends[i_path].second; - Point pt_current = pt_start; - // Find a chain of fragments with the original / reduced print height. - ExtrusionMultiPath multipath; - for (;;) { - // Find a closest end point to pt_current. - std::pair end_and_dist2 = map_fragment_starts.find(pt_current); - // There may be a bug in Clipper flipping the order of two last points in a fragment? - // assert(end_and_dist2.first != nullptr); - assert(end_and_dist2.first == nullptr || end_and_dist2.second < search_radius * search_radius); - if (end_and_dist2.first == nullptr) { - // New fragment connecting to pt_current was not found. - // Verify that the last point found is close to the original end point of the unfragmented path. - //const double d2 = (pt_end - pt_current).cast.squaredNorm(); - //assert(d2 < coordf_t(search_radius * search_radius)); - // End of the path. - break; - } - const ExtrusionPathFragmentEnd &fragment_end_min = *end_and_dist2.first; - // Fragment to consume. - ExtrusionPathFragment &frag = path_fragments[fragment_end_min.layer_idx]; - Polyline &frag_polyline = frag.polylines[fragment_end_min.polyline_idx]; - // Path to append the fragment to. - ExtrusionPath *path = multipath.paths.empty() ? nullptr : &multipath.paths.back(); - if (path != nullptr) { - // Verify whether the path is compatible with the current fragment. - assert(this_layer.layer_type == SupporLayerType::BottomContact || path->height != frag.height || path->mm3_per_mm != frag.mm3_per_mm); - if (path->height != frag.height || path->mm3_per_mm != frag.mm3_per_mm) { - path = nullptr; - } - // Merging with the previous path. This can only happen if the current layer was reduced by a base layer, which was split into a base and interface layer. - } - if (path == nullptr) { - // Allocate a new path. - multipath.paths.push_back(ExtrusionPath(extrusion_role, frag.mm3_per_mm, frag.width, frag.height)); - path = &multipath.paths.back(); - } - // The Clipper library may flip the order of the clipped polylines arbitrarily. - // Reverse the source polyline, if connecting to the end. - if (! fragment_end_min.is_start) - frag_polyline.reverse(); - // Enforce exact overlap of the end points of successive fragments. - assert(frag_polyline.points.front() == pt_current); - frag_polyline.points.front() = pt_current; - // Don't repeat the first point. - if (! path->polyline.points.empty()) - path->polyline.points.pop_back(); - // Consume the fragment's polyline, remove it from the input fragments, so it will be ignored the next time. - path->polyline.append(std::move(frag_polyline)); - frag_polyline.points.clear(); - pt_current = path->polyline.points.back(); - if (pt_current == pt_end) { - // End of the path. - break; - } - } - if (!multipath.paths.empty()) { - if (multipath.paths.size() == 1) { - // This path was not fragmented. - extrusions_in_out.push_back(new ExtrusionPath(std::move(multipath.paths.front()))); - } else { - // This path was fragmented. Copy the collection as a whole object, so the order inside the collection will not be changed - // during the chaining of extrusions_in_out. - extrusions_in_out.push_back(new ExtrusionMultiPath(std::move(multipath))); - } - } - } - // If there are any non-consumed fragments, add them separately. - //FIXME this shall not happen, if the Clipper works as expected and all paths split to fragments could be re-connected. - for (auto it_fragment = path_fragments.begin(); it_fragment != path_fragments.end(); ++ it_fragment) - extrusion_entities_append_paths(extrusions_in_out, std::move(it_fragment->polylines), extrusion_role, it_fragment->mm3_per_mm, it_fragment->width, it_fragment->height); -} - -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) -{ - // Install support layers into the object. - // A support layer installed on a PrintObject has a unique print_z. - SupportGeneratorLayersPtr layers_sorted; - layers_sorted.reserve(raft_layers.size() + bottom_contacts.size() + top_contacts.size() + intermediate_layers.size() + interface_layers.size() + base_interface_layers.size()); - layers_append(layers_sorted, raft_layers); - layers_append(layers_sorted, bottom_contacts); - layers_append(layers_sorted, top_contacts); - layers_append(layers_sorted, intermediate_layers); - layers_append(layers_sorted, interface_layers); - layers_append(layers_sorted, base_interface_layers); - // Sort the layers lexicographically by a raising print_z and a decreasing height. - std::sort(layers_sorted.begin(), layers_sorted.end(), [](auto *l1, auto *l2) { return *l1 < *l2; }); - int layer_id = 0; - int layer_id_interface = 0; - assert(object.support_layers().empty()); - for (size_t i = 0; i < layers_sorted.size();) { - // Find the last layer with roughly the same print_z, find the minimum layer height of all. - // Due to the floating point inaccuracies, the print_z may not be the same even if in theory they should. - size_t j = i + 1; - coordf_t zmax = layers_sorted[i]->print_z + EPSILON; - for (; j < layers_sorted.size() && layers_sorted[j]->print_z <= zmax; ++j) ; - // Assign an average print_z to the set of layers with nearly equal print_z. - coordf_t zavg = 0.5 * (layers_sorted[i]->print_z + layers_sorted[j - 1]->print_z); - coordf_t height_min = layers_sorted[i]->height; - bool empty = true; - // For snug supports, layers where the direction of the support interface shall change are accounted for. - size_t num_interfaces = 0; - size_t num_top_contacts = 0; - double top_contact_bottom_z = 0; - for (size_t u = i; u < j; ++u) { - SupportGeneratorLayer &layer = *layers_sorted[u]; - if (! layer.polygons.empty()) { - empty = false; - num_interfaces += one_of(layer.layer_type, support_types_interface); - if (layer.layer_type == SupporLayerType::TopContact) { - ++ num_top_contacts; - assert(num_top_contacts <= 1); - // All top contact layers sharing this print_z shall also share bottom_z. - //assert(num_top_contacts == 1 || (top_contact_bottom_z - layer.bottom_z) < EPSILON); - top_contact_bottom_z = layer.bottom_z; - } - } - layer.print_z = zavg; - height_min = std::min(height_min, layer.height); - } - if (! empty) { - // Here the upper_layer and lower_layer pointers are left to null at the support layers, - // as they are never used. These pointers are candidates for removal. - bool this_layer_contacts_only = num_top_contacts > 0 && num_top_contacts == num_interfaces; - size_t this_layer_id_interface = layer_id_interface; - if (this_layer_contacts_only) { - // Find a supporting layer for its interface ID. - for (auto it = object.support_layers().rbegin(); it != object.support_layers().rend(); ++ it) - if (const SupportLayer &other_layer = **it; std::abs(other_layer.print_z - top_contact_bottom_z) < EPSILON) { - // other_layer supports this top contact layer. Assign a different support interface direction to this layer - // from the layer that supports it. - this_layer_id_interface = other_layer.interface_id() + 1; - } - } - object.add_support_layer(layer_id ++, this_layer_id_interface, height_min, zavg); - if (num_interfaces && ! this_layer_contacts_only) - ++ layer_id_interface; - } - i = j; - } - return layers_sorted; -} - -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) -{ - // loop_interface_processor with a given circle radius. - LoopInterfaceProcessor loop_interface_processor(1.5 * support_params.support_material_interface_flow.scaled_width()); - loop_interface_processor.n_contact_loops = config.support_material_interface_contact_loops ? 1 : 0; - - std::vector angles { support_params.base_angle }; - if (config.support_material_pattern == smpRectilinearGrid) - angles.push_back(support_params.interface_angle); - - BoundingBox bbox_object(Point(-scale_(1.), -scale_(1.0)), Point(scale_(1.), scale_(1.))); - -// const coordf_t link_max_length_factor = 3.; - const coordf_t link_max_length_factor = 0.; - - // Insert the raft base layers. - auto n_raft_layers = std::min(support_layers.size(), std::max(0, int(slicing_params.raft_layers()) - 1)); - - tbb::parallel_for(tbb::blocked_range(0, n_raft_layers), - [&support_layers, &raft_layers, &intermediate_layers, &config, &support_params, &slicing_params, - &bbox_object, link_max_length_factor] - (const tbb::blocked_range& range) { - for (size_t support_layer_id = range.begin(); support_layer_id < range.end(); ++ support_layer_id) - { - assert(support_layer_id < raft_layers.size()); - SupportLayer &support_layer = *support_layers[support_layer_id]; - assert(support_layer.support_fills.entities.empty()); - SupportGeneratorLayer &raft_layer = *raft_layers[support_layer_id]; - - std::unique_ptr filler_interface = std::unique_ptr(Fill::new_from_type(support_params.raft_interface_fill_pattern)); - std::unique_ptr filler_support = std::unique_ptr(Fill::new_from_type(support_params.base_fill_pattern)); - filler_interface->set_bounding_box(bbox_object); - filler_support->set_bounding_box(bbox_object); - - // Print the tree supports cutting through the raft with the exception of the 1st layer, where a full support layer will be printed below - // both the raft and the trees. - // Trim the raft layers with the tree polygons. - const Polygons &tree_polygons = - support_layer_id > 0 && support_layer_id < intermediate_layers.size() && is_approx(intermediate_layers[support_layer_id]->print_z, support_layer.print_z) ? - intermediate_layers[support_layer_id]->polygons : Polygons(); - - // Print the support base below the support columns, or the support base for the support columns plus the contacts. - if (support_layer_id > 0) { - const Polygons &to_infill_polygons = (support_layer_id < slicing_params.base_raft_layers) ? - raft_layer.polygons : - //FIXME misusing contact_polygons for support columns. - ((raft_layer.contact_polygons == nullptr) ? Polygons() : *raft_layer.contact_polygons); - // Trees may cut through the raft layers down to a print bed. - Flow flow(float(support_params.support_material_flow.width()), float(raft_layer.height), support_params.support_material_flow.nozzle_diameter()); - assert(!raft_layer.bridging); - if (! to_infill_polygons.empty()) { - Fill *filler = filler_support.get(); - filler->angle = support_params.raft_angle_base; - filler->spacing = support_params.support_material_flow.spacing(); - filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_params.support_density)); - fill_expolygons_with_sheath_generate_paths( - // Destination - support_layer.support_fills.entities, - // Regions to fill - tree_polygons.empty() ? to_infill_polygons : diff(to_infill_polygons, tree_polygons), - // Filler and its parameters - filler, float(support_params.support_density), - // Extrusion parameters - ExtrusionRole::SupportMaterial, flow, - support_params.with_sheath, false); - } - if (! tree_polygons.empty()) - tree_supports_generate_paths(support_layer.support_fills.entities, tree_polygons, flow); - } - - Fill *filler = filler_interface.get(); - Flow flow = support_params.first_layer_flow; - float density = 0.f; - if (support_layer_id == 0) { - // Base flange. - filler->angle = support_params.raft_angle_1st_layer; - filler->spacing = support_params.first_layer_flow.spacing(); - density = float(config.raft_first_layer_density.value * 0.01); - } else if (support_layer_id >= slicing_params.base_raft_layers) { - filler->angle = support_params.raft_interface_angle(support_layer.interface_id()); - // We don't use $base_flow->spacing because we need a constant spacing - // value that guarantees that all layers are correctly aligned. - filler->spacing = support_params.support_material_flow.spacing(); - assert(! raft_layer.bridging); - flow = Flow(float(support_params.raft_interface_flow.width()), float(raft_layer.height), support_params.raft_interface_flow.nozzle_diameter()); - density = float(support_params.raft_interface_density); - } else - continue; - filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / density)); - fill_expolygons_with_sheath_generate_paths( - // Destination - support_layer.support_fills.entities, - // Regions to fill - tree_polygons.empty() ? raft_layer.polygons : diff(raft_layer.polygons, tree_polygons), - // Filler and its parameters - filler, density, - // Extrusion parameters - (support_layer_id < slicing_params.base_raft_layers) ? ExtrusionRole::SupportMaterial : ExtrusionRole::SupportMaterialInterface, flow, - // sheath at first layer - support_layer_id == 0, support_layer_id == 0); - } - }); - - struct LayerCacheItem { - LayerCacheItem(SupportGeneratorLayerExtruded *layer_extruded = nullptr) : layer_extruded(layer_extruded) {} - SupportGeneratorLayerExtruded *layer_extruded; - std::vector overlapping; - }; - struct LayerCache { - SupportGeneratorLayerExtruded bottom_contact_layer; - SupportGeneratorLayerExtruded top_contact_layer; - SupportGeneratorLayerExtruded base_layer; - SupportGeneratorLayerExtruded interface_layer; - SupportGeneratorLayerExtruded base_interface_layer; - boost::container::static_vector nonempty; - - void add_nonempty_and_sort() { - for (SupportGeneratorLayerExtruded *item : { &bottom_contact_layer, &top_contact_layer, &interface_layer, &base_interface_layer, &base_layer }) - if (! item->empty()) - this->nonempty.emplace_back(item); - // Sort the layers with the same print_z coordinate by their heights, thickest first. - std::stable_sort(this->nonempty.begin(), this->nonempty.end(), [](const LayerCacheItem &lc1, const LayerCacheItem &lc2) { return lc1.layer_extruded->layer->height > lc2.layer_extruded->layer->height; }); - } - }; - std::vector layer_caches(support_layers.size()); - - tbb::parallel_for(tbb::blocked_range(n_raft_layers, support_layers.size()), - [&config, &slicing_params, &support_params, &support_layers, &bottom_contacts, &top_contacts, &intermediate_layers, &interface_layers, &base_interface_layers, &layer_caches, &loop_interface_processor, - &bbox_object, &angles, n_raft_layers, link_max_length_factor] - (const tbb::blocked_range& range) { - // Indices of the 1st layer in their respective container at the support layer height. - size_t idx_layer_bottom_contact = size_t(-1); - size_t idx_layer_top_contact = size_t(-1); - size_t idx_layer_intermediate = size_t(-1); - size_t idx_layer_interface = size_t(-1); - size_t idx_layer_base_interface = size_t(-1); - const auto fill_type_first_layer = ipRectilinear; - auto filler_interface = std::unique_ptr(Fill::new_from_type(support_params.contact_fill_pattern)); - // Filler for the 1st layer interface, if different from filler_interface. - auto filler_first_layer_ptr = std::unique_ptr(range.begin() == 0 && support_params.contact_fill_pattern != fill_type_first_layer ? Fill::new_from_type(fill_type_first_layer) : nullptr); - // Pointer to the 1st layer interface filler. - auto filler_first_layer = filler_first_layer_ptr ? filler_first_layer_ptr.get() : filler_interface.get(); - // Filler for the 1st layer interface, if different from filler_interface. - auto filler_raft_contact_ptr = std::unique_ptr(range.begin() == n_raft_layers && config.support_material_interface_layers.value == 0 ? - Fill::new_from_type(support_params.raft_interface_fill_pattern) : nullptr); - // Pointer to the 1st layer interface filler. - auto filler_raft_contact = filler_raft_contact_ptr ? filler_raft_contact_ptr.get() : filler_interface.get(); - // Filler for the base interface (to be used for soluble interface / non soluble base, to produce non soluble interface layer below soluble interface layer). - auto filler_base_interface = std::unique_ptr(base_interface_layers.empty() ? nullptr : - Fill::new_from_type(support_params.interface_density > 0.95 || support_params.with_sheath ? ipRectilinear : ipSupportBase)); - auto filler_support = std::unique_ptr(Fill::new_from_type(support_params.base_fill_pattern)); - filler_interface->set_bounding_box(bbox_object); - if (filler_first_layer_ptr) - filler_first_layer_ptr->set_bounding_box(bbox_object); - if (filler_raft_contact_ptr) - filler_raft_contact_ptr->set_bounding_box(bbox_object); - if (filler_base_interface) - filler_base_interface->set_bounding_box(bbox_object); - filler_support->set_bounding_box(bbox_object); - for (size_t support_layer_id = range.begin(); support_layer_id < range.end(); ++ support_layer_id) - { - SupportLayer &support_layer = *support_layers[support_layer_id]; - LayerCache &layer_cache = layer_caches[support_layer_id]; - const float support_interface_angle = config.support_material_style.value == smsGrid ? - support_params.interface_angle : support_params.raft_interface_angle(support_layer.interface_id()); - - // Find polygons with the same print_z. - SupportGeneratorLayerExtruded &bottom_contact_layer = layer_cache.bottom_contact_layer; - SupportGeneratorLayerExtruded &top_contact_layer = layer_cache.top_contact_layer; - SupportGeneratorLayerExtruded &base_layer = layer_cache.base_layer; - SupportGeneratorLayerExtruded &interface_layer = layer_cache.interface_layer; - SupportGeneratorLayerExtruded &base_interface_layer = layer_cache.base_interface_layer; - // Increment the layer indices to find a layer at support_layer.print_z. - { - auto fun = [&support_layer](const SupportGeneratorLayer *l){ return l->print_z >= support_layer.print_z - EPSILON; }; - idx_layer_bottom_contact = idx_higher_or_equal(bottom_contacts, idx_layer_bottom_contact, fun); - idx_layer_top_contact = idx_higher_or_equal(top_contacts, idx_layer_top_contact, fun); - idx_layer_intermediate = idx_higher_or_equal(intermediate_layers, idx_layer_intermediate, fun); - idx_layer_interface = idx_higher_or_equal(interface_layers, idx_layer_interface, fun); - idx_layer_base_interface = idx_higher_or_equal(base_interface_layers, idx_layer_base_interface,fun); - } - // Copy polygons from the layers. - if (idx_layer_bottom_contact < bottom_contacts.size() && bottom_contacts[idx_layer_bottom_contact]->print_z < support_layer.print_z + EPSILON) - bottom_contact_layer.layer = bottom_contacts[idx_layer_bottom_contact]; - if (idx_layer_top_contact < top_contacts.size() && top_contacts[idx_layer_top_contact]->print_z < support_layer.print_z + EPSILON) - top_contact_layer.layer = top_contacts[idx_layer_top_contact]; - if (idx_layer_interface < interface_layers.size() && interface_layers[idx_layer_interface]->print_z < support_layer.print_z + EPSILON) - interface_layer.layer = interface_layers[idx_layer_interface]; - if (idx_layer_base_interface < base_interface_layers.size() && base_interface_layers[idx_layer_base_interface]->print_z < support_layer.print_z + EPSILON) - base_interface_layer.layer = base_interface_layers[idx_layer_base_interface]; - if (idx_layer_intermediate < intermediate_layers.size() && intermediate_layers[idx_layer_intermediate]->print_z < support_layer.print_z + EPSILON) - base_layer.layer = intermediate_layers[idx_layer_intermediate]; - - // This layer is a raft contact layer. Any contact polygons at this layer are raft contacts. - bool raft_layer = slicing_params.interface_raft_layers && top_contact_layer.layer && is_approx(top_contact_layer.layer->print_z, slicing_params.raft_contact_top_z); - if (config.support_material_interface_layers == 0) { - // If no top interface layers were requested, we treat the contact layer exactly as a generic base layer. - // Don't merge the raft contact layer though. - if (support_params.can_merge_support_regions && ! raft_layer) { - if (base_layer.could_merge(top_contact_layer)) - base_layer.merge(std::move(top_contact_layer)); - else if (base_layer.empty()) - base_layer = std::move(top_contact_layer); - } - } else { - loop_interface_processor.generate(top_contact_layer, support_params.support_material_interface_flow); - // If no loops are allowed, we treat the contact layer exactly as a generic interface layer. - // Merge interface_layer into top_contact_layer, as the top_contact_layer is not synchronized and therefore it will be used - // to trim other layers. - if (top_contact_layer.could_merge(interface_layer) && ! raft_layer) - top_contact_layer.merge(std::move(interface_layer)); - } - if ((config.support_material_interface_layers == 0 || config.support_material_bottom_interface_layers == 0) && support_params.can_merge_support_regions) { - if (base_layer.could_merge(bottom_contact_layer)) - base_layer.merge(std::move(bottom_contact_layer)); - else if (base_layer.empty() && ! bottom_contact_layer.empty() && ! bottom_contact_layer.layer->bridging) - base_layer = std::move(bottom_contact_layer); - } else if (bottom_contact_layer.could_merge(top_contact_layer) && ! raft_layer) - top_contact_layer.merge(std::move(bottom_contact_layer)); - else if (bottom_contact_layer.could_merge(interface_layer)) - bottom_contact_layer.merge(std::move(interface_layer)); - -#if 0 - if ( ! interface_layer.empty() && ! base_layer.empty()) { - // turn base support into interface when it's contained in our holes - // (this way we get wider interface anchoring) - //FIXME The intention of the code below is unclear. One likely wanted to just merge small islands of base layers filling in the holes - // inside interface layers, but the code below fills just too much, see GH #4570 - Polygons islands = top_level_islands(interface_layer.layer->polygons); - polygons_append(interface_layer.layer->polygons, intersection(base_layer.layer->polygons, islands)); - base_layer.layer->polygons = diff(base_layer.layer->polygons, islands); - } -#endif - - // Top and bottom contacts, interface layers. - enum class InterfaceLayerType { TopContact, BottomContact, RaftContact, Interface, InterfaceAsBase }; - auto extrude_interface = [&](SupportGeneratorLayerExtruded &layer_ex, InterfaceLayerType interface_layer_type) { - if (! layer_ex.empty() && ! layer_ex.polygons_to_extrude().empty()) { - bool interface_as_base = interface_layer_type == InterfaceLayerType::InterfaceAsBase; - bool raft_contact = interface_layer_type == InterfaceLayerType::RaftContact; - //FIXME Bottom interfaces are extruded with the briding flow. Some bridging layers have its height slightly reduced, therefore - // the bridging flow does not quite apply. Reduce the flow to area of an ellipse? (A = pi * a * b) - auto *filler = raft_contact ? filler_raft_contact : filler_interface.get(); - auto interface_flow = layer_ex.layer->bridging ? - Flow::bridging_flow(layer_ex.layer->height, support_params.support_material_bottom_interface_flow.nozzle_diameter()) : - (raft_contact ? &support_params.raft_interface_flow : - interface_as_base ? &support_params.support_material_flow : &support_params.support_material_interface_flow) - ->with_height(float(layer_ex.layer->height)); - filler->angle = interface_as_base ? - // If zero interface layers are configured, use the same angle as for the base layers. - angles[support_layer_id % angles.size()] : - // Use interface angle for the interface layers. - raft_contact ? - support_params.raft_interface_angle(support_layer.interface_id()) : - support_interface_angle; - double density = raft_contact ? support_params.raft_interface_density : interface_as_base ? support_params.support_density : support_params.interface_density; - filler->spacing = raft_contact ? support_params.raft_interface_flow.spacing() : - interface_as_base ? support_params.support_material_flow.spacing() : support_params.support_material_interface_flow.spacing(); - filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / density)); - fill_expolygons_generate_paths( - // Destination - layer_ex.extrusions, - // Regions to fill - union_safety_offset_ex(layer_ex.polygons_to_extrude()), - // Filler and its parameters - filler, float(density), - // Extrusion parameters - ExtrusionRole::SupportMaterialInterface, interface_flow); - } - }; - const bool top_interfaces = config.support_material_interface_layers.value != 0; - const bool bottom_interfaces = top_interfaces && config.support_material_bottom_interface_layers != 0; - extrude_interface(top_contact_layer, raft_layer ? InterfaceLayerType::RaftContact : top_interfaces ? InterfaceLayerType::TopContact : InterfaceLayerType::InterfaceAsBase); - extrude_interface(bottom_contact_layer, bottom_interfaces ? InterfaceLayerType::BottomContact : InterfaceLayerType::InterfaceAsBase); - extrude_interface(interface_layer, top_interfaces ? InterfaceLayerType::Interface : InterfaceLayerType::InterfaceAsBase); - - // Base interface layers under soluble interfaces - if ( ! base_interface_layer.empty() && ! base_interface_layer.polygons_to_extrude().empty()) { - Fill *filler = filler_base_interface.get(); - //FIXME Bottom interfaces are extruded with the briding flow. Some bridging layers have its height slightly reduced, therefore - // the bridging flow does not quite apply. Reduce the flow to area of an ellipse? (A = pi * a * b) - assert(! base_interface_layer.layer->bridging); - Flow interface_flow = support_params.support_material_flow.with_height(float(base_interface_layer.layer->height)); - filler->angle = support_interface_angle; - filler->spacing = support_params.support_material_interface_flow.spacing(); - filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_params.interface_density)); - fill_expolygons_generate_paths( - // Destination - base_interface_layer.extrusions, - //base_layer_interface.extrusions, - // Regions to fill - union_safety_offset_ex(base_interface_layer.polygons_to_extrude()), - // Filler and its parameters - filler, float(support_params.interface_density), - // Extrusion parameters - ExtrusionRole::SupportMaterial, interface_flow); - } - - // Base support or flange. - if (! base_layer.empty() && ! base_layer.polygons_to_extrude().empty()) { - Fill *filler = filler_support.get(); - filler->angle = angles[support_layer_id % angles.size()]; - // We don't use $base_flow->spacing because we need a constant spacing - // value that guarantees that all layers are correctly aligned. - assert(! base_layer.layer->bridging); - auto flow = support_params.support_material_flow.with_height(float(base_layer.layer->height)); - filler->spacing = support_params.support_material_flow.spacing(); - filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_params.support_density)); - float density = float(support_params.support_density); - bool sheath = support_params.with_sheath; - bool no_sort = false; - bool done = false; - if (base_layer.layer->bottom_z < EPSILON) { - // Base flange (the 1st layer). - filler = filler_first_layer; - filler->angle = Geometry::deg2rad(float(config.support_material_angle.value + 90.)); - density = float(config.raft_first_layer_density.value * 0.01); - flow = support_params.first_layer_flow; - // use the proper spacing for first layer as we don't need to align - // its pattern to the other layers - //FIXME When paralellizing, each thread shall have its own copy of the fillers. - filler->spacing = flow.spacing(); - filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / density)); - sheath = true; - no_sort = true; - } else if (config.support_material_style == SupportMaterialStyle::smsOrganic) { - tree_supports_generate_paths(base_layer.extrusions, base_layer.polygons_to_extrude(), flow); - done = true; - } - if (! done) - fill_expolygons_with_sheath_generate_paths( - // Destination - base_layer.extrusions, - // Regions to fill - base_layer.polygons_to_extrude(), - // Filler and its parameters - filler, density, - // Extrusion parameters - ExtrusionRole::SupportMaterial, flow, - sheath, no_sort); - } - - // Merge base_interface_layers to base_layers to avoid unneccessary retractions - if (! base_layer.empty() && ! base_interface_layer.empty() && ! base_layer.polygons_to_extrude().empty() && ! base_interface_layer.polygons_to_extrude().empty() && - base_layer.could_merge(base_interface_layer)) - base_layer.merge(std::move(base_interface_layer)); - - layer_cache.add_nonempty_and_sort(); - - // Collect the support areas with this print_z into islands, as there is no need - // for retraction over these islands. - Polygons polys; - // Collect the extrusions, sorted by the bottom extrusion height. - for (LayerCacheItem &layer_cache_item : layer_cache.nonempty) { - // Collect islands to polys. - layer_cache_item.layer_extruded->polygons_append(polys); - // The print_z of the top contact surfaces and bottom_z of the bottom contact surfaces are "free" - // in a sense that they are not synchronized with other support layers. As the top and bottom contact surfaces - // are inflated to achieve a better anchoring, it may happen, that these surfaces will at least partially - // overlap in Z with another support layers, leading to over-extrusion. - // Mitigate the over-extrusion by modulating the extrusion rate over these regions. - // The print head will follow the same print_z, but the layer thickness will be reduced - // where it overlaps with another support layer. - //FIXME When printing a briging path, what is an equivalent height of the squished extrudate of the same width? - // Collect overlapping top/bottom surfaces. - layer_cache_item.overlapping.reserve(20); - coordf_t bottom_z = layer_cache_item.layer_extruded->layer->bottom_print_z() + EPSILON; - auto add_overlapping = [&layer_cache_item, bottom_z](const SupportGeneratorLayersPtr &layers, size_t idx_top) { - for (int i = int(idx_top) - 1; i >= 0 && layers[i]->print_z > bottom_z; -- i) - layer_cache_item.overlapping.push_back(layers[i]); - }; - add_overlapping(top_contacts, idx_layer_top_contact); - if (layer_cache_item.layer_extruded->layer->layer_type == SupporLayerType::BottomContact) { - // Bottom contact layer may overlap with a base layer, which may be changed to interface layer. - add_overlapping(intermediate_layers, idx_layer_intermediate); - add_overlapping(interface_layers, idx_layer_interface); - add_overlapping(base_interface_layers, idx_layer_base_interface); - } - // Order the layers by lexicographically by an increasing print_z and a decreasing layer height. - std::stable_sort(layer_cache_item.overlapping.begin(), layer_cache_item.overlapping.end(), [](auto *l1, auto *l2) { return *l1 < *l2; }); - } - assert(support_layer.support_islands.empty()); - if (! polys.empty()) { - support_layer.support_islands = union_ex(polys); - support_layer.support_islands_bboxes.reserve(support_layer.support_islands.size()); - for (const ExPolygon &expoly : support_layer.support_islands) - support_layer.support_islands_bboxes.emplace_back(get_extents(expoly).inflated(SCALED_EPSILON)); - } - } // for each support_layer_id - }); - - // Now modulate the support layer height in parallel. - tbb::parallel_for(tbb::blocked_range(n_raft_layers, support_layers.size()), - [&support_layers, &layer_caches] - (const tbb::blocked_range& range) { - for (size_t support_layer_id = range.begin(); support_layer_id < range.end(); ++ support_layer_id) { - SupportLayer &support_layer = *support_layers[support_layer_id]; - LayerCache &layer_cache = layer_caches[support_layer_id]; - // For all extrusion types at this print_z, ordered by decreasing layer height: - for (LayerCacheItem &layer_cache_item : layer_cache.nonempty) { - // Trim the extrusion height from the bottom by the overlapping layers. - modulate_extrusion_by_overlapping_layers(layer_cache_item.layer_extruded->extrusions, *layer_cache_item.layer_extruded->layer, layer_cache_item.overlapping); - support_layer.support_fills.append(std::move(layer_cache_item.layer_extruded->extrusions)); - } - } - }); - -#ifndef NDEBUG - struct Test { - static bool verify_nonempty(const ExtrusionEntityCollection *collection) { - for (const ExtrusionEntity *ee : collection->entities) { - if (const ExtrusionPath *path = dynamic_cast(ee)) - assert(! path->empty()); - else if (const ExtrusionMultiPath *multipath = dynamic_cast(ee)) - assert(! multipath->empty()); - else if (const ExtrusionEntityCollection *eecol = dynamic_cast(ee)) { - assert(! eecol->empty()); - return verify_nonempty(eecol); - } else - assert(false); - } - return true; - } - }; - for (const SupportLayer *support_layer : support_layers) - assert(Test::verify_nonempty(&support_layer->support_fills)); -#endif // NDEBUG -} - /* void PrintObjectSupportMaterial::clip_by_pillars( const PrintObject &object, diff --git a/src/libslic3r/SupportMaterial.hpp b/src/libslic3r/SupportMaterial.hpp index 24dc8507e..bbb991c3e 100644 --- a/src/libslic3r/SupportMaterial.hpp +++ b/src/libslic3r/SupportMaterial.hpp @@ -12,54 +12,6 @@ namespace Slic3r { class PrintObject; -// 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); - -// 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); - -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); - // 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 @@ -84,6 +36,10 @@ public: void generate(PrintObject &object); private: + using SupportGeneratorLayersPtr = FFFSupport::SupportGeneratorLayersPtr; + using SupportGeneratorLayerStorage = FFFSupport::SupportGeneratorLayerStorage; + using SupportParameters = FFFSupport::SupportParameters; + std::vector buildplate_covered(const PrintObject &object) const; // Generate top contact layers supporting overhangs. diff --git a/src/libslic3r/TreeSupport.cpp b/src/libslic3r/TreeSupport.cpp index 9a7b203c6..a3264990a 100644 --- a/src/libslic3r/TreeSupport.cpp +++ b/src/libslic3r/TreeSupport.cpp @@ -19,9 +19,10 @@ #include "Polygon.hpp" #include "Polyline.hpp" #include "MutablePolygon.hpp" -#include "SupportMaterial.hpp" #include "TriangleMeshSlicer.hpp" +#include "Support/SupportCommon.hpp" + #include #include #include @@ -37,7 +38,6 @@ #include #include -#include #if defined(TREE_SUPPORT_SHOW_ERRORS) && defined(_WIN32) #define TREE_SUPPORT_SHOW_ERRORS_WIN32 @@ -53,6 +53,8 @@ // #define TREESUPPORT_DEBUG_SVG +using namespace Slic3r::FFFSupport; + namespace Slic3r { @@ -962,13 +964,11 @@ static LayerIndex layer_idx_floor(const SlicingParameters &slicing_params, const } static inline SupportGeneratorLayer& layer_initialize( - SupportGeneratorLayer &layer_new, - const SupporLayerType layer_type, - const SlicingParameters &slicing_params, + SupportGeneratorLayer &layer_new, + const SlicingParameters &slicing_params, const TreeSupportSettings &config, - const size_t layer_idx) + const size_t layer_idx) { - layer_new.layer_type = layer_type; 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; @@ -976,29 +976,26 @@ static inline SupportGeneratorLayer& layer_initialize( } // Using the std::deque as an allocator. -inline SupportGeneratorLayer& layer_allocate( - std::deque &layer_storage, +inline SupportGeneratorLayer& layer_allocate_unguarded( + SupportGeneratorLayerStorage &layer_storage, SupporLayerType layer_type, const SlicingParameters &slicing_params, const TreeSupportSettings &config, size_t layer_idx) { - //FIXME take raft into account. - layer_storage.push_back(SupportGeneratorLayer()); - return layer_initialize(layer_storage.back(), layer_type, slicing_params, config, layer_idx); + SupportGeneratorLayer &layer = layer_storage.allocate_unguarded(layer_type); + return layer_initialize(layer, slicing_params, config, layer_idx); } inline SupportGeneratorLayer& layer_allocate( - std::deque &layer_storage, - tbb::spin_mutex& layer_storage_mutex, + SupportGeneratorLayerStorage &layer_storage, SupporLayerType layer_type, const SlicingParameters &slicing_params, const TreeSupportSettings &config, size_t layer_idx) { - tbb::spin_mutex::scoped_lock lock(layer_storage_mutex); - layer_storage.push_back(SupportGeneratorLayer()); - return layer_initialize(layer_storage.back(), layer_type, slicing_params, config, layer_idx); + SupportGeneratorLayer &layer = layer_storage.allocate(layer_type); + return layer_initialize(layer, slicing_params, config, layer_idx); } int generate_raft_contact( @@ -1016,7 +1013,7 @@ int generate_raft_contact( while (raft_contact_layer_idx > 0 && config.raft_layers[raft_contact_layer_idx] > print_object.slicing_parameters().raft_contact_top_z + EPSILON) -- raft_contact_layer_idx; // Create the raft contact layer. - SupportGeneratorLayer &raft_contact_layer = layer_allocate(layer_storage, SupporLayerType::TopContact, print_object.slicing_parameters(), config, raft_contact_layer_idx); + SupportGeneratorLayer &raft_contact_layer = layer_allocate_unguarded(layer_storage, SupporLayerType::TopContact, print_object.slicing_parameters(), config, raft_contact_layer_idx); top_contacts[raft_contact_layer_idx] = &raft_contact_layer; const ExPolygons &lslices = print_object.get_layer(0)->lslices; double expansion = print_object.config().raft_expansion.value; @@ -1115,7 +1112,7 @@ public: { SupportGeneratorLayer*& l = top_contacts[insert_layer_idx]; if (l == nullptr) - l = &layer_allocate(layer_storage, SupporLayerType::TopContact, slicing_parameters, config, insert_layer_idx); + l = &layer_allocate_unguarded(layer_storage, SupporLayerType::TopContact, slicing_parameters, config, insert_layer_idx); // will be unioned in finalize_interface_and_support_areas() append(l->polygons, std::move(new_roofs)); } @@ -1141,7 +1138,7 @@ public: std::lock_guard lock(m_mutex_layer_storage); SupportGeneratorLayer*& l = top_contacts[0]; if (l == nullptr) - l = &layer_allocate(layer_storage, SupporLayerType::TopContact, slicing_parameters, config, 0); + l = &layer_allocate_unguarded(layer_storage, SupporLayerType::TopContact, slicing_parameters, config, 0); append(l->polygons, std::move(overhang_areas)); } @@ -3309,7 +3306,6 @@ static void finalize_interface_and_support_areas( #endif // SLIC3R_TREESUPPORTS_PROGRESS // Iterate over the generated circles in parallel and clean them up. Also add support floor. - tbb::spin_mutex layer_storage_mutex; tbb::parallel_for(tbb::blocked_range(0, support_layer_storage.size()), [&](const tbb::blocked_range &range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { @@ -3402,7 +3398,7 @@ static void finalize_interface_and_support_areas( } if (! floor_layer.empty()) { if (support_bottom == nullptr) - support_bottom = &layer_allocate(layer_storage, layer_storage_mutex, SupporLayerType::BottomContact, print_object.slicing_parameters(), config, layer_idx); + support_bottom = &layer_allocate(layer_storage, SupporLayerType::BottomContact, print_object.slicing_parameters(), config, layer_idx); support_bottom->polygons = union_(floor_layer, support_bottom->polygons); base_layer_polygons = diff_clipped(base_layer_polygons, offset(support_bottom->polygons, scaled(0.01), jtMiter, 1.2)); // Subtract the support floor from the normal support. } @@ -3410,11 +3406,11 @@ static void finalize_interface_and_support_areas( if (! support_roof_polygons.empty()) { if (support_roof == nullptr) - support_roof = top_contacts[layer_idx] = &layer_allocate(layer_storage, layer_storage_mutex, SupporLayerType::TopContact, print_object.slicing_parameters(), config, layer_idx); + support_roof = top_contacts[layer_idx] = &layer_allocate(layer_storage, SupporLayerType::TopContact, print_object.slicing_parameters(), config, layer_idx); support_roof->polygons = union_(support_roof_polygons); } if (! base_layer_polygons.empty()) { - SupportGeneratorLayer *base_layer = intermediate_layers[layer_idx] = &layer_allocate(layer_storage, layer_storage_mutex, SupporLayerType::Base, print_object.slicing_parameters(), config, layer_idx); + SupportGeneratorLayer *base_layer = intermediate_layers[layer_idx] = &layer_allocate(layer_storage, SupporLayerType::Base, print_object.slicing_parameters(), config, layer_idx); base_layer->polygons = union_(base_layer_polygons); }