void generateConcentricInfillDense(Polygons outline, Polygons& result, Polygons* in_between, int extrusionWidth, bool avoidOverlappingPerimeters) { while(outline.size() > 0) { for (unsigned int polyNr = 0; polyNr < outline.size(); polyNr++) { PolygonRef r = outline[polyNr]; result.add(r); } Polygons next_outline; PolygonUtils::offsetExtrusionWidth(outline, true, extrusionWidth, next_outline, in_between, avoidOverlappingPerimeters); outline = next_outline; } }
void Weaver::weave(PrintObject* object, CommandSocket* commandSocket) { int maxz = object->max().z; int layer_count = (maxz - initial_layer_thickness) / connectionHeight + 1; DEBUG_SHOW(layer_count); std::vector<cura::Slicer*> slicerList; for(Mesh& mesh : object->meshes) { cura::Slicer* slicer = new cura::Slicer(&mesh, initial_layer_thickness, connectionHeight, layer_count, mesh.getSettingBoolean("meshfix_keep_open_polygons"), mesh.getSettingBoolean("meshfix_extensive_stitching")); slicerList.push_back(slicer); } int starting_layer_idx; { // find first non-empty layer for (starting_layer_idx = 0; starting_layer_idx < layer_count; starting_layer_idx++) { Polygons parts; for (cura::Slicer* slicer : slicerList) parts.add(slicer->layers[starting_layer_idx].polygonList); if (parts.size() > 0) break; } if (starting_layer_idx > 0) { logError("First %i layers are empty!\n", starting_layer_idx); } } std::cerr<< "chainifying layers..." << std::endl; { int starting_z = -1; for (cura::Slicer* slicer : slicerList) wireFrame.bottom_outline.add(slicer->layers[starting_layer_idx].polygonList); if (commandSocket) commandSocket->sendPolygons(Inset0Type, 0, wireFrame.bottom_outline); wireFrame.z_bottom = slicerList[0]->layers[starting_layer_idx].z; Point starting_point_in_layer; if (wireFrame.bottom_outline.size() > 0) starting_point_in_layer = (wireFrame.bottom_outline.max() + wireFrame.bottom_outline.min()) / 2; else starting_point_in_layer = (Point(0,0) + object->max() + object->min()) / 2; for (int layer_idx = starting_layer_idx + 1; layer_idx < layer_count; layer_idx++) { logProgress("inset", layer_idx+1, layer_count); // abuse the progress system of the normal mode of CuraEngine Polygons parts1; for (cura::Slicer* slicer : slicerList) parts1.add(slicer->layers[layer_idx].polygonList); Polygons chainified; chainify_polygons(parts1, starting_point_in_layer, chainified, false); if (commandSocket) commandSocket->sendPolygons(Inset0Type, layer_idx - starting_layer_idx, chainified); if (chainified.size() > 0) { if (starting_z == -1) starting_z = slicerList[0]->layers[layer_idx-1].z; wireFrame.layers.emplace_back(); WeaveLayer& layer = wireFrame.layers.back(); layer.z0 = slicerList[0]->layers[layer_idx-1].z - starting_z; layer.z1 = slicerList[0]->layers[layer_idx].z - starting_z; layer.supported = chainified; starting_point_in_layer = layer.supported.back().back(); } } } std::cerr<< "finding horizontal parts..." << std::endl; { Polygons* lower_top_parts = &wireFrame.bottom_outline; for (unsigned int layer_idx = 0; layer_idx < wireFrame.layers.size(); layer_idx++) { logProgress("skin", layer_idx+1, wireFrame.layers.size()); // abuse the progress system of the normal mode of CuraEngine WeaveLayer& layer = wireFrame.layers[layer_idx]; Polygons empty; Polygons& layer_above = (layer_idx+1 < wireFrame.layers.size())? wireFrame.layers[layer_idx+1].supported : empty; createHorizontalFill(*lower_top_parts, layer, layer_above, layer.z1); lower_top_parts = &layer.supported; } } // at this point layer.supported still only contains the polygons to be connected // when connecting layers, we further add the supporting polygons created by the roofs std::cerr<< "connecting layers..." << std::endl; { Polygons* lower_top_parts = &wireFrame.bottom_outline; int last_z = wireFrame.z_bottom; for (unsigned int layer_idx = 0; layer_idx < wireFrame.layers.size(); layer_idx++) // use top of every layer but the last { WeaveLayer& layer = wireFrame.layers[layer_idx]; connect_polygons(*lower_top_parts, last_z, layer.supported, layer.z1, layer); layer.supported.add(layer.roofs.roof_outlines); lower_top_parts = &layer.supported; last_z = layer.z1; } } { // roofs: WeaveLayer& top_layer = wireFrame.layers.back(); Polygons to_be_supported; // empty for the top layer fillRoofs(top_layer.supported, to_be_supported, -1, top_layer.z1, top_layer.roofs); } { // bottom: Polygons to_be_supported; // is empty for the bottom layer, cause the order of insets doesn't really matter (in a sense everything is to be supported) fillRoofs(wireFrame.bottom_outline, to_be_supported, -1, wireFrame.layers.front().z0, wireFrame.bottom_infill); } }
void Weaver::weave(MeshGroup* meshgroup) { wireFrame.meshgroup = meshgroup; const coord_t maxz = meshgroup->max().z; const Settings& mesh_group_settings = Application::getInstance().current_slice->scene.current_mesh_group->settings; const coord_t initial_layer_thickness = mesh_group_settings.get<coord_t>("layer_height_0"); const coord_t connection_height = mesh_group_settings.get<coord_t>("wireframe_height"); const size_t layer_count = (maxz - initial_layer_thickness) / connection_height + 1; std::vector<AdaptiveLayer> layer_thicknesses; log("Layer count: %i\n", layer_count); std::vector<cura::Slicer*> slicerList; for(Mesh& mesh : meshgroup->meshes) { constexpr bool variable_layer_heights = false; cura::Slicer* slicer = new cura::Slicer(&mesh, connection_height, layer_count, variable_layer_heights, &layer_thicknesses); slicerList.push_back(slicer); } LayerIndex starting_layer_idx; { // find first non-empty layer for (starting_layer_idx = 0; starting_layer_idx < LayerIndex(layer_count); starting_layer_idx++) { Polygons parts; for (cura::Slicer* slicer : slicerList) parts.add(slicer->layers[starting_layer_idx].polygons); if (parts.size() > 0) break; } if (starting_layer_idx > 0) { logWarning("First %i layers are empty!\n", starting_layer_idx); } } log("Chainifying layers...\n"); { int starting_z = -1; for (cura::Slicer* slicer : slicerList) wireFrame.bottom_outline.add(slicer->layers[starting_layer_idx].polygons); Application::getInstance().communication->sendPolygons(PrintFeatureType::OuterWall, wireFrame.bottom_outline, 1, 1, 1); if (slicerList.empty()) //Wait, there is nothing to slice. { wireFrame.z_bottom = 0; } else { wireFrame.z_bottom = slicerList[0]->layers[starting_layer_idx].z; } Point starting_point_in_layer; if (wireFrame.bottom_outline.size() > 0) { starting_point_in_layer = (wireFrame.bottom_outline.max() + wireFrame.bottom_outline.min()) / 2; } else { starting_point_in_layer = (Point(0,0) + meshgroup->max() + meshgroup->min()) / 2; } Progress::messageProgressStage(Progress::Stage::INSET_SKIN, nullptr); for (LayerIndex layer_idx = starting_layer_idx + 1; layer_idx < LayerIndex(layer_count); layer_idx++) { Progress::messageProgress(Progress::Stage::INSET_SKIN, layer_idx+1, layer_count); // abuse the progress system of the normal mode of CuraEngine Polygons parts1; for (cura::Slicer* slicer : slicerList) parts1.add(slicer->layers[layer_idx].polygons); Polygons chainified; chainify_polygons(parts1, starting_point_in_layer, chainified); Application::getInstance().communication->sendPolygons(PrintFeatureType::OuterWall, chainified, 1, 1, 1); if (chainified.size() > 0) { if (starting_z == -1) starting_z = slicerList[0]->layers[layer_idx-1].z; wireFrame.layers.emplace_back(); WeaveLayer& layer = wireFrame.layers.back(); layer.z0 = slicerList[0]->layers[layer_idx-1].z - starting_z; layer.z1 = slicerList[0]->layers[layer_idx].z - starting_z; layer.supported = chainified; starting_point_in_layer = layer.supported.back().back(); } } } log("Finding horizontal parts...\n"); { Progress::messageProgressStage(Progress::Stage::SUPPORT, nullptr); for (unsigned int layer_idx = 0; layer_idx < wireFrame.layers.size(); layer_idx++) { Progress::messageProgress(Progress::Stage::SUPPORT, layer_idx+1, wireFrame.layers.size()); // abuse the progress system of the normal mode of CuraEngine WeaveLayer& layer = wireFrame.layers[layer_idx]; Polygons empty; Polygons& layer_above = (layer_idx+1 < wireFrame.layers.size())? wireFrame.layers[layer_idx+1].supported : empty; createHorizontalFill(layer, layer_above); } } // at this point layer.supported still only contains the polygons to be connected // when connecting layers, we further add the supporting polygons created by the roofs log("Connecting layers...\n"); { Polygons* lower_top_parts = &wireFrame.bottom_outline; int last_z = wireFrame.z_bottom; for (unsigned int layer_idx = 0; layer_idx < wireFrame.layers.size(); layer_idx++) // use top of every layer but the last { WeaveLayer& layer = wireFrame.layers[layer_idx]; connect_polygons(*lower_top_parts, last_z, layer.supported, layer.z1, layer); layer.supported.add(layer.roofs.roof_outlines); lower_top_parts = &layer.supported; last_z = layer.z1; } } { // roofs: if (!wireFrame.layers.empty()) //If there are no layers, create no roof. { WeaveLayer& top_layer = wireFrame.layers.back(); Polygons to_be_supported; // empty for the top layer fillRoofs(top_layer.supported, to_be_supported, -1, top_layer.z1, top_layer.roofs); } } { // bottom: if (!wireFrame.layers.empty()) //If there are no layers, create no bottom. { Polygons to_be_supported; // is empty for the bottom layer, cause the order of insets doesn't really matter (in a sense everything is to be supported) fillRoofs(wireFrame.bottom_outline, to_be_supported, -1, wireFrame.layers.front().z0, wireFrame.bottom_infill); } } }
void SlicerLayer::makePolygons(Mesh* mesh, bool keep_none_closed, bool extensive_stitching) { Polygons openPolygonList; for(unsigned int startSegment=0; startSegment < segmentList.size(); startSegment++) { if (segmentList[startSegment].addedToPolygon) continue; Polygon poly; poly.add(segmentList[startSegment].start); unsigned int segmentIndex = startSegment; bool canClose; while(true) { canClose = false; segmentList[segmentIndex].addedToPolygon = true; Point p0 = segmentList[segmentIndex].end; poly.add(p0); int nextIndex = -1; const MeshFace& face = mesh->faces[segmentList[segmentIndex].faceIndex]; for(unsigned int i=0;i<3;i++) { decltype(face_idx_to_segment_index.begin()) it; if (face.connected_face_index[i] > -1 && (it = face_idx_to_segment_index.find(face.connected_face_index[i])) != face_idx_to_segment_index.end()) { int index = (*it).second; Point p1 = segmentList[index].start; Point diff = p0 - p1; if (shorterThen(diff, MM2INT(0.01))) { if (index == static_cast<int>(startSegment)) canClose = true; if (segmentList[index].addedToPolygon) continue; nextIndex = index; } } } if (nextIndex == -1) break; segmentIndex = nextIndex; } if (canClose) polygonList.add(poly); else openPolygonList.add(poly); } //Clear the segmentList to save memory, it is no longer needed after this point. segmentList.clear(); //Connecting polygons that are not closed yet, as models are not always perfect manifold we need to join some stuff up to get proper polygons //First link up polygon ends that are within 2 microns. for(unsigned int i=0;i<openPolygonList.size();i++) { if (openPolygonList[i].size() < 1) continue; for(unsigned int j=0;j<openPolygonList.size();j++) { if (openPolygonList[j].size() < 1) continue; Point diff = openPolygonList[i][openPolygonList[i].size()-1] - openPolygonList[j][0]; int64_t distSquared = vSize2(diff); if (distSquared < MM2INT(0.02) * MM2INT(0.02)) { if (i == j) { polygonList.add(openPolygonList[i]); openPolygonList[i].clear(); break; }else{ for(unsigned int n=0; n<openPolygonList[j].size(); n++) openPolygonList[i].add(openPolygonList[j][n]); openPolygonList[j].clear(); } } } } //Next link up all the missing ends, closing up the smallest gaps first. This is an inefficient implementation which can run in O(n*n*n) time. while(1) { int64_t bestScore = MM2INT(10.0) * MM2INT(10.0); unsigned int bestA = -1; unsigned int bestB = -1; bool reversed = false; for(unsigned int i=0;i<openPolygonList.size();i++) { if (openPolygonList[i].size() < 1) continue; for(unsigned int j=0;j<openPolygonList.size();j++) { if (openPolygonList[j].size() < 1) continue; Point diff = openPolygonList[i][openPolygonList[i].size()-1] - openPolygonList[j][0]; int64_t distSquared = vSize2(diff); if (distSquared < bestScore) { bestScore = distSquared; bestA = i; bestB = j; reversed = false; } if (i != j) { Point diff = openPolygonList[i][openPolygonList[i].size()-1] - openPolygonList[j][openPolygonList[j].size()-1]; int64_t distSquared = vSize2(diff); if (distSquared < bestScore) { bestScore = distSquared; bestA = i; bestB = j; reversed = true; } } } } if (bestScore >= MM2INT(10.0) * MM2INT(10.0)) break; if (bestA == bestB) { polygonList.add(openPolygonList[bestA]); openPolygonList[bestA].clear(); }else{ if (reversed) { if (openPolygonList[bestA].polygonLength() > openPolygonList[bestB].polygonLength()) { for(unsigned int n=openPolygonList[bestB].size()-1; int(n)>=0; n--) openPolygonList[bestA].add(openPolygonList[bestB][n]); openPolygonList[bestB].clear(); }else{ for(unsigned int n=openPolygonList[bestA].size()-1; int(n)>=0; n--) openPolygonList[bestB].add(openPolygonList[bestA][n]); openPolygonList[bestA].clear(); } }else{ for(unsigned int n=0; n<openPolygonList[bestB].size(); n++) openPolygonList[bestA].add(openPolygonList[bestB][n]); openPolygonList[bestB].clear(); } } } if (extensive_stitching) { //For extensive stitching find 2 open polygons that are touching 2 closed polygons. // Then find the sortest path over this polygon that can be used to connect the open polygons, // And generate a path over this shortest bit to link up the 2 open polygons. // (If these 2 open polygons are the same polygon, then the final result is a closed polyon) while(1) { unsigned int bestA = -1; unsigned int bestB = -1; gapCloserResult bestResult; bestResult.len = POINT_MAX; bestResult.polygonIdx = -1; bestResult.pointIdxA = -1; bestResult.pointIdxB = -1; for(unsigned int i=0; i<openPolygonList.size(); i++) { if (openPolygonList[i].size() < 1) continue; { gapCloserResult res = findPolygonGapCloser(openPolygonList[i][0], openPolygonList[i][openPolygonList[i].size()-1]); if (res.len > 0 && res.len < bestResult.len) { bestA = i; bestB = i; bestResult = res; } } for(unsigned int j=0; j<openPolygonList.size(); j++) { if (openPolygonList[j].size() < 1 || i == j) continue; gapCloserResult res = findPolygonGapCloser(openPolygonList[i][0], openPolygonList[j][openPolygonList[j].size()-1]); if (res.len > 0 && res.len < bestResult.len) { bestA = i; bestB = j; bestResult = res; } } } if (bestResult.len < POINT_MAX) { if (bestA == bestB) { if (bestResult.pointIdxA == bestResult.pointIdxB) { polygonList.add(openPolygonList[bestA]); openPolygonList[bestA].clear(); } else if (bestResult.AtoB) { PolygonRef poly = polygonList.newPoly(); for(unsigned int j = bestResult.pointIdxA; j != bestResult.pointIdxB; j = (j + 1) % polygonList[bestResult.polygonIdx].size()) poly.add(polygonList[bestResult.polygonIdx][j]); for(unsigned int j = openPolygonList[bestA].size() - 1; int(j) >= 0; j--) poly.add(openPolygonList[bestA][j]); openPolygonList[bestA].clear(); } else { unsigned int n = polygonList.size(); polygonList.add(openPolygonList[bestA]); for(unsigned int j = bestResult.pointIdxB; j != bestResult.pointIdxA; j = (j + 1) % polygonList[bestResult.polygonIdx].size()) polygonList[n].add(polygonList[bestResult.polygonIdx][j]); openPolygonList[bestA].clear(); } } else { if (bestResult.pointIdxA == bestResult.pointIdxB) { for(unsigned int n=0; n<openPolygonList[bestA].size(); n++) openPolygonList[bestB].add(openPolygonList[bestA][n]); openPolygonList[bestA].clear(); } else if (bestResult.AtoB) { Polygon poly; for(unsigned int n = bestResult.pointIdxA; n != bestResult.pointIdxB; n = (n + 1) % polygonList[bestResult.polygonIdx].size()) poly.add(polygonList[bestResult.polygonIdx][n]); for(unsigned int n=poly.size()-1;int(n) >= 0; n--) openPolygonList[bestB].add(poly[n]); for(unsigned int n=0; n<openPolygonList[bestA].size(); n++) openPolygonList[bestB].add(openPolygonList[bestA][n]); openPolygonList[bestA].clear(); } else { for(unsigned int n = bestResult.pointIdxB; n != bestResult.pointIdxA; n = (n + 1) % polygonList[bestResult.polygonIdx].size()) openPolygonList[bestB].add(polygonList[bestResult.polygonIdx][n]); for(unsigned int n = openPolygonList[bestA].size() - 1; int(n) >= 0; n--) openPolygonList[bestB].add(openPolygonList[bestA][n]); openPolygonList[bestA].clear(); } } } else { break; } } } if (keep_none_closed) { for(unsigned int n=0; n<openPolygonList.size(); n++) { if (openPolygonList[n].size() > 0) polygonList.add(openPolygonList[n]); } } for(unsigned int i=0;i<openPolygonList.size();i++) { if (openPolygonList[i].size() > 0) openPolygons.newPoly() = openPolygonList[i]; } //Remove all the tiny polygons, or polygons that are not closed. As they do not contribute to the actual print. int snapDistance = MM2INT(1.0); for(unsigned int i=0;i<polygonList.size();i++) { int length = 0; for(unsigned int n=1; n<polygonList[i].size(); n++) { length += vSize(polygonList[i][n] - polygonList[i][n-1]); if (length > snapDistance) break; } if (length < snapDistance) { polygonList.remove(i); i--; } } //Finally optimize all the polygons. Every point removed saves time in the long run. optimizePolygons(polygonList); int xy_offset = mesh->getSettingInMicrons("xy_offset"); if (xy_offset != 0) { polygonList = polygonList.offset(xy_offset); } }
void combineInfillLayers(SliceMeshStorage& mesh, unsigned int amount) { if (mesh.layers.empty() || mesh.layers.size() - 1 < static_cast<size_t>(mesh.getSettingAsCount("top_layers")) || mesh.getSettingAsCount("infill_line_distance") <= 0) //No infill is even generated. { return; } if(amount <= 1) //If we must combine 1 layer, nothing needs to be combined. Combining 0 layers is invalid. { return; } /* We need to round down the layer index we start at to the nearest divisible index. Otherwise we get some parts that have infill at divisible layers and some at non-divisible layers. Those layers would then miss each other. */ size_t min_layer = mesh.getSettingAsCount("bottom_layers") + amount - 1; min_layer -= min_layer % amount; //Round upwards to the nearest layer divisible by infill_sparse_combine. size_t max_layer = mesh.layers.size() - 1 - mesh.getSettingAsCount("top_layers"); max_layer -= max_layer % amount; //Round downwards to the nearest layer divisible by infill_sparse_combine. for(size_t layer_idx = min_layer;layer_idx <= max_layer;layer_idx += amount) //Skip every few layers, but extrude more. { SliceLayer* layer = &mesh.layers[layer_idx]; for(unsigned int combine_count_here = 1; combine_count_here < amount; combine_count_here++) { if(layer_idx < combine_count_here) { break; } size_t lower_layer_idx = layer_idx - combine_count_here; if (lower_layer_idx < min_layer) { break; } SliceLayer* lower_layer = &mesh.layers[lower_layer_idx]; for (SliceLayerPart& part : layer->parts) { for (unsigned int density_idx = 0; density_idx < part.infill_area_per_combine_per_density.size(); density_idx++) { // go over each density of gradual infill (these density areas overlap!) std::vector<Polygons>& infill_area_per_combine = part.infill_area_per_combine_per_density[density_idx]; Polygons result; for (SliceLayerPart& lower_layer_part : lower_layer->parts) { if (part.boundaryBox.hit(lower_layer_part.boundaryBox)) { Polygons intersection = infill_area_per_combine[combine_count_here - 1].intersection(lower_layer_part.infill_area).offset(-200).offset(200); result.add(intersection); // add area to be thickened infill_area_per_combine[combine_count_here - 1] = infill_area_per_combine[combine_count_here - 1].difference(intersection); // remove thickened area from less thick layer here if (density_idx < lower_layer_part.infill_area_per_combine_per_density.size()) { // only remove from *same density* areas on layer below // If there are no same density areas, then it's ok to print them anyway // Don't remove other density areas unsigned int lower_density_idx = density_idx; std::vector<Polygons>& lower_infill_area_per_combine = lower_layer_part.infill_area_per_combine_per_density[lower_density_idx]; lower_infill_area_per_combine[0] = lower_infill_area_per_combine[0].difference(intersection); // remove thickened area from lower (thickened) layer } } } infill_area_per_combine.push_back(result); } } } } }
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 Weaver::weave(MeshGroup* meshgroup) { wireFrame.meshgroup = meshgroup; int maxz = meshgroup->max().z; int layer_count = (maxz - initial_layer_thickness) / connectionHeight + 1; std::cerr << "Layer count: " << layer_count << "\n"; std::vector<cura::Slicer*> slicerList; for(Mesh& mesh : meshgroup->meshes) { cura::Slicer* slicer = new cura::Slicer(&mesh, initial_layer_thickness, connectionHeight, layer_count, mesh.getSettingBoolean("meshfix_keep_open_polygons"), mesh.getSettingBoolean("meshfix_extensive_stitching")); slicerList.push_back(slicer); } int starting_layer_idx; { // find first non-empty layer for (starting_layer_idx = 0; starting_layer_idx < layer_count; starting_layer_idx++) { Polygons parts; for (cura::Slicer* slicer : slicerList) parts.add(slicer->layers[starting_layer_idx].polygons); if (parts.size() > 0) break; } if (starting_layer_idx > 0) { logWarning("First %i layers are empty!\n", starting_layer_idx); } } std::cerr<< "chainifying layers..." << std::endl; { int starting_z = -1; for (cura::Slicer* slicer : slicerList) wireFrame.bottom_outline.add(slicer->layers[starting_layer_idx].polygons); CommandSocket::sendPolygons(PrintFeatureType::OuterWall, /*0,*/ wireFrame.bottom_outline, 1); if (slicerList.empty()) //Wait, there is nothing to slice. { wireFrame.z_bottom = 0; } else { wireFrame.z_bottom = slicerList[0]->layers[starting_layer_idx].z; } Point starting_point_in_layer; if (wireFrame.bottom_outline.size() > 0) starting_point_in_layer = (wireFrame.bottom_outline.max() + wireFrame.bottom_outline.min()) / 2; else starting_point_in_layer = (Point(0,0) + meshgroup->max() + meshgroup->min()) / 2; Progress::messageProgressStage(Progress::Stage::INSET_SKIN, nullptr); for (int layer_idx = starting_layer_idx + 1; layer_idx < layer_count; layer_idx++) { Progress::messageProgress(Progress::Stage::INSET_SKIN, layer_idx+1, layer_count); // abuse the progress system of the normal mode of CuraEngine Polygons parts1; for (cura::Slicer* slicer : slicerList) parts1.add(slicer->layers[layer_idx].polygons); Polygons chainified; chainify_polygons(parts1, starting_point_in_layer, chainified, false); CommandSocket::sendPolygons(PrintFeatureType::OuterWall, /*layer_idx - starting_layer_idx,*/ chainified, 1); if (chainified.size() > 0) { if (starting_z == -1) starting_z = slicerList[0]->layers[layer_idx-1].z; wireFrame.layers.emplace_back(); WeaveLayer& layer = wireFrame.layers.back(); layer.z0 = slicerList[0]->layers[layer_idx-1].z - starting_z; layer.z1 = slicerList[0]->layers[layer_idx].z - starting_z; layer.supported = chainified; starting_point_in_layer = layer.supported.back().back(); } } } std::cerr<< "finding horizontal parts..." << std::endl; { Polygons* lower_top_parts = &wireFrame.bottom_outline; Progress::messageProgressStage(Progress::Stage::SUPPORT, nullptr); for (unsigned int layer_idx = 0; layer_idx < wireFrame.layers.size(); layer_idx++) { Progress::messageProgress(Progress::Stage::SUPPORT, layer_idx+1, wireFrame.layers.size()); // abuse the progress system of the normal mode of CuraEngine WeaveLayer& layer = wireFrame.layers[layer_idx]; Polygons empty; Polygons& layer_above = (layer_idx+1 < wireFrame.layers.size())? wireFrame.layers[layer_idx+1].supported : empty; createHorizontalFill(*lower_top_parts, layer, layer_above, layer.z1); lower_top_parts = &layer.supported; } } // at this point layer.supported still only contains the polygons to be connected // when connecting layers, we further add the supporting polygons created by the roofs std::cerr<< "connecting layers..." << std::endl; { Polygons* lower_top_parts = &wireFrame.bottom_outline; int last_z = wireFrame.z_bottom; for (unsigned int layer_idx = 0; layer_idx < wireFrame.layers.size(); layer_idx++) // use top of every layer but the last { WeaveLayer& layer = wireFrame.layers[layer_idx]; connect_polygons(*lower_top_parts, last_z, layer.supported, layer.z1, layer); layer.supported.add(layer.roofs.roof_outlines); lower_top_parts = &layer.supported; last_z = layer.z1; } } { // roofs: if (!wireFrame.layers.empty()) //If there are no layers, create no roof. { WeaveLayer& top_layer = wireFrame.layers.back(); Polygons to_be_supported; // empty for the top layer fillRoofs(top_layer.supported, to_be_supported, -1, top_layer.z1, top_layer.roofs); } } { // bottom: if (!wireFrame.layers.empty()) //If there are no layers, create no bottom. { Polygons to_be_supported; // is empty for the bottom layer, cause the order of insets doesn't really matter (in a sense everything is to be supported) fillRoofs(wireFrame.bottom_outline, to_be_supported, -1, wireFrame.layers.front().z0, wireFrame.bottom_infill); } } }
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 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); } }