mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-07-19 12:47:50 -06:00

* Additional control over bridges * Label updates * Detect and handle layers over external bridges * Label updates * To-Do placeholders * Filter out small external bridges * Apply safety offset for internal bridge polygon intersections * code comments * Increase bridge offsets to 3 perimeters total (1.5 perimeter in each dimension) * Filter out bridges based on perimeter counts to focus bridge on areas where bridge infill is actually generated in the end. * Fixing bugs * Convert tick boxes to drop down menu * Additional geometry checks for second internal bridge to ensure no small polygons are left over. * Minor code refactor for clarity * Further refinements in polygon logic * Polygon logic refinements pt3 * Further union operations to ensure clean geometry * Fix compile error * Clean up constructors * Only create bridges on stInternalSolid areas, not sparse infill. * Refactor internal second bridge logic to stand alone parallel for loop to avoid thread deadlocks * Revert change to only consider stInternalSolid areas for second internal bridge layer. This resulted in partly unsupported solid infill areas above as the remainder was too narrow to generate sparse infill * Updated beta statements and tooltip changes --------- Co-authored-by: SoftFever <softfeverever@gmail.com>
1340 lines
61 KiB
C++
1340 lines
61 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;
|
|
|
|
// Params for lattice infill angles
|
|
float lattice_angle_1 = 0.f;
|
|
float lattice_angle_2 = 0.f;
|
|
|
|
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_COMPARE_NON_EQUAL(lattice_angle_1);
|
|
RETURN_COMPARE_NON_EQUAL(lattice_angle_2);
|
|
|
|
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 &&
|
|
this->lattice_angle_1 == rhs.lattice_angle_1 &&
|
|
this->lattice_angle_2 == rhs.lattice_angle_2;
|
|
}
|
|
};
|
|
|
|
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 ®ion_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);
|
|
params.lattice_angle_1 = region_config.lattice_angle_1;
|
|
params.lattice_angle_2 = region_config.lattice_angle_2;
|
|
|
|
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 ¶ms : 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;
|
|
params.lattice_angle_1 = surface_fill.params.lattice_angle_1;
|
|
params.lattice_angle_2 = surface_fill.params.lattice_angle_2;
|
|
|
|
// 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;
|
|
}
|
|
if(surface_fill.surface.is_internal_bridge()){
|
|
params.density = f->print_object_config->internal_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 ip2DLattice:
|
|
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;
|
|
params.lattice_angle_1 = surface_fill.params.lattice_angle_1;
|
|
params.lattice_angle_2 = surface_fill.params.lattice_angle_2;
|
|
|
|
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 ®ion_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
|