update libslic3r

This commit is contained in:
QIDI TECH
2025-05-05 19:52:57 +08:00
parent eae8e18c3a
commit 126534997a
180 changed files with 24833 additions and 5679 deletions

View File

@@ -7,6 +7,7 @@
#include "Clipper2Utils.hpp"
#include "Arachne/WallToolPaths.hpp"
#include "Line.hpp"
#include "Layer.hpp"
#include <cmath>
#include <cassert>
#include <random>
@@ -18,6 +19,7 @@ static const double narrow_loop_length_threshold = 10;
static const double min_degree_gap = 0.1;
static const int max_overhang_degree = overhang_sampling_number - 1;
static const std::vector<double> non_uniform_degree_map = { 0, 10, 25, 50, 75, 100};
static const int insert_point_count = 3;
//QDS: when the width of expolygon is smaller than
//ext_perimeter_width + ext_perimeter_spacing * (1 - SMALLER_EXT_INSET_OVERLAP_TOLERANCE),
//we think it's small detail area and will generate smaller line width for it
@@ -46,14 +48,16 @@ public:
// QDS: is perimeter using smaller width
bool is_smaller_width_perimeter;
// Depth in the hierarchy. External perimeter has depth = 0. An external perimeter could be both a contour and a hole.
unsigned short depth;
unsigned short depth;
// Should this contur be fuzzyfied on path generation?
bool fuzzify;
// Slow down speed for circle
bool need_circle_compensation = false;
// Children contour, may be both CCW and CW oriented (outer contours or holes).
std::vector<PerimeterGeneratorLoop> children;
PerimeterGeneratorLoop(const Polygon &polygon, unsigned short depth, bool is_contour, bool fuzzify, bool is_small_width_perimeter = false) :
polygon(polygon), is_contour(is_contour), is_smaller_width_perimeter(is_small_width_perimeter), depth(depth), fuzzify(fuzzify) {}
PerimeterGeneratorLoop(const Polygon &polygon, unsigned short depth, bool is_contour, bool fuzzify, bool is_small_width_perimeter = false, bool need_circle_compensation_ = false) :
polygon(polygon), is_contour(is_contour), is_smaller_width_perimeter(is_small_width_perimeter), depth(depth), fuzzify(fuzzify), need_circle_compensation(need_circle_compensation_) {}
// External perimeter. It may be CCW or CW oriented (outer contour or hole contour).
bool is_external() const { return this->depth == 0; }
// An island, which may have holes, but it does not have another internal island.
@@ -103,12 +107,13 @@ static void fuzzy_extrusion_line(Arachne::ExtrusionLine& ext_lines, double fuzzy
const double range_random_point_dist = fuzzy_skin_point_dist / 2.;
double dist_left_over = double(rand()) * (min_dist_between_points / 2) / double(RAND_MAX); // the distance to be traversed on the line before making the first new point
// do not apply hole compensation in fuzzy skin mode
auto* p0 = &ext_lines.front();
std::vector<Arachne::ExtrusionJunction> out;
out.reserve(ext_lines.size());
for (auto& p1 : ext_lines) {
if (p0->p == p1.p) { // Connect endpoints.
out.emplace_back(p1.p, p1.w, p1.perimeter_index);
out.emplace_back(p1.p, p1.w, p1.perimeter_index, false);
continue;
}
@@ -119,7 +124,7 @@ static void fuzzy_extrusion_line(Arachne::ExtrusionLine& ext_lines, double fuzzy
double dist_last_point = dist_left_over + p0p1_size * 2.;
for (double p0pa_dist = dist_left_over; p0pa_dist < p0p1_size; p0pa_dist += min_dist_between_points + double(rand()) * range_random_point_dist / double(RAND_MAX)) {
double r = double(rand()) * (fuzzy_skin_thickness * 2.) / double(RAND_MAX) - fuzzy_skin_thickness;
out.emplace_back(p0->p + (p0p1 * (p0pa_dist / p0p1_size) + perp(p0p1).cast<double>().normalized() * r).cast<coord_t>(), p1.w, p1.perimeter_index);
out.emplace_back(p0->p + (p0p1 * (p0pa_dist / p0p1_size) + perp(p0p1).cast<double>().normalized() * r).cast<coord_t>(), p1.w, p1.perimeter_index, false);
dist_last_point = p0pa_dist;
}
dist_left_over = p0p1_size - dist_last_point;
@@ -128,7 +133,7 @@ static void fuzzy_extrusion_line(Arachne::ExtrusionLine& ext_lines, double fuzzy
while (out.size() < 3) {
size_t point_idx = ext_lines.size() - 2;
out.emplace_back(ext_lines[point_idx].p, ext_lines[point_idx].w, ext_lines[point_idx].perimeter_index);
out.emplace_back(ext_lines[point_idx].p, ext_lines[point_idx].w, ext_lines[point_idx].perimeter_index,false);
if (point_idx == 0)
break;
--point_idx;
@@ -190,7 +195,7 @@ static void lowpass_filter_by_paths_overhang_degree(ExtrusionPaths& paths) {
if (paths[index].role() == erOverhangPerimeter)
break;
temp_length = paths[index].length();
if (temp_length > right_total_length)
if (temp_length > right_total_length)
neighbor_path.emplace_back(std::pair<double, int>(right_total_length, old_overhang_series[index]));
else
neighbor_path.emplace_back(std::pair<double, int>(temp_length, old_overhang_series[index]));
@@ -338,6 +343,37 @@ public:
}
};
static void sampling_at_line_end(Polyline &poly, double mini_length, int insert_count)
{
Point end_point = poly.last_point();
const double length = poly.length();
if (length < mini_length * 2)
return;
if (length < mini_length * (insert_count * 2 - 1))
insert_count = 0.5 * length / mini_length;
poly.points.pop_back();
double length_count = 0;
Point dir = end_point - poly.first_point();
for (int count = 0; count < insert_count; ++count) {
length_count += mini_length;
poly.append(poly.first_point() + dir * (length_count / length));
}
dir *= -1;
for (int count = 0; count < insert_count; ++count) {
poly.append(end_point + dir * (length_count / length));
length_count -= mini_length;
}
poly.append(end_point);
}
static std::deque<PolylineWithDegree> detect_overahng_degree(Polygons lower_polygons,
Polylines middle_overhang_polyines,
const double &lower_bound,
@@ -356,6 +392,9 @@ static std::deque<PolylineWithDegree> detect_overahng_degree(Polygons low
for (size_t polyline_idx = 0; polyline_idx < middle_overhang_polyines.size(); ++polyline_idx) {
//filter too short polyline
Polyline middle_poly = middle_overhang_polyines[polyline_idx];
//sample at start and end
if (middle_poly.size() == 2)
sampling_at_line_end(middle_poly, (upper_bound - lower_bound) / 2, insert_point_count);
Polyline polyline_with_insert_points;
points_overhang.clear();
@@ -454,7 +493,8 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime
for (const PerimeterGeneratorLoop &loop : loops) {
bool is_external = loop.is_external();
bool is_small_width = loop.is_smaller_width_perimeter;
CustomizeFlag flag = loop.need_circle_compensation ? CustomizeFlag::cfCircleCompensation : CustomizeFlag::cfNone;
ExtrusionRole role;
ExtrusionLoopRole loop_role;
role = is_external ? erExternalPerimeter : erPerimeter;
@@ -526,7 +566,8 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime
remain_polines = diff_pl_2({to_polyline(polygon)}, lower_polygons_series_clipped);
bool detect_overhang_degree = perimeter_generator.config->enable_overhang_speed && perimeter_generator.config->fuzzy_skin == FuzzySkinType::None;
bool detect_overhang_degree = perimeter_generator.config->enable_overhang_speed.get_at(get_extruder_index(*(perimeter_generator.print_config), perimeter_generator.config->wall_filament - 1))
&& perimeter_generator.config->fuzzy_skin == FuzzySkinType::None;
//QDS: fuzziy skin may generate a line that approximates a point, which can cause the clipper to get empty results
if (loop.fuzzify && remain_polines.empty() && inside_polines.empty()) {
@@ -579,7 +620,7 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime
overhang_dist_boundary->second);
// QDS: add path with overhang degree
for (PolylineWithDegree polylines_collection : polylines_degree_collection) {
for (PolylineWithDegree &polylines_collection : polylines_degree_collection) {
extrusion_paths_append(paths,
std::move(polylines_collection.polyline),
polylines_collection.overhang_degree,
@@ -634,17 +675,32 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime
paths.emplace_back(std::move(path));
}
coll.append(ExtrusionLoop(std::move(paths), loop_role));
for (ExtrusionPath& path : paths) {
path.set_customize_flag(flag);
}
coll.append(ExtrusionLoop(std::move(paths), loop_role, flag));
}
// Append thin walls to the nearest-neighbor search (only for first iteration)
Point zero_point(0, 0);
if (! thin_walls.empty()) {
BoundingBox bbox;
for (auto &entity : coll.entities) { bbox.merge(entity->as_polyline().bounding_box()); }
for (auto& thin_wall : thin_walls) {
// find the corner of bbox that's farthest from the thin wall
Point corner_far = bbox.min;
if ((corner_far.cast<double>() - thin_wall.first_point().cast<double>()).squaredNorm() <
(bbox.max.cast<double>() - thin_wall.first_point().cast<double>()).squaredNorm())
corner_far = bbox.max;
zero_point = corner_far;
}
variable_width(thin_walls, erExternalPerimeter, perimeter_generator.ext_perimeter_flow, coll.entities);
thin_walls.clear();
}
// Traverse children and build the final collection.
Point zero_point(0, 0);
std::vector<std::pair<size_t, bool>> chain = chain_extrusion_entities(coll.entities, &zero_point);
ExtrusionEntityCollection out;
for (const std::pair<size_t, bool> &idx : chain) {
@@ -683,7 +739,7 @@ static ClipperLib_Z::Paths clip_extrusion(const ClipperLib_Z::Path& subject, con
clipper.ZFillFunction([](const ClipperLib_Z::IntPoint& e1bot, const ClipperLib_Z::IntPoint& e1top, const ClipperLib_Z::IntPoint& e2bot,
const ClipperLib_Z::IntPoint& e2top, ClipperLib_Z::IntPoint& pt) {
// The clipping contour may be simplified by clipping it with a bounding box of "subject" path.
// The clipping function used may produce self intersections outside of the "subject" bounding box. Such self intersections are
// The clipping function used may produce self intersections outside of the "subject" bounding box. Such self intersections are
// harmless to the result of the clipping operation,
// Both ends of each edge belong to the same source: Either they are from subject or from clipping path.
assert(e1bot.z() >= 0 && e1top.z() >= 0);
@@ -892,13 +948,32 @@ static void detect_brigde_wall_arachne(const PerimeterGenerator &perimeter_gener
static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& perimeter_generator, std::vector<PerimeterGeneratorArachneExtrusion>& pg_extrusions)
{
ExtrusionEntityCollection extrusion_coll;
for (PerimeterGeneratorArachneExtrusion& pg_extrusion : pg_extrusions) {
if (perimeter_generator.print_config->z_direction_outwall_speed_continuous)
extrusion_coll.loop_node_range.first = perimeter_generator.loop_nodes->size();
for (PerimeterGeneratorArachneExtrusion &pg_extrusion : pg_extrusions) {
Arachne::ExtrusionLine* extrusion = pg_extrusion.extrusion;
if (extrusion->empty())
continue;
//get extrusion date
const bool is_external = extrusion->inset_idx == 0;
ExtrusionRole role = is_external ? erExternalPerimeter : erPerimeter;
ExtrusionRole role = is_external ? erExternalPerimeter : erPerimeter;
if (is_external && perimeter_generator.print_config->z_direction_outwall_speed_continuous) {
LoopNode node;
NodeContour node_contour;
node_contour.is_loop = extrusion->is_closed;
for (size_t i = 0; i < extrusion->junctions.size(); i++) {
node_contour.pts.push_back(extrusion->junctions[i].p);
node_contour.widths.push_back(extrusion->junctions[i].w);
}
node.node_contour = node_contour;
node.node_id = perimeter_generator.loop_nodes->size();
node.loop_id = extrusion_coll.entities.size();
node.bbox = get_extents(node.node_contour.pts);
node.bbox.offset(perimeter_generator.config->outer_wall_line_width/2);
perimeter_generator.loop_nodes->push_back(std::move(node));
}
if (pg_extrusion.fuzzify)
fuzzy_extrusion_line(*extrusion, scaled<float>(perimeter_generator.config->fuzzy_skin_thickness.value), scaled<float>(perimeter_generator.config->fuzzy_skin_point_distance.value));
@@ -938,7 +1013,8 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p
extrusion_paths_append(temp_paths, clip_extrusion(extrusion_path, lower_slices_paths, ClipperLib_Z::ctIntersection), role,
is_external ? perimeter_generator.ext_perimeter_flow : perimeter_generator.perimeter_flow);
if (perimeter_generator.config->enable_overhang_speed && perimeter_generator.config->fuzzy_skin == FuzzySkinType::None) {
if (perimeter_generator.config->enable_overhang_speed.get_at(get_extruder_index(*(perimeter_generator.print_config), perimeter_generator.config->wall_filament - 1))
&& perimeter_generator.config->fuzzy_skin == FuzzySkinType::None) {
Flow flow = is_external ? perimeter_generator.ext_perimeter_flow : perimeter_generator.perimeter_flow;
std::map<double, std::vector<Polygons>> clipper_serise;
@@ -986,7 +1062,7 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p
} else {
paths = std::move(temp_paths);
}
// get overhang paths by checking what parts of this loop fall
// outside the grown lower slices (thus where the distance between
@@ -1036,7 +1112,8 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p
chain_and_reorder_extrusion_paths(paths, &start_point);
if (perimeter_generator.config->enable_overhang_speed && perimeter_generator.config->fuzzy_skin == FuzzySkinType::None) {
if (perimeter_generator.config->enable_overhang_speed.get_at(get_extruder_index(*(perimeter_generator.print_config), perimeter_generator.config->wall_filament - 1))
&& perimeter_generator.config->fuzzy_skin == FuzzySkinType::None) {
// QDS: filter the speed
smooth_overhang_level(paths);
}
@@ -1047,6 +1124,8 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p
extrusion_paths_append(paths, *extrusion, role, is_external ? perimeter_generator.ext_perimeter_flow : perimeter_generator.perimeter_flow);
}
bool apply_hole_compensation = extrusion->shouldApplyHoleCompensation();
// Append paths to collection.
if (!paths.empty()) {
if (extrusion->is_closed) {
@@ -1063,6 +1142,11 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p
}
assert(extrusion_loop.paths.front().first_point() == extrusion_loop.paths.back().last_point());
if (apply_hole_compensation) {
for (auto& path : extrusion_loop.paths)
path.set_customize_flag(CustomizeFlag::cfCircleCompensation);
extrusion_loop.set_customize_flag(CustomizeFlag::cfCircleCompensation);
}
extrusion_coll.append(std::move(extrusion_loop));
}
else {
@@ -1085,14 +1169,35 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p
multi_path.paths.emplace_back(std::move(*it_path));
}
if (apply_hole_compensation) {
for (auto& path : multi_path.paths)
path.set_customize_flag(CustomizeFlag::cfCircleCompensation);
multi_path.set_customize_flag(CustomizeFlag::cfCircleCompensation);
}
extrusion_coll.append(ExtrusionMultiPath(std::move(multi_path)));
}
}
}
if (perimeter_generator.print_config->z_direction_outwall_speed_continuous)
extrusion_coll.loop_node_range.second = perimeter_generator.loop_nodes->size();
return extrusion_coll;
}
static Polygons to_polygons_with_flag(const ExPolygon& src, const bool contour_flag, const std::vector<int>& holes_flag, std::vector<int>& flags_out)
{
Polygons polygons;
polygons.reserve(src.num_contours());
polygons.push_back(src.contour);
polygons.insert(polygons.end(), src.holes.begin(), src.holes.end());
flags_out.reserve(holes_flag.size() + 1);
if(contour_flag == true)
flags_out.emplace_back(0);
for (size_t idx = 0; idx < holes_flag.size(); ++idx)
flags_out.emplace_back(holes_flag[idx] + 1);
return polygons;
}
void PerimeterGenerator::process_classic()
@@ -1101,19 +1206,19 @@ void PerimeterGenerator::process_classic()
m_mm3_per_mm = this->perimeter_flow.mm3_per_mm();
coord_t perimeter_width = this->perimeter_flow.scaled_width();
coord_t perimeter_spacing = this->perimeter_flow.scaled_spacing();
// external perimeters
m_ext_mm3_per_mm = this->ext_perimeter_flow.mm3_per_mm();
coord_t ext_perimeter_width = this->ext_perimeter_flow.scaled_width();
coord_t ext_perimeter_spacing = this->ext_perimeter_flow.scaled_spacing();
coord_t ext_perimeter_spacing2 = scaled<coord_t>(0.5f * (this->ext_perimeter_flow.spacing() + this->perimeter_flow.spacing()));
// overhang perimeters
m_mm3_per_mm_overhang = this->overhang_flow.mm3_per_mm();
// solid infill
coord_t solid_infill_spacing = this->solid_infill_flow.scaled_spacing();
// Calculate the minimum required spacing between two adjacent traces.
// This should be equal to the nominal flow spacing but we experiment
// with some tolerance in order to avoid triggering medial axis when
@@ -1126,7 +1231,7 @@ void PerimeterGenerator::process_classic()
// internal flow which is unrelated.
coord_t min_spacing = coord_t(perimeter_spacing * (1 - INSET_OVERLAP_TOLERANCE));
coord_t ext_min_spacing = coord_t(ext_perimeter_spacing * (1 - INSET_OVERLAP_TOLERANCE));
bool has_gap_fill = this->config->gap_infill_speed.value > 0;
bool has_gap_fill = this->config->gap_infill_speed.get_at(get_extruder_index(*print_config, this->config->wall_filament - 1)) > 0;
// QDS: this flow is for smaller external perimeter for small area
coord_t ext_min_spacing_smaller = coord_t(ext_perimeter_spacing * (1 - SMALLER_EXT_INSET_OVERLAP_TOLERANCE));
@@ -1166,10 +1271,30 @@ void PerimeterGenerator::process_classic()
if (loop_number > 0 && ((this->object_config->top_one_wall_type != TopOneWallType::None && this->upper_slices == nullptr) || (this->object_config->only_one_wall_first_layer && layer_id == 0)))
loop_number = 0;
bool counter_circle_compensation = surface.counter_circle_compensation;
std::vector<Point> compensation_holes_centers;
for (int i : surface.holes_circle_compensation) {
Point center = surface.expolygon.holes[i].centroid();
compensation_holes_centers.emplace_back(center);
}
double eps = 1000;
auto is_compensation_hole = [&compensation_holes_centers, &eps](const Polygon &hole) -> bool {
auto iter = std::find_if(compensation_holes_centers.begin(), compensation_holes_centers.end(), [&hole, &eps](const Point &item) {
double distance = std::sqrt(std::pow(hole.centroid().x() - item.x(), 2) + std::pow(hole.centroid().y() - item.y(), 2));
return distance < eps;
});
return iter != compensation_holes_centers.end();
};
ExPolygons last = union_ex(surface.expolygon.simplify_p(surface_simplify_resolution));
if (last.size() != 1)
counter_circle_compensation = false;
ExPolygons gaps;
ExPolygons top_fills;
ExPolygons fill_clip;
std::vector<NodeContour> outwall_paths;
if (loop_number >= 0) {
// In case no perimeters are to be generated, loop_number will equal to -1.
std::vector<PerimeterGeneratorLoops> contours(loop_number+1); // depth => loops
@@ -1237,16 +1362,16 @@ void PerimeterGenerator::process_classic()
coord_t distance = (i == 1) ? ext_perimeter_spacing2 : perimeter_spacing;
//QDS
//offsets = this->config->thin_walls ?
// This path will ensure, that the perimeters do not overfill, as in
// This path will ensure, that the perimeters do not overfill, as in
// prusa3d/Slic3r GH #32, but with the cost of rounding the perimeters
// excessively, creating gaps, which then need to be filled in by the not very
// excessively, creating gaps, which then need to be filled in by the not very
// reliable gap fill algorithm.
// Also the offset2(perimeter, -x, x) may sometimes lead to a perimeter, which is larger than
// the original.
//offset2_ex(last,
// - float(distance + min_spacing / 2. - 1.),
// float(min_spacing / 2. - 1.)) :
// If "detect thin walls" is not enabled, this paths will be entered, which
// If "detect thin walls" is not enabled, this paths will be entered, which
// leads to overflows, as in prusa3d/Slic3r GH #32
//offset_ex(last, - float(distance));
@@ -1284,23 +1409,51 @@ void PerimeterGenerator::process_classic()
// outer contour may overlap with itself.
//FIXME evaluate the overlaps, annotate each point with an overlap depth,
// compensate for the depth of intersection.
contours[i].emplace_back(expolygon.contour, i, true, fuzzify_contours);
contours[i].emplace_back(PerimeterGeneratorLoop(expolygon.contour, i, true, fuzzify_contours, false, counter_circle_compensation));
if (!expolygon.holes.empty()) {
holes[i].reserve(holes[i].size() + expolygon.holes.size());
for (const Polygon& hole : expolygon.holes)
holes[i].emplace_back(hole, i, false, fuzzify_holes);
for (const Polygon &hole : expolygon.holes)
holes[i].emplace_back(hole, i, false, fuzzify_holes, false, is_compensation_hole(hole));
}
}
//QDS: save perimeter loop which use smaller width
if (i == 0) {
//store outer wall
if (print_config->z_direction_outwall_speed_continuous) {
// not loop
for (const ThickPolyline &polyline : thin_walls) {
NodeContour node_contour;
node_contour.is_loop = false;
node_contour.pts = polyline.points;
node_contour.widths.insert(node_contour.widths.end(), polyline.width.begin(), polyline.width.end());
outwall_paths.push_back(node_contour);
}
// loop
for (const Polyline &polyline : to_polylines(offsets_with_smaller_width)) {
NodeContour node_contour;
node_contour.is_loop = true;
node_contour.pts = polyline.points;
node_contour.widths.push_back(scale_(this->smaller_ext_perimeter_flow.width()));
outwall_paths.push_back(node_contour);
}
for (const Polyline &polyline : to_polylines(offsets)) {
NodeContour node_contour;
node_contour.is_loop = true;
node_contour.pts = polyline.points;
node_contour.widths.push_back(scale_(this->ext_perimeter_flow.width()));
outwall_paths.push_back(node_contour);
}
}
for (const ExPolygon& expolygon : offsets_with_smaller_width) {
contours[i].emplace_back(PerimeterGeneratorLoop(expolygon.contour, i, true, fuzzify_contours, true));
contours[i].emplace_back(PerimeterGeneratorLoop(expolygon.contour, i, true, fuzzify_contours, true, counter_circle_compensation));
if (!expolygon.holes.empty()) {
holes[i].reserve(holes[i].size() + expolygon.holes.size());
for (const Polygon& hole : expolygon.holes)
holes[i].emplace_back(PerimeterGeneratorLoop(hole, i, false, fuzzify_contours, true));
holes[i].emplace_back(PerimeterGeneratorLoop(hole, i, false, fuzzify_contours, true, is_compensation_hole(hole)));
}
}
}
@@ -1320,7 +1473,7 @@ void PerimeterGenerator::process_classic()
else
offset_top_surface = 0;
//don't takes into account too thin areas
double min_width_top_surface = (this->object_config->top_area_threshold / 100) * std::max(double(ext_perimeter_spacing / 2 + 10), 1.0 * (double(perimeter_width)));
double min_width_top_surface = (this->object_config->top_area_threshold / 100) * std::max(ext_perimeter_spacing / 2.0, perimeter_width / 2.0);
//QDS: get boungding box of last
BoundingBox last_box = get_extents(last);
@@ -1379,7 +1532,7 @@ void PerimeterGenerator::process_classic()
if (i == loop_number && (! has_gap_fill || this->config->sparse_infill_density.value == 0)) {
// The last run of this loop is executed to collect gaps for gap fill.
// As the gap fill is either disabled or not
// As the gap fill is either disabled or not
break;
}
}
@@ -1443,7 +1596,7 @@ void PerimeterGenerator::process_classic()
// if brim will be printed, reverse the order of perimeters so that
// we continue inwards after having finished the brim
// TODO: add test for perimeter order
bool is_outer_wall_first =
bool is_outer_wall_first =
this->object_config->wall_sequence == WallSequence::OuterInner;
if (is_outer_wall_first ||
//QDS: always print outer wall first when there indeed has brim.
@@ -1471,6 +1624,48 @@ void PerimeterGenerator::process_classic()
}
entities.entities = std::move( entities_reorder);
}
//QDS: add node for loops
if (!outwall_paths.empty() && this->layer_id > 0) {
entities.loop_node_range.first = this->loop_nodes->size();
if (outwall_paths.size() == 1) {
LoopNode node;
node.node_id = this->loop_nodes->size();
node.loop_id = 0;
node.node_contour = outwall_paths.front();
node.bbox = get_extents(node.node_contour.pts);
node.bbox.offset(SCALED_EPSILON);
this->loop_nodes->push_back(node);
} else {
std::vector<bool> matched;
matched.resize(outwall_paths.size(), false);
for (int entity_idx = 0; entity_idx < entities.entities.size(); ++entity_idx) {
//skip inner wall
if(entities.entities[entity_idx]->role() == erPerimeter)
continue;
for (size_t lines_idx = 0; lines_idx < outwall_paths.size(); ++lines_idx) {
if(matched[lines_idx])
continue;
if (entities.entities[entity_idx]->first_point().is_in_lines(outwall_paths[lines_idx].pts)) {
matched[lines_idx] = true;
LoopNode node;
node.node_id = this->loop_nodes->size();
node.loop_id = entity_idx;
node.node_contour = outwall_paths[lines_idx];
node.bbox = get_extents(node.node_contour.pts);
node.bbox.offset(SCALED_EPSILON);
this->loop_nodes->push_back(node);
break;
}
}
}
}
entities.loop_node_range.second = this->loop_nodes->size();
}
// append perimeters for this slice as a collection
if (! entities.empty())
this->loops->append(entities);
@@ -1478,7 +1673,7 @@ void PerimeterGenerator::process_classic()
// fill gaps
if (! gaps.empty()) {
// collapse
// collapse
double min = 0.2 * perimeter_width * (1 - INSET_OVERLAP_TOLERANCE);
double max = 2. * perimeter_spacing;
ExPolygons gaps_ex = diff_ex(
@@ -1532,7 +1727,7 @@ void PerimeterGenerator::process_classic()
// we offset by half the perimeter spacing (to get to the actual infill boundary)
// and then we offset back and forth by half the infill spacing to only consider the
// non-collapsing regions
coord_t inset =
coord_t inset =
(loop_number < 0) ? 0 :
(loop_number == 0) ?
// one loop
@@ -1624,6 +1819,7 @@ void PerimeterGenerator::process_arachne()
{
// other perimeters
m_mm3_per_mm = this->perimeter_flow.mm3_per_mm();
coord_t perimeter_width = this->perimeter_flow.scaled_width();
coord_t perimeter_spacing = this->perimeter_flow.scaled_spacing();
// external perimeters
@@ -1652,103 +1848,130 @@ void PerimeterGenerator::process_arachne()
double surface_simplify_resolution = (print_config->enable_arc_fitting && this->config->fuzzy_skin == FuzzySkinType::None) ? 0.2 * m_scaled_resolution : m_scaled_resolution;
// we need to process each island separately because we might have different
// extra perimeters for each one
for (const Surface& surface : this->slices->surfaces) {
// detect how many perimeters must be generated for this island
int loop_number = this->config->wall_loops + surface.extra_perimeters - 1; // 0-indexed loops
if (loop_number > 0 && this->object_config->only_one_wall_first_layer && layer_id == 0 ||
(this->object_config->top_one_wall_type == TopOneWallType::Topmost && this->upper_slices == nullptr))
loop_number = 0;
bool apply_circle_compensation = true;
ExPolygons last = offset_ex(surface.expolygon.simplify_p(surface_simplify_resolution), -float(ext_perimeter_width / 2. - ext_perimeter_spacing / 2.));
Polygons last_p = to_polygons(last);
int new_size = std::accumulate(last.begin(), last.end(), 0, [](int prev, const ExPolygon& expoly) { return prev + expoly.num_contours(); });
if (last.size() != 1 || new_size != surface.expolygon.num_contours())
apply_circle_compensation = false;
double min_nozzle_diameter = *std::min_element(print_config->nozzle_diameter.values.begin(), print_config->nozzle_diameter.values.end());
Arachne::WallToolPathsParams input_params;
{
if (const auto& min_feature_size_opt = object_config->min_feature_size)
input_params.min_feature_size = min_feature_size_opt.value * 0.01 * min_nozzle_diameter;
std::vector<int> circle_poly_indices;
Polygons last_p;
if (apply_circle_compensation)
last_p = to_polygons_with_flag(last.front(), surface.counter_circle_compensation, surface.holes_circle_compensation, circle_poly_indices);
else
last_p = to_polygons(last);
if (const auto& min_bead_width_opt = object_config->min_bead_width)
input_params.min_bead_width = min_bead_width_opt.value * 0.01 * min_nozzle_diameter;
std::vector<Arachne::VariableWidthLines> total_perimeters;
ExPolygons infill_contour;
if (const auto& wall_transition_filter_deviation_opt = object_config->wall_transition_filter_deviation)
input_params.wall_transition_filter_deviation = wall_transition_filter_deviation_opt.value * 0.01 * min_nozzle_diameter;
if (loop_number >= 0) {
bool generate_one_wall_by_first_layer = this->object_config->only_one_wall_first_layer && layer_id == 0;
bool generate_one_wall_by_top_most = this->object_config->top_one_wall_type != TopOneWallType::None && this->upper_slices == nullptr;
bool generate_one_wall_by_top = this->object_config->top_one_wall_type == TopOneWallType::Alltop && this->upper_slices != nullptr;
if (const auto& wall_transition_length_opt = object_config->wall_transition_length)
input_params.wall_transition_length = wall_transition_length_opt.value * 0.01 * min_nozzle_diameter;
bool is_one_wall = loop_number == 0 || generate_one_wall_by_first_layer || generate_one_wall_by_top_most;
// whether to seperate the generatation of wall into two parts,first generate outer wall,then generate the remaining wall
bool seperate_wall_generation = !is_one_wall && generate_one_wall_by_top;
input_params.wall_transition_angle = this->object_config->wall_transition_angle.value;
input_params.wall_distribution_count = this->object_config->wall_distribution_count.value;
}
double min_nozzle_diameter = *std::min_element(print_config->nozzle_diameter.values.begin(), print_config->nozzle_diameter.values.end());
Arachne::WallToolPathsParams input_params;
{
if (const auto& min_feature_size_opt = object_config->min_feature_size)
input_params.min_feature_size = min_feature_size_opt.value * 0.01 * min_nozzle_diameter;
int remain_loops = -1;
if (loop_number > 0 && this->object_config->top_one_wall_type == TopOneWallType::Alltop) {
if (this->upper_slices != nullptr)
remain_loops = loop_number - 1;
if (const auto& min_bead_width_opt = object_config->min_bead_width)
input_params.min_bead_width = min_bead_width_opt.value * 0.01 * min_nozzle_diameter;
loop_number = 0;
}
if (const auto& wall_transition_filter_deviation_opt = object_config->wall_transition_filter_deviation)
input_params.wall_transition_filter_deviation = wall_transition_filter_deviation_opt.value * 0.01 * min_nozzle_diameter;
Arachne::WallToolPaths wallToolPaths(last_p, ext_perimeter_spacing, perimeter_spacing, coord_t(loop_number + 1), 0, layer_height, input_params);
std::vector<Arachne::VariableWidthLines> perimeters = wallToolPaths.getToolPaths();
loop_number = int(perimeters.size()) - 1;
if (const auto& wall_transition_length_opt = object_config->wall_transition_length)
input_params.wall_transition_length = wall_transition_length_opt.value * 0.01 * min_nozzle_diameter;
//QDS: top one wall for arachne
ExPolygons infill_contour = union_ex(wallToolPaths.getInnerContour());
ExPolygons inner_infill_contour;
if( remain_loops >= 0 )
{
ExPolygons the_layer_surface = infill_contour;
// QDS: get boungding box of last
BoundingBox infill_contour_box = get_extents(infill_contour);
infill_contour_box.offset(SCALED_EPSILON);
// QDS: get the Polygons upper the polygon this layer
Polygons upper_polygons_series_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(*this->upper_slices, infill_contour_box);
infill_contour = diff_ex(infill_contour, upper_polygons_series_clipped);
coord_t perimeter_width = this->perimeter_flow.scaled_width();
//QDS: add bridge area
if (this->lower_slices != nullptr) {
BoundingBox infill_contour_box = get_extents(infill_contour);
infill_contour_box.offset(SCALED_EPSILON);
// QDS: get the Polygons below the polygon this layer
Polygons lower_polygons_series_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(*this->lower_slices, infill_contour_box);
ExPolygons bridge_area = offset_ex(diff_ex(infill_contour, lower_polygons_series_clipped), std::max(ext_perimeter_spacing, perimeter_width));
infill_contour = diff_ex(infill_contour, bridge_area);
input_params.wall_transition_angle = this->object_config->wall_transition_angle.value;
input_params.wall_distribution_count = this->object_config->wall_distribution_count.value;
}
//QDS: filter small area and extend top surface a bit to hide the wall line
double min_width_top_surface = (this->object_config->top_area_threshold / 100) * std::max(double(ext_perimeter_spacing / 4 + 10), double(perimeter_width / 4));
infill_contour = offset2_ex(infill_contour, -min_width_top_surface, min_width_top_surface + perimeter_width);
//QDS: get the inner surface that not export to top
ExPolygons surface_not_export_to_top = diff_ex(the_layer_surface, infill_contour);
// these variables are only valid if need to seperate wall generation
ExPolygons top_expolys_by_one_wall;
std::vector<Arachne::VariableWidthLines> first_perimeters;
ExPolygons infill_contour_by_one_wall;
//QDS: get real top surface
infill_contour = intersection_ex(infill_contour, the_layer_surface);
Polygons surface_not_export_to_top_p = to_polygons(surface_not_export_to_top);
Arachne::WallToolPaths innerWallToolPaths(surface_not_export_to_top_p, perimeter_spacing, perimeter_spacing, coord_t(remain_loops + 1), 0, layer_height, input_params);
// do detail check whether to enable one wall
if (seperate_wall_generation) {
Arachne::WallToolPaths one_wall_paths(last_p, ext_perimeter_spacing, perimeter_spacing, 1, 0, layer_height, input_params);
if (apply_circle_compensation)
one_wall_paths.EnableHoleCompensation(true, circle_poly_indices);
std::vector<Arachne::VariableWidthLines> perimeters_inner = innerWallToolPaths.getToolPaths();
remain_loops = int(perimeters_inner.size()) - 1;
first_perimeters = one_wall_paths.getToolPaths();
infill_contour_by_one_wall = union_ex(one_wall_paths.getInnerContour());
//QDS: set wall's perporsity
if (!perimeters.empty()) {
for (int perimeter_idx = 0; perimeter_idx < perimeters_inner.size(); perimeter_idx++) {
if (perimeters_inner[perimeter_idx].empty()) continue;
BoundingBox infill_bbox = get_extents(infill_contour_by_one_wall);
infill_bbox.offset(EPSILON);
for (Arachne::ExtrusionLine &wall : perimeters_inner[perimeter_idx]) {
// QDS: 0 means outer wall
wall.inset_idx++;
Polygons upper_polygons_clipped;
if (this->upper_slices)
upper_polygons_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(*this->upper_slices, infill_bbox);
top_expolys_by_one_wall = diff_ex(infill_contour_by_one_wall, upper_polygons_clipped);
Polygons lower_polygons_clipped;
if (this->lower_slices)
lower_polygons_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(*this->lower_slices, infill_bbox);
ExPolygons bottom_expolys = diff_ex(top_expolys_by_one_wall, lower_polygons_clipped);
top_expolys_by_one_wall = diff_ex(top_expolys_by_one_wall, bottom_expolys);
seperate_wall_generation = should_enable_top_one_wall(last, top_expolys_by_one_wall);
}
if (seperate_wall_generation) {
// only generate one wall around top areas
// keep the first generated wall
total_perimeters = first_perimeters;
infill_contour = union_ex(infill_contour_by_one_wall);
// deal with remaining walls to be generated
if (loop_number > 0) {
last = diff_ex(infill_contour_by_one_wall, top_expolys_by_one_wall);
last_p = to_polygons(last); // disable contour compensation in remaining walls
Arachne::WallToolPaths paths_new(last_p, perimeter_spacing, perimeter_spacing, loop_number, 0, layer_height, input_params);
auto new_perimeters = paths_new.getToolPaths();
for (auto& perimeters : new_perimeters) {
if (!perimeters.empty()) {
for (auto& p : perimeters) {
p.inset_idx += 1;
}
total_perimeters.emplace_back(std::move(perimeters));
}
}
infill_contour = union_ex(union_ex(paths_new.getInnerContour()), top_expolys_by_one_wall);
infill_contour = intersection_ex(infill_contour, infill_contour_by_one_wall);
}
}
perimeters.insert(perimeters.end(), perimeters_inner.begin(), perimeters_inner.end());
inner_infill_contour = union_ex(innerWallToolPaths.getInnerContour());
else {
if (is_one_wall) {
// plan wall width as one wall
Arachne::WallToolPaths one_wall_paths(last_p, ext_perimeter_spacing, perimeter_spacing, 1, 0, layer_height, input_params);
if (apply_circle_compensation)
one_wall_paths.EnableHoleCompensation(true, circle_poly_indices);
total_perimeters = one_wall_paths.getToolPaths();
infill_contour = union_ex(one_wall_paths.getInnerContour());
}
else {
// plan wall width as noraml
Arachne::WallToolPaths normal_paths(last_p, ext_perimeter_spacing, perimeter_spacing, loop_number + 1, 0, layer_height, input_params);
if (apply_circle_compensation)
normal_paths.EnableHoleCompensation(true, circle_poly_indices);
total_perimeters = normal_paths.getToolPaths();
infill_contour = union_ex(normal_paths.getInnerContour());
}
}
}
else {
infill_contour = last;
}
#ifdef ARACHNE_DEBUG
@@ -1761,15 +1984,15 @@ void PerimeterGenerator::process_arachne()
// All closed ExtrusionLine should have the same the first and the last point.
// But in rare cases, Arachne produce ExtrusionLine marked as closed but without
// equal the first and the last point.
assert([&perimeters = std::as_const(perimeters)]() -> bool {
for (const Arachne::VariableWidthLines& perimeter : perimeters)
assert([&total_perimeters = std::as_const(total_perimeters)]() -> bool {
for (const Arachne::VariableWidthLines& perimeter : total_perimeters)
for (const Arachne::ExtrusionLine& el : perimeter)
if (el.is_closed && el.junctions.front().p != el.junctions.back().p)
return false;
return true;
}());
int start_perimeter = int(perimeters.size()) - 1;
int start_perimeter = int(total_perimeters.size()) - 1;
int end_perimeter = -1;
int direction = -1;
@@ -1777,15 +2000,15 @@ void PerimeterGenerator::process_arachne()
this->object_config->wall_sequence == WallSequence::OuterInner || this->object_config->wall_sequence == WallSequence::InnerOuterInner;
if (is_outer_wall_first) {
start_perimeter = 0;
end_perimeter = int(perimeters.size());
end_perimeter = int(total_perimeters.size());
direction = 1;
}
std::vector<Arachne::ExtrusionLine*> all_extrusions;
for (int perimeter_idx = start_perimeter; perimeter_idx != end_perimeter; perimeter_idx += direction) {
if (perimeters[perimeter_idx].empty())
if (total_perimeters[perimeter_idx].empty())
continue;
for (Arachne::ExtrusionLine& wall : perimeters[perimeter_idx])
for (Arachne::ExtrusionLine& wall : total_perimeters[perimeter_idx])
all_extrusions.emplace_back(&wall);
}
@@ -1947,20 +2170,41 @@ void PerimeterGenerator::process_arachne()
if (ExtrusionEntityCollection extrusion_coll = traverse_extrusions(*this, ordered_extrusions); !extrusion_coll.empty())
this->loops->append(extrusion_coll);
const coord_t spacing = (perimeters.size() == 1) ? ext_perimeter_spacing2 : perimeter_spacing;
const coord_t spacing = (total_perimeters.size() == 1) ? ext_perimeter_spacing2 : perimeter_spacing;
// collapse too narrow infill areas
const auto min_perimeter_infill_spacing = coord_t(solid_infill_spacing * (1. - INSET_OVERLAP_TOLERANCE));
// append infill areas to fill_surfaces
add_infill_contour_for_arachne(infill_contour, loop_number, ext_perimeter_spacing, perimeter_spacing, min_perimeter_infill_spacing, spacing, false);
//QDS: add infill_contour of top one wall part
if( !inner_infill_contour.empty() )
add_infill_contour_for_arachne(inner_infill_contour, remain_loops, ext_perimeter_spacing, perimeter_spacing, min_perimeter_infill_spacing, spacing, true);
}
}
// expand the top expoly and determine whether to enable top one wall feature
bool PerimeterGenerator::should_enable_top_one_wall(const ExPolygons& original_expolys, ExPolygons& top)
{
coord_t perimeter_width = this->perimeter_flow.scaled_width();
coord_t ext_perimeter_spacing = this->ext_perimeter_flow.scaled_spacing();
auto get_expolygs_area = [](const ExPolygons& expolys)->double{
return std::accumulate(expolys.begin(), expolys.end(), (double)(0), [](double val, const ExPolygon& expoly) {
return val + expoly.area();
});
};
//QDS: filter small area and extend top surface a bit to hide the wall line
double min_width_top_surface = (this->object_config->top_area_threshold / 100) * std::max(ext_perimeter_spacing / 2.0, perimeter_width / 2.0);
auto shrunk_top = offset_ex(top, - min_width_top_surface);
double shrunk_area = get_expolygs_area(shrunk_top);
double original_area = get_expolygs_area(original_expolys);
if (shrunk_area / (original_area + EPSILON) < 0.1 || original_area < scale_(1)*scale_(1))
top.clear();
else
top = offset_ex(shrunk_top, min_width_top_surface + perimeter_width);
return !top.empty();
}
bool PerimeterGeneratorLoop::is_internal_contour() const
{
// An internal contour is a contour containing no other contours