Polygons AreaSupport::join(Polygons& supportLayer_up, Polygons& supportLayer_this, int64_t supportJoinDistance, int64_t smoothing_distance, int min_smoothing_area, bool conical_support, int64_t conical_support_offset, int64_t conical_smallest_breadth) { Polygons joined; if (conical_support) { Polygons insetted = supportLayer_up.offset(-conical_smallest_breadth/2); Polygons small_parts = supportLayer_up.difference(insetted.offset(conical_smallest_breadth/2+20)); joined = supportLayer_this.unionPolygons(supportLayer_up.offset(conical_support_offset)) .unionPolygons(small_parts); } else { joined = supportLayer_this.unionPolygons(supportLayer_up); } // join different parts if (supportJoinDistance > 0) { joined = joined.offset(supportJoinDistance) .offset(-supportJoinDistance); } if (smoothing_distance > 0) joined = joined.smooth(smoothing_distance, min_smoothing_area); return joined; }
void offsetSafe(Polygons& poly, int distance, int extrusionWidth, Polygons& result, bool removeOverlappingPerimeters) { int direction = (distance > 0)? 1 : -1; if (!removeOverlappingPerimeters) { result = poly.offset(distance); return; } else { result = poly.offset(distance + direction*extrusionWidth/2).offset(-direction * extrusionWidth/2); } }
Polygons AreaSupport::join(Polygons& supportLayer_up, Polygons& supportLayer_this, int64_t supportJoinDistance, int64_t smoothing_distance, int max_smoothing_angle, bool conical_support, int64_t conical_support_offset, int64_t conical_smallest_breadth) { Polygons joined; if (conical_support) { Polygons insetted = supportLayer_up.offset(-conical_smallest_breadth/2); Polygons small_parts = supportLayer_up.difference(insetted.offset(conical_smallest_breadth/2+20)); joined = supportLayer_this.unionPolygons(supportLayer_up.offset(conical_support_offset)) .unionPolygons(small_parts); } else { joined = supportLayer_this.unionPolygons(supportLayer_up); } // join different parts if (supportJoinDistance > 0) { joined = joined.offset(supportJoinDistance) .offset(-supportJoinDistance); } // remove jagged line pieces introduced by unioning separate overhang areas for consectuive layers // // support may otherwise look like: // _____________________ . // / \ } dist_from_lower_layer // /__ __\ / // /''--...........--''\ `\ . // / \ } dist_from_lower_layer // /__ __\ ./ // /''--...........--''\ `\ . // / \ } dist_from_lower_layer // /_______________________\ ,/ // rather than // _____________________ // / \ . // / \ . // | | // | | // | | // | | // | | // |_______________________| // // dist_from_lower_layer may be up to max_dist_from_lower_layer (see below), but that value may be extremely high joined = joined.smooth_outward(max_smoothing_angle, smoothing_distance); return joined; }
void offsetExtrusionWidth(Polygons& poly, bool inward, int extrusionWidth, Polygons& result, Polygons* in_between, bool removeOverlappingPerimeters) { int distance = (inward)? -extrusionWidth : extrusionWidth; if (!removeOverlappingPerimeters) { result = poly.offset(distance); return; } else { result = poly.offset(distance*3/2).offset(-distance/2); // overshoot by half the extrusionWidth if (in_between) // if a pointer for in_between is given in_between->add(poly.offset(distance/2).difference(result.offset(-distance/2))); } }
int SkirtBrim::generatePrimarySkirtBrimLines(const coord_t start_distance, size_t primary_line_count, const coord_t primary_extruder_minimal_length, const Polygons& first_layer_outline, Polygons& skirt_brim_primary_extruder) { const Settings& adhesion_settings = Application::getInstance().current_slice->scene.current_mesh_group->settings.get<ExtruderTrain&>("adhesion_extruder_nr").settings; const coord_t primary_extruder_skirt_brim_line_width = adhesion_settings.get<coord_t>("skirt_brim_line_width") * adhesion_settings.get<Ratio>("initial_layer_line_width_factor"); coord_t offset_distance = start_distance - primary_extruder_skirt_brim_line_width / 2; for (unsigned int skirt_brim_number = 0; skirt_brim_number < primary_line_count; skirt_brim_number++) { offset_distance += primary_extruder_skirt_brim_line_width; Polygons outer_skirt_brim_line = first_layer_outline.offset(offset_distance, ClipperLib::jtRound); //Remove small inner skirt and brim holes. Holes have a negative area, remove anything smaller then 100x extrusion "area" for (unsigned int n = 0; n < outer_skirt_brim_line.size(); n++) { double area = outer_skirt_brim_line[n].area(); if (area < 0 && area > -primary_extruder_skirt_brim_line_width * primary_extruder_skirt_brim_line_width * 100) { outer_skirt_brim_line.remove(n--); } } skirt_brim_primary_extruder.add(outer_skirt_brim_line); int length = skirt_brim_primary_extruder.polygonLength(); if (skirt_brim_number + 1 >= primary_line_count && length > 0 && length < primary_extruder_minimal_length) //Make brim or skirt have more lines when total length is too small. { primary_line_count++; } } return offset_distance; }
int SkirtBrim::generatePrimarySkirtBrimLines(int start_distance, unsigned int primary_line_count, const int primary_extruder_skirt_brim_line_width, const int64_t primary_extruder_minimal_length, const Polygons& first_layer_outline, Polygons& skirt_brim_primary_extruder) { int offset_distance = start_distance - primary_extruder_skirt_brim_line_width / 2; for (unsigned int skirt_brim_number = 0; skirt_brim_number < primary_line_count; skirt_brim_number++) { offset_distance += primary_extruder_skirt_brim_line_width; Polygons outer_skirt_brim_line = first_layer_outline.offset(offset_distance, ClipperLib::jtRound); //Remove small inner skirt and brim holes. Holes have a negative area, remove anything smaller then 100x extrusion "area" for (unsigned int n = 0; n < outer_skirt_brim_line.size(); n++) { double area = outer_skirt_brim_line[n].area(); if (area < 0 && area > -primary_extruder_skirt_brim_line_width * primary_extruder_skirt_brim_line_width * 100) { outer_skirt_brim_line.remove(n--); } } skirt_brim_primary_extruder.add(outer_skirt_brim_line); int length = skirt_brim_primary_extruder.polygonLength(); if (skirt_brim_number + 1 >= primary_line_count && length > 0 && length < primary_extruder_minimal_length) //Make brim or skirt have more lines when total length is too small. { primary_line_count++; } } return offset_distance; }
void generateInfill(int layerNr, SliceMeshStorage& mesh, const int innermost_wall_line_width, int infill_skin_overlap, int wall_line_count) { SliceLayer& layer = mesh.layers[layerNr]; for(SliceLayerPart& part : layer.parts) { if (int(part.insets.size()) < wall_line_count) { continue; // the last wall is not present, the part should only get inter preimeter gaps, but no infill. } Polygons infill = part.insets.back().offset(-innermost_wall_line_width / 2 - infill_skin_overlap); for(SliceLayerPart& part2 : layer.parts) { if (part.boundaryBox.hit(part2.boundaryBox)) { for(SkinPart& skin_part : part2.skin_parts) { infill = infill.difference(skin_part.outline); } } } infill.removeSmallAreas(MIN_AREA_SIZE); part.infill_area = infill.offset(infill_skin_overlap); } }
/*! * generate lines within the area of \p in_outline, at regular intervals of \p lineSpacing * * idea: * intersect a regular grid of 'scanlines' with the area inside \p in_outline * * we call the areas between two consecutive scanlines a 'scansegment'. * Scansegment x is the area between scanline x and scanline x+1 * * algorithm: * 1) for each line segment of each polygon: * store the intersections of that line segment with all scanlines in a mapping (vector of vectors) from scanline to intersections * (zigzag): add boundary segments to result * 2) for each scanline: * sort the associated intersections * and connect them using the even-odd rule * */ void generateLineInfill(const Polygons& in_outline, int outlineOffset, Polygons& result, int extrusionWidth, int lineSpacing, double infillOverlap, double rotation) { if (lineSpacing == 0) return; if (in_outline.size() == 0) return; Polygons outline = ((outlineOffset)? in_outline.offset(outlineOffset) : in_outline).offset(extrusionWidth * infillOverlap / 100); if (outline.size() == 0) return; PointMatrix matrix(rotation); outline.applyMatrix(matrix); AABB boundary(outline); int scanline_min_idx = boundary.min.X / lineSpacing; int lineCount = (boundary.max.X + (lineSpacing - 1)) / lineSpacing - scanline_min_idx; std::vector<std::vector<int64_t> > cutList; // mapping from scanline to all intersections with polygon segments for(int n=0; n<lineCount; n++) cutList.push_back(std::vector<int64_t>()); for(unsigned int poly_idx=0; poly_idx < outline.size(); poly_idx++) { Point p0 = outline[poly_idx][outline[poly_idx].size()-1]; for(unsigned int i=0; i < outline[poly_idx].size(); i++) { Point p1 = outline[poly_idx][i]; int64_t xMin = p1.X, xMax = p0.X; if (xMin == xMax) { p0 = p1; continue; } if (xMin > xMax) { xMin = p0.X; xMax = p1.X; } int scanline_idx0 = (p0.X + ((p0.X > 0)? -1 : -lineSpacing)) / lineSpacing; // -1 cause a linesegment on scanline x counts as belonging to scansegment x-1 ... int scanline_idx1 = (p1.X + ((p1.X > 0)? -1 : -lineSpacing)) / lineSpacing; // -linespacing because a line between scanline -n and -n-1 belongs to scansegment -n-1 (for n=positive natural number) int direction = 1; if (p0.X > p1.X) { direction = -1; scanline_idx1 += 1; // only consider the scanlines in between the scansegments } else scanline_idx0 += 1; // only consider the scanlines in between the scansegments for(int scanline_idx = scanline_idx0; scanline_idx != scanline_idx1+direction; scanline_idx+=direction) { int x = scanline_idx * lineSpacing; int y = p1.Y + (p0.Y - p1.Y) * (x - p1.X) / (p0.X - p1.X); cutList[scanline_idx - scanline_min_idx].push_back(y); } p0 = p1; } } addLineInfill(result, matrix, scanline_min_idx, lineSpacing, boundary, cutList, extrusionWidth); }
void Weaver::fillFloors(Polygons& supporting, Polygons& to_be_supported, int direction, int z, WeaveRoof& horizontals) { std::vector<WeaveRoofPart>& outsets = horizontals.roof_insets; if (to_be_supported.size() == 0) return; // no parts to start the floor from! if (supporting.size() == 0) return; // no parts to start the floor from! Polygons floors = to_be_supported.difference(supporting); const coord_t roof_inset = Application::getInstance().current_slice->scene.current_mesh_group->settings.get<coord_t>("wireframe_roof_inset"); floors = floors.offset(-roof_inset).offset(roof_inset); if (floors.size() == 0) return; std::vector<PolygonsPart> floor_parts = floors.splitIntoParts(); Polygons floor_outlines; Polygons floor_holes; for (PolygonsPart& floor_part : floor_parts) { floor_outlines.add(floor_part[0]); for (unsigned int hole_idx = 1; hole_idx < floor_part.size(); hole_idx++) { floor_holes.add(floor_part[hole_idx]); //floor_holes.back().reverse(); } } Polygons outset1; Polygons last_supported = supporting; for (Polygons outset0 = supporting; outset0.size() > 0; outset0 = outset1) { outset1 = outset0.offset(roof_inset * direction, ClipperLib::jtRound).intersection(floors); outset1 = outset1.remove(floor_holes); // throw away holes which appear in every intersection outset1 = outset1.remove(floor_outlines); // throw away holes which appear in every intersection outsets.emplace_back(); connect(last_supported, z, outset1, z, outsets.back()); outset1 = outset1.remove(floor_outlines);// throw away fully filled regions last_supported = outsets.back().supported; // chainified } horizontals.roof_outlines.add(floors); }
void SkirtBrim::getFirstLayerOutline(SliceDataStorage& storage, const unsigned int primary_line_count, const int primary_extruder_skirt_brim_line_width, const bool is_skirt, const bool outside_only, Polygons& first_layer_outline) { bool external_only = is_skirt; // whether to include holes or not const int layer_nr = 0; if (is_skirt) { const bool include_helper_parts = true; first_layer_outline = storage.getLayerOutlines(layer_nr, include_helper_parts, external_only); first_layer_outline = first_layer_outline.approxConvexHull(); } else { // add brim underneath support by removing support where there's brim around the model const bool include_helper_parts = false; // include manually below first_layer_outline = storage.getLayerOutlines(layer_nr, include_helper_parts, external_only); first_layer_outline.add(storage.primeTower.ground_poly); // don't remove parts of the prime tower, but make a brim for it if (outside_only) { first_layer_outline = first_layer_outline.removeEmptyHoles(); } if (storage.support.generated && primary_line_count > 0) { // remove model-brim from support // avoid gap in the middle // V // +---+ +----+ // |+-+| |+--+| // || || ||[]|| > expand to fit an extra brim line // |+-+| |+--+| // +---+ +----+ const Polygons& model_brim_covered_area = first_layer_outline.offset(primary_extruder_skirt_brim_line_width * (primary_line_count + primary_line_count % 2)); // always leave a gap of an even number of brim lines, so that it fits if it's generating brim from both sides SupportLayer& support_layer = storage.support.supportLayers[0]; support_layer.supportAreas = support_layer.supportAreas.difference(model_brim_covered_area); first_layer_outline.add(support_layer.supportAreas); first_layer_outline.add(support_layer.skin); } } constexpr int join_distance = 20; first_layer_outline = first_layer_outline.offset(join_distance).offset(-join_distance); // merge adjacent models into single polygon constexpr int smallest_line_length = 200; constexpr int largest_error_of_removed_point = 50; first_layer_outline.simplify(smallest_line_length, largest_error_of_removed_point); // simplify for faster processing of the brim lines }
void generateLineInfill(const Polygons& in_outline, Polygons& result, int extrusionWidth, int lineSpacing, int infillOverlap, double rotation) { Polygons outline = in_outline.offset(extrusionWidth * infillOverlap / 100); PointMatrix matrix(rotation); outline.applyMatrix(matrix); AABB boundary(outline); boundary.min.X = ((boundary.min.X / lineSpacing) - 1) * lineSpacing; int lineCount = (boundary.max.X - boundary.min.X + (lineSpacing - 1)) / lineSpacing; vector<vector<int64_t> > cutList; for(int n=0; n<lineCount; n++) cutList.push_back(vector<int64_t>()); for(unsigned int polyNr=0; polyNr < outline.size(); polyNr++) { Point p1 = outline[polyNr][outline[polyNr].size()-1]; for(unsigned int i=0; i < outline[polyNr].size(); i++) { Point p0 = outline[polyNr][i]; int idx0 = (p0.X - boundary.min.X) / lineSpacing; int idx1 = (p1.X - boundary.min.X) / lineSpacing; int64_t xMin = p0.X, xMax = p1.X; if (p0.X > p1.X) { xMin = p1.X; xMax = p0.X; } if (idx0 > idx1) { int tmp = idx0; idx0 = idx1; idx1 = tmp; } for(int idx = idx0; idx<=idx1; idx++) { int x = (idx * lineSpacing) + boundary.min.X + lineSpacing / 2; if (x < xMin) continue; if (x >= xMax) continue; int y = p0.Y + (p1.Y - p0.Y) * (x - p0.X) / (p1.X - p0.X); cutList[idx].push_back(y); } p1 = p0; } } int idx = 0; for(int64_t x = boundary.min.X + lineSpacing / 2; x < boundary.max.X; x += lineSpacing) { std::sort(cutList[idx].begin(), cutList[idx].end()); for(unsigned int i = 0; i + 1 < cutList[idx].size(); i+=2) { if (cutList[idx][i+1] - cutList[idx][i] < extrusionWidth / 5) continue; PolygonRef p = result.newPoly(); p.add(matrix.unapply(Point(x, cutList[idx][i]))); p.add(matrix.unapply(Point(x, cutList[idx][i+1]))); } idx += 1; } }
void generateConcentricInfill(Polygons outline, Polygons& result, int inset_value) { while(outline.size() > 0) { for (unsigned int polyNr = 0; polyNr < outline.size(); polyNr++) { PolygonRef r = outline[polyNr]; result.add(r); } outline = outline.offset(-inset_value); } }
/* * This function is executed in a parallel region based on layer_nr. * When modifying make sure any changes does not introduce data races. * * this function may only read/write the skin and infill from the *current* layer. */ void SkinInfillAreaComputation::applySkinExpansion(const Polygons& original_outline, Polygons& upskin, Polygons& downskin) { // First we set the amount of distance we want to expand, as indicated in settings coord_t top_outset = top_skin_expand_distance; coord_t bottom_outset = bottom_skin_expand_distance; coord_t top_min_width = mesh.getSettingInMicrons("min_skin_width_for_expansion") / 2; coord_t bottom_min_width = top_min_width; // Compensate for the pre-shrink applied because of the Skin Removal Width. // The skin removal width is satisfied by applying a close operation and // it's done in the calculateTopSkin and calculateBottomSkin, by expanding the infill. // The inset of that close operation is applied in calculateTopSkin and calculateBottomSkin // The outset of the close operation is applied at the same time as the skin expansion. top_outset += top_reference_wall_expansion; bottom_outset += bottom_reference_wall_expansion; // Calculate the shrinkage needed to fulfill the minimum skin with for expansion top_min_width = std::max(coord_t(0), top_min_width - top_reference_wall_expansion / 2); // if the min width is smaller than the pre-shrink then areas smaller than min_width will exist bottom_min_width = std::max(coord_t(0), bottom_min_width - bottom_reference_wall_expansion / 2); // if the min width is smaller than the pre-shrink then areas smaller than min_width will exist // skin areas are to be enlarged by skin_expand_distance but before they are expanded // the skin areas are shrunk by min_width so that very narrow regions of skin // (often caused by the model's surface having a steep incline) are not expanded top_outset += top_min_width; // increase the expansion distance to compensate for the min_width shrinkage bottom_outset += bottom_min_width; // increase the expansion distance to compensate for the min_width shrinkage // Execute shrinkage and expansion in the same operation if (top_outset) { upskin = upskin.offset(-top_min_width).offset(top_outset).unionPolygons(upskin).intersection(original_outline); } if (bottom_outset) { downskin = downskin.offset(-bottom_min_width).offset(bottom_outset).unionPolygons(downskin).intersection(original_outline); } }
void generateConcentricInfill(Polygons outline, Polygons& result, int offsets[], int offsetsSize) { int step = 0; while(1) { for(unsigned int polygonNr=0; polygonNr<outline.size(); polygonNr++) result.add(outline[polygonNr]); outline = outline.offset(-offsets[step]); if (outline.size() < 1) break; step = (step + 1) % offsetsSize; } }
Polygons join(Polygons& supportLayer_up, Polygons& supportLayer_this, int64_t supportJoinDistance, int64_t smoothing_distance, int min_smoothing_area) { Polygons joined = supportLayer_this.unionPolygons(supportLayer_up); // join different parts if (supportJoinDistance > 0) { joined = joined.offset(supportJoinDistance) .offset(-supportJoinDistance); } if (smoothing_distance > 0) joined = joined.smooth(smoothing_distance, min_smoothing_area); return joined; }
void generateRaft(SliceDataStorage& storage, int distance) { for(SliceMeshStorage& mesh : storage.meshes) { if (mesh.layers.size() < 1) continue; SliceLayer* layer = &mesh.layers[0]; for(SliceLayerPart& part : layer->parts) storage.raftOutline = storage.raftOutline.unionPolygons(part.outline.offset(distance)); } Polygons support; if (storage.support.generated) support = storage.support.supportAreasPerLayer[0]; storage.raftOutline = storage.raftOutline.unionPolygons(support.offset(distance)); storage.raftOutline = storage.raftOutline.unionPolygons(storage.wipeTower.offset(distance)); }
void generateRaft(SliceDataStorage& storage, int distance) { for(SliceMeshStorage& mesh : storage.meshes) { if (mesh.layers.size() < 1) continue; SliceLayer* layer = &mesh.layers[0]; for(SliceLayerPart& part : layer->parts) storage.raftOutline = storage.raftOutline.unionPolygons(part.outline.offset(distance)); } Polygons support; if (storage.support.generated) support = storage.support.supportLayers[0].supportAreas; storage.raftOutline = storage.raftOutline.unionPolygons(support.offset(distance)); storage.raftOutline = storage.raftOutline.unionPolygons(storage.primeTower.ground_poly.offset(distance)); storage.raftOutline = storage.raftOutline.unionPolygons(storage.draft_protection_shield.offset(distance)); }
/* * This function is executed in a parallel region based on layer_nr. * When modifying make sure any changes does not introduce data races. * * generateInfill read mesh.layers[n].parts[*].{insets,skin_parts,boundingBox} and write mesh.layers[n].parts[*].infill_area */ void SkinInfillAreaComputation::generateInfill(SliceLayerPart& part, const Polygons& skin) { if (int(part.insets.size()) < wall_line_count) { return; // the last wall is not present, the part should only get inter perimeter gaps, but no infill. } const int wall_line_count = mesh.getSettingAsCount("wall_line_count"); const coord_t infill_line_distance = mesh.getSettingInMicrons("infill_line_distance"); coord_t offset_from_inner_wall = -infill_skin_overlap; if (wall_line_count > 0) { // calculate offset_from_inner_wall coord_t extra_perimeter_offset = 0; // to align concentric polygons across layers EFillMethod fill_pattern = mesh.getSettingAsFillMethod("infill_pattern"); if ((fill_pattern == EFillMethod::CONCENTRIC || fill_pattern == EFillMethod::CONCENTRIC_3D) && infill_line_distance > mesh.getSettingInMicrons("infill_line_width") * 2) { if (mesh.getSettingBoolean("alternate_extra_perimeter") && layer_nr % 2 == 0) { // compensate shifts otherwise caused by alternating an extra perimeter extra_perimeter_offset = -innermost_wall_line_width; } if (layer_nr == 0) { // compensate for shift caused by walls being expanded by the initial line width multiplier const coord_t normal_wall_line_width_0 = mesh.getSettingInMicrons("wall_line_width_0"); const coord_t normal_wall_line_width_x = mesh.getSettingInMicrons("wall_line_width_x"); coord_t normal_walls_width = normal_wall_line_width_0 + (wall_line_count - 1) * normal_wall_line_width_x; coord_t walls_width = normal_walls_width * mesh.getSettingAsRatio("initial_layer_line_width_factor"); extra_perimeter_offset += walls_width - normal_walls_width; while (extra_perimeter_offset > 0) { extra_perimeter_offset -= infill_line_distance; } } } offset_from_inner_wall += extra_perimeter_offset - innermost_wall_line_width / 2; } Polygons infill = part.insets.back().offset(offset_from_inner_wall); infill = infill.difference(skin); infill.removeSmallAreas(MIN_AREA_SIZE); part.infill_area = infill.offset(infill_skin_overlap); }
//Expand each layer a bit and then keep the extra overlapping parts that overlap with other volumes. //This generates some overlap in dual extrusion, for better bonding in touching parts. void generateMultipleVolumesOverlap(std::vector<Slicer*> &volumes, int overlap) { if (volumes.size() < 2 || overlap <= 0) return; for(unsigned int layerNr=0; layerNr < volumes[0]->layers.size(); layerNr++) { Polygons fullLayer; for(unsigned int volIdx = 0; volIdx < volumes.size(); volIdx++) { SlicerLayer& layer1 = volumes[volIdx]->layers[layerNr]; fullLayer = fullLayer.unionPolygons(layer1.polygons.offset(20)); // TODO: put hard coded value in a variable with an explanatory name (and make var a parameter, and perhaps even a setting?) } fullLayer = fullLayer.offset(-20); // TODO: put hard coded value in a variable with an explanatory name (and make var a parameter, and perhaps even a setting?) for(unsigned int volIdx = 0; volIdx < volumes.size(); volIdx++) { SlicerLayer& layer1 = volumes[volIdx]->layers[layerNr]; layer1.polygons = fullLayer.intersection(layer1.polygons.offset(overlap / 2)); } } }
void SkinInfillAreaComputation::generateInfillSupport(SliceMeshStorage& mesh) { const coord_t layer_height = mesh.getSettingInMicrons("layer_height"); const double support_angle = mesh.getSettingInAngleRadians("infill_support_angle"); const double tan_angle = tan(support_angle) - 0.01; //The X/Y component of the support angle. 0.01 to make 90 degrees work too. const coord_t max_dist_from_lower_layer = tan_angle * layer_height; //Maximum horizontal distance that can be bridged. for (int layer_idx = mesh.layers.size() - 2; layer_idx >= 0; layer_idx--) { SliceLayer& layer = mesh.layers[layer_idx]; SliceLayer& layer_above = mesh.layers[layer_idx + 1]; Polygons inside_above; Polygons infill_above; for (SliceLayerPart& part_above : layer_above.parts) { inside_above.add(part_above.infill_area); infill_above.add(part_above.getOwnInfillArea()); } for (SliceLayerPart& part : layer.parts) { const Polygons& infill_area = part.infill_area; if (infill_area.empty()) { continue; } const Polygons unsupported = infill_area.offset(-max_dist_from_lower_layer); const Polygons basic_overhang = unsupported.difference(inside_above); const Polygons overhang_extented = basic_overhang.offset(max_dist_from_lower_layer + 50); // +50 for easier joining with support from layer above const Polygons full_overhang = overhang_extented.difference(inside_above); const Polygons infill_support = infill_above.unionPolygons(full_overhang); part.infill_area_own = infill_support.intersection(part.getOwnInfillArea()); } } }
/* * Algorithm: * From top layer to bottom layer: * - find overhang by looking at the difference between two consucutive layers * - join with support areas from layer above * - subtract current layer * - use the result for the next lower support layer (without doing XY-distance and Z bottom distance, so that a single support beam may move around the model a bit => more stability) * - perform inset using X/Y-distance and bottom Z distance * * for support buildplate only: purge all support not connected to buildplate */ void generateSupportAreas(SliceDataStorage& storage, SliceMeshStorage* object, int layer_count) { // given settings ESupportType support_type = object->settings->getSettingAsSupportType("support_type"); storage.support.generated = false; if (!object->settings->getSettingBoolean("support_enable")) return; if (support_type == Support_None) return; double supportAngle = object->settings->getSettingInAngleRadians("support_angle"); bool supportOnBuildplateOnly = support_type == Support_PlatformOnly; int supportXYDistance = object->settings->getSettingInMicrons("support_xy_distance"); int supportZDistance = object->settings->getSettingInMicrons("support_z_distance"); int supportZDistanceBottom = object->settings->getSettingInMicrons("support_bottom_distance"); int supportZDistanceTop = object->settings->getSettingInMicrons("support_top_distance"); int supportJoinDistance = object->settings->getSettingInMicrons("support_join_distance"); int support_bottom_stair_step_height = object->settings->getSettingInMicrons("support_bottom_stair_step_height"); int smoothing_distance = object->settings->getSettingInMicrons("support_area_smoothing"); int supportTowerDiameter = object->settings->getSettingInMicrons("support_tower_diameter"); int supportMinAreaSqrt = object->settings->getSettingInMicrons("support_minimal_diameter"); double supportTowerRoofAngle = object->settings->getSettingInAngleRadians("support_tower_roof_angle"); //std::cerr <<" towerDiameter=" << towerDiameter <<", supportMinAreaSqrt=" << supportMinAreaSqrt << std::endl; int min_smoothing_area = 100*100; // minimal area for which to perform smoothing int z_layer_distance_tower = 1; // start tower directly below overhang point int layerThickness = object->settings->getSettingInMicrons("layer_height"); int extrusionWidth = object->settings->getSettingInMicrons("wall_line_width_x"); // TODO check for layer0extrusionWidth! // derived settings: if (supportZDistanceBottom < 0) supportZDistanceBottom = supportZDistance; if (supportZDistanceTop < 0) supportZDistanceTop = supportZDistance; int supportLayerThickness = layerThickness; int layerZdistanceTop = supportZDistanceTop / supportLayerThickness + 1; // support must always be 1 layer below overhang int layerZdistanceBottom = supportZDistanceBottom / supportLayerThickness; double tanAngle = tan(supportAngle) - 0.01; // the XY-component of the supportAngle int maxDistFromLowerLayer = tanAngle * supportLayerThickness; // max dist which can be bridged int support_layer_count = layer_count; double tanTowerRoofAngle = tan(supportTowerRoofAngle); int towerRoofExpansionDistance = layerThickness / tanTowerRoofAngle; // computation std::vector<Polygons> joinedLayers; // join model layers of all meshes into polygons and store small areas which need tower support std::vector<std::pair<int, std::vector<Polygons>>> overhang_points; // stores overhang_points along with the layer index at which the overhang point occurs AreaSupport::joinMeshesAndDetectOverhangPoints(storage, joinedLayers, overhang_points, layer_count, supportMinAreaSqrt, extrusionWidth); // initialization of supportAreasPerLayer for (int layer_idx = 0; layer_idx < layer_count ; layer_idx++) storage.support.supportAreasPerLayer.emplace_back(); int overhang_points_pos = overhang_points.size() - 1; Polygons supportLayer_last; std::vector<Polygons> towerRoofs; for (int layer_idx = support_layer_count - 1 - layerZdistanceTop; layer_idx >= 0 ; layer_idx--) { // compute basic overhang and put in right layer ([layerZdistanceTOp] layers below) Polygons supportLayer_supportee = joinedLayers[layer_idx+layerZdistanceTop]; Polygons supportLayer_supported = joinedLayers[layer_idx-1+layerZdistanceTop].offset(maxDistFromLowerLayer); Polygons basic_overhang = supportLayer_supportee.difference(supportLayer_supported); Polygons support_extension = basic_overhang.offset(maxDistFromLowerLayer); support_extension = support_extension.intersection(supportLayer_supported); support_extension = support_extension.intersection(supportLayer_supportee); Polygons overhang = basic_overhang.unionPolygons(support_extension); /* supported * ................. * ______________| * _______| ^^^^^ basic overhang * * ^^^^^^^^^ overhang extensions * ^^^^^^^^^^^^^^ overhang */ Polygons& supportLayer_this = overhang; supportLayer_this = supportLayer_this.simplify(50); // TODO: hardcoded value! if (supportMinAreaSqrt > 0) { // handle straight walls AreaSupport::handleWallStruts(supportLayer_this, supportMinAreaSqrt, supportTowerDiameter); // handle towers AreaSupport::handleTowers(supportLayer_this, towerRoofs, overhang_points, overhang_points_pos, layer_idx, towerRoofExpansionDistance, supportTowerDiameter, supportMinAreaSqrt, layer_count, z_layer_distance_tower); } if (layer_idx+1 < support_layer_count) { // join with support from layer up Polygons& supportLayer_up = supportLayer_last; Polygons joined = supportLayer_this.unionPolygons(supportLayer_up); // join different parts if (supportJoinDistance > 0) { joined = joined.offset(supportJoinDistance); joined = joined.offset(-supportJoinDistance); } if (smoothing_distance > 0) joined = joined.smooth(smoothing_distance, min_smoothing_area); // remove layer Polygons insetted = joined.difference(joinedLayers[layer_idx]); supportLayer_this = insetted; } supportLayer_last = supportLayer_this; // inset using X/Y distance if (supportLayer_this.size() > 0) supportLayer_this = supportLayer_this.difference(joinedLayers[layer_idx].offset(supportXYDistance)); // move up from model if (layerZdistanceBottom > 0 && layer_idx >= layerZdistanceBottom) { int stepHeight = support_bottom_stair_step_height / supportLayerThickness + 1; int bottomLayer = ((layer_idx - layerZdistanceBottom) / stepHeight) * stepHeight; supportLayer_this = supportLayer_this.difference(joinedLayers[bottomLayer]); } storage.support.supportAreasPerLayer[layer_idx] = supportLayer_this; logProgress("support", support_layer_count - layer_idx, support_layer_count); } // do stuff for when support on buildplate only if (supportOnBuildplateOnly) { Polygons touching_buildplate = storage.support.supportAreasPerLayer[0]; for (unsigned int layer_idx = 1 ; layer_idx < storage.support.supportAreasPerLayer.size() ; layer_idx++) { Polygons& supportLayer = storage.support.supportAreasPerLayer[layer_idx]; touching_buildplate = supportLayer.intersection(touching_buildplate); // from bottom to top, support areas can only decrease! storage.support.supportAreasPerLayer[layer_idx] = touching_buildplate; } } joinedLayers.clear(); storage.support.generated = true; }
void generateZigZagIninfill_noEndPieces(const Polygons& in_outline, Polygons& result, int extrusionWidth, int lineSpacing, double infillOverlap, double rotation) { if (in_outline.size() == 0) return; Polygons outline = in_outline.offset(extrusionWidth * infillOverlap / 100 - extrusionWidth / 2); if (outline.size() == 0) return; PointMatrix matrix(rotation); outline.applyMatrix(matrix); auto addLine = [&](Point from, Point to) { PolygonRef p = result.newPoly(); p.add(matrix.unapply(from)); p.add(matrix.unapply(to)); }; AABB boundary(outline); int scanline_min_idx = boundary.min.X / lineSpacing; int lineCount = (boundary.max.X + (lineSpacing - 1)) / lineSpacing - scanline_min_idx; std::vector<std::vector<int64_t> > cutList; // mapping from scanline to all intersections with polygon segments for(int n=0; n<lineCount; n++) cutList.push_back(std::vector<int64_t>()); for(unsigned int polyNr=0; polyNr < outline.size(); polyNr++) { std::vector<Point> firstBoundarySegment; std::vector<Point> boundarySegment; bool isFirstBoundarySegment = true; bool firstBoundarySegmentEndsInEven; bool isEvenScanSegment = false; Point p0 = outline[polyNr][outline[polyNr].size()-1]; for(unsigned int i=0; i < outline[polyNr].size(); i++) { Point p1 = outline[polyNr][i]; int64_t xMin = p1.X, xMax = p0.X; if (xMin == xMax) { p0 = p1; continue; } if (xMin > xMax) { xMin = p0.X; xMax = p1.X; } int scanline_idx0 = (p0.X + ((p0.X > 0)? -1 : -lineSpacing)) / lineSpacing; // -1 cause a linesegment on scanline x counts as belonging to scansegment x-1 ... int scanline_idx1 = (p1.X + ((p1.X > 0)? -1 : -lineSpacing)) / lineSpacing; // -linespacing because a line between scanline -n and -n-1 belongs to scansegment -n-1 (for n=positive natural number) int direction = 1; if (p0.X > p1.X) { direction = -1; scanline_idx1 += 1; // only consider the scanlines in between the scansegments } else scanline_idx0 += 1; // only consider the scanlines in between the scansegments if (isFirstBoundarySegment) firstBoundarySegment.push_back(p0); else boundarySegment.push_back(p0); for(int scanline_idx = scanline_idx0; scanline_idx != scanline_idx1+direction; scanline_idx+=direction) { int x = scanline_idx * lineSpacing; int y = p1.Y + (p0.Y - p1.Y) * (x - p1.X) / (p0.X - p1.X); cutList[scanline_idx - scanline_min_idx].push_back(y); bool last_isEvenScanSegment = isEvenScanSegment; if (scanline_idx % 2 == 0) isEvenScanSegment = true; else isEvenScanSegment = false; if (!isFirstBoundarySegment) { if (last_isEvenScanSegment && !isEvenScanSegment) { // add whole boundarySegment (including the just obtained point) for (unsigned int p = 1; p < boundarySegment.size(); p++) { addLine(boundarySegment[p-1], boundarySegment[p]); } addLine(boundarySegment[boundarySegment.size()-1], Point(x,y)); boundarySegment.clear(); } else if (isEvenScanSegment) // we are either in an end piece or an uneven boundary segment { boundarySegment.clear(); boundarySegment.emplace_back(x,y); } else boundarySegment.clear(); } if (isFirstBoundarySegment) { firstBoundarySegment.emplace_back(x,y); firstBoundarySegmentEndsInEven = isEvenScanSegment; isFirstBoundarySegment = false; boundarySegment.emplace_back(x,y); } } if (!isFirstBoundarySegment && isEvenScanSegment) boundarySegment.push_back(p1); p0 = p1; } if (!isFirstBoundarySegment && isEvenScanSegment && !firstBoundarySegmentEndsInEven) { for (unsigned int i = 1; i < firstBoundarySegment.size() ; i++) addLine(firstBoundarySegment[i-1], firstBoundarySegment[i]); } } addLineInfill(result, matrix, scanline_min_idx, lineSpacing, boundary, cutList, extrusionWidth); }
void SkirtBrim::getFirstLayerOutline(SliceDataStorage& storage, const size_t primary_line_count, const bool is_skirt, Polygons& first_layer_outline) { const ExtruderTrain& train = Application::getInstance().current_slice->scene.current_mesh_group->settings.get<ExtruderTrain&>("adhesion_extruder_nr"); const ExtruderTrain& support_infill_extruder = Application::getInstance().current_slice->scene.current_mesh_group->settings.get<ExtruderTrain&>("support_infill_extruder_nr"); const bool external_only = is_skirt || train.settings.get<bool>("brim_outside_only"); //Whether to include holes or not. Skirt doesn't have any holes. const LayerIndex layer_nr = 0; if (is_skirt) { constexpr bool include_support = true; constexpr bool include_prime_tower = true; first_layer_outline = storage.getLayerOutlines(layer_nr, include_support, include_prime_tower, external_only); first_layer_outline = first_layer_outline.approxConvexHull(); } else { // add brim underneath support by removing support where there's brim around the model constexpr bool include_support = false; //Include manually below. constexpr bool include_prime_tower = false; //Include manually below. constexpr bool external_outlines_only = false; //Remove manually below. first_layer_outline = storage.getLayerOutlines(layer_nr, include_support, include_prime_tower, external_outlines_only); first_layer_outline = first_layer_outline.unionPolygons(); //To guard against overlapping outlines, which would produce holes according to the even-odd rule. Polygons first_layer_empty_holes; if (external_only) { first_layer_empty_holes = first_layer_outline.getEmptyHoles(); first_layer_outline = first_layer_outline.removeEmptyHoles(); } if (storage.support.generated && primary_line_count > 0 && !storage.support.supportLayers.empty()) { // remove model-brim from support SupportLayer& support_layer = storage.support.supportLayers[0]; if (support_infill_extruder.settings.get<bool>("brim_replaces_support")) { // avoid gap in the middle // V // +---+ +----+ // |+-+| |+--+| // || || ||[]|| > expand to fit an extra brim line // |+-+| |+--+| // +---+ +----+ const coord_t primary_extruder_skirt_brim_line_width = train.settings.get<coord_t>("skirt_brim_line_width") * train.settings.get<Ratio>("initial_layer_line_width_factor"); Polygons model_brim_covered_area = first_layer_outline.offset(primary_extruder_skirt_brim_line_width * (primary_line_count + primary_line_count % 2), ClipperLib::jtRound); // always leave a gap of an even number of brim lines, so that it fits if it's generating brim from both sides if (external_only) { // don't remove support within empty holes where no brim is generated. model_brim_covered_area.add(first_layer_empty_holes); } AABB model_brim_covered_area_boundary_box(model_brim_covered_area); support_layer.excludeAreasFromSupportInfillAreas(model_brim_covered_area, model_brim_covered_area_boundary_box); } for (const SupportInfillPart& support_infill_part : support_layer.support_infill_parts) { first_layer_outline.add(support_infill_part.outline); } first_layer_outline.add(support_layer.support_bottom); first_layer_outline.add(support_layer.support_roof); } if (storage.primeTower.enabled) { first_layer_outline.add(storage.primeTower.outer_poly_first_layer); // don't remove parts of the prime tower, but make a brim for it } } constexpr coord_t join_distance = 20; first_layer_outline = first_layer_outline.offset(join_distance).offset(-join_distance); // merge adjacent models into single polygon constexpr coord_t smallest_line_length = 200; constexpr coord_t largest_error_of_removed_point = 50; first_layer_outline.simplify(smallest_line_length, largest_error_of_removed_point); // simplify for faster processing of the brim lines if (first_layer_outline.size() == 0) { logError("Couldn't generate skirt / brim! No polygons on first layer.\n"); } }
void removeOverlapping(Polygons& poly, int extrusionWidth, Polygons& result) { result = poly.offset(extrusionWidth/2).offset(-extrusionWidth).offset(extrusionWidth/2); }
/* * algorithm: * 1. for each line segment of each polygon: * store the intersections of that line segment with all scanlines in a mapping (vector of vectors) from scanline to intersections * (zigzag): add boundary segments to result * 2. for each scanline: * sort the associated intersections * and connect them using the even-odd rule * * rough explanation of the zigzag algorithm: * while walking around (each) polygon (1.) * if polygon intersects with even scanline * start boundary segment (add each following segment to the [result]) * when polygon intersects with a scanline again * stop boundary segment (stop adding segments to the [result]) * (see infill/ZigzagConnectorProcessor.h for actual implementation details) * * * we call the areas between two consecutive scanlines a 'scansegment'. * Scansegment x is the area between scanline x and scanline x+1 * Edit: the term scansegment is wrong, since I call a boundary segment leaving from an even scanline to the left as belonging to an even scansegment, * while I also call a boundary segment leaving from an even scanline toward the right as belonging to an even scansegment. */ void Infill::generateLinearBasedInfill(const int outline_offset, Polygons& result, const int line_distance, const PointMatrix& rotation_matrix, ZigzagConnectorProcessor& zigzag_connector_processor, const bool connected_zigzags, int64_t extra_shift) { if (line_distance == 0) { return; } if (in_outline.size() == 0) { return; } int shift = extra_shift + this->shift; Polygons outline; if (outline_offset != 0) { outline = in_outline.offset(outline_offset); if (perimeter_gaps) { perimeter_gaps->add(in_outline.difference(outline.offset(infill_line_width / 2 + perimeter_gaps_extra_offset))); } } else { outline = in_outline; } outline = outline.offset(infill_overlap); if (outline.size() == 0) { return; } outline.applyMatrix(rotation_matrix); if (shift < 0) { shift = line_distance - (-shift) % line_distance; } else { shift = shift % line_distance; } AABB boundary(outline); int scanline_min_idx = computeScanSegmentIdx(boundary.min.X - shift, line_distance); int line_count = computeScanSegmentIdx(boundary.max.X - shift, line_distance) + 1 - scanline_min_idx; std::vector<std::vector<int64_t> > cut_list; // mapping from scanline to all intersections with polygon segments for(int scanline_idx = 0; scanline_idx < line_count; scanline_idx++) { cut_list.push_back(std::vector<int64_t>()); } for(unsigned int poly_idx = 0; poly_idx < outline.size(); poly_idx++) { PolygonRef poly = outline[poly_idx]; Point p0 = poly.back(); zigzag_connector_processor.registerVertex(p0); // always adds the first point to ZigzagConnectorProcessorEndPieces::first_zigzag_connector when using a zigzag infill type for(unsigned int point_idx = 0; point_idx < poly.size(); point_idx++) { Point p1 = poly[point_idx]; if (p1.X == p0.X) { zigzag_connector_processor.registerVertex(p1); // TODO: how to make sure it always adds the shortest line? (in order to prevent overlap with the zigzag connectors) // note: this is already a problem for normal infill, but hasn't really cothered anyone so far. p0 = p1; continue; } int scanline_idx0; int scanline_idx1; // this way of handling the indices takes care of the case where a boundary line segment ends exactly on a scanline: // in case the next segment moves back from that scanline either 2 or 0 scanline-boundary intersections are created // otherwise only 1 will be created, counting as an actual intersection int direction = 1; if (p0.X < p1.X) { scanline_idx0 = computeScanSegmentIdx(p0.X - shift, line_distance) + 1; // + 1 cause we don't cross the scanline of the first scan segment scanline_idx1 = computeScanSegmentIdx(p1.X - shift, line_distance); // -1 cause the vertex point is handled in the next segment (or not in the case which looks like >) } else { direction = -1; scanline_idx0 = computeScanSegmentIdx(p0.X - shift, line_distance); // -1 cause the vertex point is handled in the previous segment (or not in the case which looks like >) scanline_idx1 = computeScanSegmentIdx(p1.X - shift, line_distance) + 1; // + 1 cause we don't cross the scanline of the first scan segment } for(int scanline_idx = scanline_idx0; scanline_idx != scanline_idx1 + direction; scanline_idx += direction) { int x = scanline_idx * line_distance + shift; int y = p1.Y + (p0.Y - p1.Y) * (x - p1.X) / (p0.X - p1.X); assert(scanline_idx - scanline_min_idx >= 0 && scanline_idx - scanline_min_idx < int(cut_list.size()) && "reading infill cutlist index out of bounds!"); cut_list[scanline_idx - scanline_min_idx].push_back(y); Point scanline_linesegment_intersection(x, y); zigzag_connector_processor.registerScanlineSegmentIntersection(scanline_linesegment_intersection, scanline_idx % 2 == 0); } zigzag_connector_processor.registerVertex(p1); p0 = p1; } zigzag_connector_processor.registerPolyFinished(); } if (cut_list.size() == 0) { return; } if (connected_zigzags && cut_list.size() == 1 && cut_list[0].size() <= 2) { return; // don't add connection if boundary already contains whole outline! } addLineInfill(result, rotation_matrix, scanline_min_idx, line_distance, boundary, cut_list, shift); }
void generateTroctInfill(const Polygons& in_outline, Polygons& result, int extrusionWidth, int lineSpacing, int infillOverlap, double rotation, int posZ) { Polygons outline = in_outline.offset(extrusionWidth * infillOverlap / 100); PointMatrix matrix(rotation); outline.applyMatrix(matrix); AABB boundary(outline); // ignore infill for areas smaller than line spacing if((abs(boundary.min.X - boundary.max.X) + abs(boundary.min.Y - boundary.max.Y)) < lineSpacing){ return; } // fix to normalise against diagonal infill lineSpacing = lineSpacing * 2; uint64_t Zscale = SQRT2MUL(lineSpacing); int offset = abs(posZ % (Zscale) - (Zscale/2)) - (Zscale/4); boundary.min.X = ((boundary.min.X / lineSpacing) - 1) * lineSpacing; boundary.min.Y = ((boundary.min.Y / lineSpacing) - 1) * lineSpacing; unsigned int lineCountX = (boundary.max.X - boundary.min.X + (lineSpacing - 1)) / lineSpacing; unsigned int lineCountY = (boundary.max.Y - boundary.min.Y + (lineSpacing - 1)) / lineSpacing; int rtMod = int(rotation / 90) % 2; // with an odd number of lines, sides need to be swapped around if(rtMod == 1){ rtMod = (lineCountX + int(rotation / 90)) % 2; } // draw non-horizontal walls of octohedrons Polygons po; PolygonRef p = po.newPoly(); for(unsigned int ly=0; ly < lineCountY;){ for(size_t it = 0; it < 2; ly++, it++){ int side = (2*((ly + it + rtMod) % 2) - 1); int y = (ly * lineSpacing) + boundary.min.Y + lineSpacing / 2 - (offset/2 * side); int x = boundary.min.X-(offset/2); if(it == 1){ x = (lineCountX * (lineSpacing)) + boundary.min.X + lineSpacing / 2 - (offset/2); } p.add(Point(x,y)); for(unsigned int lx=0; lx < lineCountX; lx++){ if(it == 1){ side = (2*((lx + ly + it + rtMod + lineCountX) % 2) - 1); y = (ly * lineSpacing) + boundary.min.Y + lineSpacing / 2 + (offset/2 * side); x = ((lineCountX - lx - 1) * lineSpacing) + boundary.min.X + lineSpacing / 2; p.add(Point(x+lineSpacing-abs(offset/2), y)); p.add(Point(x+abs(offset/2), y)); } else { side = (2*((lx + ly + it + rtMod) % 2) - 1); y = (ly * lineSpacing) + boundary.min.Y + lineSpacing / 2 + (offset/2 * side); x = (lx * lineSpacing) + boundary.min.X + lineSpacing / 2; p.add(Point(x+abs(offset/2), y)); p.add(Point(x+lineSpacing-abs(offset/2), y)); } } x = (lineCountX * lineSpacing) + boundary.min.X + lineSpacing / 2 - (offset/2); if(it == 1){ x = boundary.min.X-(offset/2); } y = (ly * lineSpacing) + boundary.min.Y + lineSpacing / 2 - (offset/2 * side); p.add(Point(x,y)); } } // Generate tops / bottoms of octohedrons if(abs((abs(offset) - Zscale/4)) < (extrusionWidth/2)){ uint64_t startLine = (offset < 0) ? 0 : 1; uint64_t coverWidth = OCTSLEN(lineSpacing); vector<Point> points; for(size_t xi = 0; xi < (lineCountX+1); xi++){ for(size_t yi = 0; yi < (lineCountY); yi += 2){ points.push_back(Point(boundary.min.X + OCTDLEN(lineSpacing) + (xi - startLine + rtMod) * lineSpacing, boundary.min.Y + OCTDLEN(lineSpacing) + (yi + (xi%2)) * lineSpacing + extrusionWidth/2)); } } uint64_t order = 0; for(Point pp : points){ PolygonRef p = po.newPoly(); for(size_t yi = 0; yi <= coverWidth; yi += extrusionWidth) { if(order == 0){ p.add(Point(pp.X, pp.Y + yi)); p.add(Point(pp.X + coverWidth + extrusionWidth, pp.Y + yi)); } else { p.add(Point(pp.X + coverWidth + extrusionWidth, pp.Y + yi)); p.add(Point(pp.X, pp.Y + yi)); } order = (order + 1) % 2; } } } // intersect with outline polygon(s) Polygons pi = po.intersection(outline); // Hack to add intersection to result. There doesn't seem // to be a direct way to do this for(unsigned int polyNr=0; polyNr < pi.size(); polyNr++) { PolygonRef p = result.newPoly(); // = result.newPoly() for(unsigned int i=0; i < pi[polyNr].size(); i++) { Point p0 = pi[polyNr][i]; p.add(matrix.unapply(Point(p0.X,p0.Y))); } } }
void Weaver::fillRoofs(Polygons& supporting, Polygons& to_be_supported, int direction, int z, WeaveRoof& horizontals) { std::vector<WeaveRoofPart>& insets = horizontals.roof_insets; if (supporting.size() == 0) return; // no parts to start the roof from! Polygons roofs = supporting.difference(to_be_supported); roofs = roofs.offset(-roof_inset).offset(roof_inset); if (roofs.size() == 0) return; Polygons roof_outlines; Polygons roof_holes; { // split roofs into outlines and holes std::vector<PolygonsPart> roof_parts = roofs.splitIntoParts(); for (PolygonsPart& roof_part : roof_parts) { roof_outlines.add(roof_part[0]); for (unsigned int hole_idx = 1; hole_idx < roof_part.size(); hole_idx++) { roof_holes.add(roof_part[hole_idx]); roof_holes.back().reverse(); } } } Polygons supporting_outlines; std::vector<PolygonsPart> supporting_parts = supporting.splitIntoParts(); for (PolygonsPart& supporting_part : supporting_parts) supporting_outlines.add(supporting_part[0]); // only add outlines, not the holes Polygons inset1; Polygons last_inset; Polygons last_supported = supporting; for (Polygons inset0 = supporting_outlines; inset0.size() > 0; inset0 = last_inset) { last_inset = inset0.offset(direction * roof_inset, ClipperLib::jtRound); inset1 = last_inset.intersection(roof_outlines); // stay within roof area inset1 = inset1.unionPolygons(roof_holes);// make insets go around holes if (inset1.size() == 0) break; insets.emplace_back(); connect(last_supported, z, inset1, z, insets.back(), true); inset1 = inset1.remove(roof_holes); // throw away holes which appear in every intersection inset1 = inset1.remove(roof_outlines);// throw away fully filled regions last_supported = insets.back().supported; // chainified } horizontals.roof_outlines.add(roofs); // TODO just add the new lines, not the lines of the roofs which are already supported ==> make outlines into a connection from which we only print the top, not the connection }
/* * Algorithm: * From top layer to bottom layer: * - find overhang by looking at the difference between two consucutive layers * - join with support areas from layer above * - subtract current layer * - use the result for the next lower support layer (without doing XY-distance and Z bottom distance, so that a single support beam may move around the model a bit => more stability) * - perform inset using X/Y-distance and bottom Z distance * * for support buildplate only: purge all support not connected to buildplate */ void generateSupportAreas(SliceDataStorage& storage, SliceMeshStorage* object, unsigned int layer_count, CommandSocket* commandSocket) { // given settings ESupportType support_type = object->getSettingAsSupportType("support_type"); storage.support.generated = false; if (!object->getSettingBoolean("support_enable")) return; if (support_type == Support_None) return; double supportAngle = object->getSettingInAngleRadians("support_angle"); bool supportOnBuildplateOnly = support_type == Support_PlatformOnly; int supportZDistance = object->getSettingInMicrons("support_z_distance"); int supportZDistanceBottom = object->getSettingInMicrons("support_bottom_distance"); int supportZDistanceTop = object->getSettingInMicrons("support_top_distance"); int join_distance = object->getSettingInMicrons("support_join_distance"); int support_bottom_stair_step_height = object->getSettingInMicrons("support_bottom_stair_step_height"); int smoothing_distance = object->getSettingInMicrons("support_area_smoothing"); int supportTowerDiameter = object->getSettingInMicrons("support_tower_diameter"); int supportMinAreaSqrt = object->getSettingInMicrons("support_minimal_diameter"); double supportTowerRoofAngle = object->getSettingInAngleRadians("support_tower_roof_angle"); //std::cerr <<" towerDiameter=" << towerDiameter <<", supportMinAreaSqrt=" << supportMinAreaSqrt << std::endl; int min_smoothing_area = 100*100; // minimal area for which to perform smoothing int z_layer_distance_tower = 1; // start tower directly below overhang point int layerThickness = object->getSettingInMicrons("layer_height"); int extrusionWidth = object->getSettingInMicrons("support_line_width"); int supportXYDistance = object->getSettingInMicrons("support_xy_distance") + extrusionWidth / 2; bool conical_support = object->getSettingBoolean("support_conical_enabled"); // derived settings: if (supportZDistanceBottom < 0) supportZDistanceBottom = supportZDistance; if (supportZDistanceTop < 0) supportZDistanceTop = supportZDistance; int supportLayerThickness = layerThickness; int layerZdistanceTop = supportZDistanceTop / supportLayerThickness + 1; // support must always be 1 layer below overhang unsigned int layerZdistanceBottom = std::max(0, supportZDistanceBottom / supportLayerThickness); double tanAngle = tan(supportAngle) - 0.01; // the XY-component of the supportAngle int maxDistFromLowerLayer = tanAngle * supportLayerThickness; // max dist which can be bridged unsigned int support_layer_count = layer_count; double tanTowerRoofAngle = tan(supportTowerRoofAngle); int towerRoofExpansionDistance = layerThickness / tanTowerRoofAngle; // early out if ( layerZdistanceTop + 1 > (int) support_layer_count ) { storage.support.generated = false; // no (first layer) support can be generated return; } // computation std::vector<Polygons> joinedLayers; // join model layers of all meshes into polygons and store small areas which need tower support std::vector<std::pair<int, std::vector<Polygons>>> overhang_points; // stores overhang_points along with the layer index at which the overhang point occurs AreaSupport::joinMeshesAndDetectOverhangPoints(storage, joinedLayers, overhang_points, layer_count, supportMinAreaSqrt, extrusionWidth); // initialization of supportAreasPerLayer for (unsigned int layer_idx = 0; layer_idx < layer_count ; layer_idx++) storage.support.supportLayers.emplace_back(); bool still_in_upper_empty_layers = true; int overhang_points_pos = overhang_points.size() - 1; Polygons supportLayer_last; std::vector<Polygons> towerRoofs; for (unsigned int layer_idx = support_layer_count - 1 - layerZdistanceTop; layer_idx != (unsigned int) -1 ; layer_idx--) { // compute basic overhang and put in right layer ([layerZdistanceTOp] layers below) Polygons& supportLayer_supportee = joinedLayers[layer_idx+layerZdistanceTop]; // if (conical_support) // { // supportLayer_supportee = join(supportLayer_supportee, supportLayer_last, join_distance, smoothing_distance, min_smoothing_area); // } Polygons& supportLayer_supporter = joinedLayers[layer_idx-1+layerZdistanceTop]; Polygons supportLayer_this; if (conical_support) { int maxDistFromLowerLayer_support = maxDistFromLowerLayer/2; Polygons layer_above = supportLayer_supportee.unionPolygons(supportLayer_last); //join(supportLayer_supportee, supportLayer_last, join_distance, smoothing_distance, min_smoothing_area); Polygons insetted = layer_above.offset(-maxDistFromLowerLayer_support); supportLayer_this = insetted.unionPolygons( layer_above.difference(insetted.offset(maxDistFromLowerLayer_support + 100)) ).difference(supportLayer_supporter.offset(maxDistFromLowerLayer)); } else { Polygons supportLayer_supported = supportLayer_supporter.offset(maxDistFromLowerLayer); Polygons basic_overhang = supportLayer_supportee.difference(supportLayer_supported); // Polygons support_extension = basic_overhang.offset(maxDistFromLowerLayer); // support_extension = support_extension.intersection(supportLayer_supported); // support_extension = support_extension.intersection(supportLayer_supportee); // // Polygons overhang = basic_overhang.unionPolygons(support_extension); // presumably the computation above is slower than the one below Polygons overhang_extented = basic_overhang.offset(maxDistFromLowerLayer + 100); // +100 for easier joining with support from layer above Polygons overhang = overhang_extented.intersection(supportLayer_supported.unionPolygons(supportLayer_supportee)); /* layer 2 * layer 1 ______________| * _______| ^^^^^ basic overhang * * ^^^^^^^ supporter * ^^^^^^^^^^^^^^^^^ supported * ^^^^^^^^^^^^^^^^^^^^^^ supportee * ^^^^^^^^^^^^^^^^^^^^^^^^ overhang extended * ^^^^^^^^^ overhang extensions * ^^^^^^^^^^^^^^ overhang */ supportLayer_this = overhang; } supportLayer_this = supportLayer_this.simplify(50); // TODO: hardcoded value! if (supportMinAreaSqrt > 0) { // handle straight walls AreaSupport::handleWallStruts(supportLayer_this, supportMinAreaSqrt, supportTowerDiameter); // handle towers AreaSupport::handleTowers(supportLayer_this, towerRoofs, overhang_points, overhang_points_pos, layer_idx, towerRoofExpansionDistance, supportTowerDiameter, supportMinAreaSqrt, layer_count, z_layer_distance_tower); } if (!conical_support) { if (layer_idx+1 < support_layer_count) { // join with support from layer up supportLayer_this = join(supportLayer_last, supportLayer_this, join_distance, smoothing_distance, min_smoothing_area); } } // move up from model if (layerZdistanceBottom > 0 && layer_idx >= layerZdistanceBottom) { int stepHeight = support_bottom_stair_step_height / supportLayerThickness + 1; int bottomLayer = ((layer_idx - layerZdistanceBottom) / stepHeight) * stepHeight; supportLayer_this = supportLayer_this.difference(joinedLayers[bottomLayer]); } supportLayer_last = supportLayer_this; // inset using X/Y distance if (supportLayer_this.size() > 0) supportLayer_this = supportLayer_this.difference(joinedLayers[layer_idx].offset(supportXYDistance)); storage.support.supportLayers[layer_idx].supportAreas = supportLayer_this; if (still_in_upper_empty_layers && supportLayer_this.size() > 0) { storage.support.layer_nr_max_filled_layer = layer_idx; still_in_upper_empty_layers = false; } Progress::messageProgress(Progress::Stage::SUPPORT, support_layer_count - layer_idx, support_layer_count, commandSocket); } // do stuff for when support on buildplate only if (supportOnBuildplateOnly) { Polygons touching_buildplate = storage.support.supportLayers[0].supportAreas; for (unsigned int layer_idx = 1 ; layer_idx < storage.support.supportLayers.size() ; layer_idx++) { Polygons& supportLayer = storage.support.supportLayers[layer_idx].supportAreas; touching_buildplate = supportLayer.intersection(touching_buildplate); // from bottom to top, support areas can only decrease! storage.support.supportLayers[layer_idx].supportAreas = touching_buildplate; } } storage.support.generated = true; }
void SkirtBrim::generate(SliceDataStorage& storage, Polygons first_layer_outline, int start_distance, unsigned int primary_line_count) { const bool is_skirt = start_distance > 0; Scene& scene = Application::getInstance().current_slice->scene; const size_t adhesion_extruder_nr = scene.current_mesh_group->settings.get<ExtruderTrain&>("adhesion_extruder_nr").extruder_nr; const Settings& adhesion_settings = scene.extruders[adhesion_extruder_nr].settings; const coord_t primary_extruder_skirt_brim_line_width = adhesion_settings.get<coord_t>("skirt_brim_line_width") * adhesion_settings.get<Ratio>("initial_layer_line_width_factor"); const coord_t primary_extruder_minimal_length = adhesion_settings.get<coord_t>("skirt_brim_minimal_length"); Polygons& skirt_brim_primary_extruder = storage.skirt_brim[adhesion_extruder_nr]; const bool has_ooze_shield = storage.oozeShield.size() > 0 && storage.oozeShield[0].size() > 0; const bool has_draft_shield = storage.draft_protection_shield.size() > 0; if (is_skirt && (has_ooze_shield || has_draft_shield)) { // make sure we don't generate skirt through draft / ooze shield first_layer_outline = first_layer_outline.offset(start_distance - primary_extruder_skirt_brim_line_width / 2, ClipperLib::jtRound).unionPolygons(storage.draft_protection_shield); if (has_ooze_shield) { first_layer_outline = first_layer_outline.unionPolygons(storage.oozeShield[0]); } first_layer_outline = first_layer_outline.approxConvexHull(); start_distance = primary_extruder_skirt_brim_line_width / 2; } int offset_distance = generatePrimarySkirtBrimLines(start_distance, primary_line_count, primary_extruder_minimal_length, first_layer_outline, skirt_brim_primary_extruder); // handle support-brim const ExtruderTrain& support_infill_extruder = scene.current_mesh_group->settings.get<ExtruderTrain&>("support_infill_extruder_nr"); if (support_infill_extruder.settings.get<bool>("support_brim_enable")) { generateSupportBrim(storage); } // generate brim for ooze shield and draft shield if (!is_skirt && (has_ooze_shield || has_draft_shield)) { // generate areas where to make extra brim for the shields // avoid gap in the middle // V // +---+ +----+ // |+-+| |+--+| // || || ||[]|| > expand to fit an extra brim line // |+-+| |+--+| // +---+ +----+ const int64_t primary_skirt_brim_width = (primary_line_count + primary_line_count % 2) * primary_extruder_skirt_brim_line_width; // always use an even number, because we will fil the area from both sides Polygons shield_brim; if (has_ooze_shield) { shield_brim = storage.oozeShield[0].difference(storage.oozeShield[0].offset(-primary_skirt_brim_width - primary_extruder_skirt_brim_line_width)); } if (has_draft_shield) { shield_brim = shield_brim.unionPolygons(storage.draft_protection_shield.difference(storage.draft_protection_shield.offset(-primary_skirt_brim_width - primary_extruder_skirt_brim_line_width))); } const Polygons outer_primary_brim = first_layer_outline.offset(offset_distance, ClipperLib::jtRound); shield_brim = shield_brim.difference(outer_primary_brim.offset(primary_extruder_skirt_brim_line_width)); // generate brim within shield_brim skirt_brim_primary_extruder.add(shield_brim); while (shield_brim.size() > 0) { shield_brim = shield_brim.offset(-primary_extruder_skirt_brim_line_width); skirt_brim_primary_extruder.add(shield_brim); } // update parameters to generate secondary skirt around first_layer_outline = outer_primary_brim; if (has_draft_shield) { first_layer_outline = first_layer_outline.unionPolygons(storage.draft_protection_shield); } if (has_ooze_shield) { first_layer_outline = first_layer_outline.unionPolygons(storage.oozeShield[0]); } offset_distance = 0; } { // process other extruders' brim/skirt (as one brim line around the old brim) int last_width = primary_extruder_skirt_brim_line_width; std::vector<bool> extruder_is_used = storage.getExtrudersUsed(); for (size_t extruder_nr = 0; extruder_nr < Application::getInstance().current_slice->scene.extruders.size(); extruder_nr++) { if (extruder_nr == adhesion_extruder_nr || !extruder_is_used[extruder_nr]) { continue; } const ExtruderTrain& train = Application::getInstance().current_slice->scene.extruders[extruder_nr]; const coord_t width = train.settings.get<coord_t>("skirt_brim_line_width") * train.settings.get<Ratio>("initial_layer_line_width_factor"); const coord_t minimal_length = train.settings.get<coord_t>("skirt_brim_minimal_length"); offset_distance += last_width / 2 + width/2; last_width = width; while (storage.skirt_brim[extruder_nr].polygonLength() < minimal_length) { storage.skirt_brim[extruder_nr].add(first_layer_outline.offset(offset_distance, ClipperLib::jtRound)); offset_distance += width; } } } }
/* * Algorithm: * From top layer to bottom layer: * - find overhang by looking at the difference between two consucutive layers * - join with support areas from layer above * - subtract current layer * - use the result for the next lower support layer (without doing XY-distance and Z bottom distance, so that a single support beam may move around the model a bit => more stability) * - perform inset using X/Y-distance and bottom Z distance * * for support buildplate only: purge all support not connected to buildplate */ void AreaSupport::generateSupportAreas(SliceDataStorage& storage, unsigned int mesh_idx, unsigned int layer_count, std::vector<Polygons>& supportAreas) { SliceMeshStorage& mesh = storage.meshes[mesh_idx]; // given settings ESupportType support_type = storage.getSettingAsSupportType("support_type"); if (!mesh.getSettingBoolean("support_enable")) return; if (support_type == ESupportType::NONE) return; const double supportAngle = mesh.getSettingInAngleRadians("support_angle"); const bool supportOnBuildplateOnly = support_type == ESupportType::PLATFORM_ONLY; const int supportZDistanceBottom = mesh.getSettingInMicrons("support_bottom_distance"); const int supportZDistanceTop = mesh.getSettingInMicrons("support_top_distance"); const int join_distance = mesh.getSettingInMicrons("support_join_distance"); const int support_bottom_stair_step_height = mesh.getSettingInMicrons("support_bottom_stair_step_height"); const int extension_offset = mesh.getSettingInMicrons("support_offset"); const int supportTowerDiameter = mesh.getSettingInMicrons("support_tower_diameter"); const int supportMinAreaSqrt = mesh.getSettingInMicrons("support_minimal_diameter"); const double supportTowerRoofAngle = mesh.getSettingInAngleRadians("support_tower_roof_angle"); const int layerThickness = storage.getSettingInMicrons("layer_height"); const int supportXYDistance = mesh.getSettingInMicrons("support_xy_distance"); const int support_xy_distance_overhang = mesh.getSettingInMicrons("support_xy_distance_overhang"); const bool use_support_xy_distance_overhang = mesh.getSettingAsSupportDistPriority("support_xy_overrides_z") == SupportDistPriority::Z_OVERRIDES_XY; // whether to use a different xy distance at overhangs const double conical_support_angle = mesh.getSettingInAngleRadians("support_conical_angle"); const bool conical_support = mesh.getSettingBoolean("support_conical_enabled") && conical_support_angle != 0; const int64_t conical_smallest_breadth = mesh.getSettingInMicrons("support_conical_min_width"); int support_skin_extruder_nr = storage.getSettingAsIndex("support_interface_extruder_nr"); int support_infill_extruder_nr = storage.getSettingAsIndex("support_infill_extruder_nr"); bool interface_enable = mesh.getSettingBoolean("support_interface_enable"); // derived settings: const int max_smoothing_angle = 135; // maximum angle of inner corners to be smoothed int smoothing_distance; { // compute best smoothing_distance ExtruderTrain& infill_train = *storage.meshgroup->getExtruderTrain(support_infill_extruder_nr); int support_infill_line_width = infill_train.getSettingInMicrons("support_interface_line_width"); smoothing_distance = support_infill_line_width; if (interface_enable) { ExtruderTrain& interface_train = *storage.meshgroup->getExtruderTrain(support_skin_extruder_nr); int support_interface_line_width = interface_train.getSettingInMicrons("support_interface_line_width"); smoothing_distance = std::max(support_interface_line_width, smoothing_distance); } } const int z_layer_distance_tower = 1; // start tower directly below overhang point int supportLayerThickness = layerThickness; const unsigned int layerZdistanceTop = std::max(0U, round_up_divide(supportZDistanceTop, supportLayerThickness)) + 1; // support must always be 1 layer below overhang const unsigned int layerZdistanceBottom = std::max(0U, round_up_divide(supportZDistanceBottom, supportLayerThickness)); double tanAngle = tan(supportAngle) - 0.01; // the XY-component of the supportAngle int max_dist_from_lower_layer = tanAngle * supportLayerThickness; // max dist which can be bridged int64_t conical_support_offset; if (conical_support_angle > 0) { // outward ==> wider base than overhang conical_support_offset = -(tan(conical_support_angle) - 0.01) * supportLayerThickness; } else { // inward ==> smaller base than overhang conical_support_offset = (tan(-conical_support_angle) - 0.01) * supportLayerThickness; } unsigned int support_layer_count = layer_count; double tanTowerRoofAngle = tan(supportTowerRoofAngle); int towerRoofExpansionDistance = layerThickness / tanTowerRoofAngle; // early out if ( layerZdistanceTop + 1 > support_layer_count ) { return; } // computation std::vector<std::pair<int, std::vector<Polygons>>> overhang_points; // stores overhang_points along with the layer index at which the overhang point occurs AreaSupport::detectOverhangPoints(storage, mesh, overhang_points, layer_count, supportMinAreaSqrt); std::deque<std::pair<Polygons, Polygons>> basic_and_full_overhang_above; for (unsigned int layer_idx = support_layer_count - 1; layer_idx != support_layer_count - 1 - layerZdistanceTop ; layer_idx--) { basic_and_full_overhang_above.push_front(computeBasicAndFullOverhang(storage, mesh, layer_idx, max_dist_from_lower_layer)); } int overhang_points_pos = overhang_points.size() - 1; Polygons supportLayer_last; std::vector<Polygons> towerRoofs; for (unsigned int layer_idx = support_layer_count - 1 - layerZdistanceTop; layer_idx != (unsigned int) -1 ; layer_idx--) { basic_and_full_overhang_above.push_front(computeBasicAndFullOverhang(storage, mesh, layer_idx, max_dist_from_lower_layer)); Polygons overhang; { // compute basic overhang and put in right layer ([layerZdistanceTOp] layers below) overhang = basic_and_full_overhang_above.back().second; basic_and_full_overhang_above.pop_back(); } Polygons& supportLayer_this = overhang; if (extension_offset) { supportLayer_this = supportLayer_this.offset(extension_offset); } if (supportMinAreaSqrt > 0) { // handle straight walls AreaSupport::handleWallStruts(supportLayer_this, supportMinAreaSqrt, supportTowerDiameter); // handle towers AreaSupport::handleTowers(supportLayer_this, towerRoofs, overhang_points, overhang_points_pos, layer_idx, towerRoofExpansionDistance, supportTowerDiameter, supportMinAreaSqrt, layer_count, z_layer_distance_tower); } if (layer_idx+1 < support_layer_count) { // join with support from layer up supportLayer_this = AreaSupport::join(supportLayer_last, supportLayer_this, join_distance, smoothing_distance, max_smoothing_angle, conical_support, conical_support_offset, conical_smallest_breadth); } supportLayer_this = supportLayer_this.unionPolygons(storage.support.supportLayers[layer_idx].support_mesh); // move up from model if (layerZdistanceBottom > 0 && layer_idx >= layerZdistanceBottom) { int stepHeight = support_bottom_stair_step_height / supportLayerThickness + 1; int bottomLayer = ((layer_idx - layerZdistanceBottom) / stepHeight) * stepHeight; supportLayer_this = supportLayer_this.difference(storage.getLayerOutlines(bottomLayer, false)); } supportLayer_last = supportLayer_this; // inset using X/Y distance if (supportLayer_this.size() > 0) { Polygons& basic_overhang = basic_and_full_overhang_above.front().first; // basic overhang on this layer Polygons outlines = storage.getLayerOutlines(layer_idx, false); if (use_support_xy_distance_overhang) { Polygons xy_overhang_disallowed = basic_overhang.offset(supportZDistanceTop * tanAngle); Polygons xy_non_overhang_disallowed = outlines.difference(basic_overhang.offset(supportXYDistance)).offset(supportXYDistance); Polygons xy_disallowed = xy_overhang_disallowed.unionPolygons(xy_non_overhang_disallowed.unionPolygons(outlines.offset(support_xy_distance_overhang))); supportLayer_this = supportLayer_this.difference(xy_disallowed); } else { supportLayer_this = supportLayer_this.difference(outlines.offset(supportXYDistance)); } if (storage.primeTower.enabled) //Don't intersect with prime tower. { supportLayer_this = supportLayer_this.difference(storage.primeTower.ground_poly.getOutsidePolygons()); } } supportAreas[layer_idx] = supportLayer_this; Progress::messageProgress(Progress::Stage::SUPPORT, storage.meshes.size() * mesh_idx + support_layer_count - layer_idx, support_layer_count * storage.meshes.size()); } // do stuff for when support on buildplate only if (supportOnBuildplateOnly) { Polygons touching_buildplate = supportAreas[0]; // TODO: not working for conical support! for (unsigned int layer_idx = 1 ; layer_idx < storage.support.supportLayers.size() ; layer_idx++) { Polygons& supportLayer = supportAreas[layer_idx]; if (conical_support) { // with conical support the next layer is allowed to be larger than the previous touching_buildplate = touching_buildplate.offset(std::abs(conical_support_offset) + 10, ClipperLib::jtMiter, 10); // + 10 and larger miter limit cause performing an outward offset after an inward offset can disregard sharp corners // // conical support can make // layer above layer below // v v // | : | // | ==> : |__ // |____ :.... // // a miter limit would result in // | : : | // | :.. <== : |__ // .\___ :.... // } touching_buildplate = supportLayer.intersection(touching_buildplate); // from bottom to top, support areas can only decrease! supportAreas[layer_idx] = touching_buildplate; } } for (unsigned int layer_idx = supportAreas.size() - 1; layer_idx != (unsigned int) std::max(-1, storage.support.layer_nr_max_filled_layer) ; layer_idx--) { const Polygons& support_here = supportAreas[layer_idx]; if (support_here.size() > 0) { storage.support.layer_nr_max_filled_layer = layer_idx; break; } } storage.support.generated = true; }