diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 45bf47a29..e7494a6af 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -112,6 +112,8 @@ set(lisbslic3r_sources Fill/Lightning/TreeNode.hpp Fill/FillRectilinear.cpp Fill/FillRectilinear.hpp + Fill/FillContour.hpp + Fill/FillContour.cpp Flow.cpp Flow.hpp Frustum.cpp diff --git a/src/libslic3r/ExPolygon.cpp b/src/libslic3r/ExPolygon.cpp index 635a2c1fd..222cb2440 100644 --- a/src/libslic3r/ExPolygon.cpp +++ b/src/libslic3r/ExPolygon.cpp @@ -432,6 +432,29 @@ bool ExPolygon::remove_colinear_points() { return removed; } +double get_expolygons_area(const ExPolygons& expolys) +{ + return std::accumulate(expolys.begin(), expolys.end(), (double)(0), [](double val, const ExPolygon& expoly) { + return val + expoly.area(); + }); +} + +bool is_narrow_expolygon(const ExPolygon& expolygon, double min_width, double min_area, double remain_area_ratio_thres) +{ + double original_area = expolygon.area(); + if (original_area < min_area) + return true; + + ExPolygons offsets = offset_ex(expolygon, -min_width / 2); + if (offsets.empty()) + return true; + + if (get_expolygons_area(offsets) / (original_area + EPSILON) < remain_area_ratio_thres) + return true; + return false; +} + + // Do expolygons match? If they match, they must have the same topology, // however their contours may be rotated. bool expolygons_match(const ExPolygon &l, const ExPolygon &r) diff --git a/src/libslic3r/ExPolygon.hpp b/src/libslic3r/ExPolygon.hpp index 9c69580c4..34b12bbe1 100644 --- a/src/libslic3r/ExPolygon.hpp +++ b/src/libslic3r/ExPolygon.hpp @@ -456,6 +456,9 @@ inline ExPolygons expolygons_simplify(const ExPolygons &expolys, double toleranc return out; } +double get_expolygons_area(const ExPolygons& expolys); +bool is_narrow_expolygon(const ExPolygon& expolygon, double min_width, double min_area = scale_(1) * scale_(1), double remain_area_ratio_thres = 0.1); + // Do expolygons match? If they match, they must have the same topology, // however their contours may be rotated. bool expolygons_match(const ExPolygon &l, const ExPolygon &r); diff --git a/src/libslic3r/ExtrusionEntity.cpp b/src/libslic3r/ExtrusionEntity.cpp index 1a771da44..19151eb5e 100644 --- a/src/libslic3r/ExtrusionEntity.cpp +++ b/src/libslic3r/ExtrusionEntity.cpp @@ -619,6 +619,7 @@ std::string ExtrusionEntity::role_to_string(ExtrusionRole role) case erExternalPerimeter : return L("Outer wall"); case erOverhangPerimeter : return L("Overhang wall"); case erInternalInfill : return L("Sparse infill"); + case erEnsureVertical : return L("Ensure vertical"); case erSolidInfill : return L("Internal solid infill"); case erTopSolidInfill : return L("Top surface"); case erBottomSurface : return L("Bottom surface"); @@ -649,6 +650,8 @@ ExtrusionRole ExtrusionEntity::string_to_role(const std::string_view role) return erOverhangPerimeter; else if (role == L("Sparse infill")) return erInternalInfill; + else if (role == L("Ensure vertical")) + return erEnsureVertical; else if (role == L("Internal solid infill")) return erSolidInfill; else if (role == L("Top surface")) diff --git a/src/libslic3r/ExtrusionEntity.hpp b/src/libslic3r/ExtrusionEntity.hpp index 8cf6f4886..1e35f82bb 100644 --- a/src/libslic3r/ExtrusionEntity.hpp +++ b/src/libslic3r/ExtrusionEntity.hpp @@ -48,6 +48,7 @@ enum ExtrusionRole : uint8_t { erOverhangPerimeter, erInternalInfill, erSolidInfill, + erEnsureVertical, erTopSolidInfill, erBottomSurface, erIroning, @@ -68,7 +69,8 @@ enum ExtrusionRole : uint8_t { enum CustomizeFlag : uint8_t { cfNone, - cfCircleCompensation // shaft hole tolerance compensation + cfCircleCompensation, // shaft hole tolerance compensation + cfEnsureVertical }; // Special flags describing loop @@ -97,6 +99,7 @@ inline bool is_infill(ExtrusionRole role) return role == erBridgeInfill || role == erInternalInfill || role == erSolidInfill + || role == erEnsureVertical || role == erTopSolidInfill || role == erBottomSurface || role == erIroning; @@ -111,6 +114,7 @@ inline bool is_solid_infill(ExtrusionRole role) { return role == erBridgeInfill || role == erSolidInfill + || role == erEnsureVertical || role == erTopSolidInfill || role == erBottomSurface || role == erIroning; diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index bd7523f66..a13c2006f 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -14,6 +14,7 @@ #include "FillLightning.hpp" #include "FillConcentricInternal.hpp" #include "FillConcentric.hpp" +#include "FillContour.hpp" #define NARROW_INFILL_AREA_THRESHOLD 3 @@ -134,16 +135,6 @@ struct SurfaceFill { ExPolygons no_overlap_expolygons; }; -// BBS: used to judge whether the internal solid infill area is narrow -static bool is_narrow_infill_area(const ExPolygon& expolygon) -{ - ExPolygons offsets = offset_ex(expolygon, -scale_(NARROW_INFILL_AREA_THRESHOLD)); - if (offsets.empty()) - return true; - - return false; -} - std::vector group_fills(const Layer &layer) { std::vector surface_fills; @@ -177,7 +168,9 @@ std::vector group_fills(const Layer &layer) if (surface.is_solid()) { params.density = 100.f; //FIXME for non-thick bridges, shall we allow a bottom surface pattern? - if (surface.is_solid_infill()) + if (surface.is_ensure_vertical()) + params.pattern = InfillPattern::ipEnsureVertical; + else if (surface.is_solid_infill()) params.pattern = region_config.internal_solid_infill_pattern.value; else if (surface.is_external() && !is_bridge) params.pattern = surface.is_top() ? region_config.top_surface_pattern.value : region_config.bottom_surface_pattern.value; @@ -191,7 +184,7 @@ std::vector group_fills(const Layer &layer) is_bridge ? erBridgeInfill : (surface.is_solid() ? - (surface.is_top() ? erTopSolidInfill : (surface.is_bottom()? erBottomSurface : erSolidInfill)) : + (surface.is_top() ? erTopSolidInfill : (surface.is_bottom()? erBottomSurface : surface.is_ensure_vertical()?erEnsureVertical:erSolidInfill)) : erInternalInfill); params.bridge_angle = float(surface.bridge_angle); params.angle = float(Geometry::deg2rad(region_config.infill_direction.value)); @@ -210,6 +203,8 @@ std::vector group_fills(const Layer &layer) params.top_surface_speed = region_config.top_surface_speed.get_at(layer.get_extruder_id(params.extruder)); else if (params.extrusion_role == erSolidInfill) params.solid_infill_speed = region_config.internal_solid_infill_speed.get_at(layer.get_extruder_id(params.extruder)); + else if (params.extrusion_role == erEnsureVertical) + params.solid_infill_speed = region_config.bridge_speed.get_at(layer.get_extruder_id(params.extruder)); } // Calculate flow spacing for infill pattern generation. if (surface.is_solid() || is_bridge) { @@ -363,44 +358,90 @@ std::vector group_fills(const Layer &layer) // BBS: detect narrow internal solid infill area and use ipConcentricInternal pattern instead if (layer.object()->config().detect_narrow_internal_solid_infill) { + ExPolygons lower_internal_areas; + BoundingBox lower_internal_bbox; + if (layer.lower_layer) { + for (auto layerm : layer.lower_layer->regions()) { + auto internal_surfaces = layerm->fill_surfaces.filter_by_types({ stInternal,stInternalVoid }); + for (auto surface : internal_surfaces) + lower_internal_areas.push_back(surface->expolygon); + } + lower_internal_bbox = get_extents(lower_internal_areas); + } size_t surface_fills_size = surface_fills.size(); for (size_t i = 0; i < surface_fills_size; i++) { if (surface_fills[i].surface.surface_type != stInternalSolid) continue; size_t expolygons_size = surface_fills[i].expolygons.size(); - std::vector narrow_expolygons_index; - narrow_expolygons_index.reserve(expolygons_size); + std::vector narrow_expoly_idx; + std::vector narrow_floating_expoly_idx; + std::vector use_floating_filler; // BBS: get the index list of narrow expolygon - for (size_t j = 0; j < expolygons_size; j++) - if (is_narrow_infill_area(surface_fills[i].expolygons[j])) - narrow_expolygons_index.push_back(j); + for (size_t j = 0; j < expolygons_size; j++) { + auto bbox = get_extents(surface_fills[i].expolygons[j]); + if (is_narrow_expolygon(surface_fills[i].expolygons[j], scale_(NARROW_INFILL_AREA_THRESHOLD) * 2)) { + if (bbox.overlap(lower_internal_bbox) && !intersection_ex(offset_ex(surface_fills[i].expolygons[j],SCALED_EPSILON), lower_internal_areas).empty()) { + narrow_floating_expoly_idx.emplace_back(j); + } + else { + narrow_expoly_idx.emplace_back(j); + } + } + } - if (narrow_expolygons_index.size() == 0) { + if (narrow_expoly_idx.empty() && narrow_floating_expoly_idx.empty()) { // BBS: has no narrow expolygon continue; } - else if (narrow_expolygons_index.size() == expolygons_size) { - // BBS: all expolygons are narrow, directly change the fill pattern + else if (narrow_floating_expoly_idx.size() == expolygons_size) { + surface_fills[i].params.pattern = ipEnsureVertical; + surface_fills[i].params.extrusion_role = erEnsureVertical; + surface_fills[i].surface.surface_type = stEnsureVertical; + } + else if (narrow_expoly_idx.size() == expolygons_size) { surface_fills[i].params.pattern = ipConcentricInternal; } else { // BBS: some expolygons are narrow, spilit surface_fills[i] and rearrange the expolygons - params = surface_fills[i].params; - params.pattern = ipConcentricInternal; - surface_fills.emplace_back(params); - surface_fills.back().region_id = surface_fills[i].region_id; - surface_fills.back().surface.surface_type = stInternalSolid; - surface_fills.back().surface.thickness = surface_fills[i].surface.thickness; - surface_fills.back().region_id_group = surface_fills[i].region_id_group; - surface_fills.back().no_overlap_expolygons = surface_fills[i].no_overlap_expolygons; - for (size_t j = 0; j < narrow_expolygons_index.size(); j++) { - // BBS: move the narrow expolygons to new surface_fills.back(); - surface_fills.back().expolygons.emplace_back(std::move(surface_fills[i].expolygons[narrow_expolygons_index[j]])); + if (!narrow_expoly_idx.empty()) { + params = surface_fills[i].params; + params.pattern = ipConcentricInternal; + surface_fills.emplace_back(params); + surface_fills.back().region_id = surface_fills[i].region_id; + surface_fills.back().surface.surface_type = stInternalSolid; + surface_fills.back().surface.thickness = surface_fills[i].surface.thickness; + surface_fills.back().region_id_group = surface_fills[i].region_id_group; + surface_fills.back().no_overlap_expolygons = surface_fills[i].no_overlap_expolygons; + for (size_t j = 0; j < narrow_expoly_idx.size(); j++) { + // BBS: move the narrow expolygons to new surface_fills.back(); + surface_fills.back().expolygons.emplace_back(std::move(surface_fills[i].expolygons[narrow_expoly_idx[j]])); + } } - for (int j = narrow_expolygons_index.size() - 1; j >= 0; j--) { + + if (!narrow_floating_expoly_idx.empty()) { + params = surface_fills[i].params; + params.pattern = ipEnsureVertical; + params.extrusion_role = erEnsureVertical; + surface_fills.emplace_back(params); + surface_fills.back().region_id = surface_fills[i].region_id; + surface_fills.back().surface.surface_type = stEnsureVertical; + surface_fills.back().surface.thickness = surface_fills[i].surface.thickness; + surface_fills.back().region_id_group = surface_fills[i].region_id_group; + surface_fills.back().no_overlap_expolygons = surface_fills[i].no_overlap_expolygons; + for (size_t j = 0; j < narrow_floating_expoly_idx.size(); j++) { + // BBS: move the narrow expolygons to new surface_fills.back(); + surface_fills.back().expolygons.emplace_back(std::move(surface_fills[i].expolygons[narrow_floating_expoly_idx[j]])); + } + } + + std::vector to_be_delete = narrow_floating_expoly_idx; + to_be_delete.insert(to_be_delete.end(), narrow_expoly_idx.begin(), narrow_expoly_idx.end()); + std::sort(to_be_delete.begin(), to_be_delete.end()); + + for (int j = to_be_delete.size() - 1; j >= 0; j--) { // BBS: delete the narrow expolygons from old surface_fills - surface_fills[i].expolygons.erase(surface_fills[i].expolygons.begin() + narrow_expolygons_index[j]); + surface_fills[i].expolygons.erase(surface_fills[i].expolygons.begin() + to_be_delete[j]); } } } @@ -436,7 +477,6 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: for (LayerRegion *layerm : m_regions) layerm->fills.clear(); - #ifdef SLIC3R_DEBUG_SLICE_PROCESSING // this->export_region_fill_surfaces_to_svg_debug("10_fill-initial"); #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ @@ -476,9 +516,49 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: assert(fill_concentric != nullptr); fill_concentric->print_config = &this->object()->print()->config(); fill_concentric->print_object_config = &this->object()->config(); - } else if (surface_fill.params.pattern == ipLightning) + } else if (surface_fill.params.pattern == ipLightning){ dynamic_cast(f.get())->generator = lightning_generator; + } + else if (surface_fill.params.pattern == ipEnsureVertical) { + FillContour* fill_contour = dynamic_cast(f.get()); + assert(fill_contour != nullptr); + ExPolygons lower_unsuporrt_expolys; + Polygons lower_sparse_polys; + if (lower_layer) { + for (LayerRegion* layerm : lower_layer->regions()) { + auto surfaces = layerm->fill_surfaces.filter_by_types({ stInternal,stInternalVoid }); + ExPolygons sexpolys; + for (auto surface : surfaces) { + sexpolys.push_back(surface->expolygon); + } + sexpolys = union_ex(sexpolys); + lower_unsuporrt_expolys = union_ex(lower_unsuporrt_expolys, sexpolys); + } + lower_unsuporrt_expolys = shrink_ex(lower_unsuporrt_expolys, SCALED_EPSILON); + std::vector lower_fills = group_fills(*lower_layer); + bool detect_lower_sparse_lines = true; + for (auto& fill : lower_fills) { + if (fill.params.pattern == ipAdaptiveCubic || fill.params.pattern == ipLightning || fill.params.pattern == ipSupportCubic) { + detect_lower_sparse_lines = false; + break; + } + } + if (detect_lower_sparse_lines) { + float internal_infill_width = 0; + for (auto layerm : lower_layer->regions()) + internal_infill_width += layerm->flow(frInfill).scaled_width(); + internal_infill_width /= lower_layer->m_regions.size(); + Polylines lower_sparse_lines = lower_layer->generate_sparse_infill_polylines_for_anchoring(nullptr, nullptr, nullptr); + lower_sparse_polys = offset(lower_sparse_lines, internal_infill_width / 2); + lower_sparse_polys = union_(lower_sparse_polys); + } + } + fill_contour->lower_sparse_polys = lower_sparse_polys; + fill_contour->lower_layer_unsupport_areas = lower_unsuporrt_expolys; + fill_contour->print_config = &this->object()->print()->config(); + fill_contour->print_object_config = &this->object()->config(); + } // calculate flow spacing for infill pattern generation bool using_internal_flow = ! surface_fill.surface.is_solid() && ! surface_fill.params.bridge; double link_max_length = 0.; @@ -504,7 +584,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: params.anchor_length = surface_fill.params.anchor_length; params.anchor_length_max = surface_fill.params.anchor_length_max; params.resolution = resolution; - params.use_arachne = surface_fill.params.pattern == ipConcentric; + params.use_arachne = surface_fill.params.pattern == ipConcentric || surface_fill.params.pattern == ipEnsureVertical; params.layer_height = m_regions[surface_fill.region_id]->layer()->height; // BBS @@ -522,7 +602,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: params.symmetric_infill_y_axis = surface_fill.params.symmetric_infill_y_axis; } - if (surface_fill.params.pattern == ipGrid) + if (surface_fill.params.pattern == ipGrid || surface_fill.params.pattern == ipEnsureVertical) params.can_reverse = false; LayerRegion* layerm = this->m_regions[surface_fill.region_id]; for (ExPolygon& expoly : surface_fill.expolygons) { diff --git a/src/libslic3r/Fill/FillBase.cpp b/src/libslic3r/Fill/FillBase.cpp index 0cc6e288c..bbea1224b 100644 --- a/src/libslic3r/Fill/FillBase.cpp +++ b/src/libslic3r/Fill/FillBase.cpp @@ -24,6 +24,7 @@ // BBS: new infill pattern header #include "FillConcentricInternal.hpp" #include "FillCrossHatch.hpp" +#include "FillContour.hpp" // #define INFILL_DEBUG_OUTPUT @@ -58,6 +59,7 @@ Fill* Fill::new_from_type(const InfillPattern type) case ipMonotonicLine: return new FillMonotonicLineWGapFill(); case ipZigZag: return new FillZigZag(); case ipCrossZag: return new FillCrossZag(); + case ipEnsureVertical: return new FillContour(); default: throw Slic3r::InvalidArgument("unknown type"); } } diff --git a/src/libslic3r/Fill/FillContour.cpp b/src/libslic3r/Fill/FillContour.cpp new file mode 100644 index 000000000..20cc39251 --- /dev/null +++ b/src/libslic3r/Fill/FillContour.cpp @@ -0,0 +1,887 @@ +#include "../ClipperUtils.hpp" +#include "../Clipper2Utils.hpp" +#include "../ClipperZUtils.hpp" +#include "../ExPolygon.hpp" +#include "../Surface.hpp" +#include "../VariableWidth.hpp" +#include "FillContour.hpp" +#include + +namespace Slic3r { + +using ZPath = ClipperLib_Z::Path; +using ZPaths = ClipperLib_Z::Paths; + +FloatingPolyline FloatingPolyline::rebase_at(size_t idx) +{ + if (!this->is_closed()) + return {}; + + FloatingPolyline ret = *this; + static_cast(ret) = Polyline::rebase_at(idx); + size_t n = this->points.size(); + ret.is_floating.resize(n); + for (size_t j = 0; j < n - 1; ++j) { + ret.is_floating[j] = this->is_floating[(idx + j) % (n-1)]; + } + ret.is_floating.emplace_back(ret.is_floating.front()); + return ret; +} + +FloatingThickPolyline FloatingThickPolyline::rebase_at(size_t idx) +{ + if (!this->is_closed()) + return {}; + FloatingThickPolyline ret = *this; + static_cast(ret) = ThickPolyline::rebase_at(idx); + size_t n = this->points.size(); + ret.is_floating.resize(n); + for (size_t j = 0; j < n - 1; ++j) { + ret.is_floating[j] = this->is_floating[(idx + j) % (n - 1)]; + } + ret.is_floating.emplace_back(ret.is_floating.front()); + return ret; +} + +FloatingThicklines FloatingThickPolyline::floating_thicklines() const +{ + FloatingThicklines lines; + if (this->points.size() >= 2) { + lines.reserve(this->points.size() - 1); + for (size_t i = 0; i + 1 < this->points.size(); ++i) + lines.emplace_back(this->points[i], this->points[i + 1], this->width[2 * i], this->width[2 * i + 1], this->is_floating[i], this->is_floating[i + 1]); + } + return lines; +} + + +//BBS: new function to filter width to avoid too fragmented segments +static ExtrusionPaths floating_thick_polyline_to_extrusion_paths(const FloatingThickPolyline& floating_polyline, ExtrusionRole role, const Flow& flow, const float tolerance) +{ + ExtrusionPaths paths; + ExtrusionPath path(role); + FloatingThicklines lines = floating_polyline.floating_thicklines(); + + size_t start_index = 0; + double max_width, min_width; + + auto set_flow_for_path = [&flow](ExtrusionPath& path, double width) { + Flow new_flow = flow.with_width(unscale(width) + flow.height() * float(1. - 0.25 * PI)); + path.mm3_per_mm = new_flow.mm3_per_mm(); + path.width = new_flow.width(); + path.height = new_flow.height(); + }; + + auto append_path_and_reset = [set_flow_for_path, role, &paths](double& length, double& sum, ExtrusionPath& path){ + length = sum = 0; + paths.emplace_back(std::move(path)); + path = ExtrusionPath(role); + }; + + for (int i = 0; i < (int)lines.size(); ++i) { + const FloatingThickline& line = lines[i]; + + if (i == 0) { + max_width = min_width = line.a_width; + } + + const coordf_t line_len = line.length(); + if (line_len < SCALED_EPSILON) continue; + + double thickness_delta = std::max(fabs(max_width - line.b_width), fabs(min_width - line.b_width)); + //BBS: has large difference in width + if (thickness_delta > tolerance) { + //BBS: 1 generate path from start_index to i(not included) + if (start_index != i) { + path = ExtrusionPath(role); + double length = 0, sum = 0; + bool is_floating = false; + for (int idx = start_index; idx < i; idx++) { + bool curr_floating = lines[idx].is_a_floating && lines[idx].is_b_floating; + if (curr_floating != is_floating && length != 0) { + path.polyline.append(lines[idx].a); + if (is_floating) + path.set_customize_flag(CustomizeFlag::cfEnsureVertical); + set_flow_for_path(path, sum / length); + append_path_and_reset(length, sum, path); + } + is_floating = curr_floating; + + double line_length = lines[idx].length(); + length += line_length; + sum += line_length * (lines[idx].a_width + lines[idx].b_width) * 0.5; + path.polyline.append(lines[idx].a); + } + path.polyline.append(lines[i].a); + if (length > SCALED_EPSILON) { + if (lines[i].is_a_floating && lines[i].is_b_floating) + path.set_customize_flag(CustomizeFlag::cfEnsureVertical); + set_flow_for_path(path, sum / length); + paths.emplace_back(std::move(path)); + } + } + + start_index = i; + max_width = line.a_width; + min_width = line.a_width; + + //BBS: 2 handle the i-th segment + thickness_delta = fabs(line.a_width - line.b_width); + if (thickness_delta > tolerance) { + const unsigned int segments = (unsigned int)ceil(thickness_delta / tolerance); + const coordf_t seg_len = line_len / segments; + Points pp; + std::vector width; + { + pp.push_back(line.a); + width.push_back(line.a_width); + for (size_t j = 1; j < segments; ++j) { + pp.push_back((line.a.cast() + (line.b - line.a).cast().normalized() * (j * seg_len)).cast()); + + coordf_t w = line.a_width + (j * seg_len) * (line.b_width - line.a_width) / line_len; + width.push_back(w); + width.push_back(w); + } + pp.push_back(line.b); + width.push_back(line.b_width); + + assert(pp.size() == segments + 1u); + assert(width.size() == segments * 2); + } + + // delete this line and insert new ones + lines.erase(lines.begin() + i); + for (size_t j = 0; j < segments; ++j) { + FloatingThickline new_line(pp[j], pp[j + 1],width[2 * j],width[2 * j+1], line.is_a_floating,line.is_b_floating); + lines.insert(lines.begin() + i + j, new_line); + } + --i; + continue; + } + } + //BBS: just update the max and min width and continue + else { + max_width = std::max(max_width, std::max(line.a_width, line.b_width)); + min_width = std::min(min_width, std::min(line.a_width, line.b_width)); + } + } + //BBS: handle the remaining segment + size_t final_size = lines.size(); + if (start_index < final_size) { + path = ExtrusionPath(role); + double length = 0, sum = 0; + bool is_floating = false; + for (int idx = start_index; idx < final_size; idx++) { + bool curr_floating = lines[idx].is_a_floating && lines[idx].is_b_floating; + if (curr_floating!= is_floating && length != 0) { + path.polyline.append(lines[idx].a); + if(is_floating) + path.set_customize_flag(CustomizeFlag::cfEnsureVertical); + set_flow_for_path(path, sum / length); + append_path_and_reset(length, sum, path); + } + is_floating = curr_floating; + double line_length = lines[idx].length(); + length += line_length; + sum += line_length * (lines[idx].a_width + lines[idx].b_width) * 0.5; + path.polyline.append(lines[idx].a); + + } + path.polyline.append(lines[final_size - 1].b); + if (length > SCALED_EPSILON) { + if (lines[final_size - 1].is_a_floating && lines[final_size - 1].is_b_floating) + path.set_customize_flag(CustomizeFlag::cfEnsureVertical); + set_flow_for_path(path, sum / length); + paths.emplace_back(std::move(path)); + } + } + + return paths; +} + +double interpolate_width(const ZPath& path, + const ThickPolyline& line, + const int subject_idx_range, + const int default_width, + size_t idx) +{ + int prev_idx = idx; + while (prev_idx >= 0 && (path[prev_idx].z() < 0 || path[prev_idx].z() >= subject_idx_range)) + --prev_idx; + + int next_idx = idx; + while (next_idx < path.size() && (path[next_idx].z() < 0 || path[next_idx].z() >= subject_idx_range)) + ++next_idx; + + double width_prev; + double width_next; + if (prev_idx < 0) { + width_prev = default_width; + } + else { + size_t prev_z_idx = path[prev_idx].z(); + width_prev = line.get_width_at(prev_z_idx); + } + + if (next_idx >= path.size()) { + width_next = default_width; + } + else { + size_t next_z_idx = path[next_idx].z(); + width_next = line.get_width_at(next_z_idx); + } + Point prev(path[prev_idx].x(), path[prev_idx].y()); + Point next(path[next_idx].x(), path[next_idx].y()); + Point curr(path[idx].x(), path[idx].y()); + double d_total = (next - prev).cast().norm(); + double d_curr = (curr - prev).cast().norm(); + double t = (d_total > 0) ? (d_curr / d_total) : 0.0; + return (1 - t) * width_prev + t * width_next; +} + +FloatingThickPolyline merge_lines(ZPaths lines, const std::vector& mark_flags, const ThickPolyline& line, const int subject_idx_range ,const int default_width) +{ + using PathFlag = std::vector; + using PathFlags = std::vector; + + std::vectorused(lines.size(), false); + ZPaths merged_paths; + PathFlags merged_marks; + + auto update_path_flag = [](PathFlag& mark_flags, const ZPath& path, bool mark) { + for (auto p : path) + mark_flags.emplace_back(mark); + }; + + std::unordered_map> start_z_map; + std::unordered_map> end_z_map; + + for (size_t idx = 0; idx < lines.size(); ++idx) { + if (lines[idx].empty()) { + used[idx] = true; + continue; + } + start_z_map[lines[idx].front().z()].insert(idx); + end_z_map[lines[idx].back().z()].insert(idx); + } + + auto remove_from_map = [&start_z_map, &end_z_map, &lines](size_t idx) { + if (lines[idx].empty()) + return; + int64_t start_z = lines[idx].front().z(); + int64_t end_z = lines[idx].back().z(); + start_z_map[start_z].erase(idx); + if (start_z_map[start_z].empty()) + start_z_map.erase(start_z); + end_z_map[end_z].erase(idx); + if (end_z_map[end_z].empty()) + end_z_map.erase(end_z); + }; + + for (size_t idx = 0; idx < lines.size(); ++idx) { + if (used[idx]) + continue; + ZPath curr_path = lines[idx]; + PathFlag curr_mark; + update_path_flag(curr_mark, curr_path, mark_flags[idx]); + used[idx] = true; + remove_from_map(idx); + + bool merged; + do { + merged = false; + int64_t curr_end = curr_path.back().z(); + int64_t curr_start = curr_path.front().z(); + + // search after + { + if (auto start_iter = start_z_map.find(curr_end);start_iter != start_z_map.end()) { + size_t j = *start_iter->second.begin(); + remove_from_map(j); + curr_path.insert(curr_path.end(), lines[j].begin(), lines[j].end()); + update_path_flag(curr_mark, lines[j], mark_flags[j]); + used[j] = true; + merged = true; + } + else if (auto end_iter = end_z_map.find(curr_end); end_iter != end_z_map.end()) { + size_t j = *end_iter->second.begin(); + remove_from_map(j); + std::reverse(lines[j].begin(), lines[j].end()); + curr_path.insert(curr_path.end(), lines[j].begin(), lines[j].end()); + update_path_flag(curr_mark, lines[j], mark_flags[j]); + used[j] = true; + merged = true; + } + } + + if (merged) + continue; + + //search before + { + if (auto end_iter = end_z_map.find(curr_start);end_iter != end_z_map.end()) { + size_t j = *end_iter->second.begin(); + remove_from_map(j); + ZPath new_path = lines[j]; + PathFlag new_mark; + update_path_flag(new_mark, new_path, mark_flags[j]); + + new_path.insert(new_path.end(), curr_path.begin(), curr_path.end()); + new_mark.insert(new_mark.end(), curr_mark.begin(), curr_mark.end()); + curr_path = std::move(new_path); + curr_mark = std::move(new_mark); + used[j] = true; + merged = true; + } + else if (auto start_iter = start_z_map.find(curr_start); start_iter != start_z_map.end()) { + size_t j = *start_iter->second.begin(); + remove_from_map(j); + ZPath new_path = lines[j]; + std::reverse(new_path.begin(), new_path.end()); + PathFlag new_mark; + update_path_flag(new_mark, new_path, mark_flags[j]); + + new_path.insert(new_path.end(), curr_path.begin(), curr_path.end()); + new_mark.insert(new_mark.end(), curr_mark.begin(), curr_mark.end()); + curr_path = std::move(new_path); + curr_mark = std::move(new_mark); + used[j] = true; + merged = true; + } + } + + } while (merged); + + merged_paths.emplace_back(curr_path); + merged_marks.emplace_back(curr_mark); + } + + assert(merged_marks.size() == 1); + + FloatingThickPolyline res; + + auto& valid_path = merged_paths.front(); + auto& valid_mark = merged_marks.front(); + + for (size_t idx = 0; idx < valid_path.size(); ++idx) { + int zvalue = valid_path[idx].z(); + res.points.emplace_back(valid_path[idx].x(), valid_path[idx].y()); + res.is_floating.emplace_back(valid_mark[idx]); + if (0 <= zvalue && zvalue < subject_idx_range) { + res.width.emplace_back(line.get_width_at(prev_idx_modulo(zvalue, line.points))); + res.width.emplace_back(line.get_width_at(zvalue)); + } + else { + double width = interpolate_width(valid_path, line, subject_idx_range, default_width, idx); + res.width.emplace_back(width); + res.width.emplace_back(width); + } + } + res.width = std::vector(res.width.begin() + 1, res.width.end()-1); + assert(res.width.size() == 2 * res.points.size() - 2); + + return res; +} + +FloatingThickPolyline detect_floating_line(const ThickPolyline& line, const ExPolygons& floating_areas, const double default_width ,bool force_no_detect) +{ + { + Polyline polyline = line; + auto bbox_line = get_extents(polyline); + auto bbox_area = get_extents(floating_areas); + if (force_no_detect || !bbox_area.overlap(bbox_line) || intersection_pl(polyline, floating_areas).empty()) { + FloatingThickPolyline res; + res.width = line.width; + res.points = line.points; + res.is_floating.resize(res.points.size(), false); + return res; + } + } + + using ZPoint = ClipperLib_Z::IntPoint; + auto hash_function = [](const int a1, const int b1, const int a2, const int b2)->int32_t { + int32_t hash_val = 1000 * (a1 * 13 + b1) + (a2 * 17 + b2) + 1; + hash_val &= 0x7fffffff; + return hash_val; + }; + + int idx = 0; + int subject_idx_range; + ZPaths subject_paths; + { + subject_paths.emplace_back(); + for (auto p : line) + subject_paths.back().emplace_back(p.x(), p.y(), idx++); + } + + subject_idx_range = idx; + ZPaths clip_paths; + { + Polygons floating_polygons = to_polygons(floating_areas); + for (auto& poly : floating_polygons) { + clip_paths.emplace_back(); + for (const auto& p : poly) + clip_paths.back().emplace_back(p.x(), p.y(), idx++); + } + } + + ClipperLib_Z::ZFillCallback z_filler = [hash_function, subject_idx_range](const ZPoint& e1_a, const ZPoint& e1_b, const ZPoint& e2_a, const ZPoint& e2_b, ZPoint& d) { + if (e1_a.z() < subject_idx_range && e1_b.z() < subject_idx_range && e2_a.z() < subject_idx_range && e2_b.z() < subject_idx_range) { + BOOST_LOG_TRIVIAL(error) << Slic3r::format("ZFiller: both point in subject : %d, %d, %d, %d ", e1_a.z(), e1_b.z(), e2_a.z(), e2_b.z()); + } + if (e1_a.z() >= subject_idx_range && e1_b.z() >= subject_idx_range && e2_a.z() >= subject_idx_range && e2_b.z() >= subject_idx_range) { + BOOST_LOG_TRIVIAL(error) << Slic3r::format("ZFiller: both point in clip : %d, %d, %d, %d ", e1_a.z(), e1_b.z(), e2_a.z(), e2_b.z()); + } + if (e1_a.z() < 0 || e1_b.z() < 0 || e2_a.z() < 0 || e2_b.z() < 0) + BOOST_LOG_TRIVIAL(error) << Slic3r::format("ZFiller: Encounter negative z : %d, %d, %d, %d ", e1_a.z(), e1_b.z(), e2_a.z(), e2_b.z()); + if (e1_a.z() == e1_b.z() || e2_a.z() == e2_b.z()) { + BOOST_LOG_TRIVIAL(error) << Slic3r::format("ZFiller: Encounter same z in one line : %d, %d, %d, %d ", e1_a.z(), e1_b.z(), e2_a.z(), e2_b.z()); + } + if (e1_a.z() == e1_b.z() && e1_b.z() == e2_a.z() && e2_a.z() == e2_b.z()) { + BOOST_LOG_TRIVIAL(error) << Slic3r::format("ZFiller: Encounter same z in both line : %d, %d, %d, %d ", e1_a.z(), e1_b.z(), e2_a.z(), e2_b.z()); + // the intersect is generated by two lines in subject + d.z() = e1_a.z(); + return; + } // the intersect is generate by two line from subject and clip + d.z() = -hash_function(e1_a.z(), e1_b.z(), e2_a.z(), e2_b.z()); + if (d.z() >= 0) + BOOST_LOG_TRIVIAL(error) << Slic3r::format("ZFiller: hash function generate postive value : %d", d.z()); + }; + + + ZPaths intersect_out; + { + ClipperLib_Z::Clipper c; + ClipperLib_Z::PolyTree polytree; + c.ZFillFunction(z_filler); + c.AddPaths(subject_paths, ClipperLib_Z::ptSubject, false); + c.AddPaths(clip_paths, ClipperLib_Z::ptClip, true); + c.Execute(ClipperLib_Z::ctIntersection, polytree, ClipperLib_Z::pftNonZero); + ClipperLib_Z::PolyTreeToPaths(std::move(polytree), intersect_out); + } + + ZPaths diff_out; + { + ClipperLib_Z::Clipper c; + ClipperLib_Z::PolyTree polytree; + c.ZFillFunction(z_filler); + c.AddPaths(subject_paths, ClipperLib_Z::ptSubject, false); + c.AddPaths(clip_paths, ClipperLib_Z::ptClip, true); + c.Execute(ClipperLib_Z::ctDifference, polytree, ClipperLib_Z::pftNonZero); + ClipperLib_Z::PolyTreeToPaths(std::move(polytree), diff_out); + } + + + ZPaths to_merge = diff_out; + to_merge.insert(to_merge.end(), intersect_out.begin(), intersect_out.end()); + std::vectorfloating_flags(to_merge.size(), false); + for (size_t idx = diff_out.size(); idx < diff_out.size() + intersect_out.size(); ++idx) + floating_flags[idx] = true; + + for (size_t idx = 0; idx < to_merge.size(); ++idx) { + for (auto iter = to_merge[idx].begin(); iter != to_merge[idx].end();++iter) { + if (iter->z() >= subject_idx_range) { + BOOST_LOG_TRIVIAL(error) << Slic3r::format("ZFiller: encounter idx from clip: %d",iter->z()); + } + } + } + + return merge_lines(to_merge,floating_flags,line,subject_idx_range,default_width); +} + +int start_none_floating_idx(int idx, const std::vector& none_floating_count) +{ + int backtrace_idx = idx - none_floating_count[idx] + 1; + if (backtrace_idx >= 0) + return backtrace_idx; + else + return none_floating_count.size() + backtrace_idx; +} + +template +void get_none_floating_prefix(const PointContainer& container, const ExPolygons& floating_areas, const Polygons& sparse_polys, std::vector& none_floating_length, std::vector& none_floating_count) +{ + std::vector(container.points.size(), 0).swap(none_floating_length); + std::vector(container.points.size(), 0).swap(none_floating_count); + + std::vector floating_bboxs; + for (size_t idx = 0; idx < floating_areas.size(); ++idx) + floating_bboxs.emplace_back(get_extents(floating_areas[idx])); + std::vector sparse_bboxs; + for (size_t idx = 0; idx < sparse_polys.size(); ++idx) + sparse_bboxs.emplace_back(get_extents(sparse_polys[idx])); + + auto point_in_floating_area = [&floating_bboxs, &sparse_bboxs, &floating_areas, &sparse_polys](const Point& p)->bool { + for (size_t idx = 0; idx < sparse_polys.size(); ++idx) { + if (!sparse_bboxs[idx].contains(p)) + continue; + if (sparse_polys[idx].contains(p)) + return false; + } + for (size_t idx = 0; idx < floating_areas.size(); ++idx) { + if (!floating_bboxs[idx].contains(p)) + continue; + if (floating_areas[idx].contains(p)) + return true; + } + + return false; + }; + + for (size_t idx = 0; idx < container.points.size(); ++idx) { + const Point& p = container.points[idx]; + if (!point_in_floating_area(p)) { + if (idx == 0) + none_floating_count[idx] = 1; + else + none_floating_count[idx] = none_floating_count[idx - 1] + 1; + if (none_floating_count[idx] > 1) + none_floating_length[idx] = none_floating_length[idx - 1] + ((Point)(container.points[prev_idx_modulo(idx, container.points)] - p)).cast().norm(); + else + none_floating_length[idx] = 0; + } + else { + none_floating_length[idx] = 0; + none_floating_count[idx] = 0; + } + } + + if (none_floating_count.back() > 0) { + for (size_t idx = 0; idx < container.points.size(); ++idx) { + if (none_floating_count[idx] == 0) + break; + none_floating_count[idx] = none_floating_count[prev_idx_modulo(idx, container.points)] + 1; + none_floating_length[idx] = none_floating_length[prev_idx_modulo(idx, container.points)] + ((Point)(container.points[prev_idx_modulo(idx, container.points)] - container.points[idx])).cast().norm(); + } + } +} + +template +int get_best_loop_start(const PointContainer& container, const ExPolygons& floating_areas, const Polygons& sparse_polys) { + std::vector none_floating_length; + std::vector none_floating_count; + + BoundingBox floating_bbox = get_extents(floating_areas); + BoundingBox poly_bbox(container.points); + + if (!poly_bbox.overlap(floating_bbox)) + return 0; + + Polygons clipped_sparse_polys = ClipperUtils::clip_clipper_polygons_with_subject_bbox(sparse_polys, poly_bbox); + get_none_floating_prefix(container, floating_areas, clipped_sparse_polys, none_floating_length, none_floating_count); + int best_idx = std::distance(none_floating_length.begin(), std::max_element(none_floating_length.begin(), none_floating_length.end())); + return start_none_floating_idx(best_idx, none_floating_count); +} + +template +std::vector get_loop_start_candidates(const PointContainer& container, const ExPolygons& floating_areas, const Polygons& sparse_polys) +{ + std::vector none_floating_length; + std::vector none_floating_count; + + BoundingBox floating_bbox = get_extents(floating_areas); + BoundingBox poly_bbox(container.points); + std::vector candidate_list; + + if (!poly_bbox.overlap(floating_bbox)) { + candidate_list.resize(container.points.size()); + std::iota(candidate_list.begin(), candidate_list.end(), 0); + return candidate_list; + } + Polygons clipped_sparse_polys = ClipperUtils::clip_clipper_polygons_with_subject_bbox(sparse_polys, poly_bbox); + get_none_floating_prefix(container, floating_areas, clipped_sparse_polys, none_floating_length, none_floating_count); + for (size_t idx = 0; idx < none_floating_length.size(); ++idx) { + if (none_floating_length[idx] > 0) + candidate_list.emplace_back(start_none_floating_idx(idx, none_floating_count)); + } + return candidate_list; +} + + +void smooth_floating_line(FloatingThickPolyline& line,coord_t max_gap_threshold, coord_t min_floating_threshold) +{ + if (line.empty()) + return; + struct LineParts { + int start; + int end; + bool is_floating; + }; + + auto build_line_parts = [&](const FloatingThickPolyline& line)->std::vector { + std::vector line_parts; + bool current_val = line.is_floating.front(); + int start = 0; + for (size_t idx = 1; idx < line.is_floating.size(); ++idx) { + if (line.is_floating[idx] != current_val) { + line_parts.push_back({ start,(int)(idx - 1),current_val }); + current_val = line.is_floating[idx]; + start = idx; + } + } + line_parts.push_back({ start,(int)(line.is_floating.size() - 1),current_val }); + return line_parts; + }; + + std::vector distance_prefix(line.points.size(),0); + for (size_t idx = 0; idx < line.points.size();++idx) { + if (idx == 0) + distance_prefix[idx] = 0; + else { + distance_prefix[idx] = distance_prefix[idx - 1] + (line.points[idx] - line.points[idx - 1]).cast().norm(); + } + } + { + // remove too small gaps + std::vector line_parts = build_line_parts(line); + std::vector> gaps_to_merge; + + for (size_t i = 1; i + 1 < line_parts.size(); ++i) { + const auto& curr = line_parts[i]; + if (!curr.is_floating) { + const auto& prev = line_parts[i - 1]; + const auto& next = line_parts[i + 1]; + if (prev.is_floating && next.is_floating) { + double total_length = distance_prefix[next.start] - distance_prefix[prev.end]; + if (total_length < max_gap_threshold) { + gaps_to_merge.emplace_back(curr.start, curr.end); + } + } + } + } + + for (const auto& gap : gaps_to_merge) { + for (int i = gap.first; i <= gap.second; ++i) { + line.is_floating[i] = true; + } + } + } + + { + std::vector line_parts = build_line_parts(line); + std::vector> segments_to_remove; + + for (auto& part : line_parts) { + if (part.is_floating && distance_prefix[part.end] - distance_prefix[part.start] < min_floating_threshold) { + segments_to_remove.emplace_back(part.start, part.end); + } + } + + for (const auto& seg : segments_to_remove) { + for (int i = seg.first; i <= seg.second; ++i) { + line.is_floating[i] = false; + } + } + } +} + +// nearest neibour排序,但是取点时,只能取get_loop_start_candidates得到的点 +FloatingThickPolylines FillContour::resplit_order_loops(Point curr_point, std::vector all_extrusions, const ExPolygons& floating_areas, const Polygons& sparse_polys, const coord_t default_width) +{ + FloatingThickPolylines result; + + for (size_t idx = 0; idx < all_extrusions.size(); ++idx) { + if (all_extrusions[idx]->empty()) + continue; + ThickPolyline thick_polyline = Arachne::to_thick_polyline(*all_extrusions[idx]); + FloatingThickPolyline thick_line_with_floating = detect_floating_line(thick_polyline, floating_areas, default_width, print_object_config->detect_floating_vertical_shell.value); + smooth_floating_line(thick_line_with_floating, scale_(2), scale_(2)); + int split_idx = 0; + if (!floating_areas.empty() && all_extrusions[idx]->is_closed && thick_line_with_floating.points.front() == thick_line_with_floating.points.back()) { + if (idx == 0) + split_idx = get_best_loop_start(thick_line_with_floating, floating_areas,sparse_polys); + else { + auto candidates = get_loop_start_candidates(thick_line_with_floating, floating_areas,sparse_polys); + double min_dist = std::numeric_limits::max(); + for (auto candidate : candidates) { + double dist = (curr_point - thick_line_with_floating.points[candidate]).cast().norm(); + if (min_dist > dist) { + min_dist = dist; + split_idx = candidate; + } + } + } + FloatingThickPolyline new_line = thick_line_with_floating.rebase_at(split_idx); + assert(new_line.width.size() == 2 * new_line.points.size() - 2); + result.emplace_back(thick_line_with_floating.rebase_at(split_idx)); + } + else { + assert(thick_line_with_floating.width.size() == 2 * thick_line_with_floating.points.size() - 2); + result.emplace_back(thick_line_with_floating); + } + curr_point = result.back().last_point(); + } + return result; +} + +#if 0 +Polylines FillContour::resplit_order_loops(Point curr_point, Polygons loops, const ExPolygons& floating_areas) +{ + Polylines result; + for (size_t idx = 0; idx < loops.size(); ++idx) { + const Polygon& loop = loops[idx]; + int split_idx = 0; + if (!floating_areas.empty()) { + if (idx == 0) + split_idx = get_best_loop_start(loop, floating_areas); + else { + auto candidates = get_loop_start_candidates(loop, floating_areas); + double min_dist = std::numeric_limits::max(); + for (auto candidate : candidates) { + double dist = (curr_point - loop.points[candidate]).cast().norm(); + if (min_dist > dist) { + min_dist = dist; + split_idx = candidate; + } + } + } + result.emplace_back(loop.split_at_index(split_idx)); + } + else { + result.emplace_back(loop.split_at_index(curr_point.nearest_point_index(loop.points))); + } + curr_point = result.back().last_point(); + } + return result; +}; + +void FillContour::_fill_surface_single( + const FillParams& params, + unsigned int thickness_layers, + const std::pair& direction, + ExPolygon expolygon, + FloatingLines& polylines_out +) +{ + auto expoly_bbox = get_extents(expolygon); + coord_t min_spacing = scale_(this->spacing); + coord_t distance = coord_t(min_spacing / params.density); + distance = this->_adjust_solid_spacing(expoly_bbox.size()(0), distance); + this->spacing = unscale(distance); + + Polygons loops = to_polygons(expolygon); + ExPolygons offseted_expolys{ std::move(expolygon) }; + while (!offseted_expolys.empty()) { + offseted_expolys = offset2_ex(offseted_expolys, -(distance + min_spacing / 2), min_spacing / 2); + append(loops, to_polygons(offseted_expolys)); + } + // generate paths from outermost to the inner most + loops = union_pt_chained_outside_in(loops); + + auto reordered_polylines = resplit_order_loops({ 0,0 }, loops, lower_layer_unsupport_areas); + size_t i = polylines_out.size(); + for (auto& polyline : reordered_polylines) { + polylines_out.emplace_back(std::move(polyline)); + } + + size_t j = polylines_out.size(); + for (; i < polylines_out.size(); ++i) { + polylines_out[i].clip_end(this->loop_clipping); + if (polylines_out[i].is_valid()) { + if (j < i) + polylines_out[j] = std::move(polylines_out[i]); + ++j; + } + } + if (j < polylines_out.size()) + polylines_out.erase(polylines_out.begin() + j, polylines_out.end()); +} +#endif + +void FillContour::_fill_surface_single(const FillParams& params, + unsigned int thickness_layers, + const std::pair& direction, + ExPolygon expolygon, + FloatingThickPolylines& thick_polylines_out) +{ + Point bbox_size = expolygon.contour.bounding_box().size(); + coord_t min_spacing = params.flow.scaled_spacing(); + + coord_t loops_count = std::max(bbox_size.x(), bbox_size.y()) / min_spacing + 1; + Polygons polygons = to_polygons(expolygon); + + double min_nozzle_diameter = *std::min_element(print_config->nozzle_diameter.values.begin(), print_config->nozzle_diameter.values.end()); + Arachne::WallToolPathsParams input_params; + input_params.min_bead_width = 0.85 * min_nozzle_diameter; + input_params.min_feature_size = 0.25 * min_nozzle_diameter; + input_params.wall_transition_length = 0.4; + input_params.wall_transition_angle = 10; + input_params.wall_transition_filter_deviation = 0.25 * min_nozzle_diameter; + input_params.wall_distribution_count = 1; + + Arachne::WallToolPaths wallToolPaths(polygons, min_spacing, min_spacing, loops_count, 0, params.layer_height, input_params); + + std::vector loops = wallToolPaths.getToolPaths(); + std::vector all_extrusions; + for (Arachne::VariableWidthLines& loop : loops) { + if (loop.empty()) + continue; + for (const Arachne::ExtrusionLine& wall : loop) + all_extrusions.emplace_back(&wall); + } + + // Split paths using a nearest neighbor search. + size_t firts_poly_idx = thick_polylines_out.size(); + auto thick_polylines = resplit_order_loops({ 0,0 }, all_extrusions, this->lower_layer_unsupport_areas, this->lower_sparse_polys,min_spacing); + append(thick_polylines_out, thick_polylines); + + + // clip the paths to prevent the extruder from getting exactly on the first point of the loop + // Keep valid paths only. + size_t j = firts_poly_idx; + for (size_t i = firts_poly_idx; i < thick_polylines_out.size(); ++i) { + thick_polylines_out[i].clip_end(this->loop_clipping); + if (thick_polylines_out[i].is_valid()) { + if (j < i) + thick_polylines_out[j] = std::move(thick_polylines_out[i]); + ++j; + } + } + if (j < thick_polylines_out.size()) + thick_polylines_out.erase(thick_polylines_out.begin() + int(j), thick_polylines_out.end()); +} + + +FloatingThickPolylines FillContour::fill_surface_arachne_floating(const Surface* surface, const FillParams& params) +{ + // Create the infills for each of the regions. + FloatingThickPolylines floating_thick_polylines_out; + for (ExPolygon& expoly : no_overlap_expolygons) + _fill_surface_single(params, surface->thickness_layers, _infill_direction(surface), std::move(expoly), floating_thick_polylines_out); + return floating_thick_polylines_out; +} + +void FillContour::fill_surface_extrusion(const Surface* surface, const FillParams& params, ExtrusionEntitiesPtr& out) +{ + FloatingThickPolylines floating_lines = this->fill_surface_arachne_floating(surface, params); + if (floating_lines.empty()) + return; + Flow new_flow = params.flow.with_spacing(this->spacing); + double flow_mm3_per_mm = new_flow.mm3_per_mm(); + double flow_width = new_flow.width(); + + ExtrusionEntityCollection* ecc = new ExtrusionEntityCollection(); + ecc->no_sort = true; + out.push_back(ecc); + size_t idx = ecc->entities.size(); + + const float tolerance = float(scale_(0.05)); + for (const auto& line : floating_lines) { + ExtrusionPaths paths = floating_thick_polyline_to_extrusion_paths(line, params.extrusion_role, new_flow, tolerance); + // Append paths to collection. + assert(!paths.empty()); + if (!paths.empty()) { + if (paths.front().first_point() == paths.back().last_point()) + ecc->entities.emplace_back(new ExtrusionLoop(std::move(paths))); + else { + for (ExtrusionPath& path : paths) { + assert(!path.empty()); + ecc->entities.emplace_back(new ExtrusionPath(std::move(path))); + } + } + } + } +} + + +} // namespace Slic3r diff --git a/src/libslic3r/Fill/FillContour.hpp b/src/libslic3r/Fill/FillContour.hpp new file mode 100644 index 000000000..300c8a9cd --- /dev/null +++ b/src/libslic3r/Fill/FillContour.hpp @@ -0,0 +1,77 @@ +#ifndef SLIC3R_FILLCONTOUR_HPP +#define SLIC3R_FILLCONTOUR_HPP + +#include "FillBase.hpp" +#include "FillConcentric.hpp" +#include "Arachne/WallToolPaths.hpp" + +namespace Slic3r{ + struct FloatingThickline : public ThickLine + { + FloatingThickline(const Point& a, const Point& b, double wa, double wb, bool a_floating, bool b_floating) :ThickLine(a, b, wa, wb) + { + is_a_floating = a_floating; + is_b_floating = b_floating; + } + bool is_a_floating; + bool is_b_floating; + }; + using FloatingThicklines = std::vector; + + struct FloatingPolyline : public Polyline + { + std::vector is_floating; + FloatingPolyline rebase_at(size_t idx); + }; + using FloatingPolylines = std::vector; + + struct FloatingThickPolyline :public ThickPolyline + { + std::vector is_floating; + FloatingThickPolyline rebase_at(size_t idx); + FloatingThicklines floating_thicklines()const; + }; + using FloatingThickPolylines = std::vector; + + class FillContour : public FillConcentric + { + public: + ~FillContour() override = default; + ExPolygons lower_layer_unsupport_areas; + Polygons lower_sparse_polys; + + protected: + Fill* clone() const override { return new FillContour(*this); } +#if 0 + void _fill_surface_single( + const FillParams ¶ms, + unsigned int thickness_layers, + const std::pair &direction, + ExPolygon expolygon, + FloatingLines &polylines_out) ; +#endif + + void _fill_surface_single(const FillParams& params, + unsigned int thickness_layers, + const std::pair& direction, + ExPolygon expolygon, + FloatingThickPolylines& thick_polylines_out); + + FloatingThickPolylines fill_surface_arachne_floating(const Surface* surface, const FillParams& params); + + void fill_surface_extrusion(const Surface* surface, const FillParams& params, ExtrusionEntitiesPtr& out); + + FloatingThickPolylines resplit_order_loops(Point curr_point, std::vector all_extrusions, const ExPolygons& floating_areas, const Polygons& sparse_polys, const coord_t default_width); +#if 0 + Polylines resplit_order_loops(Point curr_point, Polygons loops, const ExPolygons& floating_areas); +#endif + + friend class Layer; + }; +} + + + + + +#endif \ No newline at end of file diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 808e71a3f..3bc81bba4 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -4870,7 +4870,8 @@ std::string GCode::extrude_entity(const ExtrusionEntity &entity, std::string des std::string GCode::extrude_path(ExtrusionPath path, std::string description, double speed) { // description += ExtrusionEntity::role_to_string(path.role()); - std::string gcode = this->_extrude(path, description, speed); + bool flag = path.get_customize_flag() == CustomizeFlag::cfEnsureVertical; + std::string gcode = this->_extrude(path, description, speed,flag); if (m_wipe.enable && FILAMENT_CONFIG(wipe)) { m_wipe.path = std::move(path.polyline); m_wipe.path.reverse(); @@ -5286,7 +5287,7 @@ void GCode::smooth_speed_discontinuity_area(ExtrusionPaths &paths) { paths = std::move(inter_paths); } -std::string GCode::_extrude(const ExtrusionPath &path, std::string description, double speed, bool set_holes_and_compensation_speed, bool is_first_slope) +std::string GCode::_extrude(const ExtrusionPath &path, std::string description, double speed, bool use_seperate_speed, bool is_first_slope) { std::string gcode; @@ -5380,7 +5381,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, if (path.role() == erPerimeter) { speed = m_config.inner_wall_speed.get_at(cur_extruder_index()); //reset speed by auto compensation speed - if(set_holes_and_compensation_speed) { + if(use_seperate_speed) { speed = m_config.circle_compensation_speed.get_at(cur_extruder_index()); }else if (m_config.detect_overhang_wall && m_config.smooth_speed_discontinuity_area && path.smooth_speed != 0) speed = path.smooth_speed; @@ -5392,7 +5393,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, } else if (path.role() == erExternalPerimeter) { speed = m_config.outer_wall_speed.get_at(cur_extruder_index()); // reset speed by auto compensation speed - if (set_holes_and_compensation_speed) { + if (use_seperate_speed) { speed = m_config.circle_compensation_speed.get_at(cur_extruder_index()); } else if (m_config.detect_overhang_wall && m_config.smooth_speed_discontinuity_area && path.smooth_speed != 0) speed = path.smooth_speed; @@ -5409,7 +5410,15 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, speed = m_config.sparse_infill_speed.get_at(cur_extruder_index()); } else if (path.role() == erSolidInfill) { speed = m_config.internal_solid_infill_speed.get_at(cur_extruder_index()); - } else if (path.role() == erTopSolidInfill) { + } else if (path.role() == erEnsureVertical){ + if(use_seperate_speed){ + speed = m_config.bridge_speed.get_at(cur_extruder_index()); + } + else{ + speed = m_config.vertical_shell_speed.get_at(cur_extruder_index()).get_abs_value(m_config.internal_solid_infill_speed.get_at(cur_extruder_index())); + } + } + else if (path.role() == erTopSolidInfill) { speed = m_config.top_surface_speed.get_at(cur_extruder_index()); } else if (path.role() == erIroning) { speed = m_config.get_abs_value("ironing_speed"); @@ -5485,7 +5494,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, assert(is_decimal_separator_point()); - if (set_holes_and_compensation_speed) + if (use_seperate_speed) gcode += "; Slow Down Start\n"; if (path.role() != m_last_processor_extrusion_role) { @@ -5639,7 +5648,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, } } - if (set_holes_and_compensation_speed) { + if (use_seperate_speed) { gcode += "; Slow Down End\n"; } diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 42a0cbc10..42fc99fb6 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -5506,7 +5506,12 @@ void GCodeProcessor::store_move_vertex(EMoveType type, EMovePathType path_type) void GCodeProcessor::set_extrusion_role(ExtrusionRole role) { m_used_filaments.process_role_cache(this); - m_extrusion_role = role; + if (role == erEnsureVertical) { + m_extrusion_role = erSolidInfill; + } + else { + m_extrusion_role = role; + } } void GCodeProcessor::set_skippable_type(const std::string_view type) diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index 0177c822c..63875c2ba 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -558,7 +558,7 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly std::vector expansion_zones{ ExpansionZone{std::move(shells), expansion_params_into_solid_infill}, ExpansionZone{std::move(sparse), expansion_params_into_sparse_infill}, - ExpansionZone{std::move(top_expolygons), expansion_params_into_solid_infill}, + ExpansionZone{std::move(top_expolygons), expansion_params_into_solid_infill} }; SurfaceCollection bridges; @@ -639,298 +639,6 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly #else -//#define EXTERNAL_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 3. -//#define EXTERNAL_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 1.5 -#define EXTERNAL_SURFACES_OFFSET_PARAMETERS ClipperLib::jtSquare, 0. - -void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Polygons *lower_layer_covered) -{ - const bool has_infill = this->region().config().sparse_infill_density.value > 0.; - //BBS - auto nozzle_diameter = this->region().nozzle_dmr_avg(this->layer()->object()->print()->config()); - const float margin = float(scale_(EXTERNAL_INFILL_MARGIN)); - const float bridge_margin = std::min(float(scale_(BRIDGE_INFILL_MARGIN)), float(scale_(nozzle_diameter * BRIDGE_INFILL_MARGIN / 0.4))); - - // BBS - const PrintObjectConfig& object_config = this->layer()->object()->config(); - -#ifdef SLIC3R_DEBUG_SLICE_PROCESSING - export_region_fill_surfaces_to_svg_debug("3_process_external_surfaces-initial"); -#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ - - // 1) Collect bottom and bridge surfaces, each of them grown by a fixed 3mm offset - // for better anchoring. - // Bottom surfaces, grown. - Surfaces bottom; - // Bridge surfaces, initialy not grown. - Surfaces bridges; - // Top surfaces, grown. - Surfaces top; - // Internal surfaces, not grown. - Surfaces internal; - // Areas, where an infill of various types (top, bottom, bottom bride, sparse, void) could be placed. - Polygons fill_boundaries = to_polygons(this->fill_expolygons); - Polygons lower_layer_covered_tmp; - - // Collect top surfaces and internal surfaces. - // Collect fill_boundaries: If we're slicing with no infill, we can't extend external surfaces over non-existent infill. - // This loop destroys the surfaces (aliasing this->fill_surfaces.surfaces) by moving into top/internal/fill_boundaries! - - { - // Voids are sparse infills if infill rate is zero. - Polygons voids; - - double max_grid_area = -1; - if (this->layer()->lower_layer != nullptr) - max_grid_area = this->layer()->lower_layer->get_sparse_infill_max_void_area(); - for (const Surface &surface : this->fill_surfaces.surfaces) { - if (surface.is_top()) { - // Collect the top surfaces, inflate them and trim them by the bottom surfaces. - // This gives the priority to bottom surfaces. - if (max_grid_area < 0 || surface.expolygon.area() < max_grid_area) - surfaces_append(top, offset_ex(surface.expolygon, margin, EXTERNAL_SURFACES_OFFSET_PARAMETERS), surface); - else - //BBS: Don't need to expand too much in this situation. Expand 3mm to eliminate hole and 1mm for contour - surfaces_append(top, intersection_ex(offset(surface.expolygon.contour, margin / 3.0, EXTERNAL_SURFACES_OFFSET_PARAMETERS), - offset_ex(surface.expolygon, margin, EXTERNAL_SURFACES_OFFSET_PARAMETERS)), surface); - } else if (surface.surface_type == stBottom || (surface.surface_type == stBottomBridge && lower_layer == nullptr)) { - // Grown by 3mm. - surfaces_append(bottom, offset_ex(surface.expolygon, margin, EXTERNAL_SURFACES_OFFSET_PARAMETERS), surface); - } else if (surface.surface_type == stBottomBridge) { - if (! surface.empty()) - bridges.emplace_back(surface); - } - if (surface.is_internal()) { - assert(surface.surface_type == stInternal || surface.surface_type == stInternalSolid); - if (! has_infill && lower_layer != nullptr) - polygons_append(voids, surface.expolygon); - internal.emplace_back(std::move(surface)); - } - } - if (! has_infill && lower_layer != nullptr && ! voids.empty()) { - // Remove voids from fill_boundaries, that are not supported by the layer below. - if (lower_layer_covered == nullptr) { - lower_layer_covered = &lower_layer_covered_tmp; - lower_layer_covered_tmp = to_polygons(lower_layer->lslices); - } - if (! lower_layer_covered->empty()) - voids = diff(voids, *lower_layer_covered); - fill_boundaries = diff(fill_boundaries, voids); - } - } - -#if 0 - { - static int iRun = 0; - bridges.export_to_svg(debug_out_path("bridges-before-grouping-%d.svg", iRun ++), true); - } -#endif - - if (bridges.empty()) - { - fill_boundaries = union_safety_offset(fill_boundaries); - } else - { - // 1) Calculate the inflated bridge regions, each constrained to its island. - ExPolygons fill_boundaries_ex = union_safety_offset_ex(fill_boundaries); - std::vector bridges_grown; - std::vector bridge_bboxes; - -#ifdef SLIC3R_DEBUG_SLICE_PROCESSING - { - static int iRun = 0; - SVG svg(debug_out_path("3_process_external_surfaces-fill_regions-%d.svg", iRun ++).c_str(), get_extents(fill_boundaries_ex)); - svg.draw(fill_boundaries_ex); - svg.draw_outline(fill_boundaries_ex, "black", "blue", scale_(0.05)); - svg.Close(); - } - -// export_region_fill_surfaces_to_svg_debug("3_process_external_surfaces-initial"); -#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ - - { - // Bridge expolygons, grown, to be tested for intersection with other bridge regions. - std::vector fill_boundaries_ex_bboxes = get_extents_vector(fill_boundaries_ex); - bridges_grown.reserve(bridges.size()); - bridge_bboxes.reserve(bridges.size()); - for (size_t i = 0; i < bridges.size(); ++ i) { - // Find the island of this bridge. - const Point pt = bridges[i].expolygon.contour.points.front(); - int idx_island = -1; - for (int j = 0; j < int(fill_boundaries_ex.size()); ++ j) - if (fill_boundaries_ex_bboxes[j].contains(pt) && - fill_boundaries_ex[j].contains(pt)) { - idx_island = j; - break; - } - // Grown by 3mm. - //BBS: eliminate too narrow area to avoid generating bridge on top layer when wall loop is 1 - //Polygons polys = offset(bridges[i].expolygon, bridge_margin, EXTERNAL_SURFACES_OFFSET_PARAMETERS); - Polygons polys = offset2({ bridges[i].expolygon }, -scale_(nozzle_diameter * 0.1), bridge_margin, EXTERNAL_SURFACES_OFFSET_PARAMETERS); - if (idx_island == -1) { - BOOST_LOG_TRIVIAL(trace) << "Bridge did not fall into the source region!"; - } else { - // Found an island, to which this bridge region belongs. Trim it, - polys = intersection(polys, fill_boundaries_ex[idx_island]); - } - bridge_bboxes.push_back(get_extents(polys)); - bridges_grown.push_back(std::move(polys)); - } - } - - // 2) Group the bridge surfaces by overlaps. - std::vector bridge_group(bridges.size(), (size_t)-1); - size_t n_groups = 0; - for (size_t i = 0; i < bridges.size(); ++ i) { - // A grup id for this bridge. - size_t group_id = (bridge_group[i] == size_t(-1)) ? (n_groups ++) : bridge_group[i]; - bridge_group[i] = group_id; - // For all possibly overlaping bridges: - for (size_t j = i + 1; j < bridges.size(); ++ j) { - if (! bridge_bboxes[i].overlap(bridge_bboxes[j])) - continue; - if (intersection(bridges_grown[i], bridges_grown[j]).empty()) - continue; - // The two bridge regions intersect. Give them the same group id. - if (bridge_group[j] != size_t(-1)) { - // The j'th bridge has been merged with some other bridge before. - size_t group_id_new = bridge_group[j]; - for (size_t k = 0; k < j; ++ k) - if (bridge_group[k] == group_id) - bridge_group[k] = group_id_new; - group_id = group_id_new; - } - bridge_group[j] = group_id; - } - } - - // 3) Merge the groups with the same group id, detect bridges. - { - BOOST_LOG_TRIVIAL(trace) << "Processing external surface, detecting bridges. layer" << this->layer()->print_z << ", bridge groups: " << n_groups; - for (size_t group_id = 0; group_id < n_groups; ++ group_id) { - size_t n_bridges_merged = 0; - size_t idx_last = (size_t)-1; - for (size_t i = 0; i < bridges.size(); ++ i) { - if (bridge_group[i] == group_id) { - ++ n_bridges_merged; - idx_last = i; - } - } - if (n_bridges_merged == 0) - // This group has no regions assigned as these were moved into another group. - continue; - // Collect the initial ungrown regions and the grown polygons. - ExPolygons initial; - Polygons grown; - for (size_t i = 0; i < bridges.size(); ++ i) { - if (bridge_group[i] != group_id) - continue; - initial.push_back(std::move(bridges[i].expolygon)); - polygons_append(grown, bridges_grown[i]); - } - // detect bridge direction before merging grown surfaces otherwise adjacent bridges - // would get merged into a single one while they need different directions - // also, supply the original expolygon instead of the grown one, because in case - // of very thin (but still working) anchors, the grown expolygon would go beyond them - double custom_angle = Geometry::deg2rad(this->region().config().bridge_angle.value); - if (custom_angle > 0.0) { - bridges[idx_last].bridge_angle = custom_angle; - } else { - auto [bridging_dir, unsupported_dist] = detect_bridging_direction(to_polygons(initial), to_polygons(lower_layer->lslices)); - bridges[idx_last].bridge_angle = PI + std::atan2(bridging_dir.y(), bridging_dir.x()); - } - - /* - BridgeDetector bd(initial, lower_layer->lslices, this->bridging_flow(frInfill, object_config.thick_bridges).scaled_width()); - #ifdef SLIC3R_DEBUG - printf("Processing bridge at layer %zu:\n", this->layer()->id()); - #endif - double custom_angle = Geometry::deg2rad(this->region().config().bridge_angle.value); - if (bd.detect_angle(custom_angle)) { - bridges[idx_last].bridge_angle = bd.angle; - if (this->layer()->object()->has_support()) { -// polygons_append(this->bridged, bd.coverage()); - append(this->unsupported_bridge_edges, bd.unsupported_edges()); - } - } else if (custom_angle > 0) { - // Bridge was not detected (likely it is only supported at one side). Still it is a surface filled in - // using a bridging flow, therefore it makes sense to respect the custom bridging direction. - bridges[idx_last].bridge_angle = custom_angle; - } - */ - // without safety offset, artifacts are generated (GH #2494) - surfaces_append(bottom, union_safety_offset_ex(grown), bridges[idx_last]); - } - - fill_boundaries = to_polygons(fill_boundaries_ex); - BOOST_LOG_TRIVIAL(trace) << "Processing external surface, detecting bridges - done"; - } - - #if 0 - { - static int iRun = 0; - bridges.export_to_svg(debug_out_path("bridges-after-grouping-%d.svg", iRun ++), true); - } - #endif - } - - Surfaces new_surfaces; - { - // Intersect the grown surfaces with the actual fill boundaries. - Polygons bottom_polygons = to_polygons(bottom); - // Merge top and bottom in a single collection. - surfaces_append(top, std::move(bottom)); - - for (size_t i = 0; i < top.size(); ++ i) { - Surface &s1 = top[i]; - if (s1.empty()) - continue; - Polygons polys; - polygons_append(polys, to_polygons(std::move(s1))); - for (size_t j = i + 1; j < top.size(); ++ j) { - Surface &s2 = top[j]; - if (! s2.empty() && surfaces_could_merge(s1, s2)) { - polygons_append(polys, to_polygons(std::move(s2))); - s2.clear(); - } - } - if (s1.is_top()) - // Trim the top surfaces by the bottom surfaces. This gives the priority to the bottom surfaces. - polys = diff(polys, bottom_polygons); - surfaces_append( - new_surfaces, - // Don't use a safety offset as fill_boundaries were already united using the safety offset. - intersection_ex(polys, fill_boundaries), - s1); - } - } - - // Subtract the new top surfaces from the other non-top surfaces and re-add them. - Polygons new_polygons = to_polygons(new_surfaces); - for (size_t i = 0; i < internal.size(); ++ i) { - Surface &s1 = internal[i]; - if (s1.empty()) - continue; - Polygons polys; - polygons_append(polys, to_polygons(std::move(s1))); - for (size_t j = i + 1; j < internal.size(); ++ j) { - Surface &s2 = internal[j]; - if (! s2.empty() && surfaces_could_merge(s1, s2)) { - polygons_append(polys, to_polygons(std::move(s2))); - s2.clear(); - } - } - ExPolygons new_expolys = diff_ex(polys, new_polygons); - polygons_append(new_polygons, to_polygons(new_expolys)); - surfaces_append(new_surfaces, std::move(new_expolys), s1); - } - - this->fill_surfaces.surfaces = std::move(new_surfaces); - -#ifdef SLIC3R_DEBUG_SLICE_PROCESSING - export_region_fill_surfaces_to_svg_debug("3_process_external_surfaces-final"); -#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ -} #endif void LayerRegion::prepare_fill_surfaces() diff --git a/src/libslic3r/Polyline.cpp b/src/libslic3r/Polyline.cpp index dced6928f..ae8e67cd5 100644 --- a/src/libslic3r/Polyline.cpp +++ b/src/libslic3r/Polyline.cpp @@ -618,6 +618,58 @@ ThickLines ThickPolyline::thicklines() const return lines; } +Polyline Polyline::rebase_at(size_t idx) +{ + if (!this->is_closed()) + return {}; + Polyline ret = *this; + size_t n = this->points.size(); + for (size_t j = 0; j < n - 1; ++j) { + ret.points[j] = this->points[(idx + j) % (n - 1)]; + } + ret.points[n - 1] = ret.points.front(); + return ret; +} + +ThickPolyline ThickPolyline::rebase_at(size_t idx) +{ + if (!this->is_closed()) + return {}; + + ThickPolyline ret = *this; + static_cast(ret) = Polyline::rebase_at(idx); + size_t n = this->points.size(); + ret.width.resize(2 * n - 2, 0); + + auto get_in_width = [&](size_t i)->double { + if (i == 0) return this->width[0]; + if (i == n - 1) return this->width.back(); + return this->width[2 * i - 1]; + }; + auto get_out_width = [&](size_t i)->double { + if (i == 0) return this->width[0]; + if (i == n - 1) return this->width.back(); + return this->width[2 * i]; + }; + + ret.width[0] = get_out_width(idx % (n-1)); + for (size_t j = 1; j < n - 1; ++j) { + size_t i = (idx + j) % (n-1); + ret.width[2 * j - 1] = get_in_width(i); + ret.width[2 * j] = get_out_width(i); + } + + ret.width[2 * n - 3] = ret.width.front(); + return ret; +} + +coordf_t ThickPolyline::get_width_at(size_t point_idx) const +{ + if (point_idx < 2) + return width[point_idx]; + return width[2 * point_idx - 1]; +} + Lines3 Polyline3::lines() const { Lines3 lines; diff --git a/src/libslic3r/Polyline.hpp b/src/libslic3r/Polyline.hpp index abf2575cb..25db72e08 100644 --- a/src/libslic3r/Polyline.hpp +++ b/src/libslic3r/Polyline.hpp @@ -102,6 +102,8 @@ public: void append(const Polyline& src); void append(Polyline&& src); + Polyline rebase_at(size_t idx); + Point& operator[](Points::size_type idx) { return this->points[idx]; } const Point& operator[](Points::size_type idx) const { return this->points[idx]; } @@ -264,6 +266,8 @@ public: Polyline::clear(); width.clear(); } + ThickPolyline rebase_at(size_t idx); + coordf_t get_width_at(size_t point_idx) const; std::vector width; std::pair endpoints; diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 217934ef8..0539dba71 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -898,6 +898,7 @@ static std::vector s_Preset_print_options { "default_jerk", "outer_wall_jerk", "inner_wall_jerk", "infill_jerk", "top_surface_jerk", "initial_layer_jerk", "travel_jerk", "filter_out_gap_fill", "mmu_segmented_region_max_width", "mmu_segmented_region_interlocking_depth", "small_perimeter_speed", "small_perimeter_threshold", "z_direction_outwall_speed_continuous", + "vertical_shell_speed","detect_floating_vertical_shell", // calib "print_flow_ratio", //Orca diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 7d313f31a..1a7e3d675 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -552,6 +552,7 @@ private: void bridge_over_infill(); void clip_fill_surfaces(); void discover_horizontal_shells(); + void merge_infill_types(); void combine_infill(); void _generate_support_material(); std::pair prepare_adaptive_infill_data( diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 89b6c1698..5a52622c5 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -1362,6 +1362,24 @@ void PrintConfigDef::init_fff_params() def->enum_labels.push_back(L("Enabled")); def->set_default_value(new ConfigOptionEnum(EnsureVerticalThicknessLevel::evtEnabled)); + def = this->add("vertical_shell_speed",coFloatsOrPercents); + def->label = L("Vertical shell speed"); + def->tooltip = L("Speed for vertical shell area. If expressed as percentage (for example: 80%) it will be calculated on" + "the internal solid infill speed above"); + def->category = L("Speed"); + def->sidetext = L("mm/s or %"); + def->ratio_over = "internal_solid_infill_speed"; + def->min = 0; + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsOrPercentsNullable{FloatOrPercent(80, true)}); + + def = this->add("detect_floating_vertical_shell", coBool); + def->label = L("Detect floating vertical shells"); + def->tooltip = L("Detect floating vertical shells and slow them by using bridge speed."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool{false}); + def = this->add("internal_bridge_support_thickness", coFloat); def->label = L("Internal bridge support thickness"); def->category = L("Strength"); @@ -5561,6 +5579,7 @@ std::set print_options_with_variant = { "small_perimeter_threshold", "sparse_infill_speed", "internal_solid_infill_speed", + "vertical_shell_speed", "top_surface_speed", "enable_overhang_speed", //coBools "overhang_1_4_speed", diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 246510f45..e0444cbb4 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -54,7 +54,7 @@ enum AuthorizationType { enum InfillPattern : int { ipConcentric, ipRectilinear, ipGrid, ipLine, ipCubic, ipTriangles, ipStars, ipGyroid, ipHoneycomb, ipAdaptiveCubic, ipMonotonic, ipMonotonicLine, ipAlignedRectilinear, ip3DHoneycomb, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipSupportCubic, ipSupportBase, ipConcentricInternal, - ipLightning, ipCrossHatch, ipZigZag, ipCrossZag, + ipLightning, ipCrossHatch, ipZigZag, ipCrossZag,ipEnsureVertical, ipCount, }; @@ -854,6 +854,7 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloat, tree_support_branch_diameter_angle)) ((ConfigOptionInt, tree_support_wall_count)) ((ConfigOptionBool, detect_narrow_internal_solid_infill)) + ((ConfigOptionBool, detect_floating_vertical_shell)) // ((ConfigOptionBool, adaptive_layer_height)) ((ConfigOptionFloat, support_bottom_interface_spacing)) ((ConfigOptionFloat, internal_bridge_support_thickness)) @@ -946,6 +947,7 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloatsNullable, top_surface_speed)) ((ConfigOptionFloatsOrPercentsNullable, small_perimeter_speed)) ((ConfigOptionFloatsNullable, small_perimeter_threshold)) + ((ConfigOptionFloatsOrPercentsNullable, vertical_shell_speed)) ((ConfigOptionInt, top_color_penetration_layers)) ((ConfigOptionInt, bottom_color_penetration_layers)) //BBS diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 49ba27ef8..524ef898d 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -55,6 +55,8 @@ using namespace std::literals; #include #endif +#define USE_TBB_IN_INFILL 1 + namespace Slic3r { // Constructor is called from the main thread, therefore all Model / ModelObject / ModelIntance data are valid. @@ -614,15 +616,15 @@ void PrintObject::infill() const auto& adaptive_fill_octree = this->m_adaptive_fill_octrees.first; const auto& support_fill_octree = this->m_adaptive_fill_octrees.second; - BOOST_LOG_TRIVIAL(debug) << "Filling layers in parallel - start"; + //BOOST_LOG_TRIVIAL(debug) << "Filling layers in parallel - start"; tbb::parallel_for( - tbb::blocked_range(0, m_layers.size()), - [this, &adaptive_fill_octree = adaptive_fill_octree, &support_fill_octree = support_fill_octree](const tbb::blocked_range& range) { - for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { - m_print->throw_if_canceled(); - m_layers[layer_idx]->make_fills(adaptive_fill_octree.get(), support_fill_octree.get(), this->m_lightning_generator.get()); - } - } + tbb::blocked_range(0, m_layers.size()), + [this, &adaptive_fill_octree = adaptive_fill_octree, &support_fill_octree = support_fill_octree](const tbb::blocked_range& range) { + for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { + m_print->throw_if_canceled(); + m_layers[layer_idx]->make_fills(adaptive_fill_octree.get(), support_fill_octree.get(), this->m_lightning_generator.get()); + } + } ); m_print->throw_if_canceled(); BOOST_LOG_TRIVIAL(debug) << "Filling layers in parallel - end"; @@ -1087,7 +1089,8 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "sparse_infill_anchor" || opt_key == "sparse_infill_anchor_max" || opt_key == "top_surface_line_width" - || opt_key == "initial_layer_line_width") { + || opt_key == "initial_layer_line_width" + || opt_key == "detect_floating_vertical_shell") { steps.emplace_back(posInfill); } else if (opt_key == "sparse_infill_pattern" || opt_key == "symmetric_infill_y_axis" @@ -1164,7 +1167,8 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "sparse_infill_speed" || opt_key == "inner_wall_speed" || opt_key == "internal_solid_infill_speed" - || opt_key == "top_surface_speed") { + || opt_key == "top_surface_speed" + || opt_key == "vertical_shell_speed") { invalidated |= m_print->invalidate_step(psGCodeExport); } else if ( opt_key == "flush_into_infill" @@ -1261,6 +1265,7 @@ void PrintObject::detect_surfaces_type() if (interface_shells) surfaces_new.assign(num_layers, Surfaces()); + // interface_shell 启用与否,决定着是否区分不同材料。开启后,不同材料间的接触面都会被识别为顶面、底面 tbb::parallel_for( tbb::blocked_range(0, spiral_mode ? @@ -1322,7 +1327,7 @@ void PrintObject::detect_surfaces_type() surfaces_append( bottom, opening_ex( - diff_ex(layerm->slices.surfaces, lower_layer->lslices, ApplySafetyOffset::Yes), + diff_ex(layerm->slices.surfaces, lower_layer->lslices, ApplySafetyOffset::Yes),//完全悬空 offset), surface_type_bottom_other); // if user requested internal shells, we need to identify surfaces @@ -1334,8 +1339,8 @@ void PrintObject::detect_surfaces_type() bottom, opening_ex( diff_ex( - intersection(layerm->slices.surfaces, lower_layer->lslices), // supported - lower_layer->m_regions[region_id]->slices.surfaces, + intersection(layerm->slices.surfaces, lower_layer->lslices), // 先扣掉完全悬空 + lower_layer->m_regions[region_id]->slices.surfaces,//再扣掉同材料的区域 ApplySafetyOffset::Yes), offset), stBottom); @@ -1355,9 +1360,6 @@ void PrintObject::detect_surfaces_type() // and top surfaces; let's do an intersection to discover them and consider them // as bottom surfaces (to allow for bridge detection) if (! top.empty() && ! bottom.empty()) { - // Polygons overlapping = intersection(to_polygons(top), to_polygons(bottom)); - // Slic3r::debugf " layer %d contains %d membrane(s)\n", $layerm->layer->id, scalar(@$overlapping) - // if $Slic3r::debug; Polygons top_polygons = to_polygons(std::move(top)); top.clear(); surfaces_append(top, diff_ex(top_polygons, bottom), stTop); @@ -1393,9 +1395,6 @@ void PrintObject::detect_surfaces_type() surfaces_append(surfaces_out, std::move(top)); surfaces_append(surfaces_out, std::move(bottom)); - // Slic3r::debugf " layer %d has %d bottom, %d top and %d internal surfaces\n", - // $layerm->layer->id, scalar(@bottom), scalar(@top), scalar(@internal) if $Slic3r::debug; - #ifdef SLIC3R_DEBUG_SLICE_PROCESSING layerm->export_region_slices_to_svg_debug("detect_surfaces_type-final"); #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ @@ -1556,6 +1555,8 @@ void PrintObject::discover_vertical_shells() BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells in parallel - start : cache top / bottom"; //FIXME Improve the heuristics for a grain size. size_t grain_size = std::max(num_layers / 16, size_t(1)); + // 关闭interface_shell,不区分不同材料,所以遍历顺序是按层遍历,层中遍历region + // top区域包含墙,bottom区域包含墙,holes为稀疏填充区域 tbb::parallel_for( tbb::blocked_range(0, num_layers, grain_size), [this, &cache_top_botom_regions](const tbb::blocked_range& range) { @@ -1568,21 +1569,12 @@ void PrintObject::discover_vertical_shells() // Simulate single set of perimeters over all merged regions. float perimeter_offset = 0.f; float perimeter_min_spacing = FLT_MAX; -#ifdef SLIC3R_DEBUG_SLICE_PROCESSING - static size_t debug_idx = 0; - ++debug_idx; -#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ for (size_t region_id = 0; region_id < num_regions; ++region_id) { LayerRegion& layerm = *layer.m_regions[region_id]; float top_bottom_expansion = float(layerm.flow(frSolidInfill).scaled_spacing()) * top_bottom_expansion_coeff; // Top surfaces. append(cache.top_surfaces, offset(layerm.slices.filter_by_type(stTop), top_bottom_expansion)); - // append(cache.top_surfaces, offset(layerm.fill_surfaces().filter_by_type(stTop), top_bottom_expansion)); - // Bottom surfaces. append(cache.bottom_surfaces, offset(layerm.slices.filter_by_types(surfaces_bottom), top_bottom_expansion)); - // append(cache.bottom_surfaces, offset(layerm.fill_surfaces().filter_by_types(surfaces_bottom), top_bottom_expansion)); - // Calculate the maximum perimeter offset as if the slice was extruded with a single extruder only. - // First find the maxium number of perimeters per region slice. unsigned int perimeters = 0; for (const Surface& s : layerm.slices.surfaces) perimeters = std::max(perimeters, s.extra_perimeters); @@ -1597,29 +1589,22 @@ void PrintObject::discover_vertical_shells() } polygons_append(cache.holes, to_polygons(layerm.fill_expolygons)); } - // Save some computing time by reducing the number of polygons. - cache.top_surfaces = union_(cache.top_surfaces); - cache.bottom_surfaces = union_(cache.bottom_surfaces); // For a multi-material print, simulate perimeter / infill split as if only a single extruder has been used for the whole print. if (perimeter_offset > 0.) { // The layer.lslices are forced to merge by expanding them first. + // 对于多材料,按照最大墙宽度,再算一次稀疏区域 polygons_append(cache.holes, offset2(layer.lslices, 0.3f * perimeter_min_spacing, -perimeter_offset - 0.3f * perimeter_min_spacing)); -#ifdef SLIC3R_DEBUG_SLICE_PROCESSING - { - Slic3r::SVG svg(debug_out_path("discover_vertical_shells-extra-holes-%d.svg", debug_idx), get_extents(layer.lslices)); - svg.draw(layer.lslices, "blue"); - svg.draw(union_ex(cache.holes), "red"); - svg.draw_outline(union_ex(cache.holes), "black", "blue", scale_(0.05)); - svg.Close(); - } -#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ } + // Save some computing time by reducing the number of polygons. + cache.top_surfaces = union_(cache.top_surfaces); + cache.bottom_surfaces = union_(cache.bottom_surfaces); cache.holes = union_(cache.holes); }}); m_print->throw_if_canceled(); BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells in parallel - end : cache top / bottom"; } + // 逐region遍历 for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) { //FIXME Improve the heuristics for a grain size. const PrintRegion ®ion = this->printing_region(region_id); @@ -1629,6 +1614,8 @@ void PrintObject::discover_vertical_shells() size_t grain_size = std::max(num_layers / 16, size_t(1)); + // 开启了interface_shell,代表顶底面计算时只有同region可以视为covered + // 所以此时,对于某一个region,先逐层计算cache的top,bottom,由于稀疏填充是共用的,所以算一次即可 if (! top_bottom_surfaces_all_regions) { // This is either a single material print, or a multi-material print and interface_shells are enabled, meaning that the vertical shell thickness // is calculated over a single material. @@ -1662,12 +1649,17 @@ void PrintObject::discover_vertical_shells() BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << region_id << " in parallel - start : ensure vertical wall thickness"; grain_size = 1; + // 从第低到高按层遍历 +#if USE_TBB_IN_INFILL tbb::parallel_for( tbb::blocked_range(0, num_layers, grain_size), [this, region_id, &cache_top_botom_regions] (const tbb::blocked_range& range) { // printf("discover_vertical_shells from %d to %d\n", range.begin(), range.end()); for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { +#else + for (size_t idx_layer = 0; idx_layer < num_layers; ++idx_layer) { +#endif m_print->throw_if_canceled(); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING static size_t debug_idx = 0; @@ -1692,28 +1684,6 @@ void PrintObject::discover_vertical_shells() ExPolygons shell_ex; #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ float min_perimeter_infill_spacing = float(infill_line_spacing) * 1.05f; -#if 0 -// #ifdef SLIC3R_DEBUG_SLICE_PROCESSING - { - Slic3r::SVG svg_cummulative(debug_out_path("discover_vertical_shells-perimeters-before-union-run%d.svg", debug_idx), this->bounding_box()); - for (int n = (int)idx_layer - n_extra_bottom_layers; n <= (int)idx_layer + n_extra_top_layers; ++ n) { - if (n < 0 || n >= (int)m_layers.size()) - continue; - ExPolygons &expolys = m_layers[n]->perimeter_expolygons; - for (size_t i = 0; i < expolys.size(); ++ i) { - Slic3r::SVG svg(debug_out_path("discover_vertical_shells-perimeters-before-union-run%d-layer%d-expoly%d.svg", debug_idx, n, i), get_extents(expolys[i])); - svg.draw(expolys[i]); - svg.draw_outline(expolys[i].contour, "black", scale_(0.05)); - svg.draw_outline(expolys[i].holes, "blue", scale_(0.05)); - svg.Close(); - - svg_cummulative.draw(expolys[i]); - svg_cummulative.draw_outline(expolys[i].contour, "black", scale_(0.05)); - svg_cummulative.draw_outline(expolys[i].holes, "blue", scale_(0.05)); - } - } - } -#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ polygons_append(holes, cache_top_botom_regions[idx_layer].holes); auto combine_holes = [&holes](const Polygons &holes2) { if (holes.empty() || holes2.empty()) @@ -1792,18 +1762,6 @@ void PrintObject::discover_vertical_shells() (i > ibottom || bottom_z - m_layers[i]->print_z < region_config.bottom_shell_thickness - EPSILON)) combine_holes(cache_top_botom_regions[i].holes); } -#ifdef SLIC3R_DEBUG_SLICE_PROCESSING - { - Slic3r::SVG svg(debug_out_path("discover_vertical_shells-perimeters-before-union-%d.svg", debug_idx), get_extents(shell)); - svg.draw(shell); - svg.draw_outline(shell, "black", scale_(0.05)); - svg.Close(); - } -#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ -#if 0 -// shell = union_(shell, true); - shell = union_(shell, false); -#endif #ifdef SLIC3R_DEBUG_SLICE_PROCESSING shell_ex = union_safety_offset_ex(shell); #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ @@ -1811,42 +1769,6 @@ void PrintObject::discover_vertical_shells() //if (shell.empty()) // continue; -#ifdef SLIC3R_DEBUG_SLICE_PROCESSING - { - Slic3r::SVG svg(debug_out_path("discover_vertical_shells-perimeters-after-union-%d.svg", debug_idx), get_extents(shell)); - svg.draw(shell_ex); - svg.draw_outline(shell_ex, "black", "blue", scale_(0.05)); - svg.Close(); - } -#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ - -#ifdef SLIC3R_DEBUG_SLICE_PROCESSING - { - Slic3r::SVG svg(debug_out_path("discover_vertical_shells-internal-wshell-%d.svg", debug_idx), get_extents(shell)); - svg.draw(layerm->fill_surfaces().filter_by_type(stInternal), "yellow", 0.5); - svg.draw_outline(layerm->fill_surfaces().filter_by_type(stInternal), "black", "blue", scale_(0.05)); - svg.draw(shell_ex, "blue", 0.5); - svg.draw_outline(shell_ex, "black", "blue", scale_(0.05)); - svg.Close(); - } - { - Slic3r::SVG svg(debug_out_path("discover_vertical_shells-internalvoid-wshell-%d.svg", debug_idx), get_extents(shell)); - svg.draw(layerm->fill_surfaces().filter_by_type(stInternalVoid), "yellow", 0.5); - svg.draw_outline(layerm->fill_surfaces().filter_by_type(stInternalVoid), "black", "blue", scale_(0.05)); - svg.draw(shell_ex, "blue", 0.5); - svg.draw_outline(shell_ex, "black", "blue", scale_(0.05)); - svg.Close(); - } - { - Slic3r::SVG svg(debug_out_path("discover_vertical_shells-internalsolid-wshell-%d.svg", debug_idx), get_extents(shell)); - svg.draw(layerm->fill_surfaces().filter_by_type(stInternalSolid), "yellow", 0.5); - svg.draw_outline(layerm->fill_surfaces().filter_by_type(stInternalSolid), "black", "blue", scale_(0.05)); - svg.draw(shell_ex, "blue", 0.5); - svg.draw_outline(shell_ex, "black", "blue", scale_(0.05)); - svg.Close(); - } -#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ - // Trim the shells region by the internal & internal void surfaces. const Polygons polygonsInternal = to_polygons(layerm->fill_surfaces.filter_by_types({ stInternal, stInternalVoid, stInternalSolid })); shell = intersection(shell, polygonsInternal, ApplySafetyOffset::Yes); @@ -1912,10 +1834,12 @@ void PrintObject::discover_vertical_shells() }), regularized_shell.end()); } + if (regularized_shell.empty()) continue; ExPolygons new_internal_solid = intersection_ex(polygonsInternal, regularized_shell); + #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { Slic3r::SVG svg(debug_out_path("discover_vertical_shells-regularized-%d.svg", debug_idx), get_extents(shell_before)); @@ -1928,7 +1852,6 @@ void PrintObject::discover_vertical_shells() svg.Close(); } #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ - // Trim the internal & internalvoid by the shell. Slic3r::ExPolygons new_internal = diff_ex(layerm->fill_surfaces.filter_by_type(stInternal), regularized_shell); Slic3r::ExPolygons new_internal_void = diff_ex(layerm->fill_surfaces.filter_by_type(stInternalVoid), regularized_shell); @@ -1947,7 +1870,9 @@ void PrintObject::discover_vertical_shells() layerm->fill_surfaces.append(new_internal_void, stInternalVoid); layerm->fill_surfaces.append(new_internal_solid, stInternalSolid); } // for each layer +#if USE_TBB_IN_INFILL }); +#endif m_print->throw_if_canceled(); BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << region_id << " in parallel - end"; @@ -1965,181 +1890,13 @@ void PrintObject::discover_vertical_shells() // PROFILE_OUTPUT(debug_out_path("discover_vertical_shells-profile.txt").c_str()); } -#if 0 -/* This method applies bridge flow to the first internal solid layer above - sparse infill */ -void PrintObject::bridge_over_infill() -{ - BOOST_LOG_TRIVIAL(info) << "Bridge over infill..." << log_memory_info(); - - for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) { - const PrintRegion ®ion = this->printing_region(region_id); - - // skip bridging in case there are no voids - if (region.config().sparse_infill_density.value == 100) - continue; - - for (LayerPtrs::iterator layer_it = m_layers.begin(); layer_it != m_layers.end(); ++ layer_it) { - // skip first layer - if (layer_it == m_layers.begin()) - continue; - - Layer *layer = *layer_it; - LayerRegion *layerm = layer->m_regions[region_id]; - const PrintObjectConfig& object_config = layer->object()->config(); - //BBS: enable thick bridge for internal bridge only - Flow bridge_flow = layerm->bridging_flow(frSolidInfill, true); - - // extract the stInternalSolid surfaces that might be transformed into bridges - Polygons internal_solid; - layerm->fill_surfaces.filter_by_type(stInternalSolid, &internal_solid); - - // check whether the lower area is deep enough for absorbing the extra flow - // (for obvious physical reasons but also for preventing the bridge extrudates - // from overflowing in 3D preview) - ExPolygons to_bridge; - { - Polygons to_bridge_pp = internal_solid; - - // iterate through lower layers spanned by bridge_flow - double bottom_z = layer->print_z - bridge_flow.height() - EPSILON; - for (int i = int(layer_it - m_layers.begin()) - 1; i >= 0; --i) { - const Layer* lower_layer = m_layers[i]; - - // stop iterating if layer is lower than bottom_z - if (lower_layer->print_z < bottom_z) break; - - // iterate through regions and collect internal surfaces - Polygons lower_internal; - for (LayerRegion *lower_layerm : lower_layer->m_regions) - lower_layerm->fill_surfaces.filter_by_type(stInternal, &lower_internal); - - // intersect such lower internal surfaces with the candidate solid surfaces - to_bridge_pp = intersection(to_bridge_pp, lower_internal); - } - - // BBS: expand to make avoid gap between bridge and inner wall - to_bridge_pp = expand(to_bridge_pp, bridge_flow.scaled_width()); - to_bridge_pp = intersection(to_bridge_pp, internal_solid); - - // there's no point in bridging too thin/short regions - //FIXME Vojtech: The offset2 function is not a geometric offset, - // therefore it may create 1) gaps, and 2) sharp corners, which are outside the original contour. - // The gaps will be filled by a separate region, which makes the infill less stable and it takes longer. - { - float min_width = float(bridge_flow.scaled_width()) * 3.f; - to_bridge_pp = opening(to_bridge_pp, min_width); - } - - if (to_bridge_pp.empty()) continue; - - // convert into ExPolygons - to_bridge = union_ex(to_bridge_pp); - } - - #ifdef SLIC3R_DEBUG - printf("Bridging %zu internal areas at layer %zu\n", to_bridge.size(), layer->id()); - #endif - - // compute the remaning internal solid surfaces as difference - ExPolygons not_to_bridge = diff_ex(internal_solid, to_bridge, ApplySafetyOffset::Yes); - to_bridge = intersection_ex(to_bridge, internal_solid, ApplySafetyOffset::Yes); - // build the new collection of fill_surfaces - layerm->fill_surfaces.remove_type(stInternalSolid); - for (ExPolygon &ex : to_bridge) { - layerm->fill_surfaces.surfaces.push_back(Surface(stInternalBridge, ex)); - // BBS: detect angle for internal bridge infill - InternalBridgeDetector ibd(ex, layerm->fill_no_overlap_expolygons, bridge_flow.scaled_spacing()); - if (ibd.detect_angle()) { - (layerm->fill_surfaces.surfaces.end() - 1)->bridge_angle = ibd.angle; - } - } - - for (ExPolygon &ex : not_to_bridge) - layerm->fill_surfaces.surfaces.push_back(Surface(stInternalSolid, ex)); - - //BBS: modify stInternal to be stInternalWithLoop to give better support to internal bridge - if (!to_bridge.empty()){ - float internal_loop_thickness = object_config.internal_bridge_support_thickness.value; - double bottom_z = layer->print_z - layer->height - internal_loop_thickness + EPSILON; - //BBS: lighting infill doesn't support this feature. Don't need to add loop when infill density is high than 50% - if (region.config().sparse_infill_pattern != InfillPattern::ipLightning && region.config().sparse_infill_density.value < 50) - for (int i = int(layer_it - m_layers.begin()) - 1; i >= 0; --i) { - const Layer* lower_layer = m_layers[i]; - - if (lower_layer->print_z < bottom_z) break; - - for (LayerRegion* lower_layerm : lower_layer->m_regions) { - Polygons lower_internal; - lower_layerm->fill_surfaces.filter_by_type(stInternal, &lower_internal); - ExPolygons internal_with_loop = intersection_ex(lower_internal, to_bridge); - ExPolygons internal = diff_ex(lower_internal, to_bridge); - if (internal_with_loop.empty()) { - //BBS: don't need to do anything - } - else if (internal.empty()) { - lower_layerm->fill_surfaces.change_to_new_type(stInternal, stInternalWithLoop); - } - else { - lower_layerm->fill_surfaces.remove_type(stInternal); - for (ExPolygon& ex : internal_with_loop) - lower_layerm->fill_surfaces.surfaces.push_back(Surface(stInternalWithLoop, ex)); - for (ExPolygon& ex : internal) - lower_layerm->fill_surfaces.surfaces.push_back(Surface(stInternal, ex)); - } - } - } - } - - /* - # exclude infill from the layers below if needed - # see discussion at https://github.com/alexrj/Slic3r/issues/240 - # Update: do not exclude any infill. Sparse infill is able to absorb the excess material. - if (0) { - my $excess = $layerm->extruders->{infill}->bridge_flow->width - $layerm->height; - for (my $i = $layer_id-1; $excess >= $self->get_layer($i)->height; $i--) { - Slic3r::debugf " skipping infill below those areas at layer %d\n", $i; - foreach my $lower_layerm (@{$self->get_layer($i)->regions}) { - my @new_surfaces = (); - # subtract the area from all types of surfaces - foreach my $group (@{$lower_layerm->fill_surfaces->group}) { - push @new_surfaces, map $group->[0]->clone(expolygon => $_), - @{diff_ex( - [ map $_->p, @$group ], - [ map @$_, @$to_bridge ], - )}; - push @new_surfaces, map Slic3r::Surface->new( - expolygon => $_, - surface_type => stInternalVoid, - ), @{intersection_ex( - [ map $_->p, @$group ], - [ map @$_, @$to_bridge ], - )}; - } - $lower_layerm->fill_surfaces->clear; - $lower_layerm->fill_surfaces->append($_) for @new_surfaces; - } - - $excess -= $self->get_layer($i)->height; - } - } - */ - -#ifdef SLIC3R_DEBUG_SLICE_PROCESSING - layerm->export_region_slices_to_svg_debug("7_bridge_over_infill"); - layerm->export_region_fill_surfaces_to_svg_debug("7_bridge_over_infill"); -#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ - m_print->throw_if_canceled(); - } - } -} -#else // This method applies bridge flow to the first internal solid layer above sparse infill. // This method applies bridge flow to the first internal solid layer above sparse infill. void PrintObject::bridge_over_infill() { BOOST_LOG_TRIVIAL(info) << "Bridge over infill - Start" << log_memory_info(); + // CandidateSurface存放一个需要桥接的区域 struct CandidateSurface { CandidateSurface(const Surface *original_surface, @@ -2160,6 +1917,7 @@ void PrintObject::bridge_over_infill() double bridge_angle; }; + // 按层存放surface,存放着待桥接的信息 std::map> surfaces_by_layer; // SECTION to gather and filter surfaces for expanding, and then cluster them by layer @@ -2167,6 +1925,7 @@ void PrintObject::bridge_over_infill() tbb::concurrent_vector candidate_surfaces; tbb::parallel_for(tbb::blocked_range(0, this->layers().size()), [po = static_cast(this), &candidate_surfaces](tbb::blocked_range r) { + // 按层并行 for (size_t lidx = r.begin(); lidx < r.end(); lidx++) { const Layer *layer = po->get_layer(lidx); if (layer->lower_layer == nullptr) { @@ -2174,8 +1933,9 @@ void PrintObject::bridge_over_infill() } double spacing = layer->regions().front()->flow(frSolidInfill).scaled_spacing(); // unsupported area will serve as a filter for polygons worth bridging. - Polygons unsupported_area; - Polygons lower_layer_solids; + Polygons unsupported_area; // 下一层不提供支撑的区域 + Polygons lower_layer_solids; // 下一层的实心区域,可以提供支撑 + // 取当前层和下一层的数据 for (const LayerRegion *region : layer->lower_layer->regions()) { Polygons fill_polys = to_polygons(region->fill_expolygons); // initially consider the whole layer unsupported, but also gather solid layers to later cut off supported parts @@ -2197,9 +1957,9 @@ void PrintObject::bridge_over_infill() unsupported_area = diff(unsupported_area, lower_layer_solids); for (LayerRegion *region : layer->regions()) { - SurfacesPtr region_internal_solids = region->fill_surfaces.filter_by_type(stInternalSolid); + auto region_internal_solids = region->fill_surfaces.filter_by_types({ stInternalSolid,stEnsureVertical }); // 取当前层的实心区域 for (const Surface *s : region_internal_solids) { - Polygons unsupported = intersection(to_polygons(s->expolygon), unsupported_area); + Polygons unsupported = intersection(to_polygons(s->expolygon), unsupported_area); // 当前层需要生成桥接的区域,通过当前层的实心区域与下一层的非实心区域求交得到 // The following flag marks those surfaces, which overlap with unuspported area, but at least part of them is supported. // These regions can be filtered by area, because they for sure are touching solids on lower layers, and it does not make sense to bridge their tiny overhangs bool partially_supported = area(unsupported) < area(to_polygons(s->expolygon)) - EPSILON; @@ -2369,8 +2129,8 @@ void PrintObject::bridge_over_infill() std::vector> clustered_layers_for_threads; float target_flow_height_factor = 0.9f; { - std::vector layers_with_candidates; - std::map layer_area_covered_by_candidates; + std::vector layers_with_candidates; // 存储所有需要生成桥接的层号 + std::map layer_area_covered_by_candidates; // 存储每一层,需要生成桥接区域的bbox的并集 for (const auto& pair : surfaces_by_layer) { layers_with_candidates.push_back(pair.first); layer_area_covered_by_candidates[pair.first] = {}; @@ -2381,6 +2141,7 @@ void PrintObject::bridge_over_infill() tbb::parallel_for(tbb::blocked_range(0, layers_with_candidates.size()), [&layers_with_candidates, &surfaces_by_layer, &layer_area_covered_by_candidates]( tbb::blocked_range r) { + // 按层并行 for (size_t job_idx = r.begin(); job_idx < r.end(); job_idx++) { size_t lidx = layers_with_candidates[job_idx]; for (const auto &candidate : surfaces_by_layer.at(lidx)) { @@ -2392,7 +2153,8 @@ void PrintObject::bridge_over_infill() }); // note: surfaces_by_layer is ordered map - for (auto pair : surfaces_by_layer) { + for (const auto &pair : surfaces_by_layer) { + // 初次操作 || z方向距离较远 || 桥接区域无交集, 那么就可以重新划分一个组,否则分配到前一个组 if (clustered_layers_for_threads.empty() || this->get_layer(clustered_layers_for_threads.back().back())->print_z < this->get_layer(pair.first)->print_z - @@ -2442,6 +2204,7 @@ void PrintObject::bridge_over_infill() } } } + // 收集一定z范围内的稀疏和实心区域,判断有没有交集,如果有交集,则不能使用thick bridge(thick bridge的流量会侵占实心区域) layers_sparse_infill = union_ex(layers_sparse_infill); layers_sparse_infill = closing_ex(layers_sparse_infill, float(SCALED_EPSILON)); not_sparse_infill = union_ex(not_sparse_infill); @@ -2749,6 +2512,7 @@ void PrintObject::bridge_over_infill() coordf_t spacing = surfaces_by_layer[lidx].front().region->bridging_flow(frSolidInfill, true).scaled_spacing(); coordf_t target_flow_height = surfaces_by_layer[lidx].front().region->bridging_flow(frSolidInfill, true).height() * target_flow_height_factor; + // 收集当前层中可以应用thick_bridge的区域 Polygons deep_infill_area = gather_areas_w_depth(po, lidx, target_flow_height); { @@ -2770,16 +2534,17 @@ void PrintObject::bridge_over_infill() } } } + // 再减去别的层已经生成的桥接区域 deep_infill_area = diff(deep_infill_area, filled_polyons_on_lower_layers); } - + // 得到thick_bridge区域,bridge区域扩1.5倍 deep_infill_area = expand(deep_infill_area, spacing * 1.5); // Now gather expansion polygons - internal infill on current layer, from which we can cut off anchors Polygons lightning_area; - Polygons expansion_area; - Polygons total_fill_area; - Polygons top_area; + Polygons expansion_area; // 可以提供扩张的区域 + Polygons total_fill_area; // 所有填充区域 + Polygons top_area; // 顶面区域 for (LayerRegion *region : layer->regions()) { Polygons internal_polys = to_polygons(region->fill_surfaces.filter_by_types({stInternal, stInternalSolid})); @@ -2808,7 +2573,7 @@ void PrintObject::bridge_over_infill() expanded_surfaces.reserve(surfaces_by_layer[lidx].size()); for (const CandidateSurface &candidate : surfaces_by_layer[lidx]) { const Flow &flow = candidate.region->bridging_flow(frSolidInfill, true); - Polygons area_to_be_bridge = expand(candidate.new_polys, flow.scaled_spacing()); + Polygons area_to_be_bridge = expand(candidate.new_polys, flow.scaled_spacing()); // 待生成桥接区域 area_to_be_bridge = intersection(area_to_be_bridge, deep_infill_area); ExPolygons area_to_be_bridge_ex = union_ex(area_to_be_bridge); area_to_be_bridge_ex.erase(std::remove_if(area_to_be_bridge_ex.begin(), area_to_be_bridge_ex.end(), @@ -2819,7 +2584,7 @@ void PrintObject::bridge_over_infill() area_to_be_bridge = to_polygons(area_to_be_bridge_ex); - Polygons limiting_area = union_(area_to_be_bridge, expansion_area); + Polygons limiting_area = union_(area_to_be_bridge, expansion_area); // 桥接区域 + 可扩张区域 if (area_to_be_bridge.empty()) continue; @@ -2899,18 +2664,19 @@ void PrintObject::bridge_over_infill() tbb::parallel_for(tbb::blocked_range(0, this->layers().size()), [po = this, &surfaces_by_layer](tbb::blocked_range r) { for (size_t lidx = r.begin(); lidx < r.end(); lidx++) { + // 如果既不需要生成桥接,也不是桥接的下一层,不处理 if (surfaces_by_layer.find(lidx) == surfaces_by_layer.end() && surfaces_by_layer.find(lidx + 1) == surfaces_by_layer.end()) continue; Layer *layer = po->get_layer(lidx); - Polygons cut_from_infill{}; + Polygons cut_from_infill{}; // 桥接区域 if (surfaces_by_layer.find(lidx) != surfaces_by_layer.end()) { for (const auto &surface : surfaces_by_layer.at(lidx)) { cut_from_infill.insert(cut_from_infill.end(), surface.new_polys.begin(), surface.new_polys.end()); } } - Polygons additional_ensuring_areas{}; + Polygons additional_ensuring_areas{}; // 下一层为上一层桥接需要生成的区域 if (surfaces_by_layer.find(lidx + 1) != surfaces_by_layer.end()) { for (const auto &surface : surfaces_by_layer.at(lidx + 1)) { auto additional_area = diff(surface.new_polys, @@ -2922,13 +2688,13 @@ void PrintObject::bridge_over_infill() for (LayerRegion *region : layer->regions()) { Surfaces new_surfaces; - Polygons near_perimeters = to_polygons(union_safety_offset_ex(to_polygons(region->fill_surfaces.surfaces))); + Polygons near_perimeters = to_polygons(union_safety_offset_ex(to_polygons(region->fill_surfaces.surfaces))); // 填充区域中,紧靠着外墙的区域 near_perimeters = diff(near_perimeters, shrink(near_perimeters, region->flow(frSolidInfill).scaled_spacing())); - ExPolygons additional_ensuring = intersection_ex(additional_ensuring_areas, near_perimeters); + ExPolygons additional_ensuring = intersection_ex(additional_ensuring_areas, near_perimeters); // 紧靠着外墙,能够给上一层的桥接提供支撑的区域 SurfacesPtr internal_infills = region->fill_surfaces.filter_by_type(stInternal); - ExPolygons new_internal_infills = diff_ex(internal_infills, cut_from_infill); - new_internal_infills = diff_ex(new_internal_infills, additional_ensuring); + ExPolygons new_internal_infills = diff_ex(internal_infills, cut_from_infill); // 新的稀疏填充区域,去掉生成的桥接区域 + new_internal_infills = diff_ex(new_internal_infills, additional_ensuring); for (const ExPolygon &ep : new_internal_infills) { new_surfaces.emplace_back(stInternal, ep); } @@ -2949,8 +2715,8 @@ void PrintObject::bridge_over_infill() } } } + ExPolygons new_internal_solids = to_expolygons(internal_solids); - new_internal_solids.insert(new_internal_solids.end(), additional_ensuring.begin(), additional_ensuring.end()); new_internal_solids = diff_ex(new_internal_solids, cut_from_infill); new_internal_solids = union_safety_offset_ex(new_internal_solids); for (const ExPolygon &ep : new_internal_solids) { @@ -2976,10 +2742,6 @@ void PrintObject::bridge_over_infill() } // void PrintObject::bridge_over_infill() - - - -#endif static void clamp_exturder_to_default(ConfigOptionInt &opt, size_t num_extruders) { if (opt.value > (int)num_extruders) @@ -3316,16 +3078,6 @@ void PrintObject::discover_horizontal_shells() Layer* layer = m_layers[i]; LayerRegion* layerm = layer->regions()[region_id]; const PrintRegionConfig& region_config = layerm->region().config(); -#if 0 - if (region_config.solid_infill_every_layers.value > 0 && region_config.sparse_infill_density.value > 0 && - (i % region_config.solid_infill_every_layers) == 0) { - // Insert a solid internal layer. Mark stInternal surfaces as stInternalSolid or stInternalBridge. - SurfaceType type = (region_config.sparse_infill_density == 100 || region_config.solid_infill_every_layers == 1) ? stInternalSolid : stInternalBridge; - for (Surface& surface : layerm->fill_surfaces.surfaces) - if (surface.surface_type == stInternal) - surface.surface_type = type; - } -#endif // If ensure_vertical_shell_thickness, then the rest has already been performed by discover_vertical_shells(). if (region_config.ensure_vertical_shell_thickness.value!=EnsureVerticalThicknessLevel::evtDisabled) continue; diff --git a/src/libslic3r/Surface.cpp b/src/libslic3r/Surface.cpp index 58ac7294c..5a37e2e82 100644 --- a/src/libslic3r/Surface.cpp +++ b/src/libslic3r/Surface.cpp @@ -38,6 +38,7 @@ const char* surface_type_to_color_name(const SurfaceType surface_type) case stBottom: return "rgb(0,255,0)"; // "green"; case stBottomBridge: return "rgb(0,0,255)"; // "blue"; case stInternal: return "rgb(255,255,128)"; // yellow + case stEnsureVertical: case stInternalSolid: return "rgb(255,0,255)"; // magenta case stInternalBridge: return "rgb(0,255,255)"; case stInternalVoid: return "rgb(128,128,128)"; diff --git a/src/libslic3r/Surface.hpp b/src/libslic3r/Surface.hpp index 25cf0ff27..2a64143c3 100644 --- a/src/libslic3r/Surface.hpp +++ b/src/libslic3r/Surface.hpp @@ -15,6 +15,7 @@ enum SurfaceType { stBottomBridge, // Normal sparse infill. stInternal, + stEnsureVertical, // Full infill, supporting the top surfaces and/or defining the verticall wall thickness. stInternalSolid, // 1st layer of dense infill over sparse infill, printed with a bridging extrusion flow. @@ -106,7 +107,8 @@ public: bool is_bridge() const { return this->surface_type == stBottomBridge || this->surface_type == stInternalBridge; } bool is_external() const { return this->is_top() || this->is_bottom(); } bool is_internal() const { return ! this->is_external(); } - bool is_solid() const { return this->is_external() || this->surface_type == stInternalSolid || this->surface_type == stInternalBridge; } + bool is_ensure_vertical() const { return this->surface_type == stEnsureVertical; } + bool is_solid() const { return this->is_external() || this->is_ensure_vertical() || this->surface_type == stInternalSolid || this->surface_type == stInternalBridge; } bool is_solid_infill() const { return this->surface_type == stInternalSolid; } }; diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 61c526eb8..e62bde792 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -650,6 +650,9 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in toggle_line(el, support_is_tree); toggle_line("support_critical_regions_only", is_auto(support_type) && support_is_tree); + for (auto el : { "detect_floating_vertical_shell", "vertical_shell_speed" }) + toggle_line(el, config->option>("ensure_vertical_shell_thickness")->value != EnsureVerticalThicknessLevel::evtDisabled); + // tree support use max_bridge_length instead of bridge_no_support toggle_line("bridge_no_support", !support_is_tree); // only normal support has bottom interfaces diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index e0bb102a8..9ee3383c0 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -745,6 +745,7 @@ const std::vector GCodeViewer::Extrusion_Role_Colors {{ { 0.12f, 0.12f, 1.00f, 1.0f }, // erOverhangPerimeter { 0.69f, 0.19f, 0.16f, 1.0f }, // erInternalInfill { 0.59f, 0.33f, 0.80f, 1.0f }, // erSolidInfill + { 0.90f, 0.70f, 0.70f, 1.0f }, // erEnsureVertical { 0.94f, 0.25f, 0.25f, 1.0f }, // erTopSolidInfill { 0.40f, 0.36f, 0.78f, 1.0f }, // erBottomSurface { 1.00f, 0.55f, 0.41f, 1.0f }, // erIroning @@ -1724,6 +1725,7 @@ void GCodeViewer::render_calibration_thumbnail(ThumbnailData& thumbnail_data, un m_extrusions.role_visibility_flags = m_extrusions.role_visibility_flags | (1 << erExternalPerimeter); m_extrusions.role_visibility_flags = m_extrusions.role_visibility_flags | (1 << erOverhangPerimeter); m_extrusions.role_visibility_flags = m_extrusions.role_visibility_flags | (1 << erSolidInfill); + m_extrusions.role_visibility_flags = m_extrusions.role_visibility_flags | (1 << erEnsureVertical); m_extrusions.role_visibility_flags = m_extrusions.role_visibility_flags | (1 << erTopSolidInfill); m_extrusions.role_visibility_flags = m_extrusions.role_visibility_flags | (1 << erInternalInfill); m_extrusions.role_visibility_flags = m_extrusions.role_visibility_flags | (1 << erBottomSurface); @@ -3119,6 +3121,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result, const if (move.type == EMoveType::Seam) ++seams_count; + size_t move_id = i - seams_count; if (move.type == EMoveType::Extrude) { diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 8eaac3bc0..e90f1ddb2 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -2108,6 +2108,7 @@ void TabPrint::build() optgroup->append_single_option_line("infill_combination","parameter/strength-advance-settings"); optgroup->append_single_option_line("detect_narrow_internal_solid_infill","parameter/strength-advance-settings"); optgroup->append_single_option_line("ensure_vertical_shell_thickness","parameter/strength-advance-settings"); + optgroup->append_single_option_line("detect_floating_vertical_shell","parameter/strength-advance-settings"); //optgroup->append_single_option_line("internal_bridge_support_thickness","parameter/strength-advance-settings"); page = add_options_page(L("Speed"), "empty"); @@ -2121,6 +2122,7 @@ void TabPrint::build() optgroup->append_single_option_line("small_perimeter_threshold", "", 0); optgroup->append_single_option_line("sparse_infill_speed", "", 0); optgroup->append_single_option_line("internal_solid_infill_speed", "", 0); + optgroup->append_single_option_line("vertical_shell_speed", "", 0); optgroup->append_single_option_line("top_surface_speed", "", 0); optgroup->append_single_option_line("enable_overhang_speed", "slow-down-for-overhang", 0); Line line = { L("Overhang speed"), L("This is the speed for various overhang degrees. Overhang degrees are expressed as a percentage of line width. 0 speed means no slowing down for the overhang degree range and wall speed is used") };