void generateSkins(int layerNr, SliceVolumeStorage& storage, int extrusionWidth, int downSkinCount, int upSkinCount, int infillOverlap) { SliceLayer* layer = &storage.layers[layerNr]; for(unsigned int partNr=0; partNr<layer->parts.size(); partNr++) { SliceLayerPart* part = &layer->parts[partNr]; Polygons upskin = part->insets[part->insets.size() - 1].offset(-extrusionWidth/2); Polygons downskin = upskin; if (part->insets.size() > 1) { //Add thin wall filling by taking the area between the insets. Polygons thinWalls = part->insets[0].offset(-extrusionWidth / 2 - extrusionWidth * infillOverlap / 100).difference(part->insets[1].offset(extrusionWidth * 6 / 10)); upskin.add(thinWalls); downskin.add(thinWalls); } if (int(layerNr - downSkinCount) >= 0) { SliceLayer* layer2 = &storage.layers[layerNr - downSkinCount]; for(unsigned int partNr2=0; partNr2<layer2->parts.size(); partNr2++) { if (part->boundaryBox.hit(layer2->parts[partNr2].boundaryBox)) downskin = downskin.difference(layer2->parts[partNr2].insets[layer2->parts[partNr2].insets.size() - 1]); } } if (int(layerNr + upSkinCount) < (int)storage.layers.size()) { SliceLayer* layer2 = &storage.layers[layerNr + upSkinCount]; for(unsigned int partNr2=0; partNr2<layer2->parts.size(); partNr2++) { if (part->boundaryBox.hit(layer2->parts[partNr2].boundaryBox)) upskin = upskin.difference(layer2->parts[partNr2].insets[layer2->parts[partNr2].insets.size() - 1]); } } part->skinOutline = upskin.unionPolygons(downskin); double minAreaSize = (2 * M_PI * (double(extrusionWidth) / 1000.0) * (double(extrusionWidth) / 1000.0)) * 0.3; for(unsigned int i=0; i<part->skinOutline.size(); i++) { double area = fabs(ClipperLib::Area(part->skinOutline[i])) / 1000.0 / 1000.0; if (area < minAreaSize) // Only create an up/down skin if the area is large enough. So you do not create tiny blobs of "trying to fill" { part->skinOutline.remove(i); i -= 1; } } } }
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); } }
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 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 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()); } } }
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; }
/* * 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); }
/* * 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::calculateBottomSkin(const SliceLayerPart& part, int min_infill_area, Polygons& downskin) { if (static_cast<int>(layer_nr - bottom_layer_count) >= 0 && bottom_layer_count > 0) { Polygons not_air = getWalls(part, layer_nr - bottom_layer_count, bottom_reference_wall_idx).offset(bottom_reference_wall_expansion); if (!no_small_gaps_heuristic) { for (int downskin_layer_nr = layer_nr - bottom_layer_count + 1; downskin_layer_nr < layer_nr; downskin_layer_nr++) { not_air = not_air.intersection(getWalls(part, downskin_layer_nr, bottom_reference_wall_idx).offset(bottom_reference_wall_expansion)); } } if (min_infill_area > 0) { not_air.removeSmallAreas(min_infill_area); } downskin = downskin.difference(not_air); // skin overlaps with the walls } }
void SkinInfillAreaComputation::calculateTopSkin(const SliceLayerPart& part, int min_infill_area, Polygons& upskin) { if (static_cast<int>(layer_nr + top_layer_count) < static_cast<int>(mesh.layers.size()) && top_layer_count > 0) { Polygons not_air = getWalls(part, layer_nr + top_layer_count, top_reference_wall_idx).offset(top_reference_wall_expansion); if (!no_small_gaps_heuristic) { for (int upskin_layer_nr = layer_nr + 1; upskin_layer_nr < layer_nr + top_layer_count; upskin_layer_nr++) { not_air = not_air.intersection(getWalls(part, upskin_layer_nr, top_reference_wall_idx).offset(top_reference_wall_expansion)); } } if (min_infill_area > 0) { not_air.removeSmallAreas(min_infill_area); } upskin = upskin.difference(not_air); // skin overlaps with the walls } }
void Infill::generateConcentricInfill(Polygons& first_concentric_wall, Polygons& result, int inset_value) { result.add(first_concentric_wall); Polygons* prev_inset = &first_concentric_wall; Polygons next_inset; while (prev_inset->size() > 0) { next_inset = prev_inset->offset(-inset_value); result.add(next_inset); if (perimeter_gaps) { const Polygons outer = prev_inset->offset(-infill_line_width / 2 - perimeter_gaps_extra_offset); const Polygons inner = next_inset.offset(infill_line_width / 2); const Polygons gaps_here = outer.difference(inner); perimeter_gaps->add(gaps_here); } prev_inset = &next_inset; } }
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 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; }
void generateSkinAreas(int layer_nr, SliceMeshStorage& mesh, const int innermost_wall_line_width, int downSkinCount, int upSkinCount, int wall_line_count, bool no_small_gaps_heuristic) { SliceLayer& layer = mesh.layers[layer_nr]; if (downSkinCount == 0 && upSkinCount == 0) { return; } for(unsigned int partNr = 0; partNr < layer.parts.size(); partNr++) { SliceLayerPart& part = layer.parts[partNr]; if (int(part.insets.size()) < wall_line_count) { continue; // the last wall is not present, the part should only get inter perimeter gaps, but no skin. } Polygons upskin = part.insets.back().offset(-innermost_wall_line_width / 2); Polygons downskin = (downSkinCount == 0) ? Polygons() : upskin; if (upSkinCount == 0) upskin = Polygons(); auto getInsidePolygons = [&part, wall_line_count](SliceLayer& layer2) { Polygons result; for(SliceLayerPart& part2 : layer2.parts) { if (part.boundaryBox.hit(part2.boundaryBox)) { unsigned int wall_idx = std::max(0, std::min(wall_line_count, (int) part2.insets.size()) - 1); result.add(part2.insets[wall_idx]); } } return result; }; if (no_small_gaps_heuristic) { if (static_cast<int>(layer_nr - downSkinCount) >= 0) { downskin = downskin.difference(getInsidePolygons(mesh.layers[layer_nr - downSkinCount])); // skin overlaps with the walls } if (static_cast<int>(layer_nr + upSkinCount) < static_cast<int>(mesh.layers.size())) { upskin = upskin.difference(getInsidePolygons(mesh.layers[layer_nr + upSkinCount])); // skin overlaps with the walls } } else { if (layer_nr >= downSkinCount && downSkinCount > 0) { Polygons not_air = getInsidePolygons(mesh.layers[layer_nr - 1]); for (int downskin_layer_nr = layer_nr - downSkinCount; downskin_layer_nr < layer_nr - 1; downskin_layer_nr++) { not_air = not_air.intersection(getInsidePolygons(mesh.layers[downskin_layer_nr])); } downskin = downskin.difference(not_air); // skin overlaps with the walls } if (layer_nr < static_cast<int>(mesh.layers.size()) - 1 - upSkinCount && upSkinCount > 0) { Polygons not_air = getInsidePolygons(mesh.layers[layer_nr + 1]); for (int upskin_layer_nr = layer_nr + 2; upskin_layer_nr < layer_nr + upSkinCount + 1; upskin_layer_nr++) { not_air = not_air.intersection(getInsidePolygons(mesh.layers[upskin_layer_nr])); } upskin = upskin.difference(not_air); // skin overlaps with the walls } } Polygons skin = upskin.unionPolygons(downskin); skin.removeSmallAreas(MIN_AREA_SIZE); for (PolygonsPart& skin_area_part : skin.splitIntoParts()) { part.skin_parts.emplace_back(); part.skin_parts.back().outline = skin_area_part; } } }
void generateSparse(int layerNr, SliceVolumeStorage& storage, int extrusionWidth, int downSkinCount, int upSkinCount) { SliceLayer* layer = &storage.layers[layerNr]; for(unsigned int partNr=0; partNr<layer->parts.size(); partNr++) { SliceLayerPart* part = &layer->parts[partNr]; Polygons sparse = part->insets[part->insets.size() - 1].offset(-extrusionWidth/2); Polygons downskin = sparse; Polygons upskin = sparse; if (int(layerNr - downSkinCount) >= 0) { SliceLayer* layer2 = &storage.layers[layerNr - downSkinCount]; for(unsigned int partNr2=0; partNr2<layer2->parts.size(); partNr2++) { if (part->boundaryBox.hit(layer2->parts[partNr2].boundaryBox)) { if (layer2->parts[partNr2].insets.size() > 1) { downskin = downskin.difference(layer2->parts[partNr2].insets[layer2->parts[partNr2].insets.size() - 2]); }else{ downskin = downskin.difference(layer2->parts[partNr2].insets[layer2->parts[partNr2].insets.size() - 1]); } } } } if (int(layerNr + upSkinCount) < (int)storage.layers.size()) { SliceLayer* layer2 = &storage.layers[layerNr + upSkinCount]; for(unsigned int partNr2=0; partNr2<layer2->parts.size(); partNr2++) { if (part->boundaryBox.hit(layer2->parts[partNr2].boundaryBox)) { if (layer2->parts[partNr2].insets.size() > 1) { upskin = upskin.difference(layer2->parts[partNr2].insets[layer2->parts[partNr2].insets.size() - 2]); }else{ upskin = upskin.difference(layer2->parts[partNr2].insets[layer2->parts[partNr2].insets.size() - 1]); } } } } Polygons result = upskin.unionPolygons(downskin); double minAreaSize = 3.0;//(2 * M_PI * (double(config.extrusionWidth) / 1000.0) * (double(config.extrusionWidth) / 1000.0)) * 3; for(unsigned int i=0; i<result.size(); i++) { double area = fabs(ClipperLib::Area(result[i])) / 1000.0 / 1000.0; if (area < minAreaSize) /* Only create an up/down skin if the area is large enough. So you do not create tiny blobs of "trying to fill" */ { result.remove(i); i -= 1; } } part->sparseOutline = sparse.difference(result); } }
/* * 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; }
/*! * adapted from generateLineInfill(.) * * generate lines within the area of [in_outline], at regular intervals of [lineSpacing] * idea: * intersect a regular grid of 'scanlines' with the area inside [in_outline] * sigzag: * include pieces of boundary, connecting the lines, forming an accordion like zigzag instead of separate lines |_|^|_| * * we call the areas between two consecutive scanlines a 'scansegment' * * 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 * * 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]) * if polygon intersects with even scanline again (instead of odd) * dont add the last line segment to the boundary (unless [connect_zigzags]) * * * <-- * ___ * | | | * | | | * | |___| * --> * * ^ = even scanline * * start boundary from even scanline! :D * * * _____ * | | | , * | | | | * |_____| |__/ * * ^ ^ ^ scanlines * ^ disconnected end piece */ void generateZigZagIninfill_endPieces(const Polygons& in_outline, Polygons& result, int extrusionWidth, int lineSpacing, double infillOverlap, double rotation, bool connect_zigzags) { // if (in_outline.size() == 0) return; // Polygons outline = in_outline.offset(extrusionWidth * infillOverlap / 100 - extrusionWidth / 2); Polygons empty; Polygons outline = in_outline.difference(empty); // copy 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> unevenBoundarySegment; // stored cause for connected_zigzags a boundary segment which ends in an uneven scanline needs to be included bool isFirstBoundarySegment = true; bool firstBoundarySegmentEndsInEven; bool isEvenScanSegment = false; Point p0 = outline[polyNr][outline[polyNr].size()-1]; Point lastPoint = p0; 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) { lastPoint = p1; 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); 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 && (connect_zigzags || !isEvenScanSegment)) addLine(lastPoint, Point(x,y)); else if (connect_zigzags && !last_isEvenScanSegment && !isEvenScanSegment) // if we end an uneven boundary in an uneven segment { // add whole unevenBoundarySegment (including the just obtained point) for (unsigned int p = 1; p < unevenBoundarySegment.size(); p++) { addLine(unevenBoundarySegment[p-1], unevenBoundarySegment[p]); } addLine(unevenBoundarySegment[unevenBoundarySegment.size()-1], Point(x,y)); unevenBoundarySegment.clear(); } if (connect_zigzags && last_isEvenScanSegment && !isEvenScanSegment) unevenBoundarySegment.push_back(Point(x,y)); else unevenBoundarySegment.clear(); } lastPoint = Point(x,y); if (isFirstBoundarySegment) { firstBoundarySegment.emplace_back(x,y); firstBoundarySegmentEndsInEven = isEvenScanSegment; isFirstBoundarySegment = false; } } if (!isFirstBoundarySegment) { if (isEvenScanSegment) addLine(lastPoint, p1); else if (connect_zigzags) unevenBoundarySegment.push_back(p1); } lastPoint = p1; p0 = p1; } if (isEvenScanSegment || isFirstBoundarySegment || connect_zigzags) { for (unsigned int i = 1; i < firstBoundarySegment.size() ; i++) { if (i < firstBoundarySegment.size() - 1 || !firstBoundarySegmentEndsInEven || connect_zigzags) // only add last element if connect_zigzags or boundary segment ends in uneven scanline addLine(firstBoundarySegment[i-1], firstBoundarySegment[i]); } } else if (!firstBoundarySegmentEndsInEven) addLine(firstBoundarySegment[firstBoundarySegment.size()-2], firstBoundarySegment[firstBoundarySegment.size()-1]); } if (cutList.size() == 0) return; if (connect_zigzags && cutList.size() == 1 && cutList[0].size() <= 2) return; // don't add connection if boundary already contains whole outline! addLineInfill(result, matrix, scanline_min_idx, lineSpacing, boundary, cutList, extrusionWidth); }
void generateSkirt(SliceDataStorage& storage, int distance, int extrusionWidth, int count, int minLength) { if (count == 0) return; bool externalOnly = (distance > 0); Polygons support; if (storage.support.generated) support = storage.support.supportLayers[0].supportAreas; { // get support polygons for(SliceMeshStorage& mesh : storage.meshes) { if (mesh.layers.size() < 1) continue; SliceLayer* layer = &mesh.layers[0]; for(unsigned int i=0; i<layer->parts.size(); i++) support = support.difference(layer->parts[i].outline); } // expand and contract to smooth the final polygon if (count == 1 && distance > 0) { int dist = extrusionWidth * 5; support = support.offset(dist).offset(-dist); } } int overshoot = 0; // distance by which to expand and contract the skirt to approximate the convex hull of the first layer if (count == 1 && distance > 0) { overshoot = 100000; // 10 cm } for(int skirtNr=0; skirtNr<count;skirtNr++) { int offsetDistance = distance + extrusionWidth * skirtNr + extrusionWidth / 2 + overshoot; Polygons skirtPolygons(storage.wipeTower.offset(offsetDistance)); for(SliceMeshStorage& mesh : storage.meshes) { if (mesh.layers.size() < 1) continue; for(SliceLayerPart& part : mesh.layers[0].parts) { if (externalOnly) { Polygons p; p.add(part.outline.outerPolygon()); skirtPolygons = skirtPolygons.unionPolygons(p.offset(offsetDistance, ClipperLib::jtRound)); } else { skirtPolygons = skirtPolygons.unionPolygons(part.outline.offset(offsetDistance, ClipperLib::jtRound)); } } } skirtPolygons = skirtPolygons.unionPolygons(support.offset(offsetDistance, ClipperLib::jtRound)); //Remove small inner skirt holes. Holes have a negative area, remove anything smaller then 100x extrusion "area" for(unsigned int n=0; n<skirtPolygons.size(); n++) { double area = skirtPolygons[n].area(); if (area < 0 && area > -extrusionWidth * extrusionWidth * 100) skirtPolygons.remove(n--); } storage.skirt.add(skirtPolygons); int lenght = storage.skirt.polygonLength(); if (skirtNr + 1 >= count && lenght > 0 && lenght < minLength) // make brim have more lines when total length is too small count++; } //Add a skirt under the wipetower to make it stick better. Polygons wipe_tower = storage.wipeTower.offset(-extrusionWidth / 2); while(wipe_tower.size() > 0) { storage.skirt.add(wipe_tower); wipe_tower = wipe_tower.offset(-extrusionWidth); } if (overshoot > 0) { storage.skirt = storage.skirt.offset(-overshoot, ClipperLib::jtRound); } }