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 AreaSupport::generateSupportInterface(SliceDataStorage& storage, const SliceMeshStorage& mesh, std::vector<Polygons>& support_areas, const unsigned int layer_count) { const unsigned int roof_layer_count = round_divide(mesh.getSettingInMicrons("support_roof_height"), storage.getSettingInMicrons("layer_height")); const unsigned int bottom_layer_count = round_divide(mesh.getSettingInMicrons("support_bottom_height"), storage.getSettingInMicrons("layer_height")); const unsigned int z_distance_bottom = round_up_divide(mesh.getSettingInMicrons("support_bottom_distance"), storage.getSettingInMicrons("layer_height")); const unsigned int z_distance_top = round_up_divide(mesh.getSettingInMicrons("support_top_distance"), storage.getSettingInMicrons("layer_height")); const int skip_layer_count = std::max(1u, round_divide(mesh.getSettingInMicrons("support_interface_skip_height"), storage.getSettingInMicrons("layer_height"))); const int interface_line_width = storage.meshgroup->getExtruderTrain(storage.getSettingAsIndex("support_interface_extruder_nr"))->getSettingInMicrons("support_interface_line_width"); std::vector<SupportLayer>& supportLayers = storage.support.supportLayers; for (unsigned int layer_idx = 0; layer_idx < layer_count; layer_idx++) { SupportLayer& layer = supportLayers[layer_idx]; const unsigned int top_layer_idx_above = layer_idx + roof_layer_count + z_distance_top; const unsigned int bottom_layer_idx_below = std::max(0, int(layer_idx) - int(bottom_layer_count) - int(z_distance_bottom)); if (top_layer_idx_above < supportLayers.size()) { Polygons roofs; if (roof_layer_count > 0) { Polygons model; const unsigned int n_scans = std::max(1u, (roof_layer_count - 1) / skip_layer_count); const float z_skip = std::max(1.0f, float(roof_layer_count - 1) / float(n_scans)); for (float layer_idx_above = top_layer_idx_above; layer_idx_above > layer_idx + z_distance_top; layer_idx_above -= z_skip) { const Polygons outlines_above = mesh.layers[std::round(layer_idx_above)].getOutlines(); model = model.unionPolygons(outlines_above); } roofs = support_areas[layer_idx].intersection(model); } Polygons bottoms; if (bottom_layer_count > 0) { Polygons model; const unsigned int n_scans = std::max(1u, (bottom_layer_count - 1) / skip_layer_count); const float z_skip = std::max(1.0f, float(bottom_layer_count - 1) / float(n_scans)); for (float layer_idx_below = bottom_layer_idx_below; std::round(layer_idx_below) < (int)(layer_idx - z_distance_bottom); layer_idx_below += z_skip) { const Polygons outlines_below = mesh.layers[std::round(layer_idx_below)].getOutlines(); model = model.unionPolygons(outlines_below); } bottoms = support_areas[layer_idx].intersection(model); } // expand skin a bit so that we're sure it's not too thin to be printed. Polygons skin = roofs.unionPolygons(bottoms).offset(interface_line_width).intersection(support_areas[layer_idx]); skin.removeSmallAreas(1.0); layer.skin.add(skin); layer.supportAreas.add(support_areas[layer_idx].difference(layer.skin)); } else { layer.skin.add(support_areas[layer_idx]); } } }
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; }
Polygons SliceDataStorage::getLayerOutlines(int layer_nr, bool include_helper_parts, bool external_polys_only) const { if (layer_nr < 0 && layer_nr < -Raft::getFillerLayerCount(*this)) { // when processing raft if (include_helper_parts) { if (external_polys_only) { std::vector<PolygonsPart> parts = raftOutline.splitIntoParts(); Polygons result; for (PolygonsPart& part : parts) { result.add(part.outerPolygon()); } return result; } else { return raftOutline; } } else { return Polygons(); } } else { Polygons total; if (layer_nr >= 0) { for (const SliceMeshStorage& mesh : meshes) { if (mesh.getSettingBoolean("infill_mesh") || mesh.getSettingBoolean("anti_overhang_mesh")) { continue; } const SliceLayer& layer = mesh.layers[layer_nr]; layer.getOutlines(total, external_polys_only); if (const_cast<SliceMeshStorage&>(mesh).getSettingAsSurfaceMode("magic_mesh_surface_mode") != ESurfaceMode::NORMAL) // TODO: make all getSetting functions const?? { total = total.unionPolygons(layer.openPolyLines.offsetPolyLine(100)); } } } if (include_helper_parts) { if (support.generated) { total.add(support.supportLayers[std::max(0, layer_nr)].supportAreas); total.add(support.supportLayers[std::max(0, layer_nr)].skin); } if (primeTower.enabled) { total.add(primeTower.ground_poly); } } return total; } }
Polygons SliceDataStorage::getLayerOutlines(unsigned int layer_nr, bool include_helper_parts, bool external_polys_only) { Polygons total; for (SliceMeshStorage& mesh : meshes) { SliceLayer& layer = mesh.layers[layer_nr]; for (SliceLayerPart& part : layer.parts) { if (external_polys_only) { total.add(part.outline.outerPolygon()); } else { total.add(part.outline); } } if (mesh.getSettingAsSurfaceMode("magic_mesh_surface_mode") != ESurfaceMode::NORMAL) { total = total.unionPolygons(layer.openPolyLines.offsetPolyLine(100)); } } if (include_helper_parts) { if (support.generated) { total.add(support.supportLayers[layer_nr].supportAreas); total.add(support.supportLayers[layer_nr].roofs); } total.add(primeTower.ground_poly); } return total; }
void FffPolygonGenerator::processOozeShield(SliceDataStorage& storage, unsigned int totalLayers) { if (!getSettingBoolean("ooze_shield_enabled")) { return; } int ooze_shield_dist = getSettingInMicrons("ooze_shield_dist"); for(unsigned int layer_nr=0; layer_nr<totalLayers; layer_nr++) { Polygons oozeShield; for(SliceMeshStorage& mesh : storage.meshes) { for(SliceLayerPart& part : mesh.layers[layer_nr].parts) { oozeShield = oozeShield.unionPolygons(part.outline.offset(ooze_shield_dist)); } } storage.oozeShield.push_back(oozeShield); } int largest_printed_radius = MM2INT(1.0); // TODO: make var a parameter, and perhaps even a setting? for(unsigned int layer_nr=0; layer_nr<totalLayers; layer_nr++) storage.oozeShield[layer_nr] = storage.oozeShield[layer_nr].offset(-largest_printed_radius).offset(largest_printed_radius); int offsetAngle = tan(getSettingInAngleRadians("ooze_shield_angle")) * getSettingInMicrons("layer_height");//Allow for a 60deg angle in the oozeShield. for(unsigned int layer_nr=1; layer_nr<totalLayers; layer_nr++) storage.oozeShield[layer_nr] = storage.oozeShield[layer_nr].unionPolygons(storage.oozeShield[layer_nr-1].offset(-offsetAngle)); for(unsigned int layer_nr=totalLayers-1; layer_nr>0; layer_nr--) storage.oozeShield[layer_nr-1] = storage.oozeShield[layer_nr-1].unionPolygons(storage.oozeShield[layer_nr].offset(-offsetAngle)); }
void AreaSupport::handleTowers( Polygons& supportLayer_this, std::vector<Polygons>& towerRoofs, std::vector<std::pair<int, std::vector<Polygons>>>& overhang_points, int& overhang_points_pos, int layer_idx, int towerRoofExpansionDistance, int supportTowerDiameter, int supportMinAreaSqrt, int layer_count, int z_layer_distance_tower ) { // handle new tower roof tops int layer_overhang_point = layer_idx + z_layer_distance_tower; if (overhang_points_pos >= 0 && layer_overhang_point < layer_count && overhang_points[overhang_points_pos].first == layer_overhang_point) { std::vector<Polygons>& overhang_points_here = overhang_points[overhang_points_pos].second; { // make sure we have the lowest point (make polys empty if they have small parts below) if (overhang_points_pos > 0 && overhang_points[overhang_points_pos - 1].first == layer_overhang_point - 1) { std::vector<Polygons>& overhang_points_below = overhang_points[overhang_points_pos - 1].second; for (Polygons& poly_here : overhang_points_here) { for (Polygons& poly_below : overhang_points_below) { poly_here = poly_here.difference(poly_below.offset(supportMinAreaSqrt*2)); } } } } for (Polygons& poly : overhang_points_here) if (poly.size() > 0) towerRoofs.push_back(poly); overhang_points_pos--; } // make tower roofs for (unsigned int roof_idx = 0; roof_idx < towerRoofs.size(); roof_idx++) { Polygons& tower_roof = towerRoofs[roof_idx]; if (tower_roof.size() > 0) { supportLayer_this = supportLayer_this.unionPolygons(tower_roof); if (tower_roof[0].area() < supportTowerDiameter * supportTowerDiameter) { tower_roof = tower_roof.offset(towerRoofExpansionDistance); } else { tower_roof.clear(); } } } }
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; } } } }
Polygons SliceDataStorage::getLayerOutlines(int layer_nr, bool include_helper_parts, bool external_polys_only) { if (layer_nr < 0) { // when processing raft if (include_helper_parts) { if (external_polys_only) { std::vector<PolygonsPart> parts = raftOutline.splitIntoParts(); Polygons result; for (PolygonsPart& part : parts) { result.add(part.outerPolygon()); } return result; } else { return raftOutline; } } else { return Polygons(); } } else { Polygons total; for (SliceMeshStorage& mesh : meshes) { SliceLayer& layer = mesh.layers[layer_nr]; layer.getOutlines(total, external_polys_only); if (mesh.getSettingAsSurfaceMode("magic_mesh_surface_mode") != ESurfaceMode::NORMAL) { total = total.unionPolygons(layer.openPolyLines.offsetPolyLine(100)); } } if (include_helper_parts) { if (support.generated) { total.add(support.supportLayers[layer_nr].supportAreas); total.add(support.supportLayers[layer_nr].roofs); } total.add(primeTower.ground_poly); } return total; } }
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 AreaSupport::handleWallStruts( Polygons& supportLayer_this, int supportMinAreaSqrt, int supportTowerDiameter ) { for (unsigned int p = 0; p < supportLayer_this.size(); p++) { PolygonRef poly = supportLayer_this[p]; if (poly.size() < 6) // might be a single wall { PolygonRef poly = supportLayer_this[p]; int best = -1; int best_length2 = -1; for (unsigned int i = 0; i < poly.size(); i++) { int length2 = vSize2(poly[i] - poly[(i+1) % poly.size()]); if (length2 > best_length2) { best = i; best_length2 = length2; } } if (best_length2 < supportMinAreaSqrt * supportMinAreaSqrt) break; // this is a small area, not a wall! // an estimate of the width of the area int width = sqrt( poly.area() * poly.area() / best_length2 ); // sqrt (a^2 / l^2) instead of a / sqrt(l^2) // add square tower (strut) in the middle of the wall if (width < supportMinAreaSqrt) { Point mid = (poly[best] + poly[(best+1) % poly.size()] ) / 2; Polygons struts; PolygonRef strut = struts.newPoly(); strut.add(mid + Point( supportTowerDiameter/2, supportTowerDiameter/2)); strut.add(mid + Point(-supportTowerDiameter/2, supportTowerDiameter/2)); strut.add(mid + Point(-supportTowerDiameter/2, -supportTowerDiameter/2)); strut.add(mid + Point( supportTowerDiameter/2, -supportTowerDiameter/2)); supportLayer_this = supportLayer_this.unionPolygons(struts); } } } }
Polygons SliceDataStorage::getLayerSecondOrInnermostWalls(int layer_nr, bool include_helper_parts) const { if (layer_nr < 0 && layer_nr < -Raft::getFillerLayerCount(*this)) { // when processing raft if (include_helper_parts) { return raftOutline; } else { return Polygons(); } } else { Polygons total; if (layer_nr >= 0) { for (const SliceMeshStorage& mesh : meshes) { const SliceLayer& layer = mesh.layers[layer_nr]; layer.getSecondOrInnermostWalls(total); if (const_cast<SliceMeshStorage&>(mesh).getSettingAsSurfaceMode("magic_mesh_surface_mode") != ESurfaceMode::NORMAL) // TODO: make getSetting const? make settings.setting_values mapping mutable?? { total = total.unionPolygons(layer.openPolyLines.offsetPolyLine(100)); } } } if (include_helper_parts) { if (support.generated) { total.add(support.supportLayers[std::max(0, layer_nr)].supportAreas); total.add(support.supportLayers[std::max(0, layer_nr)].skin); } if (primeTower.enabled) { total.add(primeTower.ground_poly); } } return total; } }
/* * This function is executed in a parallel region based on layer_nr. * When modifying make sure any changes does not introduce data races. * * generateSkinAreas reads data from mesh.layers[*].parts[*].insets and writes to mesh.layers[n].parts[*].skin_parts */ void SkinInfillAreaComputation::generateSkinAndInfillAreas(SliceLayerPart& part) { int min_infill_area = mesh.getSettingInMillimeters("min_infill_area"); Polygons original_outline = part.insets.back().offset(-innermost_wall_line_width / 2); // make a copy of the outline which we later intersect and union with the resized skins to ensure the resized skin isn't too large or removed completely. Polygons upskin; if (top_layer_count > 0) { upskin = Polygons(original_outline); } Polygons downskin; if (bottom_layer_count > 0) { downskin = Polygons(original_outline); } calculateBottomSkin(part, min_infill_area, downskin); calculateTopSkin(part, min_infill_area, upskin); applySkinExpansion(original_outline, upskin, downskin); // now combine the resized upskin and downskin Polygons skin = upskin.unionPolygons(downskin); skin.removeSmallAreas(MIN_AREA_SIZE); if (process_infill) { // process infill when infill density > 0 // or when other infill meshes want to modify this infill generateInfill(part, skin); } for (PolygonsPart& skin_area_part : skin.splitIntoParts()) { part.skin_parts.emplace_back(); part.skin_parts.back().outline = skin_area_part; } }
//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)); } } }
Polygons SliceDataStorage::getLayerSecondOrInnermostWalls(int layer_nr, bool include_helper_parts) { if (layer_nr < 0) { // when processing raft if (include_helper_parts) { return raftOutline; } else { return Polygons(); } } else { Polygons total; for (SliceMeshStorage& mesh : meshes) { SliceLayer& layer = mesh.layers[layer_nr]; layer.getSecondOrInnermostWalls(total); if (mesh.getSettingAsSurfaceMode("magic_mesh_surface_mode") != ESurfaceMode::NORMAL) { total = total.unionPolygons(layer.openPolyLines.offsetPolyLine(100)); } } if (include_helper_parts) { if (support.generated) { total.add(support.supportLayers[layer_nr].supportAreas); total.add(support.supportLayers[layer_nr].roofs); } total.add(primeTower.ground_poly); } return total; } }
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); } }
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 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::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 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; } } }