OrcaSlicer/src/libslic3r/Fill/Fill.cpp
2025-01-08 11:35:08 +08:00

1321 lines
60 KiB
C++

#include <assert.h>
#include <stdio.h>
#include <memory>
#include "../ClipperUtils.hpp"
#include "../Geometry.hpp"
#include "../Layer.hpp"
#include "../Print.hpp"
#include "../PrintConfig.hpp"
#include "../Surface.hpp"
#include "ExtrusionEntity.hpp"
#include "FillBase.hpp"
#include "FillRectilinear.hpp"
#include "FillLightning.hpp"
#include "FillConcentricInternal.hpp"
#include "FillConcentric.hpp"
#include "libslic3r.h"
namespace Slic3r {
struct SurfaceFillParams
{
// Zero based extruder ID.
unsigned int extruder = 0;
// Infill pattern, adjusted for the density etc.
InfillPattern pattern = InfillPattern(0);
// FillBase
// in unscaled coordinates
coordf_t spacing = 0.;
// infill / perimeter overlap, in unscaled coordinates
coordf_t overlap = 0.;
// Angle as provided by the region config, in radians.
float angle = 0.f;
bool rotate_angle = true;
// Is bridging used for this fill? Bridging parameters may be used even if this->flow.bridge() is not set.
bool bridge;
// Non-negative for a bridge.
float bridge_angle = 0.f;
// FillParams
float density = 0.f;
// Don't adjust spacing to fill the space evenly.
// bool dont_adjust = false;
// Length of the infill anchor along the perimeter line.
// 1000mm is roughly the maximum length line that fits into a 32bit coord_t.
float anchor_length = 1000.f;
float anchor_length_max = 1000.f;
// width, height of extrusion, nozzle diameter, is bridge
// For the output, for fill generator.
Flow flow;
// For the output
ExtrusionRole extrusion_role = ExtrusionRole(0);
// Various print settings?
// Index of this entry in a linear vector.
size_t idx = 0;
// infill speed settings
float sparse_infill_speed = 0;
float top_surface_speed = 0;
float solid_infill_speed = 0;
bool operator<(const SurfaceFillParams &rhs) const {
#define RETURN_COMPARE_NON_EQUAL(KEY) if (this->KEY < rhs.KEY) return true; if (this->KEY > rhs.KEY) return false;
#define RETURN_COMPARE_NON_EQUAL_TYPED(TYPE, KEY) if (TYPE(this->KEY) < TYPE(rhs.KEY)) return true; if (TYPE(this->KEY) > TYPE(rhs.KEY)) return false;
// Sort first by decreasing bridging angle, so that the bridges are processed with priority when trimming one layer by the other.
if (this->bridge_angle > rhs.bridge_angle) return true;
if (this->bridge_angle < rhs.bridge_angle) return false;
RETURN_COMPARE_NON_EQUAL(extruder);
RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, pattern);
RETURN_COMPARE_NON_EQUAL(spacing);
RETURN_COMPARE_NON_EQUAL(overlap);
RETURN_COMPARE_NON_EQUAL(angle);
RETURN_COMPARE_NON_EQUAL(rotate_angle);
RETURN_COMPARE_NON_EQUAL(density);
// RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, dont_adjust);
RETURN_COMPARE_NON_EQUAL(anchor_length);
RETURN_COMPARE_NON_EQUAL(anchor_length_max);
RETURN_COMPARE_NON_EQUAL(flow.width());
RETURN_COMPARE_NON_EQUAL(flow.height());
RETURN_COMPARE_NON_EQUAL(flow.nozzle_diameter());
RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, bridge);
RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, extrusion_role);
RETURN_COMPARE_NON_EQUAL(sparse_infill_speed);
RETURN_COMPARE_NON_EQUAL(top_surface_speed);
RETURN_COMPARE_NON_EQUAL(solid_infill_speed);
return false;
}
bool operator==(const SurfaceFillParams &rhs) const {
return this->extruder == rhs.extruder &&
this->pattern == rhs.pattern &&
this->spacing == rhs.spacing &&
this->overlap == rhs.overlap &&
this->angle == rhs.angle &&
this->rotate_angle == rhs.rotate_angle &&
this->bridge == rhs.bridge &&
this->bridge_angle == rhs.bridge_angle &&
this->density == rhs.density &&
// this->dont_adjust == rhs.dont_adjust &&
this->anchor_length == rhs.anchor_length &&
this->anchor_length_max == rhs.anchor_length_max &&
this->flow == rhs.flow &&
this->extrusion_role == rhs.extrusion_role &&
this->sparse_infill_speed == rhs.sparse_infill_speed &&
this->top_surface_speed == rhs.top_surface_speed &&
this->solid_infill_speed == rhs.solid_infill_speed;
}
};
struct SurfaceFill {
SurfaceFill(const SurfaceFillParams& params) : region_id(size_t(-1)), surface(stCount, ExPolygon()), params(params) {}
size_t region_id;
Surface surface;
ExPolygons expolygons;
SurfaceFillParams params;
// BBS
std::vector<size_t> region_id_group;
ExPolygons no_overlap_expolygons;
};
// Detect narrow infill regions
// Based on the anti-vibration algorithm from PrusaSlicer:
// https://github.com/prusa3d/PrusaSlicer/blob/5dc04b4e8f14f65bbcc5377d62cad3e86c2aea36/src/libslic3r/Fill/FillEnsuring.cpp#L37-L273
static coord_t _MAX_LINE_LENGTH_TO_FILTER() // 4 mm.
{
return scaled<coord_t>(4.);
}
const constexpr size_t MAX_SKIPS_ALLOWED = 2; // Skip means propagation through long line.
const constexpr size_t MIN_DEPTH_FOR_LINE_REMOVING = 5;
struct LineNode
{
struct State
{
// The total number of long lines visited before this node was reached.
// We just need the minimum number of all possible paths to decide whether we can remove the line or not.
int min_skips_taken = 0;
// The total number of short lines visited before this node was reached.
int total_short_lines = 0;
// Some initial line is touching some long line. This information is propagated to neighbors.
bool initial_touches_long_lines = false;
bool initialized = false;
void reset() {
this->min_skips_taken = 0;
this->total_short_lines = 0;
this->initial_touches_long_lines = false;
this->initialized = false;
}
};
explicit LineNode(const Line &line) : line(line) {}
Line line;
// Pointers to line nodes in the previous and the next section that overlap with this line.
std::vector<LineNode*> next_section_overlapping_lines;
std::vector<LineNode*> prev_section_overlapping_lines;
bool is_removed = false;
State state;
// Return true if some initial line is touching some long line and this information was propagated into the current line.
bool is_initial_line_touching_long_lines() const {
if (prev_section_overlapping_lines.empty())
return false;
for (LineNode *line_node : prev_section_overlapping_lines) {
if (line_node->state.initial_touches_long_lines)
return true;
}
return false;
}
// Return true if the current line overlaps with some long line in the previous section.
bool is_touching_long_lines_in_previous_layer() const {
if (prev_section_overlapping_lines.empty())
return false;
const auto MAX_LINE_LENGTH_TO_FILTER = _MAX_LINE_LENGTH_TO_FILTER();
for (LineNode *line_node : prev_section_overlapping_lines) {
if (!line_node->is_removed && line_node->line.length() >= MAX_LINE_LENGTH_TO_FILTER)
return true;
}
return false;
}
// Return true if the current line overlaps with some line in the next section.
bool has_next_layer_neighbours() const {
if (next_section_overlapping_lines.empty())
return false;
for (LineNode *line_node : next_section_overlapping_lines) {
if (!line_node->is_removed)
return true;
}
return false;
}
};
using LineNodes = std::vector<LineNode>;
inline bool are_lines_overlapping_in_y_axes(const Line &first_line, const Line &second_line) {
return (second_line.a.y() <= first_line.a.y() && first_line.a.y() <= second_line.b.y())
|| (second_line.a.y() <= first_line.b.y() && first_line.b.y() <= second_line.b.y())
|| (first_line.a.y() <= second_line.a.y() && second_line.a.y() <= first_line.b.y())
|| (first_line.a.y() <= second_line.b.y() && second_line.b.y() <= first_line.b.y());
}
bool can_line_note_be_removed(const LineNode &line_node) {
const auto MAX_LINE_LENGTH_TO_FILTER = _MAX_LINE_LENGTH_TO_FILTER();
return (line_node.line.length() < MAX_LINE_LENGTH_TO_FILTER)
&& (line_node.state.total_short_lines > int(MIN_DEPTH_FOR_LINE_REMOVING)
|| (!line_node.is_initial_line_touching_long_lines() && !line_node.has_next_layer_neighbours()));
}
// Remove the node and propagate its removal to the previous sections.
void propagate_line_node_remove(const LineNode &line_node) {
std::queue<LineNode *> line_node_queue;
for (LineNode *prev_line : line_node.prev_section_overlapping_lines) {
if (prev_line->is_removed)
continue;
line_node_queue.emplace(prev_line);
}
for (; !line_node_queue.empty(); line_node_queue.pop()) {
LineNode &line_to_check = *line_node_queue.front();
if (can_line_note_be_removed(line_to_check)) {
line_to_check.is_removed = true;
for (LineNode *prev_line : line_to_check.prev_section_overlapping_lines) {
if (prev_line->is_removed)
continue;
line_node_queue.emplace(prev_line);
}
}
}
}
// Filter out short extrusions that could create vibrations.
static std::vector<Lines> filter_vibrating_extrusions(const std::vector<Lines> &lines_sections) {
// Initialize all line nodes.
std::vector<LineNodes> line_nodes_sections(lines_sections.size());
for (const Lines &lines_section : lines_sections) {
const size_t section_idx = &lines_section - lines_sections.data();
line_nodes_sections[section_idx].reserve(lines_section.size());
for (const Line &line : lines_section) {
line_nodes_sections[section_idx].emplace_back(line);
}
}
// Precalculate for each line node which line nodes in the previous and next section this line node overlaps.
for (auto curr_lines_section_it = line_nodes_sections.begin(); curr_lines_section_it != line_nodes_sections.end(); ++curr_lines_section_it) {
if (curr_lines_section_it != line_nodes_sections.begin()) {
const auto prev_lines_section_it = std::prev(curr_lines_section_it);
for (LineNode &curr_line : *curr_lines_section_it) {
for (LineNode &prev_line : *prev_lines_section_it) {
if (are_lines_overlapping_in_y_axes(curr_line.line, prev_line.line)) {
curr_line.prev_section_overlapping_lines.emplace_back(&prev_line);
}
}
}
}
if (std::next(curr_lines_section_it) != line_nodes_sections.end()) {
const auto next_lines_section_it = std::next(curr_lines_section_it);
for (LineNode &curr_line : *curr_lines_section_it) {
for (LineNode &next_line : *next_lines_section_it) {
if (are_lines_overlapping_in_y_axes(curr_line.line, next_line.line)) {
curr_line.next_section_overlapping_lines.emplace_back(&next_line);
}
}
}
}
}
const auto MAX_LINE_LENGTH_TO_FILTER = _MAX_LINE_LENGTH_TO_FILTER();
// Select each section as the initial lines section and propagate line node states from this initial lines section to the last lines section.
// During this propagation, we remove those lines that meet the conditions for its removal.
// When some line is removed, we propagate this removal to previous layers.
for (size_t initial_line_section_idx = 0; initial_line_section_idx < line_nodes_sections.size(); ++initial_line_section_idx) {
// Stars from non-removed short lines.
for (LineNode &initial_line : line_nodes_sections[initial_line_section_idx]) {
if (initial_line.is_removed || initial_line.line.length() >= MAX_LINE_LENGTH_TO_FILTER)
continue;
initial_line.state.reset();
initial_line.state.total_short_lines = 1;
initial_line.state.initial_touches_long_lines = initial_line.is_touching_long_lines_in_previous_layer();
initial_line.state.initialized = true;
}
// Iterate from the initial lines section until the last lines section.
for (size_t propagation_line_section_idx = initial_line_section_idx; propagation_line_section_idx < line_nodes_sections.size(); ++propagation_line_section_idx) {
// Before we propagate node states into next lines sections, we reset the state of all line nodes in the next line section.
if (propagation_line_section_idx + 1 < line_nodes_sections.size()) {
for (LineNode &propagation_line : line_nodes_sections[propagation_line_section_idx + 1]) {
propagation_line.state.reset();
}
}
for (LineNode &propagation_line : line_nodes_sections[propagation_line_section_idx]) {
if (propagation_line.is_removed || !propagation_line.state.initialized)
continue;
for (LineNode *neighbour_line : propagation_line.next_section_overlapping_lines) {
if (neighbour_line->is_removed)
continue;
const bool is_short_line = neighbour_line->line.length() < MAX_LINE_LENGTH_TO_FILTER;
const bool is_skip_allowed = propagation_line.state.min_skips_taken < int(MAX_SKIPS_ALLOWED);
if (!is_short_line && !is_skip_allowed)
continue;
const int neighbour_total_short_lines = propagation_line.state.total_short_lines + int(is_short_line);
const int neighbour_min_skips_taken = propagation_line.state.min_skips_taken + int(!is_short_line);
if (neighbour_line->state.initialized) {
// When the state of the node was previously filled, then we need to update data in such a way
// that will maximize the possibility of removing this node.
neighbour_line->state.min_skips_taken = std::max(neighbour_line->state.min_skips_taken, neighbour_total_short_lines);
neighbour_line->state.min_skips_taken = std::min(neighbour_line->state.min_skips_taken, neighbour_min_skips_taken);
// We will keep updating neighbor initial_touches_long_lines until it is equal to false.
if (neighbour_line->state.initial_touches_long_lines) {
neighbour_line->state.initial_touches_long_lines = propagation_line.state.initial_touches_long_lines;
}
} else {
neighbour_line->state.total_short_lines = neighbour_total_short_lines;
neighbour_line->state.min_skips_taken = neighbour_min_skips_taken;
neighbour_line->state.initial_touches_long_lines = propagation_line.state.initial_touches_long_lines;
neighbour_line->state.initialized = true;
}
}
if (can_line_note_be_removed(propagation_line)) {
// Remove the current node and propagate its removal to the previous sections.
propagation_line.is_removed = true;
propagate_line_node_remove(propagation_line);
}
}
}
}
// Create lines sections without filtered-out lines.
std::vector<Lines> lines_sections_out(line_nodes_sections.size());
for (const std::vector<LineNode> &line_nodes_section : line_nodes_sections) {
const size_t section_idx = &line_nodes_section - line_nodes_sections.data();
for (const LineNode &line_node : line_nodes_section) {
if (!line_node.is_removed) {
lines_sections_out[section_idx].emplace_back(line_node.line);
}
}
}
return lines_sections_out;
}
void split_solid_surface(size_t layer_id, const SurfaceFill &fill, ExPolygons &normal_infill, ExPolygons &narrow_infill)
{
assert(fill.surface.surface_type == stInternalSolid);
switch (fill.params.pattern) {
case ipRectilinear:
case ipMonotonic:
case ipMonotonicLine:
case ipAlignedRectilinear:
// Only support straight line based infill
break;
default:
// For all other types, don't split
return;
}
Polygons normal_fill_areas; // Areas that filled with normal infill
constexpr double connect_extrusions = true;
const coord_t scaled_spacing = scaled<coord_t>(fill.params.spacing);
double distance_limit_reconnection = 2.0 * double(scaled_spacing);
double squared_distance_limit_reconnection = distance_limit_reconnection * distance_limit_reconnection;
// Calculate infill direction, see Fill::_infill_direction
double base_angle = fill.params.angle + float(M_PI / 2.);
// For pattern other than ipAlignedRectilinear, the angle are alternated
if (fill.params.pattern != ipAlignedRectilinear) {
size_t idx = layer_id / fill.surface.thickness_layers;
base_angle += (idx & 1) ? float(M_PI / 2.) : 0;
}
const double aligning_angle = -base_angle + PI;
for (const ExPolygon &expolygon : fill.expolygons) {
Polygons filled_area = to_polygons(expolygon);
polygons_rotate(filled_area, aligning_angle);
BoundingBox bb = get_extents(filled_area);
Polygons inner_area = intersection(filled_area, opening(filled_area, 2 * scaled_spacing, 3 * scaled_spacing));
inner_area = shrink(inner_area, scaled_spacing * 0.5 - scaled<double>(fill.params.overlap));
AABBTreeLines::LinesDistancer<Line> area_walls{to_lines(inner_area)};
const size_t n_vlines = (bb.max.x() - bb.min.x() + scaled_spacing - 1) / scaled_spacing;
const coord_t y_min = bb.min.y();
const coord_t y_max = bb.max.y();
Lines vertical_lines(n_vlines);
for (size_t i = 0; i < n_vlines; i++) {
coord_t x = bb.min.x() + i * double(scaled_spacing);
vertical_lines[i].a = Point{x, y_min};
vertical_lines[i].b = Point{x, y_max};
}
if (!vertical_lines.empty()) {
vertical_lines.push_back(vertical_lines.back());
vertical_lines.back().a = Point{coord_t(bb.min.x() + n_vlines * double(scaled_spacing) + scaled_spacing * 0.5), y_min};
vertical_lines.back().b = Point{vertical_lines.back().a.x(), y_max};
}
std::vector<Lines> polygon_sections(n_vlines);
for (size_t i = 0; i < n_vlines; i++) {
const auto intersections = area_walls.intersections_with_line<true>(vertical_lines[i]);
for (int intersection_idx = 0; intersection_idx < int(intersections.size()) - 1; intersection_idx++) {
const auto &a = intersections[intersection_idx];
const auto &b = intersections[intersection_idx + 1];
if (area_walls.outside((a.first + b.first) / 2) < 0) {
if (std::abs(a.first.y() - b.first.y()) > scaled_spacing) {
polygon_sections[i].emplace_back(a.first, b.first);
}
}
}
}
polygon_sections = filter_vibrating_extrusions(polygon_sections);
Polygons reconstructed_area{};
// reconstruct polygon from polygon sections
{
struct TracedPoly
{
Points lows;
Points highs;
};
std::vector<std::vector<Line>> polygon_sections_w_width = polygon_sections;
for (auto &slice : polygon_sections_w_width) {
for (Line &l : slice) {
l.a -= Point{0.0, 0.5 * scaled_spacing};
l.b += Point{0.0, 0.5 * scaled_spacing};
}
}
std::vector<TracedPoly> current_traced_polys;
for (const auto &polygon_slice : polygon_sections_w_width) {
std::unordered_set<const Line *> used_segments;
for (TracedPoly &traced_poly : current_traced_polys) {
auto candidates_begin = std::upper_bound(polygon_slice.begin(), polygon_slice.end(), traced_poly.lows.back(),
[](const Point &low, const Line &seg) { return seg.b.y() > low.y(); });
auto candidates_end = std::upper_bound(polygon_slice.begin(), polygon_slice.end(), traced_poly.highs.back(),
[](const Point &high, const Line &seg) { return seg.a.y() > high.y(); });
bool segment_added = false;
for (auto candidate = candidates_begin; candidate != candidates_end && !segment_added; candidate++) {
if (used_segments.find(&(*candidate)) != used_segments.end()) {
continue;
}
if (connect_extrusions && (traced_poly.lows.back() - candidates_begin->a).cast<double>().squaredNorm() <
squared_distance_limit_reconnection) {
traced_poly.lows.push_back(candidates_begin->a);
} else {
traced_poly.lows.push_back(traced_poly.lows.back() + Point{scaled_spacing / 2, coord_t(0)});
traced_poly.lows.push_back(candidates_begin->a - Point{scaled_spacing / 2, 0});
traced_poly.lows.push_back(candidates_begin->a);
}
if (connect_extrusions && (traced_poly.highs.back() - candidates_begin->b).cast<double>().squaredNorm() <
squared_distance_limit_reconnection) {
traced_poly.highs.push_back(candidates_begin->b);
} else {
traced_poly.highs.push_back(traced_poly.highs.back() + Point{scaled_spacing / 2, 0});
traced_poly.highs.push_back(candidates_begin->b - Point{scaled_spacing / 2, 0});
traced_poly.highs.push_back(candidates_begin->b);
}
segment_added = true;
used_segments.insert(&(*candidates_begin));
}
if (!segment_added) {
// Zero or multiple overlapping segments. Resolving this is nontrivial,
// so we just close this polygon and maybe open several new. This will hopefully happen much less often
traced_poly.lows.push_back(traced_poly.lows.back() + Point{scaled_spacing / 2, 0});
traced_poly.highs.push_back(traced_poly.highs.back() + Point{scaled_spacing / 2, 0});
Polygon &new_poly = reconstructed_area.emplace_back(std::move(traced_poly.lows));
new_poly.points.insert(new_poly.points.end(), traced_poly.highs.rbegin(), traced_poly.highs.rend());
traced_poly.lows.clear();
traced_poly.highs.clear();
}
}
current_traced_polys.erase(std::remove_if(current_traced_polys.begin(), current_traced_polys.end(),
[](const TracedPoly &tp) { return tp.lows.empty(); }),
current_traced_polys.end());
for (const auto &segment : polygon_slice) {
if (used_segments.find(&segment) == used_segments.end()) {
TracedPoly &new_tp = current_traced_polys.emplace_back();
new_tp.lows.push_back(segment.a - Point{scaled_spacing / 2, 0});
new_tp.lows.push_back(segment.a);
new_tp.highs.push_back(segment.b - Point{scaled_spacing / 2, 0});
new_tp.highs.push_back(segment.b);
}
}
}
// add not closed polys
for (TracedPoly &traced_poly : current_traced_polys) {
Polygon &new_poly = reconstructed_area.emplace_back(std::move(traced_poly.lows));
new_poly.points.insert(new_poly.points.end(), traced_poly.highs.rbegin(), traced_poly.highs.rend());
}
}
polygons_append(normal_fill_areas, reconstructed_area);
}
polygons_rotate(normal_fill_areas, -aligning_angle);
// Do the split
ExPolygons normal_fill_areas_ex = union_safety_offset_ex(normal_fill_areas);
ExPolygons narrow_fill_areas = diff_ex(fill.expolygons, normal_fill_areas_ex);
// Merge very small areas that is smaller than a single line width to the normal infill if they touches
for (auto iter = narrow_fill_areas.begin(); iter != narrow_fill_areas.end();) {
auto shrinked_expoly = offset_ex(*iter, -scaled_spacing * 0.5);
if (shrinked_expoly.empty()) {
// Too small! Check if it touches any normal infills
auto expanede_exploy = offset_ex(*iter, scaled_spacing * 0.3);
Polygons normal_fill_area_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(normal_fill_areas_ex, get_extents(expanede_exploy));
auto touch_check = intersection_ex(normal_fill_area_clipped, expanede_exploy);
if (!touch_check.empty()) {
normal_fill_areas_ex.emplace_back(*iter);
iter = narrow_fill_areas.erase(iter);
continue;
}
}
iter++;
}
if (narrow_fill_areas.empty()) {
// No split needed
return;
}
// Expand the normal infills a little bit to avoid gaps between normal and narrow infills
normal_infill = intersection_ex(offset_ex(normal_fill_areas_ex, scaled_spacing * 0.1), fill.expolygons);
narrow_infill = narrow_fill_areas;
#ifdef DEBUG_SURFACE_SPLIT
{
BoundingBox bbox = get_extents(fill.expolygons);
bbox.offset(scale_(1.));
::Slic3r::SVG svg(debug_out_path("surface_split_%d.svg", layer_id), bbox);
svg.draw(to_lines(fill.expolygons), "red", scale_(0.1));
svg.draw(normal_infill, "blue", 0.5);
svg.draw(narrow_infill, "green", 0.5);
svg.Close();
}
#endif
}
std::vector<SurfaceFill> group_fills(const Layer &layer)
{
std::vector<SurfaceFill> surface_fills;
// Fill in a map of a region & surface to SurfaceFillParams.
std::set<SurfaceFillParams> set_surface_params;
std::vector<std::vector<const SurfaceFillParams*>> region_to_surface_params(layer.regions().size(), std::vector<const SurfaceFillParams*>());
SurfaceFillParams params;
bool has_internal_voids = false;
const PrintObjectConfig& object_config = layer.object()->config();
for (size_t region_id = 0; region_id < layer.regions().size(); ++ region_id) {
const LayerRegion &layerm = *layer.regions()[region_id];
region_to_surface_params[region_id].assign(layerm.fill_surfaces.size(), nullptr);
for (const Surface &surface : layerm.fill_surfaces.surfaces)
if (surface.surface_type == stInternalVoid)
has_internal_voids = true;
else {
const PrintRegionConfig &region_config = layerm.region().config();
FlowRole extrusion_role = surface.is_top() ? frTopSolidInfill : (surface.is_solid() ? frSolidInfill : frInfill);
bool is_bridge = layer.id() > 0 && surface.is_bridge();
params.extruder = layerm.region().extruder(extrusion_role);
params.pattern = region_config.sparse_infill_pattern.value;
params.density = float(region_config.sparse_infill_density);
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())
params.pattern = region_config.internal_solid_infill_pattern.value;
else if (surface.is_external() && ! is_bridge) {
if(surface.is_top())
params.pattern = region_config.top_surface_pattern.value;
else
params.pattern = region_config.bottom_surface_pattern.value;
}
else {
if(region_config.top_surface_pattern == ipMonotonic || region_config.top_surface_pattern == ipMonotonicLine)
params.pattern = ipMonotonic;
else
params.pattern = ipRectilinear;
}
} else if (params.density <= 0)
continue;
params.extrusion_role = erInternalInfill;
if (is_bridge) {
if (surface.is_internal_bridge())
params.extrusion_role = erInternalBridgeInfill;
else
params.extrusion_role = erBridgeInfill;
} else if (surface.is_solid()) {
if (surface.is_top()) {
params.extrusion_role = erTopSolidInfill;
} else if (surface.is_bottom()) {
params.extrusion_role = erBottomSurface;
} else {
params.extrusion_role = erSolidInfill;
}
}
params.bridge_angle = float(surface.bridge_angle);
if (params.extrusion_role == erInternalInfill) {
params.angle = float(Geometry::deg2rad(region_config.infill_direction.value));
params.rotate_angle = (params.pattern == ipRectilinear || params.pattern == ipLine);
} else {
params.angle = float(Geometry::deg2rad(region_config.solid_infill_direction.value));
params.rotate_angle = region_config.rotate_solid_infill_direction;
}
// Calculate the actual flow we'll be using for this infill.
params.bridge = is_bridge || Fill::use_bridge_flow(params.pattern);
const bool is_thick_bridge = surface.is_bridge() && (surface.is_internal_bridge() ? object_config.thick_internal_bridges : object_config.thick_bridges);
params.flow = params.bridge ?
//Orca: enable thick bridge based on config
layerm.bridging_flow(extrusion_role, is_thick_bridge) :
layerm.flow(extrusion_role, (surface.thickness == -1) ? layer.height : surface.thickness);
// record speed params
if (!params.bridge) {
if (params.extrusion_role == erInternalInfill)
params.sparse_infill_speed = region_config.sparse_infill_speed;
else if (params.extrusion_role == erTopSolidInfill)
params.top_surface_speed = region_config.top_surface_speed;
else if (params.extrusion_role == erSolidInfill)
params.solid_infill_speed = region_config.internal_solid_infill_speed;
}
// Calculate flow spacing for infill pattern generation.
if (surface.is_solid() || is_bridge) {
params.spacing = params.flow.spacing();
// Don't limit anchor length for solid or bridging infill.
params.anchor_length = 1000.f;
params.anchor_length_max = 1000.f;
} else {
// Internal infill. Calculating infill line spacing independent of the current layer height and 1st layer status,
// so that internall infill will be aligned over all layers of the current region.
params.spacing = layerm.region().flow(*layer.object(), frInfill, layer.object()->config().layer_height, false).spacing();
// Anchor a sparse infill to inner perimeters with the following anchor length:
params.anchor_length = float(region_config.infill_anchor);
if (region_config.infill_anchor.percent)
params.anchor_length = float(params.anchor_length * 0.01 * params.spacing);
params.anchor_length_max = float(region_config.infill_anchor_max);
if (region_config.infill_anchor_max.percent)
params.anchor_length_max = float(params.anchor_length_max * 0.01 * params.spacing);
params.anchor_length = std::min(params.anchor_length, params.anchor_length_max);
}
auto it_params = set_surface_params.find(params);
if (it_params == set_surface_params.end())
it_params = set_surface_params.insert(it_params, params);
region_to_surface_params[region_id][&surface - &layerm.fill_surfaces.surfaces.front()] = &(*it_params);
}
}
surface_fills.reserve(set_surface_params.size());
for (const SurfaceFillParams &params : set_surface_params) {
const_cast<SurfaceFillParams&>(params).idx = surface_fills.size();
surface_fills.emplace_back(params);
}
for (size_t region_id = 0; region_id < layer.regions().size(); ++ region_id) {
const LayerRegion &layerm = *layer.regions()[region_id];
for (const Surface &surface : layerm.fill_surfaces.surfaces)
if (surface.surface_type != stInternalVoid) {
const SurfaceFillParams *params = region_to_surface_params[region_id][&surface - &layerm.fill_surfaces.surfaces.front()];
if (params != nullptr) {
SurfaceFill &fill = surface_fills[params->idx];
if (fill.region_id == size_t(-1)) {
fill.region_id = region_id;
fill.surface = surface;
fill.expolygons.emplace_back(std::move(fill.surface.expolygon));
//BBS
fill.region_id_group.push_back(region_id);
fill.no_overlap_expolygons = layerm.fill_no_overlap_expolygons;
} else {
fill.expolygons.emplace_back(surface.expolygon);
//BBS
auto t = find(fill.region_id_group.begin(), fill.region_id_group.end(), region_id);
if (t == fill.region_id_group.end()) {
fill.region_id_group.push_back(region_id);
fill.no_overlap_expolygons = union_ex(fill.no_overlap_expolygons, layerm.fill_no_overlap_expolygons);
}
}
}
}
}
{
Polygons all_polygons;
for (SurfaceFill &fill : surface_fills)
if (! fill.expolygons.empty()) {
if (fill.expolygons.size() > 1 || ! all_polygons.empty()) {
Polygons polys = to_polygons(std::move(fill.expolygons));
// Make a union of polygons, use a safety offset, subtract the preceding polygons.
// Bridges are processed first (see SurfaceFill::operator<())
fill.expolygons = all_polygons.empty() ? union_safety_offset_ex(polys) : diff_ex(polys, all_polygons, ApplySafetyOffset::Yes);
append(all_polygons, std::move(polys));
} else if (&fill != &surface_fills.back())
append(all_polygons, to_polygons(fill.expolygons));
}
}
// we need to detect any narrow surfaces that might collapse
// when adding spacing below
// such narrow surfaces are often generated in sloping walls
// by bridge_over_infill() and combine_infill() as a result of the
// subtraction of the combinable area from the layer infill area,
// which leaves small areas near the perimeters
// we are going to grow such regions by overlapping them with the void (if any)
// TODO: detect and investigate whether there could be narrow regions without
// any void neighbors
if (has_internal_voids) {
// Internal voids are generated only if "infill_only_where_needed" or "infill_every_layers" are active.
coord_t distance_between_surfaces = 0;
Polygons surfaces_polygons;
Polygons voids;
int region_internal_infill = -1;
int region_solid_infill = -1;
int region_some_infill = -1;
for (SurfaceFill &surface_fill : surface_fills)
if (! surface_fill.expolygons.empty()) {
distance_between_surfaces = std::max(distance_between_surfaces, surface_fill.params.flow.scaled_spacing());
append((surface_fill.surface.surface_type == stInternalVoid) ? voids : surfaces_polygons, to_polygons(surface_fill.expolygons));
if (surface_fill.surface.surface_type == stInternalSolid)
region_internal_infill = (int)surface_fill.region_id;
if (surface_fill.surface.is_solid())
region_solid_infill = (int)surface_fill.region_id;
if (surface_fill.surface.surface_type != stInternalVoid)
region_some_infill = (int)surface_fill.region_id;
}
if (! voids.empty() && ! surfaces_polygons.empty()) {
// First clip voids by the printing polygons, as the voids were ignored by the loop above during mutual clipping.
voids = diff(voids, surfaces_polygons);
// Corners of infill regions, which would not be filled with an extrusion path with a radius of distance_between_surfaces/2
Polygons collapsed = diff(
surfaces_polygons,
opening(surfaces_polygons, float(distance_between_surfaces /2), float(distance_between_surfaces / 2 + ClipperSafetyOffset)));
//FIXME why the voids are added to collapsed here? First it is expensive, second the result may lead to some unwanted regions being
// added if two offsetted void regions merge.
// polygons_append(voids, collapsed);
ExPolygons extensions = intersection_ex(expand(collapsed, float(distance_between_surfaces)), voids, ApplySafetyOffset::Yes);
// Now find an internal infill SurfaceFill to add these extrusions to.
SurfaceFill *internal_solid_fill = nullptr;
unsigned int region_id = 0;
if (region_internal_infill != -1)
region_id = region_internal_infill;
else if (region_solid_infill != -1)
region_id = region_solid_infill;
else if (region_some_infill != -1)
region_id = region_some_infill;
const LayerRegion& layerm = *layer.regions()[region_id];
for (SurfaceFill &surface_fill : surface_fills)
if (surface_fill.surface.surface_type == stInternalSolid && std::abs(layer.height - surface_fill.params.flow.height()) < EPSILON) {
internal_solid_fill = &surface_fill;
break;
}
if (internal_solid_fill == nullptr) {
// Produce another solid fill.
params.extruder = layerm.region().extruder(frSolidInfill);
const auto top_pattern = layerm.region().config().top_surface_pattern;
if(top_pattern == ipMonotonic || top_pattern == ipMonotonicLine)
params.pattern = top_pattern;
else
params.pattern = ipRectilinear;
params.density = 100.f;
params.extrusion_role = erSolidInfill;
params.angle = float(Geometry::deg2rad(layerm.region().config().solid_infill_direction.value));
params.rotate_angle = layerm.region().config().rotate_solid_infill_direction;
// calculate the actual flow we'll be using for this infill
params.flow = layerm.flow(frSolidInfill);
params.spacing = params.flow.spacing();
surface_fills.emplace_back(params);
surface_fills.back().surface.surface_type = stInternalSolid;
surface_fills.back().surface.thickness = layer.height;
surface_fills.back().expolygons = std::move(extensions);
} else {
append(extensions, std::move(internal_solid_fill->expolygons));
internal_solid_fill->expolygons = union_ex(extensions);
}
}
}
// BBS: detect narrow internal solid infill area and use ipConcentricInternal pattern instead
if (layer.object()->config().detect_narrow_internal_solid_infill) {
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;
ExPolygons normal_infill;
ExPolygons narrow_infill;
split_solid_surface(layer.id(), surface_fills[i], normal_infill, narrow_infill);
if (narrow_infill.empty()) {
// BBS: has no narrow expolygon
continue;
} else if (normal_infill.empty()) {
// BBS: all expolygons are narrow, directly change the fill pattern
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;
// BBS: move the narrow expolygons to new surface_fills.back();
surface_fills.back().expolygons = std::move(narrow_infill);
// BBS: delete the narrow expolygons from old surface_fills
surface_fills[i].expolygons = std::move(normal_infill);
}
}
}
return surface_fills;
}
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
void export_group_fills_to_svg(const char *path, const std::vector<SurfaceFill> &fills)
{
BoundingBox bbox;
for (const auto &fill : fills)
for (const auto &expoly : fill.expolygons)
bbox.merge(get_extents(expoly));
Point legend_size = export_surface_type_legend_to_svg_box_size();
Point legend_pos(bbox.min(0), bbox.max(1));
bbox.merge(Point(std::max(bbox.min(0) + legend_size(0), bbox.max(0)), bbox.max(1) + legend_size(1)));
SVG svg(path, bbox);
const float transparency = 0.5f;
for (const auto &fill : fills)
for (const auto &expoly : fill.expolygons)
svg.draw(expoly, surface_type_to_color_name(fill.surface.surface_type), transparency);
export_surface_type_legend_to_svg(svg, legend_pos);
svg.Close();
}
#endif
// friend to Layer
void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree, FillLightning::Generator* lightning_generator)
{
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 */
std::vector<SurfaceFill> surface_fills = group_fills(*this);
const Slic3r::BoundingBox bbox = this->object()->bounding_box();
const auto resolution = this->object()->print()->config().resolution.value;
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
{
static int iRun = 0;
export_group_fills_to_svg(debug_out_path("Layer-fill_surfaces-10_fill-final-%d.svg", iRun ++).c_str(), surface_fills);
}
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
for (SurfaceFill &surface_fill : surface_fills) {
// Create the filler object.
std::unique_ptr<Fill> f = std::unique_ptr<Fill>(Fill::new_from_type(surface_fill.params.pattern));
f->set_bounding_box(bbox);
f->layer_id = this->id();
f->z = this->print_z;
f->angle = surface_fill.params.angle;
f->rotate_angle = surface_fill.params.rotate_angle;
f->adapt_fill_octree = (surface_fill.params.pattern == ipSupportCubic) ? support_fill_octree : adaptive_fill_octree;
f->print_config = &this->object()->print()->config();
f->print_object_config = &this->object()->config();
if (surface_fill.params.pattern == ipLightning)
dynamic_cast<FillLightning::Filler*>(f.get())->generator = lightning_generator;
// 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.;
if (! surface_fill.params.bridge) {
#if 0
link_max_length = layerm.region()->config().get_abs_value(surface.is_external() ? "external_fill_link_max_length" : "fill_link_max_length", flow.spacing());
// printf("flow spacing: %f, is_external: %d, link_max_length: %lf\n", flow.spacing(), int(surface.is_external()), link_max_length);
#else
if (surface_fill.params.density > 80.) // 80%
link_max_length = 3. * f->spacing;
#endif
}
LayerRegion* layerm = this->m_regions[surface_fill.region_id];
// Maximum length of the perimeter segment linking two infill lines.
f->link_max_length = (coord_t)scale_(link_max_length);
// Used by the concentric infill pattern to clip the loops to create extrusion paths.
f->loop_clipping = coord_t(scale_(layerm->region().config().seam_gap.get_abs_value(surface_fill.params.flow.nozzle_diameter())));
// apply half spacing using this flow's own spacing and generate infill
FillParams params;
params.density = float(0.01 * surface_fill.params.density);
params.dont_adjust = false; // surface_fill.params.dont_adjust;
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 || surface_fill.params.pattern == ipConcentricInternal;
params.layer_height = layerm->layer()->height;
// BBS
params.flow = surface_fill.params.flow;
params.extrusion_role = surface_fill.params.extrusion_role;
params.using_internal_flow = using_internal_flow;
params.no_extrusion_overlap = surface_fill.params.overlap;
params.config = &layerm->region().config();
if (surface_fill.params.pattern == ipGrid)
params.can_reverse = false;
for (ExPolygon& expoly : surface_fill.expolygons) {
f->no_overlap_expolygons = intersection_ex(surface_fill.no_overlap_expolygons, ExPolygons() = {expoly}, ApplySafetyOffset::Yes);
// Spacing is modified by the filler to indicate adjustments. Reset it for each expolygon.
f->spacing = surface_fill.params.spacing;
surface_fill.surface.expolygon = std::move(expoly);
if(surface_fill.params.bridge && surface_fill.surface.is_external() && surface_fill.params.density > 99.0){
params.density = layerm->region().config().bridge_density.get_abs_value(1.0);
params.dont_adjust = true;
}
// BBS: make fill
f->fill_surface_extrusion(&surface_fill.surface,
params,
m_regions[surface_fill.region_id]->fills.entities);
}
}
// add thin fill regions
// Unpacks the collection, creates multiple collections per path.
// The path type could be ExtrusionPath, ExtrusionLoop or ExtrusionEntityCollection.
// Why the paths are unpacked?
for (LayerRegion *layerm : m_regions)
for (const ExtrusionEntity *thin_fill : layerm->thin_fills.entities) {
ExtrusionEntityCollection &collection = *(new ExtrusionEntityCollection());
layerm->fills.entities.push_back(&collection);
collection.entities.push_back(thin_fill->clone());
}
#ifndef NDEBUG
for (LayerRegion *layerm : m_regions)
for (size_t i = 0; i < layerm->fills.entities.size(); ++ i)
assert(dynamic_cast<ExtrusionEntityCollection*>(layerm->fills.entities[i]) != nullptr);
#endif
}
Polylines Layer::generate_sparse_infill_polylines_for_anchoring(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree, FillLightning::Generator* lightning_generator) const
{
std::vector<SurfaceFill> surface_fills = group_fills(*this);
const Slic3r::BoundingBox bbox = this->object()->bounding_box();
const auto resolution = this->object()->print()->config().resolution.value;
Polylines sparse_infill_polylines{};
for (SurfaceFill &surface_fill : surface_fills) {
if (surface_fill.surface.surface_type != stInternal) {
continue;
}
switch (surface_fill.params.pattern) {
case ipCount: continue; break;
case ipSupportBase: continue; break;
case ipConcentricInternal: continue; break;
case ipLightning:
case ipAdaptiveCubic:
case ipSupportCubic:
case ipRectilinear:
case ipMonotonic:
case ipMonotonicLine:
case ipAlignedRectilinear:
case ipGrid:
case ipTriangles:
case ipStars:
case ipCubic:
case ipLine:
case ipConcentric:
case ipHoneycomb:
case ip3DHoneycomb:
case ipGyroid:
case ipHilbertCurve:
case ipArchimedeanChords:
case ipOctagramSpiral: break;
}
// Create the filler object.
std::unique_ptr<Fill> f = std::unique_ptr<Fill>(Fill::new_from_type(surface_fill.params.pattern));
f->set_bounding_box(bbox);
f->layer_id = this->id() - this->object()->get_layer(0)->id(); // We need to subtract raft layers.
f->z = this->print_z;
f->angle = surface_fill.params.angle;
f->adapt_fill_octree = (surface_fill.params.pattern == ipSupportCubic) ? support_fill_octree : adaptive_fill_octree;
f->print_config = &this->object()->print()->config();
f->print_object_config = &this->object()->config();
if (surface_fill.params.pattern == ipLightning)
dynamic_cast<FillLightning::Filler *>(f.get())->generator = lightning_generator;
// calculate flow spacing for infill pattern generation
double link_max_length = 0.;
if (!surface_fill.params.bridge) {
#if 0
link_max_length = layerm.region()->config().get_abs_value(surface.is_external() ? "external_fill_link_max_length" : "fill_link_max_length", flow.spacing());
// printf("flow spacing: %f, is_external: %d, link_max_length: %lf\n", flow.spacing(), int(surface.is_external()), link_max_length);
#else
if (surface_fill.params.density > 80.) // 80%
link_max_length = 3. * f->spacing;
#endif
}
LayerRegion &layerm = *m_regions[surface_fill.region_id];
// Maximum length of the perimeter segment linking two infill lines.
f->link_max_length = (coord_t) scale_(link_max_length);
// Used by the concentric infill pattern to clip the loops to create extrusion paths.
f->loop_clipping = coord_t(scale_(layerm.region().config().seam_gap.get_abs_value(surface_fill.params.flow.nozzle_diameter())));
// apply half spacing using this flow's own spacing and generate infill
FillParams params;
params.density = float(0.01 * surface_fill.params.density);
params.dont_adjust = false; // surface_fill.params.dont_adjust;
params.anchor_length = surface_fill.params.anchor_length;
params.anchor_length_max = surface_fill.params.anchor_length_max;
params.resolution = resolution;
params.use_arachne = false;
params.layer_height = layerm.layer()->height;
for (ExPolygon &expoly : surface_fill.expolygons) {
// Spacing is modified by the filler to indicate adjustments. Reset it for each expolygon.
f->spacing = surface_fill.params.spacing;
surface_fill.surface.expolygon = std::move(expoly);
try {
Polylines polylines = f->fill_surface(&surface_fill.surface, params);
sparse_infill_polylines.insert(sparse_infill_polylines.end(), polylines.begin(), polylines.end());
} catch (InfillFailedException &) {}
}
}
return sparse_infill_polylines;
}
// Create ironing extrusions over top surfaces.
void Layer::make_ironing()
{
// LayerRegion::slices contains surfaces marked with SurfaceType.
// Here we want to collect top surfaces extruded with the same extruder.
// A surface will be ironed with the same extruder to not contaminate the print with another material leaking from the nozzle.
// First classify regions based on the extruder used.
struct IroningParams {
InfillPattern pattern;
int extruder = -1;
bool just_infill = false;
// Spacing of the ironing lines, also to calculate the extrusion flow from.
double line_spacing;
// Height of the extrusion, to calculate the extrusion flow from.
double height;
double speed;
double angle;
double inset;
bool operator<(const IroningParams &rhs) const {
if (this->extruder < rhs.extruder)
return true;
if (this->extruder > rhs.extruder)
return false;
if (int(this->just_infill) < int(rhs.just_infill))
return true;
if (int(this->just_infill) > int(rhs.just_infill))
return false;
if (this->line_spacing < rhs.line_spacing)
return true;
if (this->line_spacing > rhs.line_spacing)
return false;
if (this->height < rhs.height)
return true;
if (this->height > rhs.height)
return false;
if (this->speed < rhs.speed)
return true;
if (this->speed > rhs.speed)
return false;
if (this->angle < rhs.angle)
return true;
if (this->angle > rhs.angle)
return false;
if (this->inset < rhs.inset)
return true;
if (this->inset > rhs.inset)
return false;
return false;
}
bool operator==(const IroningParams &rhs) const {
return this->extruder == rhs.extruder && this->just_infill == rhs.just_infill &&
this->line_spacing == rhs.line_spacing && this->height == rhs.height && this->speed == rhs.speed && this->angle == rhs.angle && this->pattern == rhs.pattern && this->inset == rhs.inset;
}
LayerRegion *layerm = nullptr;
// IdeaMaker: ironing
// ironing flowrate (5% percent)
// ironing speed (10 mm/sec)
// Kisslicer:
// iron off, Sweep, Group
// ironing speed: 15 mm/sec
// Cura:
// Pattern (zig-zag / concentric)
// line spacing (0.1mm)
// flow: from normal layer height. 10%
// speed: 20 mm/sec
};
std::vector<IroningParams> by_extruder;
double default_layer_height = this->object()->config().layer_height;
for (LayerRegion *layerm : m_regions)
if (! layerm->slices.empty()) {
IroningParams ironing_params;
const PrintRegionConfig &config = layerm->region().config();
if (config.ironing_type != IroningType::NoIroning &&
(config.ironing_type == IroningType::AllSolid ||
(config.top_shell_layers > 0 &&
(config.ironing_type == IroningType::TopSurfaces ||
(config.ironing_type == IroningType::TopmostOnly && layerm->layer()->upper_layer == nullptr))))) {
if (config.wall_filament == config.solid_infill_filament || config.wall_loops == 0) {
// Iron the whole face.
ironing_params.extruder = config.solid_infill_filament;
} else {
// Iron just the infill.
ironing_params.extruder = config.solid_infill_filament;
}
}
if (ironing_params.extruder != -1) {
//TODO just_infill is currently not used.
ironing_params.just_infill = false;
ironing_params.line_spacing = config.ironing_spacing;
ironing_params.inset = config.ironing_inset;
ironing_params.height = default_layer_height * 0.01 * config.ironing_flow;
ironing_params.speed = config.ironing_speed;
ironing_params.angle = (config.ironing_angle >= 0 ? config.ironing_angle : config.infill_direction) * M_PI / 180.;
ironing_params.pattern = config.ironing_pattern;
ironing_params.layerm = layerm;
by_extruder.emplace_back(ironing_params);
}
}
std::sort(by_extruder.begin(), by_extruder.end());
FillParams fill_params;
fill_params.density = 1.;
fill_params.monotonic = true;
InfillPattern f_pattern = ipRectilinear;
std::unique_ptr<Fill> f = std::unique_ptr<Fill>(Fill::new_from_type(f_pattern));
f->set_bounding_box(this->object()->bounding_box());
f->layer_id = this->id();
f->z = this->print_z;
f->overlap = 0;
for (size_t i = 0; i < by_extruder.size();) {
// Find span of regions equivalent to the ironing operation.
IroningParams &ironing_params = by_extruder[i];
// Create the filler object.
if( f_pattern != ironing_params.pattern )
{
f_pattern = ironing_params.pattern;
f = std::unique_ptr<Fill>(Fill::new_from_type(f_pattern));
f->set_bounding_box(this->object()->bounding_box());
f->layer_id = this->id();
f->z = this->print_z;
f->overlap = 0;
}
size_t j = i;
for (++ j; j < by_extruder.size() && ironing_params == by_extruder[j]; ++ j) ;
// Create the ironing extrusions for regions <i, j)
ExPolygons ironing_areas;
double nozzle_dmr = this->object()->print()->config().nozzle_diameter.get_at(ironing_params.extruder - 1);
if (ironing_params.just_infill) {
//TODO just_infill is currently not used.
// Just infill.
} else {
// Infill and perimeter.
// Merge top surfaces with the same ironing parameters.
Polygons polys;
Polygons infills;
for (size_t k = i; k < j; ++ k) {
const IroningParams &ironing_params = by_extruder[k];
const PrintRegionConfig &region_config = ironing_params.layerm->region().config();
bool iron_everything = region_config.ironing_type == IroningType::AllSolid;
bool iron_completely = iron_everything;
if (iron_everything) {
// Check whether there is any non-solid hole in the regions.
bool internal_infill_solid = region_config.sparse_infill_density.value > 95.;
for (const Surface &surface : ironing_params.layerm->fill_surfaces.surfaces)
if ((!internal_infill_solid && surface.surface_type == stInternal) || surface.surface_type == stInternalBridge || surface.surface_type == stInternalVoid) {
// Some fill region is not quite solid. Don't iron over the whole surface.
iron_completely = false;
break;
}
}
if (iron_completely) {
// Iron everything. This is likely only good for solid transparent objects.
for (const Surface &surface : ironing_params.layerm->slices.surfaces)
polygons_append(polys, surface.expolygon);
} else {
for (const Surface &surface : ironing_params.layerm->slices.surfaces)
if ((surface.surface_type == stTop && region_config.top_shell_layers > 0) || (iron_everything && surface.surface_type == stBottom && region_config.bottom_shell_layers > 0))
// stBottomBridge is not being ironed on purpose, as it would likely destroy the bridges.
polygons_append(polys, surface.expolygon);
}
if (iron_everything && ! iron_completely) {
// Add solid fill surfaces. This may not be ideal, as one will not iron perimeters touching these
// solid fill surfaces, but it is likely better than nothing.
for (const Surface &surface : ironing_params.layerm->fill_surfaces.surfaces)
if (surface.surface_type == stInternalSolid)
polygons_append(infills, surface.expolygon);
}
}
if (! infills.empty() || j > i + 1) {
// Ironing over more than a single region or over solid internal infill.
if (! infills.empty())
// For IroningType::AllSolid only:
// Add solid infill areas for layers, that contain some non-ironable infil (sparse infill, bridge infill).
append(polys, std::move(infills));
polys = union_safety_offset(polys);
}
// Trim the top surfaces with half the nozzle diameter.
// BBS: ironing inset
double ironing_areas_offset = ironing_params.inset == 0 ? float(scale_(0.5 * nozzle_dmr)) : scale_(ironing_params.inset);
ironing_areas = intersection_ex(polys, offset(this->lslices, - ironing_areas_offset));
}
// Create the filler object.
f->spacing = ironing_params.line_spacing;
f->angle = float(ironing_params.angle);
f->link_max_length = (coord_t) scale_(3. * f->spacing);
double extrusion_height = ironing_params.height * f->spacing / nozzle_dmr;
float extrusion_width = Flow::rounded_rectangle_extrusion_width_from_spacing(float(nozzle_dmr), float(extrusion_height));
double flow_mm3_per_mm = nozzle_dmr * extrusion_height;
Surface surface_fill(stTop, ExPolygon());
for (ExPolygon &expoly : ironing_areas) {
surface_fill.expolygon = std::move(expoly);
Polylines polylines;
try {
polylines = f->fill_surface(&surface_fill, fill_params);
} catch (InfillFailedException &) {
}
if (! polylines.empty()) {
// Save into layer.
ExtrusionEntityCollection *eec = nullptr;
ironing_params.layerm->fills.entities.push_back(eec = new ExtrusionEntityCollection());
// Don't sort the ironing infill lines as they are monotonicly ordered.
eec->no_sort = true;
extrusion_entities_append_paths(
eec->entities, std::move(polylines),
erIroning,
flow_mm3_per_mm, extrusion_width, float(extrusion_height));
}
}
// Regions up to j were processed.
i = j;
}
}
} // namespace Slic3r