mirror of
https://git.mirrors.martin98.com/https://github.com/slic3r/Slic3r.git
synced 2025-07-13 09:41:47 +08:00
2212 lines
128 KiB
C++
2212 lines
128 KiB
C++
#include "PerimeterGenerator.hpp"
|
|
|
|
#include "BridgeDetector.hpp"
|
|
#include "ClipperUtils.hpp"
|
|
#include "ExtrusionEntityCollection.hpp"
|
|
#include "Geometry.hpp"
|
|
#include "ShortestPath.hpp"
|
|
#include <cmath>
|
|
#include <cassert>
|
|
#include <vector>
|
|
|
|
#include "BoundingBox.hpp"
|
|
#include "ExPolygon.hpp"
|
|
#include "Geometry.hpp"
|
|
#include "Milling/MillingPostProcess.hpp"
|
|
#include "Polygon.hpp"
|
|
#include "Line.hpp"
|
|
#include "ClipperUtils.hpp"
|
|
#include "SVG.hpp"
|
|
#include "polypartition.h"
|
|
#include "poly2tri/poly2tri.h"
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
#include <list>
|
|
|
|
namespace Slic3r {
|
|
|
|
PerimeterGeneratorLoops get_all_Childs(PerimeterGeneratorLoop loop) {
|
|
PerimeterGeneratorLoops ret;
|
|
for (PerimeterGeneratorLoop &child : loop.children) {
|
|
ret.push_back(child);
|
|
PerimeterGeneratorLoops vals = get_all_Childs(child);
|
|
ret.insert(ret.end(), vals.begin(), vals.end());
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void PerimeterGenerator::process()
|
|
{
|
|
//set spacing
|
|
this->perimeter_flow.spacing_ratio = std::min(this->perimeter_flow.spacing_ratio, (float)this->config->perimeter_overlap.get_abs_value(1));
|
|
this->ext_perimeter_flow.spacing_ratio = std::min(this->ext_perimeter_flow.spacing_ratio, (float)this->config->external_perimeter_overlap.get_abs_value(1));
|
|
|
|
// other perimeters
|
|
this->_mm3_per_mm = this->perimeter_flow.mm3_per_mm();
|
|
coord_t perimeter_width = this->perimeter_flow.scaled_width();
|
|
//spacing between internal perimeters
|
|
coord_t perimeter_spacing = this->perimeter_flow.scaled_spacing();
|
|
|
|
// external perimeters
|
|
this->_ext_mm3_per_mm = this->ext_perimeter_flow.mm3_per_mm();
|
|
coord_t ext_perimeter_width = this->ext_perimeter_flow.scaled_width();
|
|
//spacing between two external perimeter (where you don't have the space to add other loops)
|
|
coord_t ext_perimeter_spacing = this->ext_perimeter_flow.scaled_spacing();
|
|
//spacing between external perimeter and the second
|
|
coord_t ext_perimeter_spacing2 = this->ext_perimeter_flow.scaled_spacing(this->perimeter_flow);
|
|
|
|
// overhang perimeters
|
|
this->_mm3_per_mm_overhang = this->overhang_flow.mm3_per_mm();
|
|
|
|
//gap fill
|
|
coord_t gap_fill_spacing = this->config->gap_fill_overlap.get_abs_value(this->perimeter_flow.scaled_spacing())
|
|
+ this->perimeter_flow.scaled_width() * (100 - this->config->gap_fill_overlap.value) / 100.;
|
|
|
|
// solid infill
|
|
coord_t solid_infill_spacing = this->solid_infill_flow.scaled_spacing();
|
|
|
|
//infill / perimeter
|
|
coord_t infill_peri_overlap = (coord_t)scale_(this->config->get_abs_value("infill_overlap", unscale<coordf_t>(perimeter_spacing + solid_infill_spacing) / 2));
|
|
// infill gap to add vs perimeter (useful if using perimeter bonding)
|
|
coord_t infill_gap = 0;
|
|
|
|
bool round_peri = this->config->perimeter_round_corners.value;
|
|
coord_t min_round_spacing = perimeter_width / 10;
|
|
|
|
// nozzle diameter
|
|
const double nozzle_diameter = this->print_config->nozzle_diameter.get_at(this->config->perimeter_extruder - 1);
|
|
|
|
// perimeter conding set.
|
|
if (this->perimeter_flow.spacing_ratio == 1
|
|
&& this->ext_perimeter_flow.spacing_ratio == 1
|
|
&& this->config->external_perimeters_first
|
|
&& this->object_config->perimeter_bonding.value > 0) {
|
|
infill_gap = (1 - this->object_config->perimeter_bonding.get_abs_value(1)) * ext_perimeter_spacing;
|
|
ext_perimeter_spacing2 -= infill_gap;
|
|
}
|
|
|
|
// 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
|
|
// some squishing might work. Loops are still spaced by the entire
|
|
// flow spacing; this only applies to collapsing parts.
|
|
// For ext_min_spacing we use the ext_perimeter_spacing calculated for two adjacent
|
|
// external loops (which is the correct way) instead of using ext_perimeter_spacing2
|
|
// which is the spacing between external and internal, which is not correct
|
|
// and would make the collapsing (thus the details resolution) dependent on
|
|
// internal flow which is unrelated. <- i don't undertand, so revert to ext_perimeter_spacing2
|
|
coord_t min_spacing = (coord_t)( perimeter_spacing * (1 - 0.05/*INSET_OVERLAP_TOLERANCE*/) );
|
|
coord_t ext_min_spacing = (coord_t)( ext_perimeter_spacing2 * (1 - 0.05/*INSET_OVERLAP_TOLERANCE*/) );
|
|
|
|
// prepare grown lower layer slices for overhang detection
|
|
if (this->lower_slices != NULL && (this->config->overhangs_width.value > 0 || this->config->overhangs_width_speed.value > 0)) {
|
|
// We consider overhang any part where the entire nozzle diameter is not supported by the
|
|
// lower layer, so we take lower slices and offset them by overhangs_width of the nozzle diameter used
|
|
// in the current layer
|
|
|
|
//we use a range to avoid threshold issues.
|
|
coord_t overhangs_width_flow = scale_(config->overhangs_width.get_abs_value(nozzle_diameter));
|
|
coord_t overhangs_width_speed = scale_(config->overhangs_width_speed.get_abs_value(nozzle_diameter));
|
|
coord_t min_feature = std::min(overhangs_width_flow, overhangs_width_speed) / 2;
|
|
coord_t overhangs_width_flow_90 = coord_t(overhangs_width_flow * 0.99);
|
|
coord_t overhangs_width_flow_110 = coord_t(overhangs_width_flow * 1.15);
|
|
coord_t overhangs_width_speed_90 = coord_t(overhangs_width_speed * 0.99);
|
|
coord_t overhangs_width_speed_110 = coord_t(overhangs_width_speed * 1.15);
|
|
|
|
//flow offset should be greater than speed offset because the flow apply also the speed.
|
|
//check if overhangs_width_speed is low enough to be relevant (if flow is activated)
|
|
if (overhangs_width_flow > 0 && overhangs_width_speed + nozzle_diameter * 0.01 > overhangs_width_flow) {
|
|
overhangs_width_speed = 0;
|
|
overhangs_width_speed_90 = 0;
|
|
overhangs_width_speed_110 = 0;
|
|
}
|
|
if (overhangs_width_flow > 0) {
|
|
if (overhangs_width_flow_90 < overhangs_width_speed_110) {
|
|
overhangs_width_speed_110 = overhangs_width_flow_90 = (overhangs_width_flow + overhangs_width_speed) / 2;
|
|
}
|
|
}
|
|
|
|
if (overhangs_width_speed > 0 || overhangs_width_flow > 0) {
|
|
ExPolygons simplified;
|
|
//simplify the lower slices if too high (means low number) resolution (we can be very aggressive here)
|
|
if (this->print_config->resolution < min_feature / 2) {
|
|
for (const ExPolygon& expoly : *lower_slices) {
|
|
expoly.simplify(min_feature, &simplified);
|
|
}
|
|
}
|
|
|
|
if (overhangs_width_speed > 0) {
|
|
this->_lower_slices_bridge_speed_small = offset((simplified.empty() ? *this->lower_slices : simplified), (coordf_t)overhangs_width_speed_90 - (coordf_t)(ext_perimeter_width / 2));
|
|
this->_lower_slices_bridge_speed_big = offset((simplified.empty() ? *this->lower_slices : simplified), (coordf_t)overhangs_width_speed_110 - (coordf_t)(ext_perimeter_width / 2));
|
|
}
|
|
if (overhangs_width_flow > 0) {
|
|
if (overhangs_width_speed_110 == overhangs_width_flow_90)
|
|
this->_lower_slices_bridge_flow_small = this->_lower_slices_bridge_speed_big;
|
|
else
|
|
this->_lower_slices_bridge_flow_small = offset((simplified.empty() ? *this->lower_slices : simplified), (coordf_t)overhangs_width_flow_90 - (coordf_t)(ext_perimeter_width / 2));
|
|
this->_lower_slices_bridge_flow_big = offset((simplified.empty() ? *this->lower_slices : simplified), (coordf_t)overhangs_width_flow_110 - (coordf_t)(ext_perimeter_width / 2));
|
|
}
|
|
}
|
|
}
|
|
|
|
// have to grown the perimeters if mill post-process
|
|
MillingPostProcess miller(this->slices, this->lower_slices, config, object_config, print_config);
|
|
bool have_to_grow_for_miller = miller.can_be_milled(layer) && config->milling_extra_size.get_abs_value(1) > 0;
|
|
ExPolygons unmillable;
|
|
coord_t mill_extra_size = 0;
|
|
if (have_to_grow_for_miller) {
|
|
unmillable = miller.get_unmillable_areas(layer);
|
|
double spacing_vs_width = ext_perimeter_flow.width - ext_perimeter_flow.spacing();
|
|
mill_extra_size = scale_(config->milling_extra_size.get_abs_value(spacing_vs_width));
|
|
have_to_grow_for_miller = mill_extra_size > SCALED_EPSILON;
|
|
}
|
|
|
|
|
|
// we need to process each island separately because we might have different
|
|
// extra perimeters for each one
|
|
int surface_idx = 0;
|
|
Surfaces all_surfaces = this->slices->surfaces;
|
|
|
|
//store surface for bridge infill to avoid unsupported perimeters (but the first one, this one is always good)
|
|
if (this->config->no_perimeter_unsupported_algo != npuaNone
|
|
&& this->lower_slices != NULL && !this->lower_slices->empty()) {
|
|
|
|
for (surface_idx = 0; surface_idx < all_surfaces.size(); surface_idx++) {
|
|
Surface *surface = &all_surfaces[surface_idx];
|
|
ExPolygons last = union_ex(surface->expolygon);
|
|
//compute our unsupported surface
|
|
ExPolygons unsupported = diff_ex(last, *this->lower_slices, true);
|
|
if (!unsupported.empty()) {
|
|
//remove small overhangs
|
|
ExPolygons unsupported_filtered = offset2_ex(unsupported, double(-perimeter_spacing), double(perimeter_spacing));
|
|
if (!unsupported_filtered.empty()) {
|
|
//to_draw.insert(to_draw.end(), last.begin(), last.end());
|
|
//extract only the useful part of the lower layer. The safety offset is really needed here.
|
|
ExPolygons support = diff_ex(last, unsupported, true);
|
|
if (!unsupported.empty()) {
|
|
//only consider the part that can be bridged (really, by the bridge algorithm)
|
|
//first, separate into islands (ie, each ExPlolygon)
|
|
int numploy = 0;
|
|
//only consider the bottom layer that intersect unsupported, to be sure it's only on our island.
|
|
ExPolygonCollection lower_island(support);
|
|
BridgeDetector detector{ unsupported_filtered,
|
|
lower_island.expolygons,
|
|
perimeter_spacing };
|
|
if (detector.detect_angle(Geometry::deg2rad(this->config->bridge_angle.value))) {
|
|
ExPolygons bridgeable = union_ex(detector.coverage(-1, true));
|
|
if (!bridgeable.empty()) {
|
|
//check if we get everything or just the bridgeable area
|
|
if (this->config->no_perimeter_unsupported_algo.value == npuaNoPeri || this->config->no_perimeter_unsupported_algo.value == npuaFilled) {
|
|
//we bridge everything, even the not-bridgeable bits
|
|
for (size_t i = 0; i < unsupported_filtered.size();) {
|
|
ExPolygon &poly_unsupp = *(unsupported_filtered.begin() + i);
|
|
Polygons contour_simplified = poly_unsupp.contour.simplify(perimeter_spacing);
|
|
ExPolygon poly_unsupp_bigger = poly_unsupp;
|
|
Polygons contour_bigger = offset(poly_unsupp_bigger.contour, perimeter_spacing);
|
|
if (contour_bigger.size() == 1) poly_unsupp_bigger.contour = contour_bigger[0];
|
|
|
|
//check convex, has some bridge, not overhang
|
|
if (contour_simplified.size() == 1 && contour_bigger.size() == 1 && contour_simplified[0].concave_points().size() == 0
|
|
&& intersection_ex(bridgeable, { poly_unsupp }).size() > 0
|
|
&& diff_ex({ poly_unsupp_bigger }, last, true).size() == 0) {
|
|
//ok, keep it
|
|
i++;
|
|
} else {
|
|
unsupported_filtered.erase(unsupported_filtered.begin() + i);
|
|
}
|
|
}
|
|
unsupported_filtered = intersection_ex(last,
|
|
offset2_ex(unsupported_filtered, double(-perimeter_spacing / 2), double(perimeter_spacing * 3 / 2)));
|
|
if (this->config->no_perimeter_unsupported_algo.value == npuaFilled) {
|
|
for (ExPolygon &expol : unsupported_filtered) {
|
|
//check if the holes won't be covered by the upper layer
|
|
//TODO: if we want to do that, we must modify the geometry before making perimeters.
|
|
//if (this->upper_slices != nullptr && !this->upper_slices->expolygons.empty()) {
|
|
// for (Polygon &poly : expol.holes) poly.make_counter_clockwise();
|
|
// float perimeterwidth = this->config->perimeters == 0 ? 0 : (this->ext_perimeter_flow.scaled_width() + (this->config->perimeters - 1) + this->perimeter_flow.scaled_spacing());
|
|
// std::cout << "test upper slices with perimeterwidth=" << perimeterwidth << "=>" << offset_ex(this->upper_slices->expolygons, -perimeterwidth).size();
|
|
// if (intersection(Polygons() = { expol.holes }, to_polygons(offset_ex(this->upper_slices->expolygons, -this->ext_perimeter_flow.scaled_width() / 2))).empty()) {
|
|
// std::cout << " EMPTY§";
|
|
// expol.holes.clear();
|
|
// } else {
|
|
// }
|
|
// std::cout << "\n";
|
|
//} else {
|
|
expol.holes.clear();
|
|
//}
|
|
|
|
//detect inside volume
|
|
for (size_t surface_idx_other = 0; surface_idx_other < all_surfaces.size(); surface_idx_other++) {
|
|
if (surface_idx == surface_idx_other) continue;
|
|
if (intersection_ex(ExPolygons() = { expol }, ExPolygons() = { all_surfaces[surface_idx_other].expolygon }).size() > 0) {
|
|
//this means that other_surf was inside an expol holes
|
|
//as we removed them, we need to add a new one
|
|
ExPolygons new_poly = offset2_ex(all_surfaces[surface_idx_other].expolygon, double(-perimeter_spacing * 2), double(perimeter_spacing));
|
|
if (new_poly.size() == 1) {
|
|
all_surfaces[surface_idx_other].expolygon = new_poly[0];
|
|
expol.holes.push_back(new_poly[0].contour);
|
|
expol.holes.back().make_clockwise();
|
|
} else {
|
|
for (size_t idx = 0; idx < new_poly.size(); idx++) {
|
|
Surface new_surf = all_surfaces[surface_idx_other];
|
|
new_surf.expolygon = new_poly[idx];
|
|
all_surfaces.push_back(new_surf);
|
|
expol.holes.push_back(new_poly[idx].contour);
|
|
expol.holes.back().make_clockwise();
|
|
}
|
|
all_surfaces.erase(all_surfaces.begin() + surface_idx_other);
|
|
if (surface_idx_other < surface_idx) {
|
|
surface_idx--;
|
|
surface = &all_surfaces[surface_idx];
|
|
}
|
|
surface_idx_other--;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
//TODO: add other polys as holes inside this one (-margin)
|
|
} else if (this->config->no_perimeter_unsupported_algo.value == npuaBridgesOverhangs || this->config->no_perimeter_unsupported_algo.value == npuaBridges){
|
|
//simplify to avoid most of artefacts from printing lines.
|
|
ExPolygons bridgeable_simplified;
|
|
for (ExPolygon &poly : bridgeable) {
|
|
poly.simplify(perimeter_spacing, &bridgeable_simplified);
|
|
}
|
|
bridgeable_simplified = offset2_ex(bridgeable_simplified, -ext_perimeter_width, ext_perimeter_width);
|
|
//bridgeable_simplified = intersection_ex(bridgeable_simplified, unsupported_filtered);
|
|
//offset by perimeter spacing because the simplify may have reduced it a bit.
|
|
//it's not dangerous as it will be intersected by 'unsupported' later
|
|
//FIXME: add overlap in this->fill_surfaces->append
|
|
//FIXME: it overlap inside unsuppported not-bridgeable area!
|
|
|
|
//bridgeable_simplified = offset2_ex(bridgeable_simplified, (double)-perimeter_spacing, (double)perimeter_spacing * 2);
|
|
//ExPolygons unbridgeable = offset_ex(diff_ex(unsupported, bridgeable_simplified), perimeter_spacing * 3 / 2);
|
|
//ExPolygons unbridgeable = intersection_ex(unsupported, diff_ex(unsupported_filtered, offset_ex(bridgeable_simplified, ext_perimeter_width / 2)));
|
|
//unbridgeable = offset2_ex(unbridgeable, -ext_perimeter_width, ext_perimeter_width);
|
|
|
|
|
|
if (this->config->no_perimeter_unsupported_algo.value == npuaBridges) {
|
|
ExPolygons unbridgeable = unsupported_filtered;
|
|
for (ExPolygon &expol : unbridgeable)
|
|
expol.holes.clear();
|
|
unbridgeable = diff_ex(unbridgeable, bridgeable_simplified);
|
|
unbridgeable = offset2_ex(unbridgeable, -ext_perimeter_width*2, ext_perimeter_width*2);
|
|
ExPolygons bridges_temp = intersection_ex(last, diff_ex(unsupported_filtered, unbridgeable));
|
|
//remove the overhangs section from the surface polygons
|
|
ExPolygons reference = last;
|
|
last = diff_ex(last, unsupported_filtered);
|
|
//ExPolygons no_bridge = diff_ex(offset_ex(unbridgeable, ext_perimeter_width * 3 / 2), last);
|
|
//bridges_temp = diff_ex(bridges_temp, no_bridge);
|
|
unsupported_filtered = diff_ex(offset_ex(bridges_temp, ext_perimeter_width * 3 / 2), offset_ex(unbridgeable, ext_perimeter_width * 2, jtSquare));
|
|
unsupported_filtered = intersection_ex(unsupported_filtered, reference);
|
|
} else {
|
|
ExPolygons unbridgeable = intersection_ex(unsupported, diff_ex(unsupported_filtered, offset_ex(bridgeable_simplified, ext_perimeter_width / 2)));
|
|
unbridgeable = offset2_ex(unbridgeable, -ext_perimeter_width, ext_perimeter_width);
|
|
unsupported_filtered = unbridgeable;
|
|
|
|
////put the bridge area inside the unsupported_filtered variable
|
|
//unsupported_filtered = intersection_ex(last,
|
|
// diff_ex(
|
|
// offset_ex(bridgeable_simplified, (double)perimeter_spacing / 2),
|
|
// unbridgeable
|
|
// )
|
|
// );
|
|
}
|
|
}
|
|
} else {
|
|
unsupported_filtered.clear();
|
|
}
|
|
} else {
|
|
unsupported_filtered.clear();
|
|
}
|
|
}
|
|
|
|
if (!unsupported_filtered.empty()) {
|
|
|
|
//add this directly to the infill list.
|
|
// this will avoid to throw wrong offsets into a good polygons
|
|
this->fill_surfaces->append(
|
|
unsupported_filtered,
|
|
stPosInternal | stDensSparse);
|
|
|
|
// store the results
|
|
last = diff_ex(last, unsupported_filtered, true);
|
|
//remove "thin air" polygons (note: it assumes that all polygons below will be extruded)
|
|
for (int i = 0; i < last.size(); i++) {
|
|
if (intersection_ex(support, ExPolygons() = { last[i] }).empty()) {
|
|
this->fill_surfaces->append(
|
|
ExPolygons() = { last[i] },
|
|
stPosInternal | stDensSparse);
|
|
last.erase(last.begin() + i);
|
|
i--;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (last.size() == 0) {
|
|
all_surfaces.erase(all_surfaces.begin() + surface_idx);
|
|
surface_idx--;
|
|
} else {
|
|
surface->expolygon = last[0];
|
|
for (size_t idx = 1; idx < last.size(); idx++) {
|
|
all_surfaces.emplace_back(*surface, last[idx]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
surface_idx = 0;
|
|
const int extra_odd_perimeter = (config->extra_perimeters_odd_layers && layer->id() % 2 == 1 ? 1:0);
|
|
for (const Surface &surface : all_surfaces) {
|
|
// detect how many perimeters must be generated for this island
|
|
int loop_number = this->config->perimeters + surface.extra_perimeters - 1 + extra_odd_perimeter; // 0-indexed loops
|
|
surface_idx++;
|
|
|
|
if ((layer->id() == 0 && this->config->only_one_perimeter_first_layer) || (this->config->only_one_perimeter_top && loop_number > 0 && this->upper_slices == NULL)){
|
|
loop_number = 0;
|
|
}
|
|
|
|
ExPolygons gaps;
|
|
//this var store infill surface removed from last to not add any more perimeters to it.
|
|
ExPolygons top_fills;
|
|
ExPolygons fill_clip;
|
|
// simplification already done at slicing
|
|
//ExPolygons last = union_ex(surface.expolygon.simplify_p(SCALED_RESOLUTION));
|
|
ExPolygons last = union_ex(surface.expolygon);
|
|
double last_area = -1;
|
|
|
|
if (loop_number >= 0) {
|
|
|
|
//increase surface for milling_post-process
|
|
if (have_to_grow_for_miller) {
|
|
if (unmillable.empty())
|
|
last = offset_ex(last, mill_extra_size);
|
|
else {
|
|
ExPolygons growth = diff_ex(offset_ex(last, mill_extra_size), unmillable, true);
|
|
last.insert(last.end(), growth.begin(), growth.end());
|
|
last = union_ex(last);
|
|
}
|
|
}
|
|
|
|
|
|
// Add perimeters on overhangs : initialization
|
|
ExPolygons overhangs_unsupported;
|
|
if ( (this->config->extra_perimeters_overhangs || (this->config->overhangs_reverse && this->layer->id() % 2 == 1))
|
|
&& !last.empty() && this->lower_slices != NULL && !this->lower_slices->empty()) {
|
|
//remove holes from lower layer, we only ant that for overhangs, not bridges!
|
|
ExPolygons lower_without_holes;
|
|
for (const ExPolygon &exp : *this->lower_slices)
|
|
lower_without_holes.emplace_back(to_expolygon(exp.contour));
|
|
overhangs_unsupported = offset2_ex(diff_ex(last, lower_without_holes, true), -SCALED_RESOLUTION, SCALED_RESOLUTION);
|
|
if (!overhangs_unsupported.empty()) {
|
|
//only consider overhangs and let bridges alone
|
|
//only consider the part that can be bridged (really, by the bridge algorithm)
|
|
//first, separate into islands (ie, each ExPlolygon)
|
|
//only consider the bottom layer that intersect unsupported, to be sure it's only on our island.
|
|
const ExPolygonCollection lower_island(diff_ex(last, overhangs_unsupported));
|
|
BridgeDetector detector{ overhangs_unsupported,
|
|
lower_island.expolygons,
|
|
perimeter_spacing };
|
|
if (detector.detect_angle(Geometry::deg2rad(this->config->bridge_angle.value))) {
|
|
const ExPolygons bridgeable = union_ex(detector.coverage(-1, true));
|
|
if (!bridgeable.empty()) {
|
|
//simplify to avoid most of artefacts from printing lines.
|
|
ExPolygons bridgeable_simplified;
|
|
for (const ExPolygon &poly : bridgeable) {
|
|
poly.simplify(perimeter_spacing / 2, &bridgeable_simplified);
|
|
}
|
|
|
|
if (!bridgeable_simplified.empty())
|
|
bridgeable_simplified = offset_ex(bridgeable_simplified, double(perimeter_spacing) / 1.9);
|
|
if (!bridgeable_simplified.empty()) {
|
|
//offset by perimeter spacing because the simplify may have reduced it a bit.
|
|
overhangs_unsupported = diff_ex(overhangs_unsupported, bridgeable_simplified, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
bool has_steep_overhang = false;
|
|
if (this->layer->id() % 2 == 1 && this->config->overhangs_reverse //check if my option is set and good layer
|
|
&& !last.empty() && !overhangs_unsupported.empty() //has something to work with
|
|
) {
|
|
coord_t offset = scale_(config->overhangs_reverse_threshold.get_abs_value(this->perimeter_flow.width));
|
|
//version with °: scale_(std::tan(PI * (0.5f / 90) * config->overhangs_reverse_threshold.value ) * this->layer->height)
|
|
|
|
if (offset_ex(overhangs_unsupported, -offset / 2.).size() > 0) {
|
|
//allow this loop to be printed in reverse
|
|
has_steep_overhang = true;
|
|
}
|
|
}
|
|
|
|
// In case no perimeters are to be generated, loop_number will equal to -1.
|
|
std::vector<PerimeterGeneratorLoops> contours(loop_number+1); // depth => loops
|
|
std::vector<PerimeterGeneratorLoops> holes(loop_number+1); // depth => loops
|
|
ThickPolylines thin_walls;
|
|
// we loop one time more than needed in order to find gaps after the last perimeter was applied
|
|
for (int i = 0;; ++ i) { // outer loop is 0
|
|
|
|
// We can add more perimeters if there are uncovered overhangs
|
|
// improvement for future: find a way to add perimeters only where it's needed.
|
|
bool has_overhang = false;
|
|
if ( this->config->extra_perimeters_overhangs && !last.empty() && !overhangs_unsupported.empty()) {
|
|
overhangs_unsupported = intersection_ex(overhangs_unsupported, last, true);
|
|
if (overhangs_unsupported.size() > 0) {
|
|
//please don't stop adding perimeter yet.
|
|
has_overhang = true;
|
|
}
|
|
}
|
|
|
|
// allow this perimeter to overlap itself?
|
|
bool thin_perimeter = this->config->thin_perimeters && (i == 0 || this->config->thin_perimeters_all);
|
|
|
|
// Calculate next onion shell of perimeters.
|
|
//this variable stored the next onion
|
|
ExPolygons next_onion;
|
|
if (i == 0) {
|
|
// compute next onion
|
|
// the minimum thickness of a single loop is:
|
|
// ext_width/2 + ext_spacing/2 + spacing/2 + width/2
|
|
if (thin_perimeter)
|
|
next_onion = offset_ex(
|
|
last,
|
|
-(float)(ext_perimeter_width / 2),
|
|
(round_peri ? ClipperLib::JoinType::jtRound : ClipperLib::JoinType::jtMiter),
|
|
(round_peri ? min_round_spacing : 3));
|
|
else
|
|
next_onion = offset2_ex(
|
|
last,
|
|
-(float)(ext_perimeter_width / 2 + ext_min_spacing / 2 - 1),
|
|
+(float)(ext_min_spacing / 2 - 1),
|
|
(round_peri ? ClipperLib::JoinType::jtRound : ClipperLib::JoinType::jtMiter),
|
|
(round_peri ? min_round_spacing : 3));
|
|
|
|
// look for thin walls
|
|
if (this->config->thin_walls) {
|
|
// detect edge case where a curve can be split in multiple small chunks.
|
|
std::vector<float> divs = { 2.1f, 1.9f, 2.2f, 1.75f, 1.5f}; //don't go too far, it's not possible to print thin wall after that
|
|
size_t idx_div = 0;
|
|
while (next_onion.size() > last.size() && idx_div < divs.size()) {
|
|
float div = divs[idx_div];
|
|
//use a sightly bigger spacing to try to drastically improve the split, that can lead to very thick gapfill
|
|
ExPolygons next_onion_secondTry = offset2_ex(
|
|
last,
|
|
-(float)((ext_perimeter_width / 2) + (ext_min_spacing / div) - 1),
|
|
+(float)((ext_min_spacing / div) - 1));
|
|
if (next_onion.size() > next_onion_secondTry.size() * 1.2 && next_onion.size() > next_onion_secondTry.size() + 2) {
|
|
next_onion = next_onion_secondTry;
|
|
}
|
|
idx_div++;
|
|
}
|
|
|
|
// the following offset2 ensures almost nothing in @thin_walls is narrower than $min_width
|
|
// (actually, something larger than that still may exist due to mitering or other causes)
|
|
coord_t min_width = (coord_t)scale_(this->config->thin_walls_min_width.get_abs_value(this->ext_perimeter_flow.nozzle_diameter));
|
|
|
|
ExPolygons no_thin_zone = offset_ex(next_onion, double(ext_perimeter_width / 2), jtSquare);
|
|
// medial axis requires non-overlapping geometry
|
|
ExPolygons thin_zones = diff_ex(last, no_thin_zone, true);
|
|
//don't use offset2_ex, because we don't want to merge the zones that have been separated.
|
|
//a very little bit of overlap can be created here with other thin polygons, but it's more useful than worisome.
|
|
ExPolygons half_thins = offset_ex(thin_zones, double(-min_width / 2));
|
|
//simplify them
|
|
for (ExPolygon &half_thin : half_thins) {
|
|
half_thin.remove_point_too_near((coord_t)SCALED_RESOLUTION);
|
|
}
|
|
//we push the bits removed and put them into what we will use as our anchor
|
|
if (half_thins.size() > 0) {
|
|
no_thin_zone = diff_ex(last, offset_ex(half_thins, double(min_width / 2 - SCALED_EPSILON)), true);
|
|
}
|
|
ExPolygons thins;
|
|
// compute a bit of overlap to anchor thin walls inside the print.
|
|
for (ExPolygon &half_thin : half_thins) {
|
|
//growing back the polygon
|
|
ExPolygons thin = offset_ex(half_thin, double(min_width / 2));
|
|
assert(thin.size() <= 1);
|
|
if (thin.empty()) continue;
|
|
coord_t thin_walls_overlap = (coord_t)scale_(this->config->thin_walls_overlap.get_abs_value(this->ext_perimeter_flow.nozzle_diameter));
|
|
ExPolygons anchor = intersection_ex(offset_ex(half_thin, double(min_width / 2) +
|
|
(float)(thin_walls_overlap), jtSquare), no_thin_zone, true);
|
|
ExPolygons bounds = union_ex(thin, anchor, true);
|
|
for (ExPolygon &bound : bounds) {
|
|
if (!intersection_ex(thin[0], bound).empty()) {
|
|
//be sure it's not too small to extrude reliably
|
|
thin[0].remove_point_too_near((coord_t)SCALED_RESOLUTION);
|
|
if (thin[0].area() > min_width*(ext_perimeter_width + ext_perimeter_spacing)) {
|
|
thins.push_back(thin[0]);
|
|
bound.remove_point_too_near((coord_t)SCALED_RESOLUTION);
|
|
// the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop (*1.2 because of circles approx. and enlrgment from 'div')
|
|
Slic3r::MedialAxis ma{ thin[0], (coord_t)((ext_perimeter_width + ext_perimeter_spacing)*1.2),
|
|
min_width, coord_t(this->layer->height) };
|
|
ma.use_bounds(bound)
|
|
.use_min_real_width((coord_t)scale_(this->ext_perimeter_flow.nozzle_diameter))
|
|
.use_tapers(thin_walls_overlap)
|
|
.build(thin_walls);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// use perimeters to extrude area that can't be printed by thin walls
|
|
// it's a bit like re-add thin area in to perimeter area.
|
|
// it can over-extrude a bit, but it's for a better good.
|
|
{
|
|
if (thin_perimeter)
|
|
next_onion = union_ex(next_onion, offset_ex(diff_ex(last, thins, true),
|
|
-(float)(ext_perimeter_width / 2),
|
|
(round_peri ? ClipperLib::JoinType::jtRound : ClipperLib::JoinType::jtMiter),
|
|
(round_peri ? min_round_spacing : 3)));
|
|
else
|
|
next_onion = union_ex(next_onion, offset2_ex(diff_ex(last, thins, true),
|
|
-(float)((ext_perimeter_width / 2) + (ext_min_spacing / 4)),
|
|
(float)(ext_min_spacing / 4),
|
|
(round_peri ? ClipperLib::JoinType::jtRound : ClipperLib::JoinType::jtMiter),
|
|
(round_peri ? min_round_spacing : 3)));
|
|
|
|
next_onion = intersection_ex(next_onion, last);
|
|
}
|
|
}
|
|
if (m_spiral_vase && next_onion.size() > 1) {
|
|
// Remove all but the largest area polygon.
|
|
keep_largest_contour_only(next_onion);
|
|
}
|
|
} else {
|
|
//FIXME Is this offset correct if the line width of the inner perimeters differs
|
|
// from the line width of the infill?
|
|
coord_t good_spacing = (i == 1) ? ext_perimeter_spacing2 : perimeter_spacing;
|
|
if (!thin_perimeter) {
|
|
// 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
|
|
// reliable gap fill algorithm.
|
|
// Also the offset2(perimeter, -x, x) may sometimes lead to a perimeter, which is larger than
|
|
// the original.
|
|
next_onion = offset2_ex(last,
|
|
-(float)(good_spacing + min_spacing / 2 - 1),
|
|
+(float)(min_spacing / 2 - 1),
|
|
(round_peri ? ClipperLib::JoinType::jtRound : ClipperLib::JoinType::jtMiter),
|
|
(round_peri ? min_round_spacing : 3));
|
|
// now try with different min spacing if we fear some hysteresis
|
|
//TODO, do that for each polygon from last, instead to do for all of them in one go.
|
|
ExPolygons no_thin_onion = offset_ex(last, double(-good_spacing));
|
|
if (last_area < 0) {
|
|
last_area = 0;
|
|
for (const ExPolygon& expoly : last) {
|
|
last_area += expoly.area();
|
|
}
|
|
}
|
|
double new_area = 0;
|
|
for (const ExPolygon& expoly : next_onion) {
|
|
new_area += expoly.area();
|
|
}
|
|
|
|
std::vector<float> divs { 1.8f, 1.6f }; //don't over-extrude, so don't use divider >2
|
|
size_t idx_div = 0;
|
|
while ((next_onion.size() > no_thin_onion.size() || (new_area != 0 && last_area > new_area * 100)) && idx_div < divs.size()) {
|
|
float div = divs[idx_div];
|
|
//use a sightly bigger spacing to try to drastically improve the split, that can lead to very thick gapfill
|
|
ExPolygons next_onion_secondTry = offset2_ex(
|
|
last,
|
|
-(float)(good_spacing + (min_spacing / div) - 1),
|
|
+(float)((min_spacing / div) - 1));
|
|
if (next_onion.size() > next_onion_secondTry.size() * 1.2 && next_onion.size() > next_onion_secondTry.size() + 2) {
|
|
// don't get it if it creates too many
|
|
next_onion = next_onion_secondTry;
|
|
} else if (next_onion.size() > next_onion_secondTry.size() || last_area > new_area * 100) {
|
|
// don't get it if it's too small
|
|
double area_new = 0;
|
|
for (const ExPolygon& expoly : next_onion_secondTry) {
|
|
area_new += expoly.area();
|
|
}
|
|
if (last_area > area_new * 100 || new_area == 0) {
|
|
next_onion = next_onion_secondTry;
|
|
}
|
|
}
|
|
idx_div++;
|
|
}
|
|
last_area = new_area;
|
|
|
|
} else {
|
|
// If "overlapping_perimeters" is enabled, this paths will be entered, which
|
|
// leads to overflows, as in prusa3d/Slic3r GH #32
|
|
next_onion = offset_ex(last, double( - good_spacing),
|
|
(round_peri ? ClipperLib::JoinType::jtRound : ClipperLib::JoinType::jtMiter),
|
|
(round_peri ? min_round_spacing : 3));
|
|
}
|
|
// look for gaps
|
|
if (this->config->gap_fill
|
|
//check if we are going to have an other perimeter
|
|
&& (i <= loop_number || has_overhang || next_onion.empty() || (this->config->gap_fill_last.value && i == loop_number+1)))
|
|
// not using safety offset here would "detect" very narrow gaps
|
|
// (but still long enough to escape the area threshold) that gap fill
|
|
// won't be able to fill but we'd still remove from infill area
|
|
append(gaps, diff_ex(
|
|
offset(last, -0.5f * gap_fill_spacing),
|
|
offset(next_onion, 0.5f * good_spacing + 10,
|
|
(round_peri ? ClipperLib::JoinType::jtRound : ClipperLib::JoinType::jtMiter),
|
|
(round_peri ? min_round_spacing : 3)))); // safety offset
|
|
}
|
|
|
|
if (next_onion.empty()) {
|
|
// Store the number of loops actually generated.
|
|
loop_number = i - 1;
|
|
// No region left to be filled in.
|
|
last.clear();
|
|
break;
|
|
} else if (i > loop_number) {
|
|
if (has_overhang) {
|
|
loop_number++;
|
|
contours.emplace_back();
|
|
holes.emplace_back();
|
|
} else {
|
|
// If i > loop_number, we were looking just for gaps.
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (const ExPolygon &expolygon : next_onion) {
|
|
//TODO: add width here to allow variable width (if we want to extrude a sightly bigger perimeter, see thin wall)
|
|
contours[i].emplace_back(expolygon.contour, i, true, has_steep_overhang);
|
|
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, has_steep_overhang);
|
|
}
|
|
}
|
|
last = std::move(next_onion);
|
|
|
|
//store surface for top infill if only_one_perimeter_top
|
|
if(i==0 && ((layer->id() == 0 && this->config->only_one_perimeter_first_layer) || (config->only_one_perimeter_top && this->upper_slices != NULL))){
|
|
if (this->config->only_one_perimeter_top_other_algo) {
|
|
//split the polygons with top/not_top
|
|
//get the offset from solid surface anchor
|
|
coord_t offset_top_surface = scale_(config->external_infill_margin.get_abs_value(
|
|
config->perimeters.value == 0 ? 0. : unscaled(double(ext_perimeter_width + perimeter_spacing * int(int(config->perimeters.value) - int(1))))));
|
|
// if possible, try to not push the extra perimeters inside the sparse infill
|
|
if (offset_top_surface > 0.9 * (config->perimeters.value <= 1 ? 0. : (perimeter_spacing * (config->perimeters.value - 1))))
|
|
offset_top_surface -= coord_t(0.9 * (config->perimeters.value <= 1 ? 0. : (perimeter_spacing * (config->perimeters.value - 1))));
|
|
else offset_top_surface = 0;
|
|
//don't takes into account too thin areas
|
|
double min_width_top_surface = std::max(double(ext_perimeter_spacing / 2 + 10), this->config->min_width_top_surface.get_abs_value(double(perimeter_width)));
|
|
//make thin upper surfaces disapear with -+offset_top_surface
|
|
ExPolygons grown_upper_slices;
|
|
//do offset2 per island, to avoid big blob merging
|
|
//remove polygon too thin (but don't mess with holes)
|
|
for (const ExPolygon& expoly_to_grow : *this->upper_slices) {
|
|
//only offset the contour, as it can merge holes
|
|
Polygons contour = offset2({ expoly_to_grow.contour }, -offset_top_surface, offset_top_surface + min_width_top_surface + (have_to_grow_for_miller ? (double)mill_extra_size : 0));
|
|
if (!contour.empty()) {
|
|
if (expoly_to_grow.holes.empty()) {
|
|
for (Polygon& p : contour)
|
|
grown_upper_slices.push_back(ExPolygon{ p });
|
|
} else {
|
|
Polygons holes = expoly_to_grow.holes;
|
|
for (Polygon& h : holes)
|
|
h.reverse();
|
|
holes = offset(holes, -min_width_top_surface - (have_to_grow_for_miller ? (double)mill_extra_size : 0));
|
|
for (ExPolygon p : diff_ex(contour, holes))
|
|
grown_upper_slices.push_back(p);
|
|
}
|
|
}
|
|
}
|
|
grown_upper_slices = union_ex(grown_upper_slices);
|
|
//set the clip to a virtual "second perimeter"
|
|
fill_clip = offset_ex(last, -double(ext_perimeter_spacing));
|
|
auto fill_clip_old = fill_clip;
|
|
// get the real top surface
|
|
const ExPolygons top_grown_polygons = (!have_to_grow_for_miller)
|
|
? diff_ex(last, grown_upper_slices, true)
|
|
: (unmillable.empty())
|
|
? diff_ex(last, grown_upper_slices, true)
|
|
: diff_ex(last, diff_ex(grown_upper_slices, unmillable, true));
|
|
|
|
//get the not-top surface, from the "real top" but enlarged by external_infill_margin (and the min_width_top_surface we removed a bit before)
|
|
const ExPolygons inner_polygons = diff_ex(last,
|
|
offset_ex(top_grown_polygons, offset_top_surface + min_width_top_surface
|
|
//also remove the ext_perimeter_spacing/2 width because we are faking the external periemter, and we will remove ext_perimeter_spacing2
|
|
- double(ext_perimeter_spacing / 2)), true);
|
|
// get the enlarged top surface, by using inner_polygons instead of upper_slices, and clip it for it to be exactly the polygons to fill.
|
|
const ExPolygons top_polygons = diff_ex(fill_clip, inner_polygons, true);
|
|
// increase by half peri the inner space to fill the frontier between last and stored.
|
|
top_fills = union_ex(top_fills, top_polygons);
|
|
//set the clip to the external wall but go back inside by infill_extrusion_width/2 to be sure the extrusion won't go outside even with a 100% overlap.
|
|
double infill_spacing_unscaled = this->config->infill_extrusion_width.get_abs_value(nozzle_diameter);
|
|
if (infill_spacing_unscaled == 0) infill_spacing_unscaled = Flow::auto_extrusion_width(frInfill, nozzle_diameter);
|
|
fill_clip = offset_ex(last, double(ext_perimeter_spacing / 2) - scale_d(infill_spacing_unscaled / 2));
|
|
last = intersection_ex(inner_polygons, last);
|
|
//{
|
|
// std::stringstream stri;
|
|
// stri << this->layer->id() << "_1_"<< i <<"_only_one_peri"<< ".svg";
|
|
// SVG svg(stri.str());
|
|
// svg.draw(to_polylines(oldLast), "orange");
|
|
// svg.draw(to_polylines(fill_clip), "purple");
|
|
// svg.draw(to_polylines(inner_polygons), "yellow");
|
|
// svg.draw(to_polylines(top_polygons), "cyan");
|
|
// svg.draw(to_polylines(last), "red");
|
|
// svg.draw(to_polylines(fill_clip_old), "green");
|
|
// svg.Close();
|
|
//}
|
|
} else {
|
|
|
|
//split the polygons with top/not_top
|
|
//get the offset from solid surface anchor
|
|
coord_t offset_top_surface = scale_(config->external_infill_margin.get_abs_value(
|
|
config->perimeters.value == 0 ? 0. : unscaled(double(ext_perimeter_width + perimeter_spacing * int(int(config->perimeters.value) - int(1))))));
|
|
// if possible, try to not push the extra perimeters inside the sparse infill
|
|
if (offset_top_surface > 0.9 * (config->perimeters.value <= 1 ? 0. : (perimeter_spacing * (config->perimeters.value - 1))))
|
|
offset_top_surface -= coord_t(0.9 * (config->perimeters.value <= 1 ? 0. : (perimeter_spacing * (config->perimeters.value - 1))));
|
|
else offset_top_surface = 0;
|
|
//don't takes into account too thin areas
|
|
double min_width_top_surface = std::max(double(ext_perimeter_spacing / 2 + 10), this->config->min_width_top_surface.get_abs_value(double(perimeter_width)));
|
|
ExPolygons grown_upper_slices = offset_ex(*this->upper_slices, min_width_top_surface);
|
|
//set the clip to a virtual "second perimeter"
|
|
fill_clip = offset_ex(last, -double(ext_perimeter_spacing));
|
|
// get the real top surface
|
|
ExPolygons top_polygons = (!have_to_grow_for_miller)
|
|
? diff_ex(last, grown_upper_slices, true)
|
|
: (unmillable.empty())
|
|
? diff_ex(last, offset_ex(grown_upper_slices, (double)mill_extra_size), true)
|
|
: diff_ex(last, diff_ex(offset_ex(grown_upper_slices, (double)mill_extra_size), unmillable, true));
|
|
|
|
|
|
//get the not-top surface, from the "real top" but enlarged by external_infill_margin (and the min_width_top_surface we removed a bit before)
|
|
ExPolygons inner_polygons = diff_ex(last, offset_ex(top_polygons, offset_top_surface + min_width_top_surface
|
|
//also remove the ext_perimeter_spacing/2 width because we are faking the external perimeter, and we will remove ext_perimeter_spacing2
|
|
- double(ext_perimeter_spacing / 2)), true);
|
|
// get the enlarged top surface, by using inner_polygons instead of upper_slices, and clip it for it to be exactly the polygons to fill.
|
|
top_polygons = diff_ex(fill_clip, inner_polygons, true);
|
|
// increase by half peri the inner space to fill the frontier between last and stored.
|
|
top_fills = union_ex(top_fills, top_polygons);
|
|
//set the clip to the external wall but go back inside by infill_extrusion_width/2 to be sure the extrusion won't go outside even with a 100% overlap.
|
|
fill_clip = offset_ex(last, double(ext_perimeter_spacing / 2) - this->config->infill_extrusion_width.get_abs_value(nozzle_diameter) / 2);
|
|
//ExPolygons oldLast = last;
|
|
last = intersection_ex(inner_polygons, last);
|
|
//{
|
|
// std::stringstream stri;
|
|
// stri << this->layer->id() << "_1_"<< i <<"_only_one_peri"<< ".svg";
|
|
// SVG svg(stri.str());
|
|
// svg.draw(to_polylines(top_fills), "green");
|
|
// svg.draw(to_polylines(inner_polygons), "yellow");
|
|
// svg.draw(to_polylines(top_polygons), "cyan");
|
|
// svg.draw(to_polylines(oldLast), "orange");
|
|
// svg.draw(to_polylines(last), "red");
|
|
// svg.Close();
|
|
//}
|
|
}
|
|
}
|
|
}
|
|
|
|
//{
|
|
//std::stringstream stri;
|
|
//stri << this->layer->id() << "_1p5_stored_" << this->layer->id() << ".svg";
|
|
//SVG svg(stri.str());
|
|
//svg.draw(to_polylines(last), "yellow");
|
|
//svg.draw(to_polylines(top_fills), "green");
|
|
//svg.Close();
|
|
//}
|
|
// check for extracting extra perimeters from gapfill
|
|
if (!gaps.empty()) {
|
|
// if needed, add it to the first empty contour list
|
|
const size_t contours_size = loop_number + 1;
|
|
//first, find loops and try to extract a perimeter from them.
|
|
for (size_t i = 0; i < gaps.size(); i++) {
|
|
ExPolygon& expoly = gaps[i];
|
|
if (!expoly.holes.empty()) {
|
|
//this is a a sort of a loop
|
|
//try to see if it's possible to add a "perimeter"
|
|
ExPolygons contour_expolygon = offset_ex(expoly, -(float)(perimeter_spacing / 2), ClipperLib::jtMiter, 3);
|
|
if (contour_expolygon.size() == 1 && !contour_expolygon.front().holes.empty()) {
|
|
//OK
|
|
// update list & variable to let the new perimeter be taken into account
|
|
loop_number = contours_size;
|
|
if (contours_size >= contours.size()) {
|
|
contours.emplace_back();
|
|
holes.emplace_back();
|
|
}
|
|
//Add the new periemter
|
|
contours[contours_size].emplace_back(contour_expolygon.front().contour, contours_size, true, has_steep_overhang);
|
|
//create the new gapfills
|
|
ExPolygons gapfill_area = offset_ex(expoly.contour, -(float)(perimeter_spacing));
|
|
ExPolygons to_add = intersection_ex({ expoly }, gapfill_area);
|
|
//add the new gapfill
|
|
if (to_add.size() == 0)
|
|
expoly.clear();
|
|
else
|
|
expoly = to_add.front();
|
|
for (size_t j = 1; j < to_add.size(); j++)
|
|
gaps.emplace_back(to_add[j]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// nest loops: holes first
|
|
for (int d = 0; d <= loop_number; ++d) {
|
|
PerimeterGeneratorLoops &holes_d = holes[d];
|
|
// loop through all holes having depth == d
|
|
for (int i = 0; i < (int)holes_d.size(); ++i) {
|
|
const PerimeterGeneratorLoop &loop = holes_d[i];
|
|
// find the hole loop that contains this one, if any
|
|
for (int t = d+1; t <= loop_number; ++t) {
|
|
for (int j = 0; j < (int)holes[t].size(); ++j) {
|
|
PerimeterGeneratorLoop &candidate_parent = holes[t][j];
|
|
if (candidate_parent.polygon.contains(loop.polygon.first_point())) {
|
|
candidate_parent.children.push_back(loop);
|
|
holes_d.erase(holes_d.begin() + i);
|
|
--i;
|
|
goto NEXT_LOOP;
|
|
}
|
|
}
|
|
}
|
|
// if no hole contains this hole, find the contour loop that contains it
|
|
for (int t = loop_number; t >= 0; --t) {
|
|
for (int j = 0; j < (int)contours[t].size(); ++j) {
|
|
PerimeterGeneratorLoop &candidate_parent = contours[t][j];
|
|
if (candidate_parent.polygon.contains(loop.polygon.first_point())) {
|
|
candidate_parent.children.push_back(loop);
|
|
holes_d.erase(holes_d.begin() + i);
|
|
--i;
|
|
goto NEXT_LOOP;
|
|
}
|
|
}
|
|
}
|
|
NEXT_LOOP: ;
|
|
}
|
|
}
|
|
// nest contour loops
|
|
for (int d = loop_number; d >= 1; --d) {
|
|
PerimeterGeneratorLoops &contours_d = contours[d];
|
|
// loop through all contours having depth == d
|
|
for (int i = 0; i < (int)contours_d.size(); ++i) {
|
|
const PerimeterGeneratorLoop &loop = contours_d[i];
|
|
// find the contour loop that contains it
|
|
for (int t = d - 1; t >= 0; -- t) {
|
|
for (size_t j = 0; j < contours[t].size(); ++ j) {
|
|
PerimeterGeneratorLoop &candidate_parent = contours[t][j];
|
|
if (candidate_parent.polygon.contains(loop.polygon.first_point())) {
|
|
candidate_parent.children.push_back(loop);
|
|
contours_d.erase(contours_d.begin() + i);
|
|
--i;
|
|
goto NEXT_CONTOUR;
|
|
}
|
|
}
|
|
}
|
|
NEXT_CONTOUR: ;
|
|
}
|
|
}
|
|
// at this point, all loops should be in contours[0] (= contours.front() )
|
|
// collection of loops to add into loops
|
|
ExtrusionEntityCollection entities;
|
|
if (config->perimeter_loop.value) {
|
|
//onlyone_perimter = >fusion all perimeterLoops
|
|
for (PerimeterGeneratorLoop &loop : contours.front()) {
|
|
ExtrusionLoop extr_loop = this->_traverse_and_join_loops(loop, get_all_Childs(loop), loop.polygon.points.front());
|
|
//ExtrusionLoop extr_loop = this->_traverse_and_join_loops_old(loop, loop.polygon.points.front(), true);
|
|
extr_loop.paths.back().polyline.points.push_back(extr_loop.paths.front().polyline.points.front());
|
|
entities.append(extr_loop);
|
|
}
|
|
|
|
// append thin walls
|
|
if (!thin_walls.empty()) {
|
|
ExtrusionEntityCollection tw = thin_variable_width
|
|
(thin_walls, erThinWall, this->ext_perimeter_flow);
|
|
|
|
entities.append(tw.entities);
|
|
thin_walls.clear();
|
|
}
|
|
} else {
|
|
if (this->object_config->thin_walls_merge) {
|
|
ThickPolylines no_thin_walls;
|
|
entities = this->_traverse_loops(contours.front(), no_thin_walls);
|
|
_merge_thin_walls(entities, thin_walls);
|
|
} else {
|
|
entities = this->_traverse_loops(contours.front(), thin_walls);
|
|
}
|
|
}
|
|
|
|
|
|
// 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
|
|
if (this->config->external_perimeters_first ||
|
|
(this->layer->id() == 0 && this->object_config->brim_width.value > 0)) {
|
|
if (this->config->external_perimeters_nothole.value ||
|
|
(this->layer->id() == 0 && this->object_config->brim_width.value > 0)) {
|
|
if (this->config->external_perimeters_hole.value ||
|
|
(this->layer->id() == 0 && this->object_config->brim_width.value > 0)) {
|
|
entities.reverse();
|
|
} else {
|
|
//reverse only not-hole perimeters
|
|
ExtrusionEntityCollection coll2;
|
|
for (const auto loop : entities.entities) {
|
|
if (loop->is_loop() && !(((ExtrusionLoop*)loop)->loop_role() & ExtrusionLoopRole::elrHole) != 0) {
|
|
coll2.entities.push_back(loop);
|
|
}
|
|
}
|
|
coll2.reverse();
|
|
for (const auto loop : entities.entities) {
|
|
if (!loop->is_loop() || (((ExtrusionLoop*)loop)->loop_role() & ExtrusionLoopRole::elrHole) != 0) {
|
|
coll2.entities.push_back(loop);
|
|
}
|
|
}
|
|
//note: this hacky thing is possible because coll2.entities contains in fact entities's entities
|
|
//if you does entities = coll2, you'll delete entities's entities and then you have nothing.
|
|
entities.entities = coll2.entities;
|
|
//and you have to empty coll2 or it will delete his content, hence crashing our hack
|
|
coll2.entities.clear();
|
|
}
|
|
} else if (this->config->external_perimeters_hole.value) {
|
|
//reverse the hole, and put them in first place.
|
|
ExtrusionEntityCollection coll2;
|
|
for (const auto loop : entities.entities) {
|
|
if (loop->is_loop() && (((ExtrusionLoop*)loop)->loop_role() & ExtrusionLoopRole::elrHole) != 0) {
|
|
coll2.entities.push_back(loop);
|
|
}
|
|
}
|
|
coll2.reverse();
|
|
for (const auto loop : entities.entities) {
|
|
if (!loop->is_loop() || !(((ExtrusionLoop*)loop)->loop_role() & ExtrusionLoopRole::elrHole) != 0) {
|
|
coll2.entities.push_back(loop);
|
|
}
|
|
}
|
|
//note: this hacky thing is possible because coll2.entities contains in fact entities's entities
|
|
//if you does entities = coll2, you'll delete entities's entities and then you have nothing.
|
|
entities.entities = coll2.entities;
|
|
//and you have to empty coll2 or it will delete his content, hence crashing our hack
|
|
coll2.entities.clear();
|
|
}
|
|
|
|
}
|
|
// append perimeters for this slice as a collection
|
|
if (!entities.empty()) {
|
|
//move it, to avoid to clone evrything and then delete it
|
|
this->loops->entities.emplace_back( new ExtrusionEntityCollection(std::move(entities)));
|
|
}
|
|
} // for each loop of an island
|
|
|
|
// fill gaps
|
|
if (!gaps.empty()) {
|
|
// collapse
|
|
double min = 0.2 * perimeter_width * (1 - INSET_OVERLAP_TOLERANCE);
|
|
//be sure we don't gapfill where the perimeters are already touching each other (negative spacing).
|
|
min = std::max(min, double(Flow::new_from_spacing(EPSILON, (float)nozzle_diameter, (float)this->layer->height, this->perimeter_flow.spacing_ratio, false).scaled_width()));
|
|
double max = 2.2 * perimeter_spacing;
|
|
//remove areas that are too big (shouldn't occur...)
|
|
ExPolygons too_big = offset2_ex(gaps, double(-max / 2), double(+max / 2));
|
|
ExPolygons gaps_ex_to_test = too_big.empty()? gaps : diff_ex(gaps,too_big,true);
|
|
ExPolygons gaps_ex;
|
|
const double minarea = scale_(scale_(this->config->gap_fill_min_area.get_abs_value(unscaled((double)perimeter_width)*unscaled((double)perimeter_width))));
|
|
// check each gapfill area to see if it's printable.
|
|
for (const ExPolygon &expoly : gaps_ex_to_test) {
|
|
//remove too small gaps that are too hard to fill.
|
|
//ie one that are smaller than an extrusion with width of min and a length of max.
|
|
if (expoly.area() > minarea) {
|
|
ExPolygons expoly_after_shrink_test = offset_ex(ExPolygons{ expoly }, double(-min * 0.5));
|
|
//if the shrink split the area in multipe bits
|
|
if (expoly_after_shrink_test.size() > 1) {
|
|
//remove too small bits
|
|
for (int i = 0; i < expoly_after_shrink_test.size(); i++) {
|
|
if (expoly_after_shrink_test[i].area() < (SCALED_EPSILON * SCALED_EPSILON * 4)) {
|
|
expoly_after_shrink_test.erase(expoly_after_shrink_test.begin() + i);
|
|
i--;
|
|
} else {
|
|
ExPolygons wider = offset_ex(ExPolygons{ expoly_after_shrink_test[i] }, min * 0.5);
|
|
if (wider.empty() || wider[0].area() < minarea) {
|
|
expoly_after_shrink_test.erase(expoly_after_shrink_test.begin() + i);
|
|
i--;
|
|
}
|
|
}
|
|
}
|
|
//maybe some areas are a just bit too thin, try with just a little more offset to remove them.
|
|
ExPolygons expoly_after_shrink_test2 = offset_ex(ExPolygons{ expoly }, double(-min *0.8));
|
|
for (int i = 0; i < expoly_after_shrink_test2.size(); i++) {
|
|
if (expoly_after_shrink_test2[i].area() < (SCALED_EPSILON * SCALED_EPSILON * 4)) {
|
|
expoly_after_shrink_test2.erase(expoly_after_shrink_test2.begin() + i);
|
|
i--;
|
|
|
|
}else{
|
|
ExPolygons wider = offset_ex(ExPolygons{ expoly_after_shrink_test2[i] }, min * 0.5);
|
|
if (wider.empty() || wider[0].area() < minarea) {
|
|
expoly_after_shrink_test2.erase(expoly_after_shrink_test2.begin() + i);
|
|
i--;
|
|
}
|
|
}
|
|
}
|
|
//it's better if there are significantly less extrusions
|
|
if (expoly_after_shrink_test.size()/1.42 > expoly_after_shrink_test2.size()) {
|
|
expoly_after_shrink_test2 = offset_ex(expoly_after_shrink_test2, double(min * 0.8));
|
|
//insert with move instead of copy
|
|
std::move(expoly_after_shrink_test2.begin(), expoly_after_shrink_test2.end(), std::back_inserter(gaps_ex));
|
|
} else {
|
|
expoly_after_shrink_test = offset_ex(expoly_after_shrink_test, double(min * 0.8));
|
|
std::move(expoly_after_shrink_test.begin(), expoly_after_shrink_test.end(), std::back_inserter(gaps_ex));
|
|
}
|
|
} else {
|
|
expoly_after_shrink_test = offset_ex(expoly_after_shrink_test, double(min * 0.8));
|
|
std::move(expoly_after_shrink_test.begin(), expoly_after_shrink_test.end(), std::back_inserter(gaps_ex));
|
|
}
|
|
}
|
|
}
|
|
// create lines from the area
|
|
ThickPolylines polylines;
|
|
for (const ExPolygon &ex : gaps_ex) {
|
|
MedialAxis{ ex, coord_t(max*1.1), coord_t(min), coord_t(this->layer->height) }.build(polylines);
|
|
}
|
|
// create extrusion from lines
|
|
if (!polylines.empty()) {
|
|
ExtrusionEntityCollection gap_fill = thin_variable_width(polylines,
|
|
erGapFill, this->solid_infill_flow);
|
|
this->gap_fill->append(gap_fill.entities);
|
|
/* Make sure we don't infill narrow parts that are already gap-filled
|
|
(we only consider this surface's gaps to reduce the diff() complexity).
|
|
Growing actual extrusions ensures that gaps not filled by medial axis
|
|
are not subtracted from fill surfaces (they might be too short gaps
|
|
that medial axis skips but infill might join with other infill regions
|
|
and use zigzag). */
|
|
//FIXME Vojtech: This grows by a rounded extrusion width, not by line spacing,
|
|
// therefore it may cover the area, but no the volume.
|
|
last = diff_ex(to_polygons(last), gap_fill.polygons_covered_by_width(10.f));
|
|
}
|
|
}
|
|
//TODO: if a gapfill extrusion is a loop and with width always >= perimeter width then change the type to perimeter and put it at the right place in the loops vector.
|
|
|
|
// create one more offset to be used as boundary for fill
|
|
// 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 =
|
|
(loop_number < 0) ? 0 :
|
|
(loop_number == 0) ?
|
|
// one loop
|
|
ext_perimeter_spacing / 2 :
|
|
// two or more loops?
|
|
perimeter_spacing / 2;
|
|
// only apply infill overlap if we actually have one perimeter
|
|
if (inset == 0) {
|
|
infill_peri_overlap = 0;
|
|
}
|
|
// simplify infill contours according to resolution
|
|
Polygons not_filled_p;
|
|
for (ExPolygon &ex : last)
|
|
ex.simplify_p(SCALED_RESOLUTION, ¬_filled_p);
|
|
ExPolygons not_filled_exp = union_ex(not_filled_p);
|
|
// collapse too narrow infill areas
|
|
coord_t min_perimeter_infill_spacing = (coord_t)( solid_infill_spacing * (1. - INSET_OVERLAP_TOLERANCE) );
|
|
// append infill areas to fill_surfaces
|
|
//auto it_surf = this->fill_surfaces->surfaces.end();
|
|
ExPolygons infill_exp = offset2_ex(not_filled_exp,
|
|
double(-inset - min_perimeter_infill_spacing / 2 + infill_peri_overlap - infill_gap),
|
|
double(min_perimeter_infill_spacing / 2));
|
|
//if any top_fills, grow them by ext_perimeter_spacing/2 to have the real un-anchored fill
|
|
ExPolygons top_infill_exp = intersection_ex(fill_clip, offset_ex(top_fills, double(ext_perimeter_spacing / 2)));
|
|
if (!top_fills.empty()) {
|
|
infill_exp = union_ex(infill_exp, offset_ex(top_infill_exp, double(infill_peri_overlap)));
|
|
}
|
|
this->fill_surfaces->append(infill_exp, stPosInternal | stDensSparse);
|
|
|
|
if (infill_peri_overlap != 0) {
|
|
ExPolygons polyWithoutOverlap;
|
|
if (min_perimeter_infill_spacing / 2 > infill_peri_overlap)
|
|
polyWithoutOverlap = offset2_ex(
|
|
not_filled_exp,
|
|
double(-inset - infill_gap - min_perimeter_infill_spacing / 2 + infill_peri_overlap),
|
|
double(min_perimeter_infill_spacing / 2 - infill_peri_overlap));
|
|
else
|
|
polyWithoutOverlap = offset_ex(
|
|
not_filled_exp,
|
|
double(-inset - infill_gap));
|
|
if (!top_fills.empty()) {
|
|
polyWithoutOverlap = union_ex(polyWithoutOverlap, top_infill_exp);
|
|
}
|
|
this->fill_no_overlap.insert(this->fill_no_overlap.end(), polyWithoutOverlap.begin(), polyWithoutOverlap.end());
|
|
/*{
|
|
std::stringstream stri;
|
|
stri << this->layer->id() << "_2_end_makeperimeter_" << this->layer->id() << ".svg";
|
|
SVG svg(stri.str());
|
|
svg.draw(to_polylines(infill_exp), "blue");
|
|
svg.draw(to_polylines(fill_no_overlap), "cyan");
|
|
svg.draw(to_polylines(not_filled_exp), "green");
|
|
svg.draw(to_polylines(last), "yellow");
|
|
svg.draw(to_polylines(offset_ex(fill_clip, ext_perimeter_spacing / 2)), "yellow");
|
|
svg.draw(to_polylines(top_infill_exp), "orange");
|
|
svg.Close();
|
|
}*/
|
|
}
|
|
} // for each island
|
|
}
|
|
|
|
|
|
ExtrusionPaths PerimeterGenerator::create_overhangs(const Polyline& loop_polygons, ExtrusionRole role, bool is_external) const {
|
|
ExtrusionPaths paths;
|
|
double nozzle_diameter = this->print_config->nozzle_diameter.get_at(this->config->perimeter_extruder - 1);
|
|
if (this->config->overhangs_width.get_abs_value(nozzle_diameter) == 0 && 0 == this->config->overhangs_width_speed.get_abs_value(nozzle_diameter)) {
|
|
//error
|
|
ExtrusionPath path(role);
|
|
path.polyline = loop_polygons;
|
|
path.mm3_per_mm = is_external ? this->_ext_mm3_per_mm : this->_mm3_per_mm;
|
|
path.width = is_external ? this->ext_perimeter_flow.width : this->perimeter_flow.width;
|
|
path.height = (float)this->layer->height;
|
|
assert(path.mm3_per_mm == path.mm3_per_mm);
|
|
assert(path.width == path.width);
|
|
assert(path.height == path.height);
|
|
paths.push_back(path);
|
|
return paths;
|
|
|
|
}
|
|
//set the fan & speed before the flow
|
|
Polylines ok_polylines = { loop_polygons };
|
|
|
|
Polylines small_speed;
|
|
Polylines big_speed;
|
|
bool no_small_flow = _lower_slices_bridge_speed_big == _lower_slices_bridge_flow_small;
|
|
Polylines small_flow;
|
|
Polylines big_flow;
|
|
|
|
Polylines* previous = &ok_polylines;
|
|
if (this->config->overhangs_width_speed.value > 0) {
|
|
if (!this->_lower_slices_bridge_speed_small.empty()) {
|
|
small_speed = diff_pl(*previous, this->_lower_slices_bridge_speed_small);
|
|
if (!small_speed.empty()) {
|
|
*previous = intersection_pl(*previous, this->_lower_slices_bridge_speed_small);
|
|
previous = &small_speed;
|
|
}
|
|
}
|
|
if (!this->_lower_slices_bridge_speed_big.empty()) {
|
|
big_speed = diff_pl(*previous, this->_lower_slices_bridge_speed_big);
|
|
if (!big_speed.empty()) {
|
|
*previous = intersection_pl(*previous, this->_lower_slices_bridge_speed_big);
|
|
previous = &big_speed;
|
|
}
|
|
}
|
|
}
|
|
if (this->config->overhangs_width.value > 0) {
|
|
if (!this->_lower_slices_bridge_flow_small.empty()) {
|
|
small_flow = diff_pl(*previous, this->_lower_slices_bridge_flow_small);
|
|
if (!small_flow.empty()) {
|
|
*previous = intersection_pl(*previous, this->_lower_slices_bridge_flow_small);
|
|
previous = &small_flow;
|
|
}
|
|
}
|
|
if (!this->_lower_slices_bridge_flow_big.empty()) {
|
|
big_flow = diff_pl(*previous, this->_lower_slices_bridge_flow_big);
|
|
if (!big_flow.empty()) {
|
|
*previous = intersection_pl(*previous, this->_lower_slices_bridge_flow_big);
|
|
previous = &big_flow;
|
|
}
|
|
}
|
|
}
|
|
|
|
//note: layer height is used to identify the path type
|
|
if (!ok_polylines.empty()) {
|
|
extrusion_paths_append(
|
|
paths,
|
|
ok_polylines,
|
|
role,
|
|
is_external ? this->_ext_mm3_per_mm : this->_mm3_per_mm,
|
|
is_external ? this->ext_perimeter_flow.width : this->perimeter_flow.width,
|
|
0);
|
|
}
|
|
if (!small_speed.empty()) {
|
|
extrusion_paths_append(
|
|
paths,
|
|
small_speed,
|
|
erOverhangPerimeter,
|
|
is_external ? this->_ext_mm3_per_mm : this->_mm3_per_mm,
|
|
is_external ? this->ext_perimeter_flow.width : this->perimeter_flow.width,
|
|
no_small_flow ? 2 : 1);
|
|
}
|
|
if (!big_speed.empty()) {
|
|
extrusion_paths_append(
|
|
paths,
|
|
big_speed,
|
|
erOverhangPerimeter,
|
|
is_external ? this->_ext_mm3_per_mm : this->_mm3_per_mm,
|
|
is_external ? this->ext_perimeter_flow.width : this->perimeter_flow.width,
|
|
no_small_flow ? 3 : 2);
|
|
}
|
|
if (!small_flow.empty()) {
|
|
extrusion_paths_append(
|
|
paths,
|
|
small_flow,
|
|
erOverhangPerimeter,
|
|
this->_mm3_per_mm_overhang,
|
|
this->overhang_flow.width,
|
|
3);
|
|
}
|
|
if (!big_flow.empty()) {
|
|
extrusion_paths_append(
|
|
paths,
|
|
big_flow,
|
|
erOverhangPerimeter,
|
|
this->_mm3_per_mm_overhang,
|
|
this->overhang_flow.width,
|
|
4);
|
|
}
|
|
|
|
// reapply the nearest point search for starting point
|
|
// We allow polyline reversal because Clipper may have randomly reversed polylines during clipping.
|
|
if(!paths.empty())
|
|
chain_and_reorder_extrusion_paths(paths, &paths.front().first_point());
|
|
|
|
bool has_normal = !ok_polylines.empty();
|
|
bool has_speed = !small_speed.empty() || !big_speed.empty();
|
|
bool has_flow = !small_flow.empty() || !big_flow.empty();
|
|
|
|
std::function<void(ExtrusionPaths&, const std::function<bool(ExtrusionPath&, ExtrusionPath&, ExtrusionPath&)>&)> foreach = [](ExtrusionPaths &paths, const std::function<bool(ExtrusionPath&, ExtrusionPath&, ExtrusionPath&)>& doforeach) {
|
|
if (paths.size() > 2)
|
|
for (int i = 1; i < paths.size() - 1; i++) {
|
|
if (doforeach(paths[i - 1], paths[i], paths[i + 1])) {
|
|
paths.erase(paths.begin() + i);
|
|
i--;
|
|
if (paths[i].height == paths[i + 1].height) {
|
|
paths[i].polyline.points.insert(paths[i].polyline.points.end(), paths[i + 1].polyline.points.begin() + 1, paths[i + 1].polyline.points.end());
|
|
paths.erase(paths.begin() + i + 1);
|
|
}
|
|
}
|
|
}
|
|
if (paths.size() > 2)
|
|
if (doforeach(paths[paths.size() - 2], paths.back(), paths.front())) {
|
|
paths.erase(paths.end() - 1);
|
|
if (paths.back().height == paths.front().height) {
|
|
paths.front().polyline.points.insert(paths.front().polyline.points.begin(), paths.back().polyline.points.begin(), paths.back().polyline.points.end() - 1);
|
|
paths.erase(paths.end() - 1);
|
|
}
|
|
}
|
|
if (paths.size() > 2)
|
|
if (doforeach(paths.back(), paths.front(), paths[1])) {
|
|
paths.erase(paths.begin());
|
|
if (paths.back().height == paths.front().height) {
|
|
paths.front().polyline.points.insert(paths.front().polyline.points.begin(), paths.back().polyline.points.begin(), paths.back().polyline.points.end() - 1);
|
|
paths.erase(paths.end() - 1);
|
|
}
|
|
}
|
|
};
|
|
|
|
if (paths.size() > 2) {
|
|
double min_length = this->perimeter_flow.scaled_width() * 2;
|
|
double ok_length = this->perimeter_flow.scaled_width() * 20;
|
|
|
|
foreach(paths, [min_length, ok_length](ExtrusionPath& prev, ExtrusionPath& curr, ExtrusionPath& next) {
|
|
if (curr.length() < min_length) {
|
|
float diff_height = std::abs(prev.height - curr.height) - std::abs(next.height - curr.height);
|
|
//have to choose the rigth path
|
|
if (diff_height < 0 || (diff_height == 0 && prev.length() > next.length())) {
|
|
//merge to previous
|
|
assert(prev.last_point() == curr.first_point());
|
|
assert(curr.polyline.points.size() > 1);
|
|
prev.polyline.points.insert(prev.polyline.points.end(), curr.polyline.points.begin() + 1, curr.polyline.points.end());
|
|
} else {
|
|
//merge to next
|
|
assert(curr.last_point() == next.first_point());
|
|
assert(curr.polyline.points.size() > 1);
|
|
next.polyline.points.insert(next.polyline.points.begin(), curr.polyline.points.begin(), curr.polyline.points.end() - 1);
|
|
}
|
|
return true;
|
|
} else if(((int)curr.height) % 2 == 1 && curr.length() > ok_length){
|
|
curr.height++;
|
|
if (prev.height == curr.height) {
|
|
prev.polyline.points.insert(prev.polyline.points.end(), curr.polyline.points.begin() + 1, curr.polyline.points.end());
|
|
return true;
|
|
} else if (next.height == curr.height) {
|
|
next.polyline.points.insert(next.polyline.points.begin(), curr.polyline.points.begin(), curr.polyline.points.end() - 1);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
});
|
|
|
|
foreach(paths, [](ExtrusionPath& prev, ExtrusionPath& curr, ExtrusionPath& next) {
|
|
if (curr.height == 3) {
|
|
//have to choose the rigth path
|
|
if (prev.height == 4 || (prev.height == 2 && next.height < 2)) {
|
|
//merge to previous
|
|
assert(prev.last_point() == curr.first_point());
|
|
assert(curr.polyline.points.size() > 1);
|
|
prev.polyline.points.insert(prev.polyline.points.end(), curr.polyline.points.begin() + 1, curr.polyline.points.end());
|
|
} else {
|
|
//merge to next
|
|
assert(curr.last_point() == next.first_point());
|
|
assert(curr.polyline.points.size() > 1);
|
|
next.polyline.points.insert(next.polyline.points.begin(), curr.polyline.points.begin(), curr.polyline.points.end() - 1);
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
foreach(paths, [](ExtrusionPath& prev, ExtrusionPath& curr, ExtrusionPath& next) {
|
|
if (curr.height == 1) {
|
|
//have to choose the rigth path
|
|
if (prev.height == 2 || (prev.height == 0 && next.height > 2)) {
|
|
//merge to previous
|
|
assert(prev.last_point() == curr.first_point());
|
|
assert(curr.polyline.points.size() > 1);
|
|
prev.polyline.points.insert(prev.polyline.points.end(), curr.polyline.points.begin() + 1, curr.polyline.points.end());
|
|
} else {
|
|
//merge to next
|
|
assert(curr.last_point() == next.first_point());
|
|
assert(curr.polyline.points.size() > 1);
|
|
next.polyline.points.insert(next.polyline.points.begin(), curr.polyline.points.begin(), curr.polyline.points.end() - 1);
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
}
|
|
if(paths.size() == 2){
|
|
double min_length = this->perimeter_flow.scaled_width() * 2;
|
|
if (paths.front().length() < min_length) {
|
|
paths.back().polyline.points.insert(paths.back().polyline.points.begin(), paths.front().polyline.points.begin(), paths.front().polyline.points.end() - 1);
|
|
paths.erase(paths.begin());
|
|
}else if (paths.back().length() < min_length) {
|
|
paths.front().polyline.points.insert(paths.front().polyline.points.end(), paths.back().polyline.points.begin() + 1, paths.back().polyline.points.end());
|
|
paths.erase(paths.begin() + 1);
|
|
}
|
|
}
|
|
//set correct height
|
|
for (ExtrusionPath& path : paths) {
|
|
path.height = path.height < 3 ? (float)this->layer->height : this->overhang_flow.height;
|
|
}
|
|
|
|
return paths;
|
|
}
|
|
|
|
ExtrusionEntityCollection PerimeterGenerator::_traverse_loops(
|
|
const PerimeterGeneratorLoops &loops, ThickPolylines &thin_walls) const
|
|
{
|
|
// loops is an arrayref of ::Loop objects
|
|
// turn each one into an ExtrusionLoop object
|
|
ExtrusionEntityCollection coll;
|
|
for (const PerimeterGeneratorLoop &loop : loops) {
|
|
bool is_external = loop.is_external();
|
|
|
|
ExtrusionRole role;
|
|
ExtrusionLoopRole loop_role = ExtrusionLoopRole::elrDefault;
|
|
role = is_external ? erExternalPerimeter : erPerimeter;
|
|
if (loop.is_internal_contour()) {
|
|
// Note that we set loop role to ContourInternalPerimeter
|
|
// also when loop is both internal and external (i.e.
|
|
// there's only one contour loop).
|
|
loop_role = ExtrusionLoopRole::elrInternal;
|
|
}
|
|
if (!loop.is_contour) {
|
|
loop_role = (ExtrusionLoopRole)(loop_role | ExtrusionLoopRole::elrHole);
|
|
}
|
|
if (loop.children.empty()) {
|
|
loop_role = ExtrusionLoopRole(loop_role | ExtrusionLoopRole::elrFirstLoop);
|
|
}
|
|
if (this->config->external_perimeters_vase.value && this->config->external_perimeters_first.value && is_external) {
|
|
if ((loop.is_contour && this->config->external_perimeters_nothole.value) || (!loop.is_contour && this->config->external_perimeters_hole.value)) {
|
|
loop_role = (ExtrusionLoopRole)(loop_role | ExtrusionLoopRole::elrVase);
|
|
}
|
|
}
|
|
|
|
// detect overhanging/bridging perimeters
|
|
ExtrusionPaths paths;
|
|
if ( this->config->overhangs_width_speed.value > 0 && this->layer->id() > 0
|
|
&& !(this->object_config->support_material && this->object_config->support_material_contact_distance_type.value == zdNone)) {
|
|
paths = this->create_overhangs(loop.polygon.split_at_first_point(), role, is_external);
|
|
} else {
|
|
ExtrusionPath path(role);
|
|
path.polyline = loop.polygon.split_at_first_point();
|
|
path.mm3_per_mm = is_external ? this->_ext_mm3_per_mm : this->_mm3_per_mm;
|
|
path.width = is_external ? this->ext_perimeter_flow.width : this->perimeter_flow.width;
|
|
path.height = (float) this->layer->height;
|
|
assert(path.mm3_per_mm == path.mm3_per_mm);
|
|
assert(path.width == path.width);
|
|
assert(path.height == path.height);
|
|
paths.push_back(path);
|
|
}
|
|
|
|
coll.append(ExtrusionLoop(paths, loop_role));
|
|
}
|
|
|
|
// append thin walls to the nearest-neighbor search (only for first iteration)
|
|
if (!thin_walls.empty()) {
|
|
ExtrusionEntityCollection tw = thin_variable_width(thin_walls, erThinWall, this->ext_perimeter_flow);
|
|
coll.append(tw.entities);
|
|
thin_walls.clear();
|
|
}
|
|
|
|
// traverse children and build the final collection
|
|
Point zero_point(0, 0);
|
|
//result is [idx, needReverse] ?
|
|
std::vector<std::pair<size_t, bool>> chain = chain_extrusion_entities(coll.entities, &zero_point);
|
|
ExtrusionEntityCollection coll_out;
|
|
if (chain.empty()) return coll_out;
|
|
|
|
//little check: if you have external holes with only one extrusion and internal things, please draw the internal first, just in case it can help print the hole better.
|
|
std::vector<std::pair<size_t, bool>> better_chain;
|
|
for (const std::pair<size_t, bool>& idx : chain) {
|
|
if(idx.first < loops.size())
|
|
if (!loops[idx.first].is_external() || (!loops[idx.first].is_contour && !loops[idx.first].children.empty()))
|
|
better_chain.push_back(idx);
|
|
}
|
|
for (const std::pair<size_t, bool>& idx : chain) {
|
|
if (idx.first < loops.size())
|
|
if (idx.first < loops.size() && loops[idx.first].is_external() && !(!loops[idx.first].is_contour && !loops[idx.first].children.empty()))
|
|
better_chain.push_back(idx);
|
|
}
|
|
//thin walls always last!
|
|
for (const std::pair<size_t, bool>& idx : chain) {
|
|
if (idx.first >= loops.size())
|
|
better_chain.push_back(idx);
|
|
}
|
|
|
|
//move from coll to coll_out and getting children of each in the same time. (deep first)
|
|
for (const std::pair<size_t, bool> &idx : better_chain) {
|
|
|
|
if (idx.first >= loops.size()) {
|
|
// this is a thin wall
|
|
// let's get it from the sorted collection as it might have been reversed
|
|
coll_out.entities.reserve(coll_out.entities.size() + 1);
|
|
coll_out.entities.emplace_back(coll.entities[idx.first]);
|
|
coll.entities[idx.first] = nullptr;
|
|
if (idx.second)
|
|
coll_out.entities.back()->reverse();
|
|
//if thin extrusion is a loop, make it ccw like a normal contour.
|
|
if (ExtrusionLoop* loop = dynamic_cast<ExtrusionLoop*>(coll_out.entities.back())) {
|
|
loop->make_counter_clockwise();
|
|
}
|
|
} else {
|
|
const PerimeterGeneratorLoop &loop = loops[idx.first];
|
|
assert(thin_walls.empty());
|
|
ExtrusionEntityCollection children = this->_traverse_loops(loop.children, thin_walls);
|
|
coll_out.entities.reserve(coll_out.entities.size() + children.entities.size() + 1);
|
|
ExtrusionLoop *eloop = static_cast<ExtrusionLoop*>(coll.entities[idx.first]);
|
|
coll.entities[idx.first] = nullptr;
|
|
if (loop.is_contour) {
|
|
//note: this->layer->id() % 2 == 1 already taken into account in the is_steep_overhang compute (to save time).
|
|
if (loop.is_steep_overhang && this->layer->id() % 2 == 1)
|
|
eloop->make_clockwise();
|
|
else
|
|
eloop->make_counter_clockwise();
|
|
coll_out.append(std::move(children.entities));
|
|
coll_out.append(*eloop);
|
|
} else {
|
|
eloop->make_clockwise();
|
|
coll_out.append(*eloop);
|
|
coll_out.append(std::move(children.entities));
|
|
}
|
|
}
|
|
}
|
|
return coll_out;
|
|
}
|
|
|
|
void PerimeterGenerator::_merge_thin_walls(ExtrusionEntityCollection &extrusions, ThickPolylines &thin_walls) const {
|
|
class ChangeFlow : public ExtrusionVisitor {
|
|
public:
|
|
float percent_extrusion;
|
|
std::vector<ExtrusionPath> paths;
|
|
virtual void use(ExtrusionPath &path) override {
|
|
path.mm3_per_mm *= percent_extrusion;
|
|
path.width *= percent_extrusion;
|
|
paths.emplace_back(std::move(path));
|
|
}
|
|
virtual void use(ExtrusionPath3D &path3D) override { /*shouldn't happen*/ }
|
|
virtual void use(ExtrusionMultiPath &multipath) override { /*shouldn't happen*/ }
|
|
virtual void use(ExtrusionMultiPath3D &multipath) { /*shouldn't happen*/ }
|
|
virtual void use(ExtrusionLoop &loop) override {
|
|
for (ExtrusionPath &path : loop.paths)
|
|
this->use(path);
|
|
}
|
|
virtual void use(ExtrusionEntityCollection &collection) override {
|
|
for (ExtrusionEntity *entity : collection.entities)
|
|
entity->visit(*this);
|
|
}
|
|
};
|
|
struct BestPoint {
|
|
//Point p;
|
|
ExtrusionPath *path;
|
|
size_t idx_path;
|
|
ExtrusionLoop *loop;
|
|
size_t idx_line;
|
|
Line line;
|
|
double dist;
|
|
bool from_start;
|
|
};
|
|
//use a visitor to found the best point.
|
|
class SearchBestPoint : public ExtrusionVisitor {
|
|
public:
|
|
ThickPolyline* thin_wall;
|
|
BestPoint search_result;
|
|
size_t idx_path;
|
|
ExtrusionLoop *current_loop = nullptr;
|
|
virtual void use(ExtrusionPath &path) override {
|
|
//don't consider other thin walls.
|
|
if (path.role() == erThinWall) return;
|
|
//for each segment
|
|
Lines lines = path.polyline.lines();
|
|
for (size_t idx_line = 0; idx_line < lines.size(); idx_line++) {
|
|
//look for nearest point
|
|
double dist = lines[idx_line].distance_to_squared(thin_wall->points.front());
|
|
if (dist < search_result.dist) {
|
|
search_result.path = &path;
|
|
search_result.idx_path = idx_path;
|
|
search_result.idx_line = idx_line;
|
|
search_result.line = lines[idx_line];
|
|
search_result.dist = dist;
|
|
search_result.from_start = true;
|
|
search_result.loop = current_loop;
|
|
}
|
|
dist = lines[idx_line].distance_to_squared(thin_wall->points.back());
|
|
if (dist < search_result.dist) {
|
|
search_result.path = &path;
|
|
search_result.idx_path = idx_path;
|
|
search_result.idx_line = idx_line;
|
|
search_result.line = lines[idx_line];
|
|
search_result.dist = dist;
|
|
search_result.from_start = false;
|
|
search_result.loop = current_loop;
|
|
}
|
|
}
|
|
}
|
|
virtual void use(ExtrusionPath3D &path3D) override { /*shouldn't happen*/ }
|
|
virtual void use(ExtrusionMultiPath &multipath) override { /*shouldn't happen*/ }
|
|
virtual void use(ExtrusionMultiPath3D &multipath) { /*shouldn't happen*/ }
|
|
virtual void use(ExtrusionLoop &loop) override {
|
|
ExtrusionLoop * last_loop = current_loop;
|
|
current_loop = &loop;
|
|
//for each extrusion path
|
|
idx_path = 0;
|
|
for (ExtrusionPath &path : loop.paths) {
|
|
this->use(path);
|
|
idx_path++;
|
|
}
|
|
current_loop = last_loop;
|
|
}
|
|
virtual void use(ExtrusionEntityCollection &collection) override {
|
|
collection.no_sort = false;
|
|
//for each loop? (or other collections)
|
|
for (ExtrusionEntity *entity : collection.entities)
|
|
entity->visit(*this);
|
|
}
|
|
};
|
|
//max dist to branch: ~half external periemeter width
|
|
coord_t max_width = this->ext_perimeter_flow.scaled_width();
|
|
SearchBestPoint searcher;
|
|
ThickPolylines not_added;
|
|
//search the best extusion/point to branch into
|
|
//for each thin wall
|
|
int idx = 0;
|
|
for (ThickPolyline &tw : thin_walls) {
|
|
searcher.thin_wall = &tw;
|
|
searcher.search_result.dist = double(max_width);
|
|
searcher.search_result.dist *= searcher.search_result.dist;
|
|
searcher.search_result.path = nullptr;
|
|
searcher.use(extrusions);
|
|
idx++;
|
|
//now insert thin wall if it has a point
|
|
//it found a segment
|
|
if (searcher.search_result.path != nullptr) {
|
|
if (!searcher.search_result.from_start)
|
|
tw.reverse();
|
|
//get the point
|
|
Point point = tw.points.front().projection_onto(searcher.search_result.line);
|
|
//we have to create 3 paths: 1: thinwall extusion, 2: thinwall return, 3: end of the path
|
|
//create new path : end of the path
|
|
Polyline poly_after;
|
|
poly_after.points.push_back(point);
|
|
poly_after.points.insert(poly_after.points.end(),
|
|
searcher.search_result.path->polyline.points.begin() + searcher.search_result.idx_line + 1,
|
|
searcher.search_result.path->polyline.points.end());
|
|
searcher.search_result.path->polyline.points.erase(
|
|
searcher.search_result.path->polyline.points.begin() + searcher.search_result.idx_line + 1,
|
|
searcher.search_result.path->polyline.points.end());
|
|
searcher.search_result.loop->paths[searcher.search_result.idx_path].polyline.points.push_back(point);
|
|
searcher.search_result.loop->paths.insert(searcher.search_result.loop->paths.begin() + 1 + searcher.search_result.idx_path,
|
|
ExtrusionPath(poly_after, *searcher.search_result.path));
|
|
//create thin wall path exttrusion
|
|
ExtrusionEntityCollection tws = thin_variable_width({ tw }, erThinWall, this->ext_perimeter_flow);
|
|
ChangeFlow change_flow;
|
|
if (tws.entities.size() == 1 && tws.entities[0]->is_loop()) {
|
|
//loop, just add it
|
|
change_flow.percent_extrusion = 1;
|
|
change_flow.use(tws);
|
|
//add move back
|
|
|
|
searcher.search_result.loop->paths.insert(searcher.search_result.loop->paths.begin() + 1 + searcher.search_result.idx_path,
|
|
change_flow.paths.begin(), change_flow.paths.end());
|
|
//add move to
|
|
|
|
} else {
|
|
//first add the return path
|
|
ExtrusionEntityCollection tws_second = tws;
|
|
tws_second.reverse();
|
|
change_flow.percent_extrusion = 0.1f;
|
|
change_flow.use(tws_second);
|
|
for (ExtrusionPath &path : change_flow.paths)
|
|
path.reverse();
|
|
//std::reverse(change_flow.paths.begin(), change_flow.paths.end());
|
|
searcher.search_result.loop->paths.insert(searcher.search_result.loop->paths.begin() + 1 + searcher.search_result.idx_path,
|
|
change_flow.paths.begin(), change_flow.paths.end());
|
|
//add the real extrusion path
|
|
change_flow.percent_extrusion = 0.9f;
|
|
change_flow.paths = std::vector<ExtrusionPath>();
|
|
change_flow.use(tws);
|
|
searcher.search_result.loop->paths.insert(searcher.search_result.loop->paths.begin() + 1 + searcher.search_result.idx_path,
|
|
change_flow.paths.begin(), change_flow.paths.end());
|
|
}
|
|
} else {
|
|
not_added.push_back(tw);
|
|
}
|
|
}
|
|
|
|
//now add thinwalls that have no anchor (make them reversable)
|
|
ExtrusionEntityCollection tws = thin_variable_width(not_added, erThinWall, this->ext_perimeter_flow);
|
|
extrusions.append(tws.entities);
|
|
}
|
|
|
|
PerimeterIntersectionPoint
|
|
PerimeterGenerator::_get_nearest_point(const PerimeterGeneratorLoops &children, ExtrusionLoop &myPolylines, const coord_t dist_cut, const coord_t max_dist) const {
|
|
//find best points of intersections
|
|
PerimeterIntersectionPoint intersect;
|
|
intersect.distance = 0x7FFFFFFF; // ! assumption on intersect type & max value
|
|
intersect.idx_polyline_outter = -1;
|
|
intersect.idx_children = -1;
|
|
for (size_t idx_child = 0; idx_child < children.size(); idx_child++) {
|
|
const PerimeterGeneratorLoop &child = children[idx_child];
|
|
for (size_t idx_poly = 0; idx_poly < myPolylines.paths.size(); idx_poly++) {
|
|
//if (myPolylines.paths[idx_poly].extruder_id == (unsigned int)-1) continue;
|
|
if (myPolylines.paths[idx_poly].length() < dist_cut + SCALED_RESOLUTION) continue;
|
|
|
|
if ((myPolylines.paths[idx_poly].role() == erExternalPerimeter || child.is_external() )
|
|
&& this->object_config->seam_position.value != SeamPosition::spRandom) {
|
|
//first, try to find 2 point near enough
|
|
for (size_t idx_point = 0; idx_point < myPolylines.paths[idx_poly].polyline.points.size(); idx_point++) {
|
|
const Point &p = myPolylines.paths[idx_poly].polyline.points[idx_point];
|
|
const Point &nearest_p = *child.polygon.closest_point(p);
|
|
const double dist = nearest_p.distance_to(p);
|
|
//Try to find a point in the far side, aligning them
|
|
if (dist + dist_cut / 20 < intersect.distance ||
|
|
(config->perimeter_loop_seam.value == spRear && (intersect.idx_polyline_outter <0 || p.y() > intersect.outter_best.y())
|
|
&& dist <= max_dist && intersect.distance + dist_cut / 20)) {
|
|
//ok, copy the idx
|
|
intersect.distance = (coord_t)nearest_p.distance_to(p);
|
|
intersect.idx_children = idx_child;
|
|
intersect.idx_polyline_outter = idx_poly;
|
|
intersect.outter_best = p;
|
|
intersect.child_best = nearest_p;
|
|
}
|
|
}
|
|
} else {
|
|
//first, try to find 2 point near enough
|
|
for (size_t idx_point = 0; idx_point < myPolylines.paths[idx_poly].polyline.points.size(); idx_point++) {
|
|
const Point &p = myPolylines.paths[idx_poly].polyline.points[idx_point];
|
|
const Point &nearest_p = *child.polygon.closest_point(p);
|
|
const double dist = nearest_p.distance_to(p);
|
|
if (dist + SCALED_EPSILON < intersect.distance ||
|
|
(config->perimeter_loop_seam.value == spRear && (intersect.idx_polyline_outter<0 || p.y() < intersect.outter_best.y())
|
|
&& dist <= max_dist && intersect.distance + dist_cut / 20)) {
|
|
//ok, copy the idx
|
|
intersect.distance = (coord_t)nearest_p.distance_to(p);
|
|
intersect.idx_children = idx_child;
|
|
intersect.idx_polyline_outter = idx_poly;
|
|
intersect.outter_best = p;
|
|
intersect.child_best = nearest_p;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (intersect.distance <= max_dist) {
|
|
return intersect;
|
|
}
|
|
|
|
for (size_t idx_child = 0; idx_child < children.size(); idx_child++) {
|
|
const PerimeterGeneratorLoop &child = children[idx_child];
|
|
for (size_t idx_poly = 0; idx_poly < myPolylines.paths.size(); idx_poly++) {
|
|
//if (myPolylines.paths[idx_poly].extruder_id == (unsigned int)-1) continue;
|
|
if (myPolylines.paths[idx_poly].length() < dist_cut + SCALED_RESOLUTION) continue;
|
|
|
|
//second, try to check from one of my points
|
|
//don't check the last point, as it's used to go outter, can't use it to go inner.
|
|
for (size_t idx_point = 1; idx_point < myPolylines.paths[idx_poly].polyline.points.size()-1; idx_point++) {
|
|
const Point &p = myPolylines.paths[idx_poly].polyline.points[idx_point];
|
|
Point nearest_p = child.polygon.point_projection(p);
|
|
coord_t dist = (coord_t)nearest_p.distance_to(p);
|
|
//if no projection, go to next
|
|
if (dist == 0) continue;
|
|
if (dist + SCALED_EPSILON / 2 < intersect.distance) {
|
|
//ok, copy the idx
|
|
intersect.distance = dist;
|
|
intersect.idx_children = idx_child;
|
|
intersect.idx_polyline_outter = idx_poly;
|
|
intersect.outter_best = p;
|
|
intersect.child_best = nearest_p;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (intersect.distance <= max_dist) {
|
|
return intersect;
|
|
}
|
|
|
|
for (size_t idx_child = 0; idx_child < children.size(); idx_child++) {
|
|
const PerimeterGeneratorLoop &child = children[idx_child];
|
|
for (size_t idx_poly = 0; idx_poly < myPolylines.paths.size(); idx_poly++) {
|
|
//if (myPolylines.paths[idx_poly].extruder_id == (unsigned int)-1) continue;
|
|
if (myPolylines.paths[idx_poly].length() < dist_cut + SCALED_RESOLUTION) continue;
|
|
|
|
//lastly, try to check from one of his points
|
|
for (size_t idx_point = 0; idx_point < child.polygon.points.size(); idx_point++) {
|
|
const Point &p = child.polygon.points[idx_point];
|
|
Point nearest_p = myPolylines.paths[idx_poly].polyline.point_projection(p);
|
|
coord_t dist = (coord_t)nearest_p.distance_to(p);
|
|
//if no projection, go to next
|
|
if (dist == 0) continue;
|
|
if (dist + SCALED_EPSILON / 2 < intersect.distance) {
|
|
//ok, copy the idx
|
|
intersect.distance = dist;
|
|
intersect.idx_children = idx_child;
|
|
intersect.idx_polyline_outter = idx_poly;
|
|
intersect.outter_best = nearest_p;
|
|
intersect.child_best = p;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return intersect;
|
|
}
|
|
|
|
|
|
ExtrusionLoop
|
|
PerimeterGenerator::_extrude_and_cut_loop(const PerimeterGeneratorLoop &loop, const Point entry_point, const Line &direction) const
|
|
{
|
|
|
|
bool need_to_reverse = false;
|
|
Polyline initial_polyline;
|
|
const coord_t dist_cut = (coord_t)scale_(this->print_config->nozzle_diameter.get_at(this->config->perimeter_extruder - 1));
|
|
|
|
|
|
if (loop.polygon.points.size() < 3) return ExtrusionLoop(elrDefault);
|
|
if (loop.polygon.length() < dist_cut * 2) {
|
|
ExtrusionLoop single_point(elrDefault);
|
|
Polyline poly_point;
|
|
poly_point.append(loop.polygon.centroid());
|
|
single_point.paths.emplace_back(
|
|
loop.is_external() ? erExternalPerimeter : erPerimeter,
|
|
(double)(loop.is_external() ? this->_ext_mm3_per_mm : this->_mm3_per_mm),
|
|
(float)(loop.is_external() ? this->ext_perimeter_flow.width : this->perimeter_flow.width),
|
|
(float)(this->layer->height));
|
|
single_point.paths.back().polyline = poly_point;
|
|
return single_point;
|
|
}
|
|
const size_t idx_closest_from_entry_point = loop.polygon.closest_point_index(entry_point);
|
|
if (loop.polygon.points[idx_closest_from_entry_point].distance_to(entry_point) > SCALED_EPSILON) {
|
|
//create new Point
|
|
//get first point
|
|
size_t idx_before = -1;
|
|
for (size_t idx_p_a = 0; idx_p_a < loop.polygon.points.size(); ++idx_p_a) {
|
|
Line l(loop.polygon.points[idx_p_a], loop.polygon.points[(idx_p_a + 1 == loop.polygon.points.size()) ? 0 : (idx_p_a + 1)]);
|
|
if (entry_point.distance_to(l) < SCALED_EPSILON) {
|
|
idx_before = idx_p_a;
|
|
break;
|
|
}
|
|
}
|
|
if (idx_before == (size_t)-1) std::cerr << "ERROR: _traverse_and_join_loops : idx_before can't be finded to create new point\n";
|
|
initial_polyline = loop.polygon.split_at_index(idx_before);
|
|
initial_polyline.points.push_back(entry_point);
|
|
initial_polyline.points[0] = entry_point;
|
|
} else {
|
|
initial_polyline = loop.polygon.split_at_index(idx_closest_from_entry_point);
|
|
}
|
|
|
|
|
|
//std::vector<PerimeterPolylineNode> myPolylines;
|
|
ExtrusionLoop my_loop;
|
|
|
|
//overhang / notoverhang
|
|
{
|
|
bool is_external = loop.is_external();
|
|
|
|
ExtrusionRole role;
|
|
ExtrusionLoopRole loop_role;
|
|
role = is_external ? erExternalPerimeter : erPerimeter;
|
|
if (loop.is_internal_contour()) {
|
|
// Note that we set loop role to ContourInternalPerimeter
|
|
// also when loop is both internal and external (i.e.
|
|
// there's only one contour loop).
|
|
loop_role = elrInternal;
|
|
} else {
|
|
loop_role = elrDefault;
|
|
}
|
|
if (!loop.is_contour) {
|
|
loop_role = (ExtrusionLoopRole)(loop_role | elrHole);
|
|
}
|
|
|
|
// detect overhanging/bridging perimeters
|
|
if ( this->config->overhangs_width_speed.value > 0 && this->layer->id() > 0
|
|
&& !(this->object_config->support_material && this->object_config->support_material_contact_distance_type.value == zdNone)) {
|
|
ExtrusionPaths paths = this->create_overhangs(initial_polyline, role, is_external);
|
|
|
|
if (direction.length() > 0) {
|
|
Polyline direction_polyline;
|
|
for (ExtrusionPath &path : paths) {
|
|
direction_polyline.points.insert(direction_polyline.points.end(), path.polyline.points.begin(), path.polyline.points.end());
|
|
}
|
|
direction_polyline.clip_start(SCALED_RESOLUTION);
|
|
direction_polyline.clip_end(SCALED_RESOLUTION);
|
|
coord_t dot = direction.dot(Line(direction_polyline.points.back(), direction_polyline.points.front()));
|
|
need_to_reverse = dot>0;
|
|
}
|
|
if (need_to_reverse) {
|
|
std::reverse(paths.begin(), paths.end());
|
|
}
|
|
//search for the first path
|
|
size_t good_idx = 0;
|
|
for (size_t idx_path = 0; idx_path < paths.size(); idx_path++) {
|
|
const ExtrusionPath &path = paths[idx_path];
|
|
if (need_to_reverse) {
|
|
if (path.polyline.points.back().coincides_with_epsilon(initial_polyline.points.front())) {
|
|
good_idx = idx_path;
|
|
break;
|
|
}
|
|
} else {
|
|
if (path.polyline.points.front().coincides_with_epsilon(initial_polyline.points.front())) {
|
|
good_idx = idx_path;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
for (size_t idx_path = good_idx; idx_path < paths.size(); idx_path++) {
|
|
ExtrusionPath &path = paths[idx_path];
|
|
if (need_to_reverse) path.reverse();
|
|
my_loop.paths.emplace_back(path);
|
|
}
|
|
for (size_t idx_path = 0; idx_path < good_idx; idx_path++) {
|
|
ExtrusionPath &path = paths[idx_path];
|
|
if (need_to_reverse) path.reverse();
|
|
my_loop.paths.emplace_back(path);
|
|
}
|
|
} else {
|
|
|
|
if (direction.length() > 0) {
|
|
Polyline direction_polyline = initial_polyline;
|
|
direction_polyline.clip_start(SCALED_RESOLUTION);
|
|
direction_polyline.clip_end(SCALED_RESOLUTION);
|
|
coord_t dot = direction.dot(Line(direction_polyline.points.back(), direction_polyline.points.front()));
|
|
need_to_reverse = dot>0;
|
|
}
|
|
|
|
ExtrusionPath path(role);
|
|
path.polyline = initial_polyline;
|
|
if (need_to_reverse) path.polyline.reverse();
|
|
path.mm3_per_mm = is_external ? this->_ext_mm3_per_mm : this->_mm3_per_mm;
|
|
path.width = is_external ? this->ext_perimeter_flow.width : this->perimeter_flow.width;
|
|
path.height = (float)(this->layer->height);
|
|
my_loop.paths.emplace_back(path);
|
|
}
|
|
|
|
}
|
|
|
|
return my_loop;
|
|
}
|
|
|
|
ExtrusionLoop
|
|
PerimeterGenerator::_traverse_and_join_loops(const PerimeterGeneratorLoop &loop, const PerimeterGeneratorLoops &children, const Point entry_point) const
|
|
{
|
|
//std::cout << " === ==== _traverse_and_join_loops ==== ===\n";
|
|
// other perimeters
|
|
//this->_mm3_per_mm = this->perimeter_flow.mm3_per_mm();
|
|
//coord_t perimeter_width = this->perimeter_flow.scaled_width();
|
|
const coord_t perimeter_spacing = this->perimeter_flow.scaled_spacing();
|
|
|
|
//// external perimeters
|
|
//this->_ext_mm3_per_mm = this->ext_perimeter_flow.mm3_per_mm();
|
|
//coord_t ext_perimeter_width = this->ext_perimeter_flow.scaled_width();
|
|
const coord_t ext_perimeter_spacing = this->ext_perimeter_flow.scaled_spacing();
|
|
//coord_t ext_perimeter_spacing2 = this->ext_perimeter_flow.scaled_spacing(this->perimeter_flow);
|
|
|
|
//const coord_t dist_cut = (coord_t)scale_(this->print_config->nozzle_diameter.get_at(this->config->perimeter_extruder - 1));
|
|
//TODO change this->external_perimeter_flow.scaled_width() if it's the first one!
|
|
const coord_t max_width_extrusion = this->perimeter_flow.scaled_width();
|
|
ExtrusionLoop my_loop = _extrude_and_cut_loop(loop, entry_point);
|
|
|
|
int child_idx = 0;
|
|
//Polylines myPolylines = { myPolyline };
|
|
//iterate on each point ot find the best place to go into the child
|
|
PerimeterGeneratorLoops childs = children;
|
|
while (!childs.empty()) {
|
|
child_idx++;
|
|
PerimeterIntersectionPoint nearest = this->_get_nearest_point(childs, my_loop, coord_t(this->perimeter_flow.scaled_width()), coord_t(this->perimeter_flow.scaled_width()* 1.42));
|
|
if (nearest.idx_children == (size_t)-1) {
|
|
//return ExtrusionEntityCollection();
|
|
break;
|
|
} else {
|
|
const PerimeterGeneratorLoop &child = childs[nearest.idx_children];
|
|
//std::cout << "c." << child_idx << " === i have " << my_loop.paths.size() << " paths" << " == cut_path_is_ccw size " << path_is_ccw.size() << "\n";
|
|
//std::cout << "change to child " << nearest.idx_children << " @ " << unscale(nearest.outter_best.x) << ":" << unscale(nearest.outter_best.y)
|
|
// << ", idxpolyline = " << nearest.idx_polyline_outter << "\n";
|
|
//PerimeterGeneratorLoops less_childs = childs;
|
|
//less_childs.erase(less_childs.begin() + nearest.idx_children);
|
|
//create new node with recursive ask for the inner perimeter & COPY of the points, ready to be cut
|
|
my_loop.paths.insert(my_loop.paths.begin() + nearest.idx_polyline_outter + 1, my_loop.paths[nearest.idx_polyline_outter]);
|
|
|
|
ExtrusionPath *outer_start = &my_loop.paths[nearest.idx_polyline_outter];
|
|
ExtrusionPath *outer_end = &my_loop.paths[nearest.idx_polyline_outter + 1];
|
|
Line deletedSection;
|
|
|
|
//cut our polyline
|
|
//separate them
|
|
size_t nearest_idx_outter = outer_start->polyline.closest_point_index(nearest.outter_best);
|
|
if (outer_start->polyline.points[nearest_idx_outter].coincides_with_epsilon(nearest.outter_best)) {
|
|
if (nearest_idx_outter < outer_start->polyline.points.size() - 1) {
|
|
outer_start->polyline.points.erase(outer_start->polyline.points.begin() + nearest_idx_outter + 1, outer_start->polyline.points.end());
|
|
}
|
|
if (nearest_idx_outter > 0) {
|
|
outer_end->polyline.points.erase(outer_end->polyline.points.begin(), outer_end->polyline.points.begin() + nearest_idx_outter);
|
|
}
|
|
} else {
|
|
//get first point
|
|
size_t idx_before = -1;
|
|
for (size_t idx_p_a = 0; idx_p_a < outer_start->polyline.points.size() - 1; ++idx_p_a) {
|
|
Line l(outer_start->polyline.points[idx_p_a], outer_start->polyline.points[idx_p_a + 1]);
|
|
if (nearest.outter_best.distance_to(l) < SCALED_EPSILON) {
|
|
idx_before = idx_p_a;
|
|
break;
|
|
}
|
|
}
|
|
if (idx_before == (size_t)-1) {
|
|
std::cerr << "ERROR: idx_before can't be finded\n";
|
|
continue;
|
|
}
|
|
|
|
Points &my_polyline_points = outer_start->polyline.points;
|
|
my_polyline_points.erase(my_polyline_points.begin() + idx_before + 1, my_polyline_points.end());
|
|
my_polyline_points.push_back(nearest.outter_best);
|
|
|
|
if (idx_before < outer_end->polyline.points.size()-1)
|
|
outer_end->polyline.points.erase(outer_end->polyline.points.begin(), outer_end->polyline.points.begin() + idx_before + 1);
|
|
else
|
|
outer_end->polyline.points.erase(outer_end->polyline.points.begin()+1, outer_end->polyline.points.end());
|
|
outer_end->polyline.points.insert(outer_end->polyline.points.begin(), nearest.outter_best);
|
|
}
|
|
Polyline to_reduce = outer_start->polyline;
|
|
if (to_reduce.points.size()>1) to_reduce.clip_end(SCALED_RESOLUTION);
|
|
deletedSection.a = to_reduce.points.back();
|
|
to_reduce = outer_end->polyline;
|
|
if (to_reduce.points.size()>1) to_reduce.clip_start(SCALED_RESOLUTION);
|
|
deletedSection.b = to_reduce.points.front();
|
|
|
|
//get the inner loop to connect to us.
|
|
ExtrusionLoop child_loop = _extrude_and_cut_loop(child, nearest.child_best, deletedSection);
|
|
|
|
const coord_t inner_child_spacing = child.is_external() ? ext_perimeter_spacing : perimeter_spacing;
|
|
const coord_t outer_start_spacing = scale_(outer_start->width - outer_start->height * (1. - 0.25 * PI));
|
|
const coord_t outer_end_spacing = scale_(outer_end->width - outer_end->height * (1. - 0.25 * PI));
|
|
|
|
//FIXME: if child_loop has no point or 1 point or not enough space !!!!!!!
|
|
const size_t child_paths_size = child_loop.paths.size();
|
|
if (child_paths_size == 0) continue;
|
|
my_loop.paths.insert(my_loop.paths.begin() + nearest.idx_polyline_outter + 1, child_loop.paths.begin(), child_loop.paths.end());
|
|
|
|
//add paths into my_loop => need to re-get the refs
|
|
outer_start = &my_loop.paths[nearest.idx_polyline_outter];
|
|
outer_end = &my_loop.paths[nearest.idx_polyline_outter + child_paths_size + 1];
|
|
ExtrusionPath *inner_start = &my_loop.paths[nearest.idx_polyline_outter+1];
|
|
ExtrusionPath *inner_end = &my_loop.paths[nearest.idx_polyline_outter + child_paths_size];
|
|
//TRIM
|
|
//choose trim direction
|
|
if (outer_start->polyline.points.size() == 1 && outer_end->polyline.points.size() == 1) {
|
|
//do nothing
|
|
} else if (outer_start->polyline.points.size() == 1) {
|
|
outer_end->polyline.clip_start(double(outer_end_spacing));
|
|
if (inner_end->polyline.length() > inner_child_spacing)
|
|
inner_end->polyline.clip_end(double(inner_child_spacing));
|
|
else
|
|
inner_end->polyline.clip_end(inner_end->polyline.length() / 2);
|
|
} else if (outer_end->polyline.points.size() == 1) {
|
|
outer_start->polyline.clip_end(double(outer_start_spacing));
|
|
if (inner_start->polyline.length() > inner_child_spacing)
|
|
inner_start->polyline.clip_start(double(inner_child_spacing));
|
|
else
|
|
inner_start->polyline.clip_start(inner_start->polyline.length()/2);
|
|
} else {
|
|
coord_t length_poly_1 = (coord_t)outer_start->polyline.length();
|
|
coord_t length_poly_2 = (coord_t)outer_end->polyline.length();
|
|
coord_t length_trim_1 = outer_start_spacing / 2;
|
|
coord_t length_trim_2 = outer_end_spacing / 2;
|
|
if (length_poly_1 < length_trim_1) {
|
|
length_trim_2 = length_trim_1 + length_trim_2 - length_poly_1;
|
|
}
|
|
if (length_poly_2 < length_trim_1) {
|
|
length_trim_1 = length_trim_1 + length_trim_2 - length_poly_2;
|
|
}
|
|
if (length_poly_1 > length_trim_1) {
|
|
outer_start->polyline.clip_end(double(length_trim_1));
|
|
} else {
|
|
outer_start->polyline.points.erase(outer_start->polyline.points.begin() + 1, outer_start->polyline.points.end());
|
|
}
|
|
if (length_poly_2 > length_trim_2) {
|
|
outer_end->polyline.clip_start(double(length_trim_2));
|
|
} else {
|
|
outer_end->polyline.points.erase(outer_end->polyline.points.begin(), outer_end->polyline.points.end() - 1);
|
|
}
|
|
|
|
length_poly_1 = coord_t(inner_start->polyline.length());
|
|
length_poly_2 = coord_t(inner_end->polyline.length());
|
|
length_trim_1 = inner_child_spacing / 2;
|
|
length_trim_2 = inner_child_spacing / 2;
|
|
if (length_poly_1 < length_trim_1) {
|
|
length_trim_2 = length_trim_1 + length_trim_2 - length_poly_1;
|
|
}
|
|
if (length_poly_2 < length_trim_1) {
|
|
length_trim_1 = length_trim_1 + length_trim_2 - length_poly_2;
|
|
}
|
|
if (length_poly_1 > length_trim_1) {
|
|
inner_start->polyline.clip_start(double(length_trim_1));
|
|
} else {
|
|
inner_start->polyline.points.erase(
|
|
inner_start->polyline.points.begin(),
|
|
inner_start->polyline.points.end() - 1);
|
|
}
|
|
if (length_poly_2 > length_trim_2) {
|
|
inner_end->polyline.clip_end(double(length_trim_2));
|
|
} else {
|
|
inner_end->polyline.points.erase(
|
|
inner_end->polyline.points.begin() + 1,
|
|
inner_end->polyline.points.end());
|
|
}
|
|
}
|
|
|
|
//last check to see if we need a reverse
|
|
{
|
|
Line l1(outer_start->polyline.points.back(), inner_start->polyline.points.front());
|
|
Line l2(inner_end->polyline.points.back(), outer_end->polyline.points.front());
|
|
Point p_inter(0, 0);
|
|
bool is_interect = l1.intersection(l2, &p_inter);
|
|
if (is_interect && p_inter.distance_to(l1) < SCALED_EPSILON && p_inter.distance_to(l2) < SCALED_EPSILON) {
|
|
//intersection! need to reverse!
|
|
std::reverse(my_loop.paths.begin() + nearest.idx_polyline_outter + 1, my_loop.paths.begin() + nearest.idx_polyline_outter + child_paths_size + 1);
|
|
for (size_t idx = nearest.idx_polyline_outter + 1; idx < nearest.idx_polyline_outter + child_paths_size + 1; idx++) {
|
|
my_loop.paths[idx].reverse();
|
|
}
|
|
outer_start = &my_loop.paths[nearest.idx_polyline_outter];
|
|
inner_start = &my_loop.paths[nearest.idx_polyline_outter + 1];
|
|
inner_end = &my_loop.paths[nearest.idx_polyline_outter + child_paths_size];
|
|
outer_end = &my_loop.paths[nearest.idx_polyline_outter + child_paths_size + 1];
|
|
}
|
|
|
|
}
|
|
|
|
//now add extrusionPAths to connect the two loops
|
|
ExtrusionPaths travel_path_begin;// (ExtrusionRole::erNone, 0, outer_start->width, outer_start->height);
|
|
//travel_path_begin.extruder_id = -1;
|
|
ExtrusionPaths travel_path_end;// (ExtrusionRole::erNone, 0, outer_end->width, outer_end->height);
|
|
//travel_path_end.extruder_id = -1;
|
|
double dist_travel = outer_start->polyline.points.back().distance_to(inner_start->polyline.points.front());
|
|
if (dist_travel > max_width_extrusion*1.5 && this->config->fill_density.value > 0) {
|
|
travel_path_begin.emplace_back(ExtrusionRole::erPerimeter, outer_start->mm3_per_mm, outer_start->width, outer_start->height);
|
|
travel_path_begin.emplace_back(ExtrusionRole::erNone, 0, outer_start->width, outer_start->height);
|
|
travel_path_begin.emplace_back(ExtrusionRole::erPerimeter, outer_start->mm3_per_mm, outer_start->width, outer_start->height);
|
|
//travel_path_begin[0].extruder_id = -1;
|
|
//travel_path_begin[1].extruder_id = -1;
|
|
//travel_path_begin[2].extruder_id = -1;
|
|
Line line(outer_start->polyline.points.back(), inner_start->polyline.points.front());
|
|
Point p_dist_cut_extrude = (line.b - line.a);
|
|
p_dist_cut_extrude.x() = (coord_t)(p_dist_cut_extrude.x() * ((double)max_width_extrusion) / (line.length() * 2));
|
|
p_dist_cut_extrude.y() = (coord_t)(p_dist_cut_extrude.y() * ((double)max_width_extrusion) / (line.length() * 2));
|
|
//extrude a bit after the turn, to close the loop
|
|
Point p_start_travel = line.a;
|
|
p_start_travel += p_dist_cut_extrude;
|
|
travel_path_begin[0].polyline.append(outer_start->polyline.points.back());
|
|
travel_path_begin[0].polyline.append(p_start_travel);
|
|
//extrude a bit before the final turn, to close the loop
|
|
Point p_end_travel = line.b;
|
|
p_end_travel -= p_dist_cut_extrude;
|
|
travel_path_begin[2].polyline.append(p_end_travel);
|
|
travel_path_begin[2].polyline.append(inner_start->polyline.points.front());
|
|
//fake travel in the middle
|
|
travel_path_begin[1].polyline.append(p_start_travel);
|
|
travel_path_begin[1].polyline.append(p_end_travel);
|
|
} else {
|
|
// the path is small enough to extrude all along.
|
|
double flow_mult = 1;
|
|
if (dist_travel > max_width_extrusion && this->config->fill_density.value > 0) {
|
|
// the path is a bit too long, reduce the extrusion flow.
|
|
flow_mult = max_width_extrusion / dist_travel;
|
|
}
|
|
travel_path_begin.emplace_back(ExtrusionRole::erPerimeter, outer_start->mm3_per_mm * flow_mult, (float)(outer_start->width * flow_mult), outer_start->height);
|
|
//travel_path_begin[0].extruder_id = -1;
|
|
travel_path_begin[0].polyline.append(outer_start->polyline.points.back());
|
|
travel_path_begin[0].polyline.append(inner_start->polyline.points.front());
|
|
}
|
|
dist_travel = inner_end->polyline.points.back().distance_to(outer_end->polyline.points.front());
|
|
if (dist_travel > max_width_extrusion*1.5 && this->config->fill_density.value > 0) {
|
|
travel_path_end.emplace_back(ExtrusionRole::erPerimeter, outer_end->mm3_per_mm, outer_end->width, outer_end->height);
|
|
travel_path_end.emplace_back(ExtrusionRole::erNone, 0, outer_end->width, outer_end->height);
|
|
travel_path_end.emplace_back(ExtrusionRole::erPerimeter, outer_end->mm3_per_mm, outer_end->width, outer_end->height);
|
|
//travel_path_end[0].extruder_id = -1;
|
|
//travel_path_end[1].extruder_id = -1;
|
|
//travel_path_end[2].extruder_id = -1;
|
|
Line line(inner_end->polyline.points.back(), outer_end->polyline.points.front());
|
|
Point p_dist_cut_extrude = (line.b - line.a);
|
|
p_dist_cut_extrude.x() = (coord_t)(p_dist_cut_extrude.x() * ((double)max_width_extrusion) / (line.length() * 2));
|
|
p_dist_cut_extrude.y() = (coord_t)(p_dist_cut_extrude.y() * ((double)max_width_extrusion) / (line.length() * 2));
|
|
//extrude a bit after the turn, to close the loop
|
|
Point p_start_travel_2 = line.a;
|
|
p_start_travel_2 += p_dist_cut_extrude;
|
|
travel_path_end[0].polyline.append(inner_end->polyline.points.back());
|
|
travel_path_end[0].polyline.append(p_start_travel_2);
|
|
//extrude a bit before the final turn, to close the loop
|
|
Point p_end_travel_2 = line.b;
|
|
p_end_travel_2 -= p_dist_cut_extrude;
|
|
travel_path_end[2].polyline.append(p_end_travel_2);
|
|
travel_path_end[2].polyline.append(outer_end->polyline.points.front());
|
|
//fake travel in the middle
|
|
travel_path_end[1].polyline.append(p_start_travel_2);
|
|
travel_path_end[1].polyline.append(p_end_travel_2);
|
|
} else {
|
|
// the path is small enough to extrude all along.
|
|
double flow_mult = 1;
|
|
if (dist_travel > max_width_extrusion && this->config->fill_density.value > 0) {
|
|
// the path is a bit too long, reduce the extrusion flow.
|
|
flow_mult = max_width_extrusion / dist_travel;
|
|
}
|
|
travel_path_end.emplace_back(ExtrusionRole::erPerimeter, outer_end->mm3_per_mm * flow_mult, (float)(outer_end->width * flow_mult), outer_end->height);
|
|
//travel_path_end[0].extruder_id = -1;
|
|
travel_path_end[0].polyline.append(inner_end->polyline.points.back());
|
|
travel_path_end[0].polyline.append(outer_end->polyline.points.front());
|
|
}
|
|
//check if we add path or reuse bits
|
|
//FIXME
|
|
/*if (outer_start->polyline.points.size() == 1) {
|
|
outer_start->polyline = travel_path_begin.front().polyline;
|
|
travel_path_begin.erase(travel_path_begin.begin());
|
|
outer_start->extruder_id = -1;
|
|
} else if (outer_end->polyline.points.size() == 1) {
|
|
outer_end->polyline = travel_path_end.back().polyline;
|
|
travel_path_end.erase(travel_path_end.end() - 1);
|
|
outer_end->extruder_id = -1;
|
|
}*/
|
|
//add paths into my_loop => after that all ref are wrong!
|
|
for (size_t i = travel_path_end.size() - 1; i < travel_path_end.size(); i--) {
|
|
my_loop.paths.insert(my_loop.paths.begin() + nearest.idx_polyline_outter + child_paths_size + 1, travel_path_end[i]);
|
|
}
|
|
for (size_t i = travel_path_begin.size() - 1; i < travel_path_begin.size(); i--) {
|
|
my_loop.paths.insert(my_loop.paths.begin() + nearest.idx_polyline_outter + 1, travel_path_begin[i]);
|
|
}
|
|
}
|
|
|
|
//update for next loop
|
|
childs.erase(childs.begin() + nearest.idx_children);
|
|
}
|
|
|
|
return my_loop;
|
|
}
|
|
|
|
bool PerimeterGeneratorLoop::is_internal_contour() const
|
|
{
|
|
// An internal contour is a contour containing no other contours
|
|
if (! this->is_contour)
|
|
return false;
|
|
for (const PerimeterGeneratorLoop &loop : this->children)
|
|
if (loop.is_contour)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
}
|