#include "FffPolygonGenerator.h" #include #include // multimap (ordered map allowing duplicate keys) #include "utils/math.h" #include "slicer.h" #include "utils/gettime.h" #include "utils/logoutput.h" #include "MeshGroup.h" #include "support.h" #include "multiVolumes.h" #include "layerPart.h" #include "WallsComputation.h" #include "SkirtBrim.h" #include "skin.h" #include "infill.h" #include "raft.h" #include "progress/Progress.h" #include "PrintFeature.h" #include "ConicalOverhang.h" #include "progress/ProgressEstimator.h" #include "progress/ProgressStageEstimator.h" #include "progress/ProgressEstimatorLinear.h" namespace cura { bool FffPolygonGenerator::generateAreas(SliceDataStorage& storage, MeshGroup* meshgroup, TimeKeeper& timeKeeper) { if (!sliceModel(meshgroup, timeKeeper, storage)) { return false; } slices2polygons(storage, timeKeeper); return true; } unsigned int FffPolygonGenerator::getDraftShieldLayerCount(const unsigned int total_layers) const { if (!getSettingBoolean("draft_shield_enabled")) { return 0; } switch (getSettingAsDraftShieldHeightLimitation("draft_shield_height_limitation")) { default: case DraftShieldHeightLimitation::FULL: return total_layers; case DraftShieldHeightLimitation::LIMITED: return std::max(0, (getSettingInMicrons("draft_shield_height") - getSettingInMicrons("layer_height_0")) / getSettingInMicrons("layer_height") + 1); } } bool FffPolygonGenerator::sliceModel(MeshGroup* meshgroup, TimeKeeper& timeKeeper, SliceDataStorage& storage) /// slices the model { Progress::messageProgressStage(Progress::Stage::SLICING, &timeKeeper); storage.model_min = meshgroup->min(); storage.model_max = meshgroup->max(); storage.model_size = storage.model_max - storage.model_min; log("Slicing model...\n"); int initial_layer_thickness = getSettingInMicrons("layer_height_0"); if(initial_layer_thickness <= 0) //Initial layer height of 0 is not allowed. Negative layer height is nonsense. { logError("Initial layer height %i is disallowed.\n", initial_layer_thickness); return false; } int layer_thickness = getSettingInMicrons("layer_height"); if(layer_thickness <= 0) //Layer height of 0 is not allowed. Negative layer height is nonsense. { logError("Layer height %i is disallowed.\n", layer_thickness); return false; } int initial_slice_z = initial_layer_thickness - layer_thickness / 2; int slice_layer_count = (storage.model_max.z - initial_slice_z) / layer_thickness + 1; if (slice_layer_count <= 0) //Model is shallower than layer_height_0, so not even the first layer is sliced. Return an empty model then. { return true; //This is NOT an error state! } std::vector slicerList; for(unsigned int mesh_idx = 0; mesh_idx < meshgroup->meshes.size(); mesh_idx++) { Mesh& mesh = meshgroup->meshes[mesh_idx]; Slicer* slicer = new Slicer(&mesh, initial_slice_z, layer_thickness, slice_layer_count, mesh.getSettingBoolean("meshfix_keep_open_polygons"), mesh.getSettingBoolean("meshfix_extensive_stitching")); slicerList.push_back(slicer); /* for(SlicerLayer& layer : slicer->layers) { //Reporting the outline here slows down the engine quite a bit, so only do so when debugging. sendPolygons("outline", layer_nr, layer.z, layer.polygonList); sendPolygons("openoutline", layer_nr, layer.openPolygonList); } */ Progress::messageProgress(Progress::Stage::SLICING, mesh_idx + 1, meshgroup->meshes.size()); } meshgroup->clear();///Clear the mesh face and vertex data, it is no longer needed after this point, and it saves a lot of memory. for(unsigned int meshIdx=0; meshIdx < slicerList.size(); meshIdx++) { Mesh& mesh = storage.meshgroup->meshes[meshIdx]; if (mesh.getSettingBoolean("conical_overhang_enabled")) { ConicalOverhang::apply(slicerList[meshIdx], mesh.getSettingInAngleRadians("conical_overhang_angle"), layer_thickness); } } Progress::messageProgressStage(Progress::Stage::PARTS, &timeKeeper); carveMultipleVolumes(slicerList); generateMultipleVolumesOverlap(slicerList); storage.meshes.reserve(slicerList.size()); // causes there to be no resize in meshes so that the pointers in sliceMeshStorage._config to retraction_config don't get invalidated. for (unsigned int meshIdx = 0; meshIdx < slicerList.size(); meshIdx++) { Slicer* slicer = slicerList[meshIdx]; storage.meshes.emplace_back(&meshgroup->meshes[meshIdx], slicer->layers.size()); // new mesh in storage had settings from the Mesh SliceMeshStorage& meshStorage = storage.meshes.back(); Mesh& mesh = storage.meshgroup->meshes[meshIdx]; createLayerParts(meshStorage, slicer, mesh.getSettingBoolean("meshfix_union_all"), mesh.getSettingBoolean("meshfix_union_all_remove_holes")); delete slicerList[meshIdx]; bool has_raft = getSettingAsPlatformAdhesion("adhesion_type") == EPlatformAdhesion::RAFT; //Add the raft offset to each layer. for(unsigned int layer_nr=0; layer_nrgetExtruderTrain(getSettingAsIndex("adhesion_extruder_nr")); layer.printZ += Raft::getTotalThickness(storage) + train->getSettingInMicrons("raft_airgap") - train->getSettingInMicrons("layer_0_z_overlap"); // shift all layers (except 0) down if (layer_nr == 0) { layer.printZ += train->getSettingInMicrons("layer_0_z_overlap"); // undo shifting down of first layer } } if (layer.parts.size() > 0 || (mesh.getSettingAsSurfaceMode("magic_mesh_surface_mode") != ESurfaceMode::NORMAL && layer.openPolyLines.size() > 0) ) { meshStorage.layer_nr_max_filled_layer = layer_nr; // last set by the highest non-empty layer } } Progress::messageProgress(Progress::Stage::PARTS, meshIdx + 1, slicerList.size()); } return true; } void FffPolygonGenerator::slices2polygons(SliceDataStorage& storage, TimeKeeper& time_keeper) { // compute layer count and remove first empty layers // there is no separate progress stage for removeEmptyFisrtLayer (TODO) unsigned int slice_layer_count = 0; for (SliceMeshStorage& mesh : storage.meshes) { if (!mesh.getSettingBoolean("infill_mesh")) { slice_layer_count = std::max(slice_layer_count, mesh.layers.size()); } } // handle meshes std::vector mesh_timings; for (unsigned int mesh_idx = 0; mesh_idx < storage.meshes.size(); mesh_idx++) { mesh_timings.push_back(1.0); // TODO: have a more accurate estimate of the relative time it takes per mesh, based on the height and number of polygons } ProgressStageEstimator inset_skin_progress_estimate(mesh_timings); Progress::messageProgressStage(Progress::Stage::INSET_SKIN, &time_keeper); std::vector mesh_order; { // compute mesh order std::multimap order_to_mesh_indices; for (unsigned int mesh_idx = 0; mesh_idx < storage.meshes.size(); mesh_idx++) { order_to_mesh_indices.emplace(storage.meshes[mesh_idx].getSettingAsIndex("infill_mesh_order"), mesh_idx); } for (std::pair& order_and_mesh_idx : order_to_mesh_indices) { mesh_order.push_back(order_and_mesh_idx.second); } } for (unsigned int mesh_order_idx(0); mesh_order_idx < mesh_order.size(); ++mesh_order_idx) { processBasicWallsSkinInfill(storage, mesh_order_idx, mesh_order, slice_layer_count, inset_skin_progress_estimate); Progress::messageProgress(Progress::Stage::INSET_SKIN, mesh_order_idx + 1, storage.meshes.size()); } unsigned int print_layer_count = 0; for (unsigned int layer_nr = 0; layer_nr < slice_layer_count; layer_nr++) { SliceLayer* layer = nullptr; for (unsigned int mesh_idx = 0; mesh_idx < storage.meshes.size(); mesh_idx++) { // find first mesh which has this layer SliceMeshStorage& mesh = storage.meshes[mesh_idx]; if (int(layer_nr) <= mesh.layer_nr_max_filled_layer) { layer = &mesh.layers[layer_nr]; print_layer_count = layer_nr + 1; break; } } if (layer != nullptr) { if (CommandSocket::isInstantiated()) { // send layer info CommandSocket::getInstance()->sendOptimizedLayerInfo(layer_nr, layer->printZ, layer_nr == 0? getSettingInMicrons("layer_height_0") : getSettingInMicrons("layer_height")); } } } log("Layer count: %i\n", print_layer_count); //layerparts2HTML(storage, "output/output.html"); Progress::messageProgressStage(Progress::Stage::SUPPORT, &time_keeper); AreaSupport::generateSupportAreas(storage, print_layer_count); // we need to remove empty layers after we have procesed the insets // processInsets might throw away parts if they have no wall at all (cause it doesn't fit) // brim depends on the first layer not being empty // only remove empty layers if we haven't generate support, because then support was added underneath the model. // for some materials it's better to print on support than on the buildplate. removeEmptyFirstLayers(storage, getSettingInMicrons("layer_height"), print_layer_count); // changes total_layers! if (print_layer_count == 0) { log("Stopping process because there are no non-empty layers.\n"); return; } /* if (storage.support.generated) { for (unsigned int layer_idx = 0; layer_idx < total_layers; layer_idx++) { Polygons& support = storage.support.supportLayers[layer_idx].supportAreas; ExtruderTrain* infill_extr = storage.meshgroup->getExtruderTrain(storage.getSettingAsIndex("support_infill_extruder_nr")); CommandSocket::sendPolygons(PrintFeatureType::Infill, support, 100); // infill_extr->getSettingInMicrons("support_line_width")); } } */ // handle helpers storage.primeTower.computePrimeTowerMax(storage); storage.primeTower.generatePaths(storage, print_layer_count); logDebug("Processing ooze shield\n"); processOozeShield(storage); logDebug("Processing draft shield\n"); processDraftShield(storage, print_layer_count); logDebug("Processing platform adhesion\n"); processPlatformAdhesion(storage); // meshes post processing for (SliceMeshStorage& mesh : storage.meshes) { processDerivedWallsSkinInfill(mesh, print_layer_count); } } void FffPolygonGenerator::processBasicWallsSkinInfill(SliceDataStorage& storage, unsigned int mesh_order_idx, std::vector& mesh_order, size_t total_layers, ProgressStageEstimator& inset_skin_progress_estimate) { unsigned int mesh_idx = mesh_order[mesh_order_idx]; SliceMeshStorage& mesh = storage.meshes[mesh_idx]; if (mesh.getSettingBoolean("infill_mesh")) { processInfillMesh(storage, mesh_order_idx, mesh_order, total_layers); } // TODO: make progress more accurate!! // note: estimated time for insets : skins = 22.953 : 48.858 std::vector walls_vs_skin_timing({22.953, 48.858}); ProgressStageEstimator* mesh_inset_skin_progress_estimator = new ProgressStageEstimator(walls_vs_skin_timing); inset_skin_progress_estimate.nextStage(mesh_inset_skin_progress_estimator); // the stage of this function call ProgressEstimatorLinear* inset_estimator = new ProgressEstimatorLinear(total_layers); mesh_inset_skin_progress_estimator->nextStage(inset_estimator); // walls for(unsigned int layer_number = 0; layer_number < total_layers; layer_number++) { logDebug("Processing insets for layer %i of %i\n", layer_number, total_layers); processInsets(mesh, layer_number); double progress = inset_skin_progress_estimate.progress(layer_number); Progress::messageProgress(Progress::Stage::INSET_SKIN, progress * 100, 100); } ProgressEstimatorLinear* skin_estimator = new ProgressEstimatorLinear(total_layers); mesh_inset_skin_progress_estimator->nextStage(skin_estimator); bool process_infill = mesh.getSettingInMicrons("infill_line_distance") > 0; if (!process_infill) { // do process infill anyway if it's modified by modifier meshes for (unsigned int other_mesh_order_idx(mesh_order_idx + 1); other_mesh_order_idx < mesh_order.size(); ++other_mesh_order_idx) { unsigned int other_mesh_idx = mesh_order[other_mesh_order_idx]; SliceMeshStorage& other_mesh = storage.meshes[other_mesh_idx]; if (other_mesh.getSettingBoolean("infill_mesh")) { AABB3D aabb = storage.meshgroup->meshes[mesh_idx].getAABB(); AABB3D other_aabb = storage.meshgroup->meshes[other_mesh_idx].getAABB(); if (aabb.hit(other_aabb)) { process_infill = true; } } } } // skin & infill // Progress::messageProgressStage(Progress::Stage::SKIN, &time_keeper); int mesh_max_bottom_layer_count = 0; if (mesh.getSettingBoolean("magic_spiralize")) { mesh_max_bottom_layer_count = std::max(mesh_max_bottom_layer_count, mesh.getSettingAsCount("bottom_layers")); } for(unsigned int layer_number = 0; layer_number < total_layers; layer_number++) { logDebug("Processing skins and infill layer %i of %i\n", layer_number, total_layers); if (!mesh.getSettingBoolean("magic_spiralize") || static_cast(layer_number) < mesh_max_bottom_layer_count) //Only generate up/downskin and infill for the first X layers when spiralize is choosen. { processSkinsAndInfill(mesh, layer_number, process_infill); } double progress = inset_skin_progress_estimate.progress(layer_number); Progress::messageProgress(Progress::Stage::INSET_SKIN, progress * 100, 100); } } void FffPolygonGenerator::processInfillMesh(SliceDataStorage& storage, unsigned int mesh_order_idx, std::vector& mesh_order, size_t total_layers) { unsigned int mesh_idx = mesh_order[mesh_order_idx]; SliceMeshStorage& mesh = storage.meshes[mesh_idx]; mesh.layer_nr_max_filled_layer = -1; for (unsigned int layer_idx = 0; layer_idx < mesh.layers.size(); layer_idx++) { SliceLayer& layer = mesh.layers[layer_idx]; std::vector new_parts; for (unsigned int other_mesh_idx : mesh_order) { // limit the infill mesh's outline to within the infill of all meshes with lower order if (other_mesh_idx == mesh_idx) { break; // all previous meshes have been processed } SliceMeshStorage& other_mesh = storage.meshes[other_mesh_idx]; if (layer_idx >= other_mesh.layers.size()) { // there can be no interaction between the infill mesh and this other non-infill mesh continue; } SliceLayer& other_layer = other_mesh.layers[layer_idx]; for (SliceLayerPart& part : layer.parts) { for (SliceLayerPart& other_part : other_layer.parts) { // limit the outline of each part of this infill mesh to the infill of parts of the other mesh with lower infill mesh order if (!part.boundaryBox.hit(other_part.boundaryBox)) { // early out continue; } Polygons new_outline = part.outline.intersection(other_part.getOwnInfillArea()); if (new_outline.size() == 1) { // we don't have to call splitIntoParts, because a single polygon can only be a single part PolygonsPart outline_part_here; outline_part_here.add(new_outline[0]); new_parts.push_back(outline_part_here); } else if (new_outline.size() > 1) { // we don't know whether it's a multitude of parts because of newly introduced holes, or because the polygon has been split up std::vector new_parts_here = new_outline.splitIntoParts(); for (PolygonsPart& new_part_here : new_parts_here) { new_parts.push_back(new_part_here); } } // change the infill area of the non-infill mesh which is to be filled with e.g. lines other_part.infill_area_own = other_part.getOwnInfillArea().difference(part.outline); // note: don't change the part.infill_area, because we change the structure of that area, while the basic area in which infill is printed remains the same // the infill area remains the same for combing } } } layer.parts.clear(); for (PolygonsPart& part : new_parts) { layer.parts.emplace_back(); layer.parts.back().outline = part; layer.parts.back().boundaryBox.calculate(part); } if (layer.parts.size() > 0 || (mesh.getSettingAsSurfaceMode("magic_mesh_surface_mode") != ESurfaceMode::NORMAL && layer.openPolyLines.size() > 0) ) { mesh.layer_nr_max_filled_layer = layer_idx; // last set by the highest non-empty layer } } } void FffPolygonGenerator::processDerivedWallsSkinInfill(SliceMeshStorage& mesh, size_t total_layers) { // create gradual infill areas SkinInfillAreaComputation::generateGradualInfill(mesh, mesh.getSettingInMicrons("gradual_infill_step_height"), mesh.getSettingAsCount("gradual_infill_steps")); //SubDivCube Pre-compute Octree if(mesh.getSettingAsFillMethod("infill_pattern") == EFillMethod::CUBICSUBDIV){ SubDivCube::init(mesh); } // combine infill unsigned int combined_infill_layers = std::max(1U, round_divide(mesh.getSettingInMicrons("infill_sparse_thickness"), std::max(getSettingInMicrons("layer_height"), 1))); //How many infill layers to combine to obtain the requested sparse thickness. combineInfillLayers(mesh,combined_infill_layers); // fuzzy skin if (mesh.getSettingBoolean("magic_fuzzy_skin_enabled")) { processFuzzyWalls(mesh); } } void FffPolygonGenerator::processInsets(SliceMeshStorage& mesh, unsigned int layer_nr) { SliceLayer* layer = &mesh.layers[layer_nr]; if (mesh.getSettingAsSurfaceMode("magic_mesh_surface_mode") != ESurfaceMode::SURFACE) { int inset_count = mesh.getSettingAsCount("wall_line_count"); if (mesh.getSettingBoolean("magic_spiralize") && static_cast(layer_nr) < mesh.getSettingAsCount("bottom_layers") && layer_nr % 2 == 1)//Add extra insets every 2 layers when spiralizing, this makes bottoms of cups watertight. inset_count += 5; int line_width_x = mesh.getSettingInMicrons("wall_line_width_x"); int line_width_0 = mesh.getSettingInMicrons("wall_line_width_0"); if (mesh.getSettingBoolean("alternate_extra_perimeter")) inset_count += layer_nr % 2; bool recompute_outline_based_on_outer_wall = mesh.getSettingBoolean("support_enable"); WallsComputation walls_computation(mesh.getSettingInMicrons("wall_0_inset"), line_width_0, line_width_x, inset_count, recompute_outline_based_on_outer_wall); walls_computation.generateInsets(layer); } } void FffPolygonGenerator::removeEmptyFirstLayers(SliceDataStorage& storage, const int layer_height, unsigned int& total_layers) { // only remove empty layers if we haven't generate support, because then support was added underneath the model. // for some materials it's better to print on support than on the buildplate. if (storage.support.generated) { return; // the first layer will have support and therefore not be empty } int n_empty_first_layers = 0; for (unsigned int layer_idx = 0; layer_idx < total_layers; layer_idx++) { bool layer_is_empty = true; for (SliceMeshStorage& mesh : storage.meshes) { SliceLayer& layer = mesh.layers[layer_idx]; if (layer.parts.size() > 0 || (mesh.getSettingAsSurfaceMode("magic_mesh_surface_mode") != ESurfaceMode::NORMAL && layer.openPolyLines.size() > 0) ) { layer_is_empty = false; break; } } if (layer_is_empty) { n_empty_first_layers++; } else { break; } } if (n_empty_first_layers > 0) { log("Removing %d layers because they are empty\n", n_empty_first_layers); for (SliceMeshStorage& mesh : storage.meshes) { std::vector& layers = mesh.layers; layers.erase(layers.begin(), layers.begin() + n_empty_first_layers); for (SliceLayer& layer : layers) { layer.printZ -= n_empty_first_layers * layer_height; } } total_layers -= n_empty_first_layers; } } void FffPolygonGenerator::processSkinsAndInfill(SliceMeshStorage& mesh, unsigned int layer_nr, bool process_infill) { if (mesh.getSettingAsSurfaceMode("magic_mesh_surface_mode") == ESurfaceMode::SURFACE) { return; } const int wall_line_count = mesh.getSettingAsCount("wall_line_count"); const int innermost_wall_line_width = (wall_line_count == 1) ? mesh.getSettingInMicrons("wall_line_width_0") : mesh.getSettingInMicrons("wall_line_width_x"); generateSkins(layer_nr, mesh, mesh.getSettingAsCount("bottom_layers"), mesh.getSettingAsCount("top_layers"), wall_line_count, innermost_wall_line_width, mesh.getSettingAsCount("skin_outline_count"), mesh.getSettingBoolean("skin_no_small_gaps_heuristic")); if (process_infill) { // process infill when infill density > 0 // or when other infill meshes want to modify this infill int infill_skin_overlap = 0; bool infill_is_dense = mesh.getSettingInMicrons("infill_line_distance") < mesh.getSettingInMicrons("infill_line_width") + 10; if (!infill_is_dense && mesh.getSettingAsFillMethod("infill_pattern") != EFillMethod::CONCENTRIC) { infill_skin_overlap = innermost_wall_line_width / 2; } generateInfill(layer_nr, mesh, innermost_wall_line_width, infill_skin_overlap, wall_line_count); } } void FffPolygonGenerator::processOozeShield(SliceDataStorage& storage) { if (!getSettingBoolean("ooze_shield_enabled")) { return; } const int ooze_shield_dist = getSettingInMicrons("ooze_shield_dist"); for (int layer_nr = 0; layer_nr <= storage.max_object_height_second_to_last_extruder; layer_nr++) { storage.oozeShield.push_back(storage.getLayerOutlines(layer_nr, true).offset(ooze_shield_dist, ClipperLib::jtRound)); } double angle = getSettingInAngleDegrees("ooze_shield_angle"); if (angle <= 89) { int allowed_angle_offset = tan(getSettingInAngleRadians("ooze_shield_angle")) * getSettingInMicrons("layer_height"); // Allow for a 60deg angle in the oozeShield. for (int layer_nr = 1; layer_nr <= storage.max_object_height_second_to_last_extruder; layer_nr++) { storage.oozeShield[layer_nr] = storage.oozeShield[layer_nr].unionPolygons(storage.oozeShield[layer_nr - 1].offset(-allowed_angle_offset)); } for (int layer_nr = storage.max_object_height_second_to_last_extruder; layer_nr > 0; layer_nr--) { storage.oozeShield[layer_nr - 1] = storage.oozeShield[layer_nr - 1].unionPolygons(storage.oozeShield[layer_nr].offset(-allowed_angle_offset)); } } const float largest_printed_area = 1.0; // TODO: make var a parameter, and perhaps even a setting? for (int layer_nr = 0; layer_nr <= storage.max_object_height_second_to_last_extruder; layer_nr++) { storage.oozeShield[layer_nr].removeSmallAreas(largest_printed_area); } } void FffPolygonGenerator::processDraftShield(SliceDataStorage& storage, unsigned int total_layers) { const unsigned int draft_shield_layers = getDraftShieldLayerCount(total_layers); if (draft_shield_layers <= 0) { return; } const int layer_height = getSettingInMicrons("layer_height"); const unsigned int layer_skip = 500 / layer_height + 1; Polygons& draft_shield = storage.draft_protection_shield; for (unsigned int layer_nr = 0; layer_nr < total_layers && layer_nr < draft_shield_layers; layer_nr += layer_skip) { draft_shield = draft_shield.unionPolygons(storage.getLayerOutlines(layer_nr, true)); } const int draft_shield_dist = getSettingInMicrons("draft_shield_dist"); storage.draft_protection_shield = draft_shield.approxConvexHull(draft_shield_dist); } void FffPolygonGenerator::processPlatformAdhesion(SliceDataStorage& storage) { SettingsBaseVirtual* train = storage.meshgroup->getExtruderTrain(getSettingBoolean("adhesion_extruder_nr")); switch(getSettingAsPlatformAdhesion("adhesion_type")) { case EPlatformAdhesion::SKIRT: { constexpr bool outside_polygons_only = true; SkirtBrim::generate(storage, train->getSettingInMicrons("skirt_gap"), train->getSettingAsCount("skirt_line_count"), outside_polygons_only); } break; case EPlatformAdhesion::BRIM: SkirtBrim::generate(storage, 0, train->getSettingAsCount("brim_line_count"), train->getSettingBoolean("brim_outside_only")); break; case EPlatformAdhesion::RAFT: Raft::generate(storage, train->getSettingInMicrons("raft_margin")); break; } } void FffPolygonGenerator::processFuzzyWalls(SliceMeshStorage& mesh) { if (mesh.getSettingAsCount("wall_line_count") == 0) { return; } int64_t fuzziness = mesh.getSettingInMicrons("magic_fuzzy_skin_thickness"); int64_t avg_dist_between_points = mesh.getSettingInMicrons("magic_fuzzy_skin_point_dist"); int64_t min_dist_between_points = avg_dist_between_points * 3 / 4; // hardcoded: the point distance may vary between 3/4 and 5/4 the supplied value int64_t range_random_point_dist = avg_dist_between_points / 2; for (unsigned int layer_nr = 0; layer_nr < mesh.layers.size(); layer_nr++) { SliceLayer& layer = mesh.layers[layer_nr]; for (SliceLayerPart& part : layer.parts) { Polygons results; Polygons& skin = (mesh.getSettingAsSurfaceMode("magic_mesh_surface_mode") == ESurfaceMode::SURFACE)? part.outline : part.insets[0]; for (PolygonRef poly : skin) { // generate points in between p0 and p1 PolygonRef result = results.newPoly(); int64_t dist_left_over = rand() % (min_dist_between_points / 2); // the distance to be traversed on the line before making the first new point Point* p0 = &poly.back(); for (Point& p1 : poly) { // 'a' is the (next) new point between p0 and p1 Point p0p1 = p1 - *p0; int64_t p0p1_size = vSize(p0p1); int64_t dist_last_point = dist_left_over + p0p1_size * 2; // so that p0p1_size - dist_last_point evaulates to dist_left_over - p0p1_size for (int64_t p0pa_dist = dist_left_over; p0pa_dist < p0p1_size; p0pa_dist += min_dist_between_points + rand() % range_random_point_dist) { int r = rand() % (fuzziness * 2) - fuzziness; Point perp_to_p0p1 = turn90CCW(p0p1); Point fuzz = normal(perp_to_p0p1, r); Point pa = *p0 + normal(p0p1, p0pa_dist) + fuzz; result.add(pa); dist_last_point = p0pa_dist; } dist_left_over = p0p1_size - dist_last_point; p0 = &p1; } while (result.size() < 3 ) { unsigned int point_idx = poly.size() - 2; result.add(poly[point_idx]); if (point_idx == 0) { break; } point_idx--; } if (result.size() < 3) { result.clear(); for (Point& p : poly) result.add(p); } } skin = results; } } } }//namespace cura