#include #include #include #include #include #include #include #include #include #include "ClipperUtils.hpp" #include "ElephantFootCompensation.hpp" #include "I18N.hpp" #include "Layer.hpp" #include "MultiMaterialSegmentation.hpp" #include "Print.hpp" #include "SVG.hpp" //BBS #include "ShortestPath.hpp" #include "libslic3r/Feature/Interlocking/InterlockingGenerator.hpp" //! macro used to mark string used at localization, return same string #define L(s) Slic3r::I18N::translate(s) namespace Slic3r { bool PrintObject::clip_multipart_objects = true; bool PrintObject::infill_only_where_needed = false; LayerPtrs new_layers( PrintObject *print_object, // Object layers (pairs of bottom/top Z coordinate), without the raft. const std::vector &object_layers) { LayerPtrs out; out.reserve(object_layers.size()); auto id = int(print_object->slicing_parameters().raft_layers()); coordf_t zmin = print_object->slicing_parameters().object_print_z_min; Layer *prev = nullptr; for (size_t i_layer = 0; i_layer < object_layers.size(); i_layer += 2) { coordf_t lo = object_layers[i_layer]; coordf_t hi = object_layers[i_layer + 1]; coordf_t slice_z = 0.5 * (lo + hi); Layer *layer = new Layer(id ++, print_object, hi - lo, hi + zmin, slice_z); out.emplace_back(layer); if (prev != nullptr) { prev->upper_layer = layer; layer->lower_layer = prev; } prev = layer; } return out; } // Slice single triangle mesh. static std::vector slice_volume( const ModelVolume &volume, const std::vector &zs, const MeshSlicingParamsEx ¶ms, const std::function &throw_on_cancel_callback) { std::vector layers; if (! zs.empty()) { indexed_triangle_set its = volume.mesh().its; if (its.indices.size() > 0) { MeshSlicingParamsEx params2 { params }; params2.trafo = params2.trafo * volume.get_matrix(); if (params2.trafo.rotation().determinant() < 0.) its_flip_triangles(its); layers = slice_mesh_ex(its, zs, params2, throw_on_cancel_callback); throw_on_cancel_callback(); } } return layers; } // Slice single triangle mesh. // Filter the zs not inside the ranges. The ranges are closed at the bottom and open at the top, they are sorted lexicographically and non overlapping. static std::vector slice_volume( const ModelVolume &volume, const std::vector &z, const std::vector &ranges, const MeshSlicingParamsEx ¶ms, const std::function &throw_on_cancel_callback) { std::vector out; if (! z.empty() && ! ranges.empty()) { if (ranges.size() == 1 && z.front() >= ranges.front().first && z.back() < ranges.front().second) { // All layers fit into a single range. out = slice_volume(volume, z, params, throw_on_cancel_callback); } else { std::vector z_filtered; std::vector> n_filtered; z_filtered.reserve(z.size()); n_filtered.reserve(2 * ranges.size()); size_t i = 0; for (const t_layer_height_range &range : ranges) { for (; i < z.size() && z[i] < range.first; ++ i) ; size_t first = i; for (; i < z.size() && z[i] < range.second; ++ i) z_filtered.emplace_back(z[i]); if (i > first) n_filtered.emplace_back(std::make_pair(first, i)); } if (! n_filtered.empty()) { std::vector layers = slice_volume(volume, z_filtered, params, throw_on_cancel_callback); out.assign(z.size(), ExPolygons()); i = 0; for (const std::pair &span : n_filtered) for (size_t j = span.first; j < span.second; ++ j) out[j] = std::move(layers[i ++]); } } } return out; } static inline bool model_volume_needs_slicing(const ModelVolume &mv) { ModelVolumeType type = mv.type(); return type == ModelVolumeType::MODEL_PART || type == ModelVolumeType::NEGATIVE_VOLUME || type == ModelVolumeType::PARAMETER_MODIFIER; } // Slice printable volumes, negative volumes and modifier volumes, sorted by ModelVolume::id(). // Apply closing radius. // Apply positive XY compensation to ModelVolumeType::MODEL_PART and ModelVolumeType::PARAMETER_MODIFIER, not to ModelVolumeType::NEGATIVE_VOLUME. // Apply contour simplification. static std::vector slice_volumes_inner( const PrintConfig &print_config, const PrintObjectConfig &print_object_config, const Transform3d &object_trafo, ModelVolumePtrs model_volumes, const std::vector &layer_ranges, const std::vector &zs, const std::function &throw_on_cancel_callback) { model_volumes_sort_by_id(model_volumes); std::vector out; out.reserve(model_volumes.size()); std::vector slicing_ranges; if (layer_ranges.size() > 1) slicing_ranges.reserve(layer_ranges.size()); MeshSlicingParamsEx params_base; params_base.closing_radius = print_object_config.slice_closing_radius.value; params_base.extra_offset = 0; params_base.trafo = object_trafo; //BBS: 0.0025mm is safe enough to simplify the data to speed slicing up for high-resolution model. //Also has on influence on arc fitting which has default resolution 0.0125mm. params_base.resolution = print_config.resolution <= 0.001 ? 0.0f : 0.0025; switch (print_object_config.slicing_mode.value) { case SlicingMode::Regular: params_base.mode = MeshSlicingParams::SlicingMode::Regular; break; case SlicingMode::EvenOdd: params_base.mode = MeshSlicingParams::SlicingMode::EvenOdd; break; case SlicingMode::CloseHoles: params_base.mode = MeshSlicingParams::SlicingMode::Positive; break; } params_base.mode_below = params_base.mode; // BBS const size_t num_extruders = print_config.filament_diameter.size(); const bool is_mm_painted = num_extruders > 1 && std::any_of(model_volumes.cbegin(), model_volumes.cend(), [](const ModelVolume *mv) { return mv->is_mm_painted(); }); // BBS: don't do size compensation when slice volume. // Will handle contour and hole size compensation seperately later. //const auto extra_offset = is_mm_painted ? 0.f : std::max(0.f, float(print_object_config.xy_contour_compensation.value)); const auto extra_offset = 0.f; for (const ModelVolume *model_volume : model_volumes) if (model_volume_needs_slicing(*model_volume)) { MeshSlicingParamsEx params { params_base }; if (! model_volume->is_negative_volume()) params.extra_offset = extra_offset; if (layer_ranges.size() == 1) { if (const PrintObjectRegions::LayerRangeRegions &layer_range = layer_ranges.front(); layer_range.has_volume(model_volume->id())) { if (model_volume->is_model_part() && print_config.spiral_mode) { auto it = std::find_if(layer_range.volume_regions.begin(), layer_range.volume_regions.end(), [model_volume](const auto &slice){ return model_volume == slice.model_volume; }); params.mode = MeshSlicingParams::SlicingMode::PositiveLargestContour; // Slice the bottom layers with SlicingMode::Regular. // This needs to be in sync with LayerRegion::make_perimeters() spiral_mode! const PrintRegionConfig ®ion_config = it->region->config(); params.slicing_mode_normal_below_layer = size_t(region_config.bottom_shell_layers.value); for (; params.slicing_mode_normal_below_layer < zs.size() && zs[params.slicing_mode_normal_below_layer] < region_config.bottom_shell_thickness - EPSILON; ++ params.slicing_mode_normal_below_layer); } out.push_back({ model_volume->id(), slice_volume(*model_volume, zs, params, throw_on_cancel_callback) }); } } else { assert(! print_config.spiral_mode); slicing_ranges.clear(); for (const PrintObjectRegions::LayerRangeRegions &layer_range : layer_ranges) if (layer_range.has_volume(model_volume->id())) slicing_ranges.emplace_back(layer_range.layer_height_range); if (! slicing_ranges.empty()) out.push_back({ model_volume->id(), slice_volume(*model_volume, zs, slicing_ranges, params, throw_on_cancel_callback) }); } if (! out.empty() && out.back().slices.empty()) out.pop_back(); } return out; } static inline VolumeSlices& volume_slices_find_by_id(std::vector &volume_slices, const ObjectID id) { auto it = lower_bound_by_predicate(volume_slices.begin(), volume_slices.end(), [id](const VolumeSlices &vs) { return vs.volume_id < id; }); assert(it != volume_slices.end() && it->volume_id == id); return *it; } static inline bool overlap_in_xy(const PrintObjectRegions::BoundingBox &l, const PrintObjectRegions::BoundingBox &r) { return ! (l.max().x() < r.min().x() || l.min().x() > r.max().x() || l.max().y() < r.min().y() || l.min().y() > r.max().y()); } static std::vector::const_iterator layer_range_first(const std::vector &layer_ranges, double z) { auto it = lower_bound_by_predicate(layer_ranges.begin(), layer_ranges.end(), [z](const PrintObjectRegions::LayerRangeRegions &lr) { return lr.layer_height_range.second < z && abs(lr.layer_height_range.second - z) > EPSILON; }); assert(it != layer_ranges.end() && it->layer_height_range.first <= z && z <= it->layer_height_range.second); if (z == it->layer_height_range.second) if (auto it_next = it; ++ it_next != layer_ranges.end() && it_next->layer_height_range.first == z) it = it_next; assert(it != layer_ranges.end() && it->layer_height_range.first <= z && z <= it->layer_height_range.second); return it; } static std::vector::const_iterator layer_range_next( const std::vector &layer_ranges, std::vector::const_iterator it, double z) { for (; it->layer_height_range.second <= z + EPSILON; ++ it) assert(it != layer_ranges.end()); assert(it != layer_ranges.end() && it->layer_height_range.first <= z && z < it->layer_height_range.second); return it; } static std::vector> slices_to_regions( const PrintConfig &print_config, const PrintObject &print_object, ModelVolumePtrs model_volumes, const PrintObjectRegions &print_object_regions, const std::vector &zs, std::vector &&volume_slices, // If clipping is disabled, then ExPolygons produced by different volumes will never be merged, thus they will be allowed to overlap. // It is up to the model designer to handle these overlaps. const bool clip_multipart_objects, const std::function &throw_on_cancel_callback) { model_volumes_sort_by_id(model_volumes); std::vector> slices_by_region(print_object_regions.all_regions.size(), std::vector(zs.size(), ExPolygons())); // First shuffle slices into regions if there is no overlap with another region possible, collect zs of the complex cases. std::vector> zs_complex; { size_t z_idx = 0; for (const PrintObjectRegions::LayerRangeRegions &layer_range : print_object_regions.layer_ranges) { for (; z_idx < zs.size() && zs[z_idx] < layer_range.layer_height_range.first; ++ z_idx) ; if (layer_range.volume_regions.empty()) { } else if (layer_range.volume_regions.size() == 1) { const ModelVolume *model_volume = layer_range.volume_regions.front().model_volume; assert(model_volume != nullptr); if (model_volume->is_model_part()) { VolumeSlices &slices_src = volume_slices_find_by_id(volume_slices, model_volume->id()); auto &slices_dst = slices_by_region[layer_range.volume_regions.front().region->print_object_region_id()]; for (; z_idx < zs.size() && zs[z_idx] < layer_range.layer_height_range.second; ++ z_idx) slices_dst[z_idx] = std::move(slices_src.slices[z_idx]); } } else { zs_complex.reserve(zs.size()); for (; z_idx < zs.size() && zs[z_idx] < layer_range.layer_height_range.second; ++ z_idx) { float z = zs[z_idx]; int idx_first_printable_region = -1; bool complex = false; for (int idx_region = 0; idx_region < int(layer_range.volume_regions.size()); ++ idx_region) { const PrintObjectRegions::VolumeRegion ®ion = layer_range.volume_regions[idx_region]; if (region.bbox->min().z() <= z && region.bbox->max().z() >= z) { if (idx_first_printable_region == -1 && region.model_volume->is_model_part()) idx_first_printable_region = idx_region; else if (idx_first_printable_region != -1) { // Test for overlap with some other region. for (int idx_region2 = idx_first_printable_region; idx_region2 < idx_region; ++ idx_region2) { const PrintObjectRegions::VolumeRegion ®ion2 = layer_range.volume_regions[idx_region2]; if (region2.bbox->min().z() <= z && region2.bbox->max().z() >= z && overlap_in_xy(*region.bbox, *region2.bbox)) { complex = true; break; } } } } } if (complex) zs_complex.push_back({ z_idx, z }); else if (idx_first_printable_region >= 0) { const PrintObjectRegions::VolumeRegion ®ion = layer_range.volume_regions[idx_first_printable_region]; slices_by_region[region.region->print_object_region_id()][z_idx] = std::move(volume_slices_find_by_id(volume_slices, region.model_volume->id()).slices[z_idx]); } } } throw_on_cancel_callback(); } } // Second perform region clipping and assignment in parallel. if (! zs_complex.empty()) { std::vector> layer_ranges_regions_to_slices(print_object_regions.layer_ranges.size(), std::vector()); for (const PrintObjectRegions::LayerRangeRegions &layer_range : print_object_regions.layer_ranges) { std::vector &layer_range_regions_to_slices = layer_ranges_regions_to_slices[&layer_range - print_object_regions.layer_ranges.data()]; layer_range_regions_to_slices.reserve(layer_range.volume_regions.size()); for (const PrintObjectRegions::VolumeRegion ®ion : layer_range.volume_regions) layer_range_regions_to_slices.push_back(&volume_slices_find_by_id(volume_slices, region.model_volume->id())); } tbb::parallel_for( tbb::blocked_range(0, zs_complex.size()), [&slices_by_region, &print_object_regions, &zs_complex, &layer_ranges_regions_to_slices, clip_multipart_objects, &throw_on_cancel_callback] (const tbb::blocked_range &range) { float z = zs_complex[range.begin()].second; auto it_layer_range = layer_range_first(print_object_regions.layer_ranges, z); // Per volume_regions slices at this Z height. struct RegionSlice { ExPolygons expolygons; // Identifier of this region in PrintObjectRegions::all_regions int region_id; ObjectID volume_id; bool operator<(const RegionSlice &rhs) const { bool this_empty = this->region_id < 0 || this->expolygons.empty(); bool rhs_empty = rhs.region_id < 0 || rhs.expolygons.empty(); // Sort the empty items to the end of the list. // Sort by region_id & volume_id lexicographically. return ! this_empty && (rhs_empty || (this->region_id < rhs.region_id || (this->region_id == rhs.region_id && volume_id < volume_id))); } }; // BBS auto trim_overlap = [](ExPolygons& expolys_a, ExPolygons& expolys_b) { ExPolygons trimming_a; ExPolygons trimming_b; for (ExPolygon& expoly_a : expolys_a) { BoundingBox bbox_a = get_extents(expoly_a); ExPolygons expolys_new; for (ExPolygon& expoly_b : expolys_b) { BoundingBox bbox_b = get_extents(expoly_b); if (!bbox_a.overlap(bbox_b)) continue; ExPolygons temp = intersection_ex(expoly_b, expoly_a, ApplySafetyOffset::Yes); if (temp.empty()) continue; if (expoly_a.contour.length() > expoly_b.contour.length()) trimming_a.insert(trimming_a.end(), temp.begin(), temp.end()); else trimming_b.insert(trimming_b.end(), temp.begin(), temp.end()); } } expolys_a = diff_ex(expolys_a, trimming_a); expolys_b = diff_ex(expolys_b, trimming_b); }; std::vector temp_slices; for (size_t zs_complex_idx = range.begin(); zs_complex_idx < range.end(); ++ zs_complex_idx) { auto [z_idx, z] = zs_complex[zs_complex_idx]; it_layer_range = layer_range_next(print_object_regions.layer_ranges, it_layer_range, z); const PrintObjectRegions::LayerRangeRegions &layer_range = *it_layer_range; { std::vector &layer_range_regions_to_slices = layer_ranges_regions_to_slices[it_layer_range - print_object_regions.layer_ranges.begin()]; // Per volume_regions slices at thiz Z height. temp_slices.clear(); temp_slices.reserve(layer_range.volume_regions.size()); for (VolumeSlices* &slices : layer_range_regions_to_slices) { const PrintObjectRegions::VolumeRegion &volume_region = layer_range.volume_regions[&slices - layer_range_regions_to_slices.data()]; temp_slices.push_back({ std::move(slices->slices[z_idx]), volume_region.region ? volume_region.region->print_object_region_id() : -1, volume_region.model_volume->id() }); } } for (int idx_region = 0; idx_region < int(layer_range.volume_regions.size()); ++ idx_region) if (! temp_slices[idx_region].expolygons.empty()) { const PrintObjectRegions::VolumeRegion ®ion = layer_range.volume_regions[idx_region]; if (region.model_volume->is_modifier()) { assert(region.parent > -1); bool next_region_same_modifier = idx_region + 1 < int(temp_slices.size()) && layer_range.volume_regions[idx_region + 1].model_volume == region.model_volume; RegionSlice &parent_slice = temp_slices[region.parent]; RegionSlice &this_slice = temp_slices[idx_region]; ExPolygons source = std::move(this_slice.expolygons); if (parent_slice.expolygons.empty()) { this_slice .expolygons.clear(); } else { this_slice .expolygons = intersection_ex(parent_slice.expolygons, source); parent_slice.expolygons = diff_ex (parent_slice.expolygons, source); } if (next_region_same_modifier) // To be used in the following iteration. temp_slices[idx_region + 1].expolygons = std::move(source); } else if ((region.model_volume->is_model_part() && clip_multipart_objects) || region.model_volume->is_negative_volume()) { // Clip every non-zero region preceding it. for (int idx_region2 = 0; idx_region2 < idx_region; ++ idx_region2) if (! temp_slices[idx_region2].expolygons.empty()) { // Skip trim_overlap for now, because it slow down the performace so much for some special cases #if 1 if (const PrintObjectRegions::VolumeRegion& region2 = layer_range.volume_regions[idx_region2]; !region2.model_volume->is_negative_volume() && overlap_in_xy(*region.bbox, *region2.bbox)) temp_slices[idx_region2].expolygons = diff_ex(temp_slices[idx_region2].expolygons, temp_slices[idx_region].expolygons); #else const PrintObjectRegions::VolumeRegion& region2 = layer_range.volume_regions[idx_region2]; if (!region2.model_volume->is_negative_volume() && overlap_in_xy(*region.bbox, *region2.bbox)) //BBS: handle negative_volume seperately, always minus the negative volume and don't need to trim overlap if (!region.model_volume->is_negative_volume()) trim_overlap(temp_slices[idx_region2].expolygons, temp_slices[idx_region].expolygons); else temp_slices[idx_region2].expolygons = diff_ex(temp_slices[idx_region2].expolygons, temp_slices[idx_region].expolygons); #endif } } } // Sort by region_id, push empty slices to the end. std::sort(temp_slices.begin(), temp_slices.end()); // Remove the empty slices. temp_slices.erase(std::find_if(temp_slices.begin(), temp_slices.end(), [](const auto &slice) { return slice.region_id == -1 || slice.expolygons.empty(); }), temp_slices.end()); // Merge slices and store them to the output. for (int i = 0; i < int(temp_slices.size());) { // Find a range of temp_slices with the same region_id. int j = i; bool merged = false; ExPolygons &expolygons = temp_slices[i].expolygons; for (++ j; j < int(temp_slices.size()) && temp_slices[i].region_id == temp_slices[j].region_id; ++ j) if (ExPolygons &expolygons2 = temp_slices[j].expolygons; ! expolygons2.empty()) { if (expolygons.empty()) { expolygons = std::move(expolygons2); } else { append(expolygons, std::move(expolygons2)); merged = true; } } // Don't unite the regions if ! clip_multipart_objects. In that case it is user's responsibility // to handle region overlaps. Indeed, one may intentionally let the regions overlap to produce crossing perimeters // for example. if (merged && clip_multipart_objects) expolygons = closing_ex(expolygons, float(scale_(EPSILON))); slices_by_region[temp_slices[i].region_id][z_idx] = std::move(expolygons); i = j; } throw_on_cancel_callback(); } }); } return slices_by_region; } //BBS: justify whether a volume is connected to another one bool doesVolumeIntersect(VolumeSlices& vs1, VolumeSlices& vs2) { if (vs1.volume_id == vs2.volume_id) return true; // two volumes in the same object should have same number of layers, otherwise the slicing is incorrect. if (vs1.slices.size() != vs2.slices.size()) return false; auto& vs1s = vs1.slices; auto& vs2s = vs2.slices; bool is_intersect = false; tbb::parallel_for(tbb::blocked_range(0, vs1s.size()), [&vs1s, &vs2s, &is_intersect](const tbb::blocked_range& range) { for (auto i = range.begin(); i != range.end(); ++i) { if (vs1s[i].empty()) continue; if (overlaps(vs1s[i], vs2s[i])) { is_intersect = true; break; } if (i + 1 != vs2s.size() && overlaps(vs1s[i], vs2s[i + 1])) { is_intersect = true; break; } if (i - 1 >= 0 && overlaps(vs1s[i], vs2s[i - 1])) { is_intersect = true; break; } } }); return is_intersect; } //BBS: grouping the volumes of an object according to their connection relationship bool groupingVolumes(std::vector objSliceByVolume, std::vector& groups, double resolution, int firstLayerReplacedBy) { std::vector groupIndex(objSliceByVolume.size(), -1); double offsetValue = 0.05 / SCALING_FACTOR; std::vector> osvIndex; for (int i = 0; i != objSliceByVolume.size(); ++i) { for (int j = 0; j != objSliceByVolume[i].slices.size(); ++j) { osvIndex.push_back({ i,j }); } } tbb::parallel_for(tbb::blocked_range(0, osvIndex.size()), [&osvIndex, &objSliceByVolume, &offsetValue, &resolution](const tbb::blocked_range& range) { for (auto k = range.begin(); k != range.end(); ++k) { for (ExPolygon& poly_ex : objSliceByVolume[osvIndex[k][0]].slices[osvIndex[k][1]]) poly_ex.douglas_peucker(resolution); } }); tbb::parallel_for(tbb::blocked_range(0, osvIndex.size()), [&osvIndex, &objSliceByVolume,&offsetValue, &resolution](const tbb::blocked_range& range) { for (auto k = range.begin(); k != range.end(); ++k) { objSliceByVolume[osvIndex[k][0]].slices[osvIndex[k][1]] = offset_ex(objSliceByVolume[osvIndex[k][0]].slices[osvIndex[k][1]], offsetValue); } }); for (int i = 0; i != objSliceByVolume.size(); ++i) { if (groupIndex[i] < 0) { groupIndex[i] = i; } for (int j = i + 1; j != objSliceByVolume.size(); ++j) { if (doesVolumeIntersect(objSliceByVolume[i], objSliceByVolume[j])) { if (groupIndex[j] < 0) groupIndex[j] = groupIndex[i]; if (groupIndex[j] != groupIndex[i]) { int retain = std::min(groupIndex[i], groupIndex[j]); int cover = std::max(groupIndex[i], groupIndex[j]); for (int k = 0; k != objSliceByVolume.size(); ++k) { if (groupIndex[k] == cover) groupIndex[k] = retain; } } } } } std::vector groupVector{}; for (int gi : groupIndex) { bool exist = false; for (int gv : groupVector) { if (gv == gi) { exist = true; break; } } if (!exist) groupVector.push_back(gi); } // group volumes and their slices according to the grouping Vector groups.clear(); for (int gv : groupVector) { groupedVolumeSlices gvs; gvs.groupId = gv; for (int i = 0; i != objSliceByVolume.size(); ++i) { if (groupIndex[i] == gv) { gvs.volume_ids.push_back(objSliceByVolume[i].volume_id); append(gvs.slices, objSliceByVolume[i].slices[firstLayerReplacedBy]); } } // the slices of a group should be unioned gvs.slices = offset_ex(union_ex(gvs.slices), -offsetValue); for (ExPolygon& poly_ex : gvs.slices) poly_ex.douglas_peucker(resolution); groups.push_back(gvs); } return true; } //BBS: filter the members of "objSliceByVolume" such that only "model_part" are included std::vector findPartVolumes(const std::vector& objSliceByVolume, ModelVolumePtrs model_volumes) { std::vector outPut; for (const auto& vs : objSliceByVolume) { for (const auto& mv : model_volumes) { if (vs.volume_id == mv->id() && mv->is_model_part()) outPut.push_back(vs); } } return outPut; } void applyNegtiveVolumes(ModelVolumePtrs model_volumes, const std::vector& objSliceByVolume, std::vector& groups, double resolution) { ExPolygons negTotal; for (const auto& vs : objSliceByVolume) { for (const auto& mv : model_volumes) { if (vs.volume_id == mv->id() && mv->is_negative_volume()) { if (vs.slices.size() > 0) { append(negTotal, vs.slices.front()); } } } } for (auto& g : groups) { g.slices = diff_ex(g.slices, negTotal); for (ExPolygon& poly_ex : g.slices) poly_ex.douglas_peucker(resolution); } } void reGroupingLayerPolygons(std::vector& gvss, ExPolygons &eps, double resolution) { std::vector epsIndex; epsIndex.resize(eps.size(), -1); auto gvssc = gvss; auto epsc = eps; for (ExPolygon& poly_ex : epsc) poly_ex.douglas_peucker(resolution); for (int i = 0; i != gvssc.size(); ++i) { for (ExPolygon& poly_ex : gvssc[i].slices) poly_ex.douglas_peucker(resolution); } tbb::parallel_for(tbb::blocked_range(0, epsc.size()), [&epsc, &gvssc, &epsIndex](const tbb::blocked_range& range) { for (auto ie = range.begin(); ie != range.end(); ++ie) { if (epsc[ie].area() <= 0) continue; double minArea = epsc[ie].area(); for (int iv = 0; iv != gvssc.size(); iv++) { auto clipedExPolys = diff_ex(epsc[ie], gvssc[iv].slices); double area = 0; for (const auto& ce : clipedExPolys) { area += ce.area(); } if (area < minArea) { minArea = area; epsIndex[ie] = iv; } } } }); for (int iv = 0; iv != gvss.size(); iv++) gvss[iv].slices.clear(); for (int ie = 0; ie != eps.size(); ie++) { if (epsIndex[ie] >= 0) gvss[epsIndex[ie]].slices.push_back(eps[ie]); } } /* std::string fix_slicing_errors(PrintObject* object, LayerPtrs &layers, const std::function &throw_if_canceled, int &firstLayerReplacedBy) { std::string error_msg;//BBS if (layers.size() == 0) return error_msg; // Collect layers with slicing errors. // These layers will be fixed in parallel. std::vector buggy_layers; buggy_layers.reserve(layers.size()); // BBS: get largest external perimenter width of all layers auto get_ext_peri_width = [](Layer* layer) {return layer->m_regions.empty() ? 0 : layer->m_regions[0]->flow(frExternalPerimeter).scaled_width(); }; auto it = std::max_element(layers.begin(), layers.end(), [get_ext_peri_width](auto& a, auto& b) {return get_ext_peri_width(a) < get_ext_peri_width(b); }); coord_t thresh = get_ext_peri_width(*it) * 0.5;// half of external perimeter width // 0.5 * scale_(this->config().line_width); for (size_t idx_layer = 0; idx_layer < layers.size(); ++idx_layer) { // BBS: detect empty layers (layers with very small regions) and mark them as problematic, then these layers will copy the nearest good layer auto layer = layers[idx_layer]; ExPolygons lslices; for (size_t region_id = 0; region_id < layer->m_regions.size(); ++region_id) { LayerRegion* layerm = layer->m_regions[region_id]; for (auto& surface : layerm->slices.surfaces) { auto expoly = offset_ex(surface.expolygon, -thresh); lslices.insert(lslices.begin(), expoly.begin(), expoly.end()); } } if (lslices.empty()) { layer->slicing_errors = true; } if (layers[idx_layer]->slicing_errors) { buggy_layers.push_back(idx_layer); } else break; // only detect empty layers near bed } BOOST_LOG_TRIVIAL(debug) << "Slicing objects - fixing slicing errors in parallel - begin"; std::atomic is_replaced = false; tbb::parallel_for( tbb::blocked_range(0, buggy_layers.size()), [&layers, &throw_if_canceled, &buggy_layers, &is_replaced](const tbb::blocked_range& range) { for (size_t buggy_layer_idx = range.begin(); buggy_layer_idx < range.end(); ++ buggy_layer_idx) { throw_if_canceled(); size_t idx_layer = buggy_layers[buggy_layer_idx]; // BBS: only replace empty layers lower than 1mm const coordf_t thresh_empty_layer_height = 1; Layer* layer = layers[idx_layer]; if (layer->print_z>= thresh_empty_layer_height) continue; assert(layer->slicing_errors); // Try to repair the layer surfaces by merging all contours and all holes from neighbor layers. // BOOST_LOG_TRIVIAL(trace) << "Attempting to repair layer" << idx_layer; for (size_t region_id = 0; region_id < layer->region_count(); ++ region_id) { LayerRegion *layerm = layer->get_region(region_id); // Find the first valid layer below / above the current layer. const Surfaces *upper_surfaces = nullptr; const Surfaces *lower_surfaces = nullptr; //BBS: only repair empty layers lowers than 1mm for (size_t j = idx_layer + 1; j < layers.size(); ++j) { if (!layers[j]->slicing_errors) { upper_surfaces = &layers[j]->regions()[region_id]->slices.surfaces; break; } if (layers[j]->print_z >= thresh_empty_layer_height) break; } for (int j = int(idx_layer) - 1; j >= 0; --j) { if (layers[j]->print_z >= thresh_empty_layer_height) continue; if (!layers[j]->slicing_errors) { lower_surfaces = &layers[j]->regions()[region_id]->slices.surfaces; break; } } // Collect outer contours and holes from the valid layers above & below. ExPolygons expolys; expolys.reserve( ((upper_surfaces == nullptr) ? 0 : upper_surfaces->size()) + ((lower_surfaces == nullptr) ? 0 : lower_surfaces->size())); if (upper_surfaces) for (const auto &surface : *upper_surfaces) { expolys.emplace_back(surface.expolygon); } if (lower_surfaces) for (const auto &surface : *lower_surfaces) { expolys.emplace_back(surface.expolygon); } if (!expolys.empty()) { //BBS is_replaced = true; layerm->slices.set(union_ex(expolys), stInternal); } } // Update layer slices after repairing the single regions. layer->make_slices(); } }); throw_if_canceled(); BOOST_LOG_TRIVIAL(debug) << "Slicing objects - fixing slicing errors in parallel - end"; if(is_replaced) error_msg = L("Empty layers around bottom are replaced by nearest normal layers."); // remove empty layers from bottom while (! layers.empty() && (layers.front()->lslices.empty() || layers.front()->empty())) { delete layers.front(); layers.erase(layers.begin()); layers.front()->lower_layer = nullptr; for (size_t i = 0; i < layers.size(); ++ i) layers[i]->set_id(layers[i]->id() - 1); } //BBS if(error_msg.empty() && !buggy_layers.empty()) error_msg = L("The model has too many empty layers."); // BBS: first layer slices are sorted by volume group, if the first layer is empty and replaced by the 2nd layer // the later will be stored in "object->firstLayerObjGroupsMod()" if (!buggy_layers.empty() && buggy_layers.front() == 0 && layers.size() > 1) firstLayerReplacedBy = 1; return error_msg; } */ void groupingVolumesForBrim(PrintObject* object, LayerPtrs& layers, int firstLayerReplacedBy) { const auto scaled_resolution = scaled(object->print()->config().resolution.value); auto partsObjSliceByVolume = findPartVolumes(object->firstLayerObjSliceMod(), object->model_object()->volumes); groupingVolumes(partsObjSliceByVolume, object->firstLayerObjGroupsMod(), scaled_resolution, firstLayerReplacedBy); applyNegtiveVolumes(object->model_object()->volumes, object->firstLayerObjSliceMod(), object->firstLayerObjGroupsMod(), scaled_resolution); // BBS: the actual first layer slices stored in layers are re-sorted by volume group and will be used to generate brim reGroupingLayerPolygons(object->firstLayerObjGroupsMod(), layers.front()->lslices, scaled_resolution); } // Called by make_perimeters() // 1) Decides Z positions of the layers, // 2) Initializes layers and their regions // 3) Slices the object meshes // 4) Slices the modifier meshes and reclassifies the slices of the object meshes by the slices of the modifier meshes // 5) Applies size compensation (offsets the slices in XY plane) // 6) Replaces bad slices by the slices reconstructed from the upper/lower layer // Resulting expolygons of layer regions are marked as Internal. void PrintObject::slice() { if (! this->set_started(posSlice)) return; //BBS: add flag to reload scene for shell rendering m_print->set_status(5, L("Slicing mesh"), PrintBase::SlicingStatus::RELOAD_SCENE); std::vector layer_height_profile; this->update_layer_height_profile(*this->model_object(), m_slicing_params, layer_height_profile, this); m_print->throw_if_canceled(); m_typed_slices = false; this->clear_layers(); m_layers = new_layers(this, generate_object_layers(m_slicing_params, layer_height_profile, m_config.precise_z_height.value)); this->slice_volumes(); m_print->throw_if_canceled(); int firstLayerReplacedBy = 0; #if 0 // Fix the model. //FIXME is this the right place to do? It is done repeateadly at the UI and now here at the backend. std::string warning = fix_slicing_errors(this, m_layers, [this](){ m_print->throw_if_canceled(); }, firstLayerReplacedBy); m_print->throw_if_canceled(); //BBS: send warning message to slicing callback // This warning is inaccurate, because the empty layers may have been replaced, or the model has supports. //if (!warning.empty()) { // BOOST_LOG_TRIVIAL(info) << warning; // this->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL, warning, PrintStateBase::SlicingReplaceInitEmptyLayers); //} #endif // Detect and process holes that should be converted to polyholes this->_transform_hole_to_polyholes(); // BBS: the actual first layer slices stored in layers are re-sorted by volume group and will be used to generate brim groupingVolumesForBrim(this, m_layers, firstLayerReplacedBy); // Update bounding boxes, back up raw slices of complex models. tbb::parallel_for( tbb::blocked_range(0, m_layers.size()), [this](const tbb::blocked_range& range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { m_print->throw_if_canceled(); Layer &layer = *m_layers[layer_idx]; layer.lslices_bboxes.clear(); layer.lslices_bboxes.reserve(layer.lslices.size()); for (const ExPolygon &expoly : layer.lslices) layer.lslices_bboxes.emplace_back(get_extents(expoly)); layer.backup_untyped_slices(); } }); if (m_layers.empty()) throw Slic3r::SlicingError(L("No layers were detected. You might want to repair your STL file(s) or check their size or thickness and retry.\n")); // BBS this->set_done(posSlice); } static bool bool_from_full_config(const DynamicPrintConfig &full_cfg, const char *key, bool fallback) { if (!full_cfg.has(key)) return fallback; if (const ConfigOptionBool *opt = full_cfg.option(key)) return opt->value; if (const ConfigOptionInt *opt = full_cfg.option(key)) return opt->value != 0; return fallback; } static coordf_t float_from_full_config(const DynamicPrintConfig &full_cfg, const char *key, coordf_t fallback) { if (!full_cfg.has(key)) return fallback; if (const ConfigOptionFloat *opt = full_cfg.option(key)) return coordf_t(opt->value); return coordf_t(full_cfg.opt_float(key)); } static bool fit_pass_heights_to_interval(std::vector &passes, double base_height, double lo, double hi) { if (passes.empty() || base_height <= EPSILON) return false; double sum = std::accumulate(passes.begin(), passes.end(), 0.0); double delta = base_height - sum; auto within = [lo, hi](double h) { return h >= lo - EPSILON && h <= hi + EPSILON; }; if (std::abs(delta) > EPSILON) { if (within(passes.back() + delta)) { passes.back() += delta; delta = 0.0; } else if (delta > 0.0) { for (size_t i = passes.size(); i > 0 && delta > EPSILON; --i) { double &h = passes[i - 1]; const double room = hi - h; if (room <= EPSILON) continue; const double take = std::min(room, delta); h += take; delta -= take; } } else { for (size_t i = passes.size(); i > 0 && delta < -EPSILON; --i) { double &h = passes[i - 1]; const double room = h - lo; if (room <= EPSILON) continue; const double take = std::min(room, -delta); h -= take; delta += take; } } } if (std::abs(delta) > 1e-6) return false; return std::all_of(passes.begin(), passes.end(), within); } static std::vector build_uniform_local_z_pass_heights(double base_height, double lo, double hi) { std::vector out; if (base_height <= EPSILON) return out; size_t min_passes = size_t(std::max(1.0, std::ceil((base_height - EPSILON) / hi))); size_t max_passes = size_t(std::max(1.0, std::floor((base_height + EPSILON) / lo))); size_t pass_count = min_passes; if (max_passes >= min_passes) { const double target_step = 0.5 * (lo + hi); const size_t target_passes = size_t(std::max(1.0, std::llround(base_height / std::max(target_step, EPSILON)))); pass_count = std::clamp(target_passes, min_passes, max_passes); } if (pass_count == 1 && base_height >= 2.0 * lo - EPSILON && max_passes >= 2) pass_count = 2; if (pass_count <= 1) { out.emplace_back(base_height); return out; } const double uniform_height = base_height / double(pass_count); out.assign(pass_count, uniform_height); // Keep the accumulated numeric error at the very top of the interval. double accumulated = 0.0; for (size_t i = 0; i + 1 < out.size(); ++i) accumulated += out[i]; out.back() = std::max(EPSILON, base_height - accumulated); return out; } static inline void compute_local_z_gradient_component_heights(int mix_b_percent, double lower_bound, double upper_bound, double &h_a, double &h_b) { const int mix_b = std::clamp(mix_b_percent, 0, 100); const double pct_b = double(mix_b) / 100.0; const double pct_a = 1.0 - pct_b; const double lo = std::max(0.01, lower_bound); const double hi = std::max(lo, upper_bound); h_a = lo + pct_a * (hi - lo); h_b = lo + pct_b * (hi - lo); } static std::vector build_local_z_alternating_pass_heights(double base_height, double lower_bound, double upper_bound, double gradient_h_a, double gradient_h_b) { if (base_height <= EPSILON) return {}; const double lo = std::max(0.01, lower_bound); const double hi = std::max(lo, upper_bound); if (base_height < 2.0 * lo - EPSILON) return { base_height }; const double cycle_h = std::max(EPSILON, gradient_h_a + gradient_h_b); const double ratio_a = std::clamp(gradient_h_a / cycle_h, 0.0, 1.0); const double ratio_b = 1.0 - ratio_a; size_t min_passes = size_t(std::max(2.0, std::ceil((base_height - EPSILON) / hi))); if ((min_passes % 2) != 0) ++min_passes; size_t max_passes = size_t(std::max(2.0, std::floor((base_height + EPSILON) / lo))); if ((max_passes % 2) != 0) --max_passes; if (max_passes < 2 || min_passes > max_passes) return build_uniform_local_z_pass_heights(base_height, lo, hi); for (size_t pass_count = min_passes; pass_count <= max_passes; pass_count += 2) { const size_t pair_count = pass_count / 2; const double pair_h = base_height / double(pair_count); const double h_a = pair_h * ratio_a; const double h_b = pair_h * ratio_b; std::vector out; out.reserve(pass_count); for (size_t pair_idx = 0; pair_idx < pair_count; ++pair_idx) { out.emplace_back(h_a); out.emplace_back(h_b); } if (fit_pass_heights_to_interval(out, base_height, lo, hi)) return out; } return build_uniform_local_z_pass_heights(base_height, lo, hi); } static std::vector build_local_z_pass_heights(double base_height, double lower_bound, double upper_bound, double preferred_a, double preferred_b) { if (base_height <= EPSILON) return {}; const double lo = std::max(0.01, lower_bound); const double hi = std::max(lo, upper_bound); std::vector cadence_unit; if (preferred_a > EPSILON) cadence_unit.push_back(std::clamp(preferred_a, lo, hi)); if (preferred_b > EPSILON) cadence_unit.push_back(std::clamp(preferred_b, lo, hi)); if (!cadence_unit.empty()) { std::vector out; out.reserve(size_t(std::ceil(base_height / lo)) + 2); double z_used = 0.0; size_t idx = 0; size_t guard = 0; while (z_used + cadence_unit[idx] < base_height - EPSILON && guard++ < 100000) { out.push_back(cadence_unit[idx]); z_used += cadence_unit[idx]; idx = (idx + 1) % cadence_unit.size(); } const double remainder = base_height - z_used; if (remainder > EPSILON) out.push_back(remainder); if (fit_pass_heights_to_interval(out, base_height, lo, hi)) return out; } return build_uniform_local_z_pass_heights(base_height, lo, hi); } static std::vector decode_manual_pattern_sequence(const MixedFilament &mf, size_t num_physical) { std::vector sequence; if (mf.manual_pattern.empty()) return sequence; sequence.reserve(mf.manual_pattern.size()); for (const char token : mf.manual_pattern) { unsigned int extruder_id = 0; if (token == '1') extruder_id = mf.component_a; else if (token == '2') extruder_id = mf.component_b; else if (token >= '3' && token <= '9') extruder_id = unsigned(token - '0'); if (extruder_id >= 1 && extruder_id <= num_physical) sequence.emplace_back(extruder_id); } return sequence; } static std::vector decode_gradient_component_ids(const MixedFilament &mf, size_t num_physical) { std::vector ids; if (mf.gradient_component_ids.empty() || num_physical == 0) return ids; bool seen[10] = { false }; ids.reserve(mf.gradient_component_ids.size()); for (const char c : mf.gradient_component_ids) { if (c < '1' || c > '9') continue; const unsigned int id = unsigned(c - '0'); if (id == 0 || id > num_physical || seen[id]) continue; seen[id] = true; ids.emplace_back(id); } return ids; } static std::vector decode_gradient_component_weights(const MixedFilament &mf, size_t expected_components) { std::vector out; if (mf.gradient_component_weights.empty() || expected_components == 0) return out; std::string token; for (const char c : mf.gradient_component_weights) { if (c >= '0' && c <= '9') { token.push_back(c); continue; } if (!token.empty()) { out.emplace_back(std::max(0, std::atoi(token.c_str()))); token.clear(); } } if (!token.empty()) out.emplace_back(std::max(0, std::atoi(token.c_str()))); if (out.size() != expected_components) return {}; int sum = 0; for (const int v : out) sum += std::max(0, v); if (sum <= 0) return {}; return out; } static std::vector build_weighted_gradient_sequence(const std::vector &ids, const std::vector &weights) { if (ids.empty()) return {}; std::vector filtered_ids; std::vector counts; filtered_ids.reserve(ids.size()); counts.reserve(ids.size()); for (size_t i = 0; i < ids.size(); ++i) { const int w = (i < weights.size()) ? std::max(0, weights[i]) : 0; if (w <= 0) continue; filtered_ids.emplace_back(ids[i]); counts.emplace_back(w); } if (filtered_ids.empty()) { filtered_ids = ids; counts.assign(ids.size(), 1); } int g = 0; for (const int c : counts) g = std::gcd(g, std::max(1, c)); if (g > 1) { for (int &c : counts) c = std::max(1, c / g); } int cycle = std::accumulate(counts.begin(), counts.end(), 0); constexpr int k_max_cycle = 48; if (cycle > k_max_cycle) { const double scale = double(k_max_cycle) / double(cycle); for (int &c : counts) c = std::max(1, int(std::round(double(c) * scale))); cycle = std::accumulate(counts.begin(), counts.end(), 0); while (cycle > k_max_cycle) { auto it = std::max_element(counts.begin(), counts.end()); if (it == counts.end() || *it <= 1) break; --(*it); --cycle; } } if (cycle <= 0) return {}; std::vector sequence; sequence.reserve(size_t(cycle)); std::vector emitted(counts.size(), 0); for (int pos = 0; pos < cycle; ++pos) { size_t best_idx = 0; double best_score = -1e9; for (size_t i = 0; i < counts.size(); ++i) { const double target = double((pos + 1) * counts[i]) / double(cycle); const double score = target - double(emitted[i]); if (score > best_score) { best_score = score; best_idx = i; } } ++emitted[best_idx]; sequence.emplace_back(filtered_ids[best_idx]); } return sequence; } static std::vector pointillism_sequence_for_row(const MixedFilament &mf, size_t num_physical) { if (!mf.enabled || num_physical == 0) return {}; if (mf.distribution_mode != int(MixedFilament::SameLayerPointillisme)) return {}; if (!mf.manual_pattern.empty()) return decode_manual_pattern_sequence(mf, num_physical); const std::vector selected_gradient_ids = decode_gradient_component_ids(mf, num_physical); if (selected_gradient_ids.size() >= 2) { const std::vector selected_gradient_weights = decode_gradient_component_weights(mf, selected_gradient_ids.size()); const std::vector weighted_sequence = build_weighted_gradient_sequence(selected_gradient_ids, selected_gradient_weights.empty() ? std::vector(selected_gradient_ids.size(), 1) : selected_gradient_weights); if (!weighted_sequence.empty()) return weighted_sequence; } if (mf.component_a < 1 || mf.component_a > num_physical || mf.component_b < 1 || mf.component_b > num_physical || mf.component_a == mf.component_b) return {}; int ratio_a = std::max(0, mf.ratio_a); int ratio_b = std::max(0, mf.ratio_b); if (ratio_a == 0 && ratio_b == 0) ratio_a = 1; if (ratio_a > 0 && ratio_b > 0) { const int g = std::gcd(ratio_a, ratio_b); if (g > 1) { ratio_a /= g; ratio_b /= g; } } constexpr int k_max_cycle = 24; if (ratio_a + ratio_b > k_max_cycle) { const double scale = double(k_max_cycle) / double(ratio_a + ratio_b); ratio_a = std::max(1, int(std::round(double(ratio_a) * scale))); ratio_b = std::max(1, int(std::round(double(ratio_b) * scale))); } const int cycle = std::max(1, ratio_a + ratio_b); std::vector sequence; sequence.reserve(size_t(cycle)); for (int pos = 0; pos < cycle; ++pos) { const int b_before = (pos * ratio_b) / cycle; const int b_after = ((pos + 1) * ratio_b) / cycle; sequence.emplace_back((b_after > b_before) ? mf.component_b : mf.component_a); } bool seen_a = false; bool seen_b = false; for (const unsigned int extruder_id : sequence) { seen_a = seen_a || (extruder_id == mf.component_a); seen_b = seen_b || (extruder_id == mf.component_b); if (seen_a && seen_b) break; } if (!seen_a || !seen_b) return {}; return sequence; } static size_t unique_extruder_count(const std::vector &sequence, size_t num_physical) { if (sequence.empty() || num_physical == 0) return 0; std::vector seen(num_physical + 1, false); size_t unique_count = 0; for (const unsigned int extruder_id : sequence) { if (extruder_id == 0 || extruder_id > num_physical) continue; if (!seen[extruder_id]) { seen[extruder_id] = true; ++unique_count; } } return unique_count; } static bool split_masks_pointillism_stripes(const ExPolygons &source_masks, const std::vector &sequence, size_t num_physical, size_t layer_id, coord_t stripe_pitch, bool flip_orientation, std::vector &out_by_extruder) { if (source_masks.empty() || sequence.empty() || num_physical == 0 || stripe_pitch <= 0) return false; const BoundingBox bbox = get_extents(source_masks); if (!bbox.defined || bbox.min.x() >= bbox.max.x() || bbox.min.y() >= bbox.max.y()) return false; out_by_extruder.assign(num_physical, ExPolygons()); const size_t slot_count = sequence.size(); const size_t phase = slot_count > 0 ? (layer_id % slot_count) : 0; auto align_down_to_grid = [stripe_pitch](coord_t value) { coord_t rem = value % stripe_pitch; if (rem < 0) rem += stripe_pitch; return value - rem; }; std::vector stripe_polygons_by_slot(slot_count); const bool vertical_base = (bbox.max.x() - bbox.min.x()) >= (bbox.max.y() - bbox.min.y()); // Alternate stripe orientation every layer so different faces of the model // receive mixed-color variation instead of long single-direction bands. const bool layer_alternates = (layer_id & 1) != 0; bool vertical = layer_alternates ? !vertical_base : vertical_base; if (flip_orientation) vertical = !vertical; if (vertical) { const coord_t y0 = bbox.min.y(); const coord_t y1 = bbox.max.y(); const coord_t x_start_aligned = align_down_to_grid(bbox.min.x()); size_t stripe_idx = 0; for (coord_t x = x_start_aligned; x < bbox.max.x(); x += stripe_pitch, ++stripe_idx) { const coord_t x0 = std::max(x, bbox.min.x()); const coord_t x1 = std::min(x + stripe_pitch, bbox.max.x()); if (x1 <= x0) continue; const size_t slot = (stripe_idx + phase) % slot_count; stripe_polygons_by_slot[slot].emplace_back(BoundingBox(Point(x0, y0), Point(x1, y1)).polygon()); } } else { const coord_t x0 = bbox.min.x(); const coord_t x1 = bbox.max.x(); const coord_t y_start_aligned = align_down_to_grid(bbox.min.y()); size_t stripe_idx = 0; for (coord_t y = y_start_aligned; y < bbox.max.y(); y += stripe_pitch, ++stripe_idx) { const coord_t y0 = std::max(y, bbox.min.y()); const coord_t y1 = std::min(y + stripe_pitch, bbox.max.y()); if (y1 <= y0) continue; const size_t slot = (stripe_idx + phase) % slot_count; stripe_polygons_by_slot[slot].emplace_back(BoundingBox(Point(x0, y0), Point(x1, y1)).polygon()); } } unsigned int fallback_extruder = 0; for (const unsigned int extruder_id : sequence) { if (extruder_id >= 1 && extruder_id <= num_physical) { fallback_extruder = extruder_id; break; } } if (fallback_extruder == 0) return false; for (size_t slot = 0; slot < slot_count; ++slot) { const unsigned int extruder_id = sequence[slot]; if (extruder_id == 0 || extruder_id > num_physical || stripe_polygons_by_slot[slot].empty()) continue; ExPolygons clipped = intersection_ex(source_masks, stripe_polygons_by_slot[slot], ApplySafetyOffset::Yes); if (!clipped.empty()) append(out_by_extruder[extruder_id - 1], std::move(clipped)); } ExPolygons assigned_union; for (ExPolygons &masks : out_by_extruder) { if (masks.size() > 1) masks = union_ex(masks); append(assigned_union, masks); } if (assigned_union.empty()) { append(out_by_extruder[fallback_extruder - 1], source_masks); return true; } if (assigned_union.size() > 1) assigned_union = union_ex(assigned_union); ExPolygons remainder = diff_ex(source_masks, assigned_union, ApplySafetyOffset::Yes); if (!remainder.empty()) { append(out_by_extruder[fallback_extruder - 1], std::move(remainder)); ExPolygons &fallback_masks = out_by_extruder[fallback_extruder - 1]; if (fallback_masks.size() > 1) fallback_masks = union_ex(fallback_masks); } return true; } static size_t non_empty_mask_count(const std::vector &masks_by_extruder) { size_t count = 0; for (const ExPolygons &masks : masks_by_extruder) if (!masks.empty()) ++count; return count; } template static bool apply_pointillism_mixed_segmentation(PrintObject &print_object, std::vector> &segmentation, ThrowOnCancel throw_on_cancel) { const Print *print = print_object.print(); if (print == nullptr || segmentation.empty()) return false; const PrintConfig &print_cfg = print->config(); const size_t num_physical = print_cfg.filament_colour.size(); if (num_physical < 2) return false; const MixedFilamentManager &mixed_mgr = print->mixed_filament_manager(); const auto &mixed_rows = mixed_mgr.mixed_filaments(); if (mixed_rows.empty()) return false; const size_t num_channels = segmentation.front().size(); if (num_channels <= num_physical) return false; const double nozzle = print_cfg.nozzle_diameter.values.empty() ? 0.4 : print_cfg.nozzle_diameter.get_at(0); // Keep stripe width at or above roughly one printable line to avoid // non-printable slivers that can get dropped later and create holes. const double stripe_pitch_mm = std::max(0.25, 1.10 * nozzle); const coord_t stripe_pitch = std::max(scale_(0.25), scale_(stripe_pitch_mm)); std::vector> same_layer_sequences(mixed_rows.size()); std::vector same_layer_row_active(mixed_rows.size(), false); std::vector same_layer_row_indices; for (size_t mixed_idx = 0; mixed_idx < mixed_rows.size(); ++mixed_idx) { const MixedFilament &mf = mixed_rows[mixed_idx]; if (!mf.enabled || mf.distribution_mode != int(MixedFilament::SameLayerPointillisme)) continue; same_layer_sequences[mixed_idx] = pointillism_sequence_for_row(mf, num_physical); if (unique_extruder_count(same_layer_sequences[mixed_idx], num_physical) >= 2) { same_layer_row_active[mixed_idx] = true; same_layer_row_indices.emplace_back(mixed_idx); } } auto find_sequence_override = [&](size_t mixed_idx) -> const std::vector * { if (mixed_idx >= mixed_rows.size()) return nullptr; if (same_layer_row_active[mixed_idx]) return &same_layer_sequences[mixed_idx]; const MixedFilament &src = mixed_rows[mixed_idx]; for (size_t idx : same_layer_row_indices) { if (idx >= mixed_rows.size()) continue; const MixedFilament &candidate = mixed_rows[idx]; if ((candidate.component_a == src.component_a && candidate.component_b == src.component_b) || (candidate.component_a == src.component_b && candidate.component_b == src.component_a)) return &same_layer_sequences[idx]; } if (same_layer_row_indices.size() == 1) return &same_layer_sequences[same_layer_row_indices.front()]; return nullptr; }; size_t same_layer_rows = 0; for (size_t mixed_idx = 0; mixed_idx < mixed_rows.size(); ++mixed_idx) { const MixedFilament &mf = mixed_rows[mixed_idx]; if (!same_layer_row_active[mixed_idx]) continue; const std::vector &seq = same_layer_sequences[mixed_idx]; const size_t unique = unique_extruder_count(seq, num_physical); BOOST_LOG_TRIVIAL(debug) << "Same-layer pointillisme row" << " mixed_idx=" << mixed_idx << " component_a=" << mf.component_a << " component_b=" << mf.component_b << " mix_b_percent=" << mf.mix_b_percent << " manual_pattern_len=" << mf.manual_pattern.size() << " gradient_components=" << mf.gradient_component_ids << " sequence_len=" << seq.size() << " unique_extruders=" << unique; if (unique >= 2) ++same_layer_rows; } size_t transformed_layers = 0; size_t transformed_states = 0; size_t transformed_masks = 0; size_t skipped_states = 0; size_t retried_states = 0; size_t weak_split_states = 0; size_t pair_override_states = 0; size_t global_override_states = 0; for (size_t layer_id = 0; layer_id < segmentation.size(); ++layer_id) { throw_on_cancel(); if (segmentation[layer_id].size() != num_channels) { ++skipped_states; continue; } bool layer_transformed = false; std::vector touched_physical(num_physical, false); for (size_t channel_idx = num_physical; channel_idx < num_channels; ++channel_idx) { ExPolygons &state_masks = segmentation[layer_id][channel_idx]; if (state_masks.empty()) continue; const unsigned int state_id = unsigned(channel_idx + 1); const int mixed_idx = mixed_mgr.mixed_index_from_filament_id(state_id, num_physical); if (mixed_idx < 0 || size_t(mixed_idx) >= mixed_rows.size()) { ++skipped_states; continue; } const MixedFilament &mf = mixed_rows[size_t(mixed_idx)]; const std::vector *sequence_ptr = find_sequence_override(size_t(mixed_idx)); if (sequence_ptr == nullptr || sequence_ptr->empty() || unique_extruder_count(*sequence_ptr, num_physical) < 2) { ++skipped_states; continue; } if (!same_layer_row_active[size_t(mixed_idx)]) { bool pair_match = false; for (size_t idx : same_layer_row_indices) { const MixedFilament &candidate = mixed_rows[idx]; if ((candidate.component_a == mf.component_a && candidate.component_b == mf.component_b) || (candidate.component_a == mf.component_b && candidate.component_b == mf.component_a)) { pair_match = true; break; } } if (pair_match) ++pair_override_states; else if (same_layer_row_indices.size() == 1) ++global_override_states; } std::vector split_by_extruder; if (!split_masks_pointillism_stripes(state_masks, *sequence_ptr, num_physical, layer_id, stripe_pitch, false, split_by_extruder)) { ++skipped_states; continue; } size_t split_unique = non_empty_mask_count(split_by_extruder); if (split_unique < 2) { std::vector retry_split; if (split_masks_pointillism_stripes(state_masks, *sequence_ptr, num_physical, layer_id, stripe_pitch, true, retry_split)) { const size_t retry_unique = non_empty_mask_count(retry_split); if (retry_unique > split_unique) { split_by_extruder = std::move(retry_split); split_unique = retry_unique; } ++retried_states; } } if (split_unique < 2) ++weak_split_states; for (size_t extruder_idx = 0; extruder_idx < num_physical; ++extruder_idx) { if (split_by_extruder[extruder_idx].empty()) continue; append(segmentation[layer_id][extruder_idx], std::move(split_by_extruder[extruder_idx])); touched_physical[extruder_idx] = true; } transformed_masks += state_masks.size(); state_masks.clear(); layer_transformed = true; ++transformed_states; } if (layer_transformed) { ++transformed_layers; for (size_t extruder_idx = 0; extruder_idx < num_physical; ++extruder_idx) { if (!touched_physical[extruder_idx] || segmentation[layer_id][extruder_idx].size() <= 1) continue; segmentation[layer_id][extruder_idx] = union_ex(segmentation[layer_id][extruder_idx]); } } } if (transformed_states > 0) { BOOST_LOG_TRIVIAL(warning) << "Mixed interleaved-stripe segmentation applied" << " object=" << (print_object.model_object() ? print_object.model_object()->name : std::string("")) << " same_layer_rows=" << same_layer_rows << " transformed_layers=" << transformed_layers << " transformed_states=" << transformed_states << " transformed_masks=" << transformed_masks << " retried_states=" << retried_states << " weak_split_states=" << weak_split_states << " pair_override_states=" << pair_override_states << " global_override_states=" << global_override_states << " stripe_pitch_mm=" << stripe_pitch_mm << " skipped_states=" << skipped_states; return true; } if (same_layer_rows > 0) { BOOST_LOG_TRIVIAL(warning) << "Same-layer pointillisme requested but produced no transformed states" << " object=" << (print_object.model_object() ? print_object.model_object()->name : std::string("")) << " same_layer_rows=" << same_layer_rows << " stripe_pitch_mm=" << stripe_pitch_mm << " skipped_states=" << skipped_states; } return false; } static ExPolygons collect_layer_region_slices(const Layer &layer) { ExPolygons out; for (const LayerRegion *layerm : layer.regions()) append(out, to_expolygons(layerm->slices.surfaces)); if (!out.empty()) out = union_ex(out); return out; } static void export_local_z_plan_debug(const PrintObject &print_object, coordf_t lower_bound, coordf_t upper_bound) { const std::vector &intervals = print_object.local_z_intervals(); const std::vector &plans = print_object.local_z_sublayer_plan(); if (intervals.empty() || plans.empty()) return; const int object_id = int(print_object.id().id); std::ofstream json(debug_out_path("local-z-plan-obj-%d.json", object_id), std::ios::out | std::ios::trunc); if (json.good()) { json << std::fixed << std::setprecision(6); json << "{\n"; json << " \"object_id\": " << object_id << ",\n"; json << " \"mixed_height_lower_bound\": " << lower_bound << ",\n"; json << " \"mixed_height_upper_bound\": " << upper_bound << ",\n"; json << " \"interval_count\": " << intervals.size() << ",\n"; json << " \"sublayer_count\": " << plans.size() << ",\n"; json << " \"intervals\": [\n"; for (size_t i = 0; i < intervals.size(); ++i) { const LocalZInterval &interval = intervals[i]; json << " {\"layer_id\": " << interval.layer_id << ", \"z_lo\": " << interval.z_lo << ", \"z_hi\": " << interval.z_hi << ", \"base_height\": " << interval.base_height << ", \"sublayer_height\": " << interval.sublayer_height << ", \"has_mixed_paint\": " << (interval.has_mixed_paint ? "true" : "false") << ", \"sublayer_count\": " << interval.sublayer_count << "}"; if (i + 1 < intervals.size()) json << ","; json << "\n"; } json << " ],\n"; json << " \"sublayers\": [\n"; for (size_t i = 0; i < plans.size(); ++i) { const SubLayerPlan &plan = plans[i]; json << " {\"layer_id\": " << plan.layer_id << ", \"pass_index\": " << plan.pass_index << ", \"split_interval\": " << (plan.split_interval ? "true" : "false") << ", \"z_lo\": " << plan.z_lo << ", \"z_hi\": " << plan.z_hi << ", \"print_z\": " << plan.print_z << ", \"flow_height\": " << plan.flow_height << ", \"base_mask_count\": " << plan.base_masks.size() << ", \"painted_mask_counts\": ["; for (size_t eidx = 0; eidx < plan.painted_masks_by_extruder.size(); ++eidx) { json << plan.painted_masks_by_extruder[eidx].size(); if (eidx + 1 < plan.painted_masks_by_extruder.size()) json << ", "; } json << "]}"; if (i + 1 < plans.size()) json << ","; json << "\n"; } json << " ]\n"; json << "}\n"; } static const std::array colors { "#E53935", "#1E88E5", "#43A047", "#FB8C00", "#8E24AA", "#00897B", "#6D4C41", "#3949AB", "#C0CA33", "#F4511E" }; for (const SubLayerPlan &plan : plans) { bool has_painted = std::any_of(plan.painted_masks_by_extruder.begin(), plan.painted_masks_by_extruder.end(), [](const ExPolygons &masks) { return !masks.empty(); }); if (!plan.split_interval && !has_painted) continue; if (!has_painted && plan.base_masks.empty()) continue; std::vector> layers; if (!plan.base_masks.empty()) { layers.emplace_back(plan.base_masks, SVG::ExPolygonAttributes("base", "#D6D6D6", "#6A6A6A", "#6A6A6A", scale_(0.03), 0.45f)); } for (size_t eidx = 0; eidx < plan.painted_masks_by_extruder.size(); ++eidx) { if (plan.painted_masks_by_extruder[eidx].empty()) continue; const char *color = colors[eidx % colors.size()]; layers.emplace_back(plan.painted_masks_by_extruder[eidx], SVG::ExPolygonAttributes("E" + std::to_string(eidx + 1), color, color, color, scale_(0.03), 0.55f)); } if (!layers.empty()) { SVG::export_expolygons(debug_out_path("local-z-plan-obj-%d-layer-%d-pass-%d.svg", object_id, int(plan.layer_id), int(plan.pass_index)), layers); } } } template static void build_local_z_plan(PrintObject &print_object, const std::vector> &segmentation, ThrowOnCancel throw_on_cancel) { print_object.clear_local_z_plan(); const Print *print = print_object.print(); const std::string object_name = print_object.model_object() ? print_object.model_object()->name : std::string(""); if (print == nullptr || print_object.layer_count() == 0 || segmentation.size() != print_object.layer_count()) { BOOST_LOG_TRIVIAL(debug) << "Local-Z plan skipped: invalid preconditions" << " object=" << object_name << " print_ptr=" << (print != nullptr) << " layer_count=" << print_object.layer_count() << " segmentation_layers=" << segmentation.size(); return; } const DynamicPrintConfig &full_cfg = print->full_print_config(); const PrintConfig &print_cfg = print->config(); const bool local_z_mode = bool_from_full_config(full_cfg, "dithering_local_z_mode", print_cfg.dithering_local_z_mode.value); if (!local_z_mode) { BOOST_LOG_TRIVIAL(debug) << "Local-Z plan skipped: mode disabled" << " object=" << object_name; return; } coordf_t mixed_lower = float_from_full_config(full_cfg, "mixed_filament_height_lower_bound", coordf_t(print_cfg.mixed_filament_height_lower_bound.value)); coordf_t mixed_upper = float_from_full_config(full_cfg, "mixed_filament_height_upper_bound", coordf_t(print_cfg.mixed_filament_height_upper_bound.value)); coordf_t preferred_a = float_from_full_config(full_cfg, "mixed_color_layer_height_a", coordf_t(print_cfg.mixed_color_layer_height_a.value)); coordf_t preferred_b = float_from_full_config(full_cfg, "mixed_color_layer_height_b", coordf_t(print_cfg.mixed_color_layer_height_b.value)); mixed_lower = std::max(0.01f, mixed_lower); mixed_upper = std::max(mixed_lower, mixed_upper); preferred_a = std::max(0.f, preferred_a); preferred_b = std::max(0.f, preferred_b); const size_t num_physical = print_cfg.filament_colour.size(); if (num_physical == 0) { BOOST_LOG_TRIVIAL(warning) << "Local-Z plan skipped: no physical filaments" << " object=" << object_name; return; } const MixedFilamentManager &mixed_mgr = print->mixed_filament_manager(); const auto &mixed_rows = mixed_mgr.mixed_filaments(); size_t pointillism_rows = 0; for (const MixedFilament &mf : mixed_rows) { const std::vector sequence = pointillism_sequence_for_row(mf, num_physical); if (unique_extruder_count(sequence, num_physical) >= 2) ++pointillism_rows; } if (pointillism_rows > 0) { BOOST_LOG_TRIVIAL(warning) << "Local-Z plan skipped: interleaved stripe mixed pattern active" << " object=" << object_name << " interleaved_rows=" << pointillism_rows; return; } BOOST_LOG_TRIVIAL(debug) << "Local-Z plan start" << " object=" << object_name << " layers=" << print_object.layer_count() << " mixed_lower=" << mixed_lower << " mixed_upper=" << mixed_upper << " preferred_a=" << preferred_a << " preferred_b=" << preferred_b << " physical_filaments=" << num_physical; std::vector intervals; std::vector plans; intervals.reserve(print_object.layer_count()); size_t mixed_intervals = 0; size_t split_intervals = 0; size_t non_split_mixed_intervals = 0; size_t total_generated_sublayer_cnt = 0; size_t total_mixed_state_layers = 0; size_t forced_height_resolve_calls = 0; size_t forced_height_resolve_non_custom_calls = 0; size_t forced_height_resolve_invalid_target = 0; size_t split_passes_total = 0; size_t split_passes_with_painted_masks = 0; size_t split_intervals_without_painted_masks = 0; size_t strict_ab_assignments = 0; size_t alternating_height_intervals = 0; size_t gradient_lock_mismatch_layers = 0; size_t gradient_lock_unset_mixed_layers = 0; size_t locked_gradient_source_layer = size_t(-1); size_t locked_gradient_mixed_idx = size_t(-1); double locked_gradient_h_a = 0.0; double locked_gradient_h_b = 0.0; bool locked_gradient_valid = false; int cadence_index = 0; for (size_t layer_id = 0; layer_id < print_object.layer_count(); ++layer_id) { throw_on_cancel(); const Layer &layer = *print_object.get_layer(int(layer_id)); LocalZInterval interval; interval.layer_id = layer_id; interval.z_lo = layer.print_z - layer.height; interval.z_hi = layer.print_z; interval.base_height = layer.height; interval.sublayer_height = layer.height; interval.first_sublayer_idx = plans.size(); ExPolygons mixed_masks; size_t mixed_state_count = 0; size_t dominant_mixed_idx = size_t(-1); double dominant_mixed_area = -1.0; double dominant_gradient_h_a = 0.0; double dominant_gradient_h_b = 0.0; bool dominant_gradient_valid = false; for (size_t channel_idx = 0; channel_idx < segmentation[layer_id].size(); ++channel_idx) { const ExPolygons &state_masks = segmentation[layer_id][channel_idx]; if (state_masks.empty()) continue; const unsigned int state_id = unsigned(channel_idx + 1); if (mixed_mgr.is_mixed(state_id, num_physical)) { interval.has_mixed_paint = true; ++mixed_state_count; append(mixed_masks, state_masks); const double mixed_area = std::abs(area(state_masks)); if (mixed_area > dominant_mixed_area) { dominant_mixed_area = mixed_area; const int resolved_mixed_idx = mixed_mgr.mixed_index_from_filament_id(state_id, num_physical); dominant_mixed_idx = resolved_mixed_idx >= 0 ? size_t(resolved_mixed_idx) : size_t(-1); } } } if (dominant_mixed_idx < mixed_rows.size()) { compute_local_z_gradient_component_heights(mixed_rows[dominant_mixed_idx].mix_b_percent, mixed_lower, mixed_upper, dominant_gradient_h_a, dominant_gradient_h_b); dominant_gradient_valid = true; } if (interval.has_mixed_paint && preferred_a <= EPSILON && preferred_b <= EPSILON) { if (!locked_gradient_valid && dominant_gradient_valid) { locked_gradient_valid = true; locked_gradient_source_layer = layer_id; locked_gradient_mixed_idx = dominant_mixed_idx; locked_gradient_h_a = dominant_gradient_h_a; locked_gradient_h_b = dominant_gradient_h_b; BOOST_LOG_TRIVIAL(warning) << "Local-Z gradient lock acquired" << " object=" << object_name << " layer_id=" << layer_id << " mixed_idx=" << locked_gradient_mixed_idx << " h_a=" << locked_gradient_h_a << " h_b=" << locked_gradient_h_b; } if (!locked_gradient_valid) ++gradient_lock_unset_mixed_layers; else if (dominant_gradient_valid && dominant_mixed_idx != locked_gradient_mixed_idx) ++gradient_lock_mismatch_layers; } total_mixed_state_layers += mixed_state_count; if (!mixed_masks.empty()) mixed_masks = union_ex(mixed_masks); if (interval.has_mixed_paint) ++mixed_intervals; const ExPolygons layer_masks = collect_layer_region_slices(layer); ExPolygons base_masks = layer_masks; if (interval.has_mixed_paint && !base_masks.empty() && !mixed_masks.empty()) { base_masks = diff_ex(base_masks, mixed_masks); if (!base_masks.empty()) { const Polygons filtered = opening(to_polygons(base_masks), scaled(5. * EPSILON), scaled(5. * EPSILON)); base_masks = union_ex(filtered); } } std::vector pass_heights; if (interval.has_mixed_paint) { // Local-Z mode should emit an A/B/A/B pattern for mixed regions and // derive relative heights from mixed-filament gradient bounds. if (preferred_a <= EPSILON && preferred_b <= EPSILON) { if (locked_gradient_valid) { pass_heights = build_local_z_alternating_pass_heights(interval.base_height, mixed_lower, mixed_upper, locked_gradient_h_a, locked_gradient_h_b); if (pass_heights.size() > 1) ++alternating_height_intervals; } else if (dominant_gradient_valid) { pass_heights = build_local_z_alternating_pass_heights(interval.base_height, mixed_lower, mixed_upper, dominant_gradient_h_a, dominant_gradient_h_b); if (pass_heights.size() > 1) ++alternating_height_intervals; } else { pass_heights = build_local_z_pass_heights(interval.base_height, mixed_lower, mixed_upper, preferred_a, preferred_b); } } else { pass_heights = build_local_z_pass_heights(interval.base_height, mixed_lower, mixed_upper, preferred_a, preferred_b); } } else pass_heights.emplace_back(interval.base_height); const bool split_interval = interval.has_mixed_paint && pass_heights.size() > 1; if (split_interval) { ++split_intervals; double z_cursor = interval.z_lo; size_t pass_idx = 0; bool interval_has_split_painted_masks = false; interval.sublayer_height = *std::min_element(pass_heights.begin(), pass_heights.end()); for (const double pass_height_nominal : pass_heights) { if (z_cursor >= interval.z_hi - EPSILON) break; const double pass_height = std::min(pass_height_nominal, interval.z_hi - z_cursor); const double z_next = std::min(interval.z_hi, z_cursor + pass_height); SubLayerPlan plan; plan.layer_id = layer_id; plan.pass_index = pass_idx; plan.split_interval = true; plan.z_lo = z_cursor; plan.z_hi = z_next; plan.print_z = z_next; plan.flow_height = pass_height; plan.painted_masks_by_extruder.assign(num_physical, ExPolygons()); ++split_passes_total; bool pass_has_painted_masks = false; for (size_t channel_idx = 0; channel_idx < segmentation[layer_id].size(); ++channel_idx) { const ExPolygons &state_masks = segmentation[layer_id][channel_idx]; if (state_masks.empty()) continue; const unsigned int state_id = unsigned(channel_idx + 1); if (!mixed_mgr.is_mixed(state_id, num_physical)) continue; ++forced_height_resolve_calls; const int mixed_idx = mixed_mgr.mixed_index_from_filament_id(state_id, num_physical); if (mixed_idx < 0 || size_t(mixed_idx) >= mixed_rows.size() || !mixed_rows[size_t(mixed_idx)].custom) ++forced_height_resolve_non_custom_calls; unsigned int target_extruder = 0; if (mixed_idx >= 0 && size_t(mixed_idx) < mixed_rows.size()) { const MixedFilament &mf = mixed_rows[size_t(mixed_idx)]; if (mf.component_a > 0 && mf.component_a <= num_physical && mf.component_b > 0 && mf.component_b <= num_physical) { // Enforce strict per-pass alternation inside split local-Z intervals. target_extruder = ((pass_idx % 2) == 0) ? mf.component_a : mf.component_b; ++strict_ab_assignments; } } if (target_extruder == 0) { target_extruder = mixed_mgr.resolve(state_id, num_physical, cadence_index, float(plan.print_z), float(plan.flow_height), true); } if (target_extruder == 0 || target_extruder > num_physical) { ++forced_height_resolve_invalid_target; continue; } append(plan.painted_masks_by_extruder[target_extruder - 1], state_masks); pass_has_painted_masks = true; } for (ExPolygons &masks : plan.painted_masks_by_extruder) if (masks.size() > 1) masks = union_ex(masks); if (pass_has_painted_masks) { ++split_passes_with_painted_masks; interval_has_split_painted_masks = true; } if (z_next >= interval.z_hi - EPSILON) plan.base_masks = base_masks; plans.emplace_back(std::move(plan)); ++interval.sublayer_count; ++total_generated_sublayer_cnt; ++pass_idx; ++cadence_index; z_cursor = z_next; } if (!interval_has_split_painted_masks) ++split_intervals_without_painted_masks; } else { if (interval.has_mixed_paint) ++non_split_mixed_intervals; SubLayerPlan plan; plan.layer_id = layer_id; plan.pass_index = 0; plan.split_interval = false; plan.z_lo = interval.z_lo; plan.z_hi = interval.z_hi; plan.print_z = interval.z_hi; plan.flow_height = interval.base_height; plan.base_masks = base_masks; plan.painted_masks_by_extruder.assign(num_physical, ExPolygons()); for (size_t channel_idx = 0; channel_idx < segmentation[layer_id].size(); ++channel_idx) { const ExPolygons &state_masks = segmentation[layer_id][channel_idx]; if (state_masks.empty()) continue; const unsigned int state_id = unsigned(channel_idx + 1); if (!mixed_mgr.is_mixed(state_id, num_physical)) continue; ++forced_height_resolve_calls; const int mixed_idx = mixed_mgr.mixed_index_from_filament_id(state_id, num_physical); if (mixed_idx < 0 || size_t(mixed_idx) >= mixed_rows.size() || !mixed_rows[size_t(mixed_idx)].custom) ++forced_height_resolve_non_custom_calls; const unsigned int target_extruder = mixed_mgr.resolve(state_id, num_physical, cadence_index, float(plan.print_z), float(plan.flow_height), true); if (target_extruder == 0 || target_extruder > num_physical) { ++forced_height_resolve_invalid_target; continue; } append(plan.painted_masks_by_extruder[target_extruder - 1], state_masks); } for (ExPolygons &masks : plan.painted_masks_by_extruder) if (masks.size() > 1) masks = union_ex(masks); plans.emplace_back(std::move(plan)); interval.sublayer_count = 1; ++total_generated_sublayer_cnt; ++cadence_index; } if (interval.has_mixed_paint) { BOOST_LOG_TRIVIAL(debug) << "Local-Z interval" << " object=" << object_name << " layer_id=" << layer_id << " base_height=" << interval.base_height << " split=" << split_interval << " mixed_states=" << mixed_state_count << " pass_count=" << pass_heights.size() << " pass_min_height=" << (pass_heights.empty() ? 0.0 : *std::min_element(pass_heights.begin(), pass_heights.end())) << " pass_max_height=" << (pass_heights.empty() ? 0.0 : *std::max_element(pass_heights.begin(), pass_heights.end())) << " mixed_mask_count=" << mixed_masks.size() << " base_mask_count=" << base_masks.size(); } intervals.emplace_back(std::move(interval)); } if (!intervals.empty() && !plans.empty()) { print_object.set_local_z_plan(std::move(intervals), std::move(plans)); export_local_z_plan_debug(print_object, mixed_lower, mixed_upper); BOOST_LOG_TRIVIAL(warning) << "Local-Z plan built" << " object=" << object_name << " mixed_intervals=" << mixed_intervals << " split_intervals=" << split_intervals << " non_split_mixed_intervals=" << non_split_mixed_intervals << " split_intervals_without_painted_masks=" << split_intervals_without_painted_masks << " sublayer_passes=" << total_generated_sublayer_cnt << " split_passes_total=" << split_passes_total << " split_passes_with_painted_masks=" << split_passes_with_painted_masks << " alternating_height_intervals=" << alternating_height_intervals << " strict_ab_assignments=" << strict_ab_assignments << " mixed_state_layers=" << total_mixed_state_layers << " forced_height_resolve_calls=" << forced_height_resolve_calls << " forced_height_resolve_non_custom_calls=" << forced_height_resolve_non_custom_calls << " forced_height_resolve_invalid_target=" << forced_height_resolve_invalid_target << " gradient_lock_valid=" << locked_gradient_valid << " gradient_lock_source_layer=" << locked_gradient_source_layer << " gradient_lock_mixed_idx=" << locked_gradient_mixed_idx << " gradient_lock_h_a=" << locked_gradient_h_a << " gradient_lock_h_b=" << locked_gradient_h_b << " gradient_lock_mismatch_layers=" << gradient_lock_mismatch_layers << " gradient_lock_unset_mixed_layers=" << gradient_lock_unset_mixed_layers << " mixed_lower=" << mixed_lower << " mixed_upper=" << mixed_upper << " preferred_a=" << preferred_a << " preferred_b=" << preferred_b; } else { BOOST_LOG_TRIVIAL(warning) << "Local-Z plan empty after build" << " object=" << object_name << " intervals=" << intervals.size() << " plans=" << plans.size() << " mixed_intervals=" << mixed_intervals; } } template static inline void apply_mm_segmentation(PrintObject &print_object, std::vector> segmentation, ThrowOnCancel throw_on_cancel) { assert(segmentation.size() == print_object.layer_count()); tbb::parallel_for( tbb::blocked_range(0, segmentation.size(), std::max(segmentation.size() / 128, size_t(1))), [&print_object, &segmentation, throw_on_cancel](const tbb::blocked_range &range) { const auto &layer_ranges = print_object.shared_regions()->layer_ranges; double z = print_object.get_layer(int(range.begin()))->slice_z; auto it_layer_range = layer_range_first(layer_ranges, z); // MM segmentation channels correspond to filament IDs (1-based), which now // include enabled mixed / virtual filaments. const size_t num_extruders = segmentation.empty() ? 0 : segmentation.front().size(); struct ByExtruder { ExPolygons expolygons; BoundingBox bbox; }; struct ByRegion { ExPolygons expolygons; bool needs_merge { false }; }; std::vector by_extruder; std::vector by_region; for (size_t layer_id = range.begin(); layer_id < range.end(); ++layer_id) { throw_on_cancel(); Layer &layer = *print_object.get_layer(int(layer_id)); it_layer_range = layer_range_next(layer_ranges, it_layer_range, layer.slice_z); const PrintObjectRegions::LayerRangeRegions &layer_range = *it_layer_range; // Gather per extruder expolygons. assert(segmentation[layer_id].size() == num_extruders); by_extruder.assign(num_extruders, ByExtruder()); by_region.assign(layer.region_count(), ByRegion()); bool layer_split = false; size_t missing_target_regions = 0; std::vector missing_target_extruders; for (size_t extruder_id = 0; extruder_id < num_extruders; ++ extruder_id) { ByExtruder ®ion = by_extruder[extruder_id]; append(region.expolygons, std::move(segmentation[layer_id][extruder_id])); if (! region.expolygons.empty()) { region.bbox = get_extents(region.expolygons); layer_split = true; } } if (!layer_split) continue; // Split LayerRegions by by_extruder regions. // layer_range.painted_regions are sorted by extruder ID and parent PrintObject region ID. auto it_painted_region_begin = layer_range.painted_regions.cbegin(); for (int parent_layer_region_idx = 0; parent_layer_region_idx < layer.region_count(); ++parent_layer_region_idx) { if (it_painted_region_begin == layer_range.painted_regions.cend()) continue; const LayerRegion &parent_layer_region = *layer.get_region(parent_layer_region_idx); const PrintRegion &parent_print_region = parent_layer_region.region(); assert(parent_print_region.print_object_region_id() == parent_layer_region_idx); if (parent_layer_region.slices.empty()) continue; // Find the first PaintedRegion, which overrides the parent PrintRegion. auto it_first_painted_region = std::find_if(it_painted_region_begin, layer_range.painted_regions.cend(), [&layer_range, &parent_print_region](const auto &painted_region) { return layer_range.volume_regions[painted_region.parent].region->print_object_region_id() == parent_print_region.print_object_region_id(); }); if (it_first_painted_region == layer_range.painted_regions.cend()) continue; // This LayerRegion isn't overrides by any PaintedRegion. assert(&parent_print_region == layer_range.volume_regions[it_first_painted_region->parent].region); // Update the beginning PaintedRegion iterator for the next iteration. it_painted_region_begin = it_first_painted_region; const BoundingBox parent_layer_region_bbox = get_extents(parent_layer_region.slices.surfaces); bool self_trimmed = false; int self_extruder_id = -1; // 1-based extruder ID if (const int cfg_wall = parent_print_region.config().wall_filament.value; cfg_wall >= 1 && cfg_wall <= int(by_extruder.size())) self_extruder_id = cfg_wall; std::vector assigned_extruder(by_extruder.size(), false); std::vector alias_to_self_extruders; for (int extruder_id = 1; extruder_id <= int(by_extruder.size()); ++extruder_id) { const ByExtruder &segmented = by_extruder[extruder_id - 1]; if (!segmented.bbox.defined || !parent_layer_region_bbox.overlap(segmented.bbox)) continue; // Find the matching target region for this parent and extruder ID. auto it_target_region = std::find_if(it_painted_region_begin, layer_range.painted_regions.cend(), [&layer_range, &parent_print_region, extruder_id](const auto &painted_region) { return layer_range.volume_regions[painted_region.parent].region == &parent_print_region && int(painted_region.extruder_id) == extruder_id; }); if (it_target_region == layer_range.painted_regions.cend()) { ++missing_target_regions; missing_target_extruders.emplace_back(extruder_id); continue; } // Update the beginning PaintedRegion iterator for the next iteration. it_painted_region_begin = it_target_region; // FIXME: Don't trim by self, it is not reliable. if (it_target_region->region == &parent_print_region) { if (self_extruder_id < 0) self_extruder_id = extruder_id; if (extruder_id != self_extruder_id) alias_to_self_extruders.emplace_back(extruder_id); continue; } assigned_extruder[size_t(extruder_id - 1)] = true; // Steal from this region. int target_region_id = it_target_region->region->print_object_region_id(); ExPolygons stolen = intersection_ex(parent_layer_region.slices.surfaces, segmented.expolygons); if (!stolen.empty()) { ByRegion &dst = by_region[target_region_id]; if (dst.expolygons.empty()) { dst.expolygons = std::move(stolen); } else { append(dst.expolygons, std::move(stolen)); dst.needs_merge = true; } } } if (!self_trimmed) { // Trim slices of this LayerRegion with all the MM regions. Polygons mine = to_polygons(parent_layer_region.slices.surfaces); for (size_t extruder_idx = 0; extruder_idx < by_extruder.size(); ++extruder_idx) { const ByExtruder &segmented = by_extruder[extruder_idx]; if (!assigned_extruder[extruder_idx]) continue; if (int(extruder_idx + 1) != self_extruder_id && segmented.bbox.defined && parent_layer_region_bbox.overlap(segmented.bbox)) { mine = diff(mine, segmented.expolygons); if (mine.empty()) break; } } // Filter out unprintable polygons produced by subtraction multi-material painted regions from layerm.region(). // ExPolygon returned from multi-material segmentation does not precisely match ExPolygons in layerm.region() // (because of preprocessing of the input regions in multi-material segmentation). Therefore, subtraction from // layerm.region() could produce a huge number of small unprintable regions for the model's base extruder. // This could, on some models, produce bulges with the model's base color (#7109). if (!mine.empty()) { mine = opening(union_ex(mine), scaled(5. * EPSILON), scaled(5. * EPSILON)); } if (!mine.empty()) { ByRegion &dst = by_region[parent_print_region.print_object_region_id()]; if (dst.expolygons.empty()) { dst.expolygons = union_ex(mine); } else { append(dst.expolygons, union_ex(mine)); dst.needs_merge = true; } } } if (!alias_to_self_extruders.empty()) { std::sort(alias_to_self_extruders.begin(), alias_to_self_extruders.end()); alias_to_self_extruders.erase(std::unique(alias_to_self_extruders.begin(), alias_to_self_extruders.end()), alias_to_self_extruders.end()); std::string alias_ids; for (size_t i = 0; i < alias_to_self_extruders.size(); ++i) { if (i > 0) alias_ids += ","; alias_ids += std::to_string(alias_to_self_extruders[i]); } BOOST_LOG_TRIVIAL(warning) << "MM segmentation alias-to-parent channels ignored" << " object=" << (print_object.model_object() ? print_object.model_object()->name : std::string("")) << " layer_id=" << layer_id << " parent_region_id=" << parent_print_region.print_object_region_id() << " self_extruder_id=" << self_extruder_id << " alias_extruders=[" << alias_ids << "]"; } } if (missing_target_regions > 0) { std::sort(missing_target_extruders.begin(), missing_target_extruders.end()); missing_target_extruders.erase(std::unique(missing_target_extruders.begin(), missing_target_extruders.end()), missing_target_extruders.end()); std::string missing_ids; for (size_t i = 0; i < missing_target_extruders.size(); ++i) { if (i > 0) missing_ids += ","; missing_ids += std::to_string(missing_target_extruders[i]); } BOOST_LOG_TRIVIAL(warning) << "MM segmentation missing painted target regions" << " object=" << (print_object.model_object() ? print_object.model_object()->name : std::string("")) << " layer_id=" << layer_id << " missing_targets=" << missing_target_regions << " missing_extruders=[" << missing_ids << "]" << " segmentation_channels=" << num_extruders << " painted_regions=" << layer_range.painted_regions.size(); } // Re-create Surfaces of LayerRegions. for (int region_id = 0; region_id < layer.region_count(); ++region_id) { ByRegion &src = by_region[region_id]; if (src.needs_merge) { // Multiple regions were merged into one. src.expolygons = closing_ex(src.expolygons, scaled(10. * EPSILON)); } layer.get_region(region_id)->slices.set(std::move(src.expolygons), stInternal); } } }); } template void apply_fuzzy_skin_segmentation(PrintObject &print_object, ThrowOnCancel throw_on_cancel) { // Returns fuzzy skin segmentation based on painting in the fuzzy skin painting gizmo. std::vector> segmentation = fuzzy_skin_segmentation_by_painting(print_object, throw_on_cancel); assert(segmentation.size() == print_object.layer_count()); struct ByRegion { ExPolygons expolygons; bool needs_merge { false }; }; tbb::parallel_for(tbb::blocked_range(0, segmentation.size(), std::max(segmentation.size() / 128, size_t(1))), [&print_object, &segmentation, throw_on_cancel](const tbb::blocked_range &range) { const auto &layer_ranges = print_object.shared_regions()->layer_ranges; auto it_layer_range = layer_range_first(layer_ranges, print_object.get_layer(int(range.begin()))->slice_z); for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { throw_on_cancel(); Layer &layer = *print_object.get_layer(int(layer_idx)); it_layer_range = layer_range_next(layer_ranges, it_layer_range, layer.slice_z); const PrintObjectRegions::LayerRangeRegions &layer_range = *it_layer_range; assert(segmentation[layer_idx].size() == 1); const ExPolygons &fuzzy_skin_segmentation = segmentation[layer_idx][0]; const BoundingBox fuzzy_skin_segmentation_bbox = get_extents(fuzzy_skin_segmentation); if (fuzzy_skin_segmentation.empty()) continue; // Split LayerRegions by painted fuzzy skin regions. // layer_range.fuzzy_skin_painted_regions are sorted by parent PrintObject region ID. std::vector by_region(layer.region_count()); auto it_fuzzy_skin_region_begin = layer_range.fuzzy_skin_painted_regions.cbegin(); for (int parent_layer_region_idx = 0; parent_layer_region_idx < layer.region_count(); ++parent_layer_region_idx) { if (it_fuzzy_skin_region_begin == layer_range.fuzzy_skin_painted_regions.cend()) continue; const LayerRegion &parent_layer_region = *layer.get_region(parent_layer_region_idx); const PrintRegion &parent_print_region = parent_layer_region.region(); assert(parent_print_region.print_object_region_id() == parent_layer_region_idx); if (parent_layer_region.slices.empty()) continue; // Find the first FuzzySkinPaintedRegion, which overrides the parent PrintRegion. auto it_fuzzy_skin_region = std::find_if(it_fuzzy_skin_region_begin, layer_range.fuzzy_skin_painted_regions.cend(), [&layer_range, &parent_print_region](const auto &fuzzy_skin_region) { return fuzzy_skin_region.parent_print_object_region_id(layer_range) == parent_print_region.print_object_region_id(); }); if (it_fuzzy_skin_region == layer_range.fuzzy_skin_painted_regions.cend()) continue; // This LayerRegion isn't overrides by any FuzzySkinPaintedRegion. assert(it_fuzzy_skin_region->parent_print_object_region(layer_range) == &parent_print_region); // Update the beginning FuzzySkinPaintedRegion iterator for the next iteration. it_fuzzy_skin_region_begin = std::next(it_fuzzy_skin_region); const BoundingBox parent_layer_region_bbox = get_extents(parent_layer_region.slices.surfaces); Polygons layer_region_remaining_polygons = to_polygons(parent_layer_region.slices.surfaces); // Don't trim by self, it is not reliable. if (parent_layer_region_bbox.overlap(fuzzy_skin_segmentation_bbox) && it_fuzzy_skin_region->region != &parent_print_region) { // Steal from this region. const int target_region_id = it_fuzzy_skin_region->region->print_object_region_id(); ExPolygons stolen = intersection_ex(parent_layer_region.slices.surfaces, fuzzy_skin_segmentation); if (!stolen.empty()) { ByRegion &dst = by_region[target_region_id]; if (dst.expolygons.empty()) { dst.expolygons = std::move(stolen); } else { append(dst.expolygons, std::move(stolen)); dst.needs_merge = true; } } // Trim slices of this LayerRegion by the fuzzy skin region. layer_region_remaining_polygons = diff(layer_region_remaining_polygons, fuzzy_skin_segmentation); // Filter out unprintable polygons. Detailed explanation is inside apply_mm_segmentation. if (!layer_region_remaining_polygons.empty()) { layer_region_remaining_polygons = opening(union_ex(layer_region_remaining_polygons), scaled(5. * EPSILON), scaled(5. * EPSILON)); } } if (!layer_region_remaining_polygons.empty()) { ByRegion &dst = by_region[parent_print_region.print_object_region_id()]; if (dst.expolygons.empty()) { dst.expolygons = union_ex(layer_region_remaining_polygons); } else { append(dst.expolygons, union_ex(layer_region_remaining_polygons)); dst.needs_merge = true; } } } // Re-create Surfaces of LayerRegions. for (int region_id = 0; region_id < layer.region_count(); ++region_id) { ByRegion &src = by_region[region_id]; if (src.needs_merge) { // Multiple regions were merged into one. src.expolygons = closing_ex(src.expolygons, scaled(10. * EPSILON)); } layer.get_region(region_id)->slices.set(std::move(src.expolygons), stInternal); } } }); // end of parallel_for } // 1) Decides Z positions of the layers, // 2) Initializes layers and their regions // 3) Slices the object meshes // 4) Slices the modifier meshes and reclassifies the slices of the object meshes by the slices of the modifier meshes // 5) Applies size compensation (offsets the slices in XY plane) // 6) Replaces bad slices by the slices reconstructed from the upper/lower layer // Resulting expolygons of layer regions are marked as Internal. // // this should be idempotent void PrintObject::slice_volumes() { BOOST_LOG_TRIVIAL(info) << "Slicing volumes..." << log_memory_info(); const Print *print = this->print(); const auto throw_on_cancel_callback = std::function([print](){ print->throw_if_canceled(); }); // Clear old LayerRegions, allocate for new PrintRegions. for (Layer* layer : m_layers) { //BBS: should delete all LayerRegionPtr to avoid memory leak while (!layer->m_regions.empty()) { if (layer->m_regions.back()) delete layer->m_regions.back(); layer->m_regions.pop_back(); } layer->m_regions.reserve(m_shared_regions->all_regions.size()); for (const std::unique_ptr &pr : m_shared_regions->all_regions) layer->m_regions.emplace_back(new LayerRegion(layer, pr.get())); } std::vector slice_zs = zs_from_layers(m_layers); std::vector objSliceByVolume; if (!slice_zs.empty()) { objSliceByVolume = slice_volumes_inner( print->config(), this->config(), this->trafo_centered(), this->model_object()->volumes, m_shared_regions->layer_ranges, slice_zs, throw_on_cancel_callback); } //BBS: "model_part" volumes are grouded according to their connections //const auto scaled_resolution = scaled(print->config().resolution.value); //firstLayerObjSliceByVolume = findPartVolumes(objSliceByVolume, this->model_object()->volumes); //groupingVolumes(objSliceByVolumeParts, firstLayerObjSliceByGroups, scaled_resolution); //applyNegtiveVolumes(this->model_object()->volumes, objSliceByVolume, firstLayerObjSliceByGroups, scaled_resolution); firstLayerObjSliceByVolume = objSliceByVolume; std::vector> region_slices = slices_to_regions(print->config(), *this, this->model_object()->volumes, *m_shared_regions, slice_zs, std::move(objSliceByVolume), PrintObject::clip_multipart_objects, throw_on_cancel_callback); for (size_t region_id = 0; region_id < region_slices.size(); ++ region_id) { std::vector &by_layer = region_slices[region_id]; for (size_t layer_id = 0; layer_id < by_layer.size(); ++ layer_id) m_layers[layer_id]->regions()[region_id]->slices.append(std::move(by_layer[layer_id]), stInternal); } region_slices.clear(); BOOST_LOG_TRIVIAL(debug) << "Slicing volumes - removing top empty layers"; while (! m_layers.empty()) { const Layer *layer = m_layers.back(); if (! layer->empty()) break; delete layer; m_layers.pop_back(); } if (! m_layers.empty()) m_layers.back()->upper_layer = nullptr; m_print->throw_if_canceled(); this->apply_conical_overhang(); // Is any ModelVolume multi-material painted? if (const auto& volumes = this->model_object()->volumes; m_print->config().filament_diameter.size() > 1 && // BBS std::find_if(volumes.begin(), volumes.end(), [](const ModelVolume* v) { return !v->mmu_segmentation_facets.empty(); }) != volumes.end()) { // If XY Size compensation is also enabled, notify the user that XY Size compensation // would not be used because the object is multi-material painted. if (m_config.xy_hole_compensation.value != 0.f || m_config.xy_contour_compensation.value != 0.f) { this->active_step_add_warning( PrintStateBase::WarningLevel::CRITICAL, L("An object's XY size compensation will not be used because it is also color-painted.\nXY Size " "compensation cannot be combined with color-painting.")); BOOST_LOG_TRIVIAL(info) << "xy compensation will not work for object " << this->model_object()->name << " for multi filament."; } BOOST_LOG_TRIVIAL(debug) << "Slicing volumes - MMU segmentation"; std::vector> mm_segmentation = multi_material_segmentation_by_painting(*this, [print]() { print->throw_if_canceled(); }); // Same-layer pointillisme is applied in G-code path domain (segment-level assignment), // not by XY state mask splitting, to avoid boolean-induced voids. BOOST_LOG_TRIVIAL(info) << "Same-layer pointillisme uses path-domain G-code segmentation"; build_local_z_plan(*this, mm_segmentation, [print]() { print->throw_if_canceled(); }); apply_mm_segmentation(*this, std::move(mm_segmentation), [print]() { print->throw_if_canceled(); }); } // Is any ModelVolume fuzzy skin painted? if (this->model_object()->is_fuzzy_skin_painted()) { // If XY Size compensation is also enabled, notify the user that XY Size compensation // would not be used because the object has custom fuzzy skin painted. if (m_config.xy_hole_compensation.value != 0.f || m_config.xy_contour_compensation.value != 0.f) { this->active_step_add_warning( PrintStateBase::WarningLevel::CRITICAL, _u8L("An object has enabled XY Size compensation which will not be used because it is also fuzzy skin painted.\nXY Size " "compensation cannot be combined with fuzzy skin painting.") + "\n" + (_u8L("Object name")) + ": " + this->model_object()->name); } BOOST_LOG_TRIVIAL(debug) << "Slicing volumes - Fuzzy skin segmentation"; apply_fuzzy_skin_segmentation(*this, [print]() { print->throw_if_canceled(); }); } InterlockingGenerator::generate_interlocking_structure(this); m_print->throw_if_canceled(); BOOST_LOG_TRIVIAL(debug) << "Slicing volumes - make_slices in parallel - begin"; { // Compensation value, scaled. Only applying the negative scaling here, as the positive scaling has already been applied during slicing. const size_t num_extruders = print->config().filament_diameter.size(); const auto xy_hole_scaled = (num_extruders > 1 && this->is_mm_painted()) ? scaled(0.f) : scaled(m_config.xy_hole_compensation.value); const auto xy_contour_scaled = (num_extruders > 1 && this->is_mm_painted()) ? scaled(0.f) : scaled(m_config.xy_contour_compensation.value); const float elephant_foot_compensation_scaled = (m_config.raft_layers == 0) ? // Only enable Elephant foot compensation if printing directly on the print bed. float(scale_(m_config.elefant_foot_compensation.value)) : 0.f; // Uncompensated slices for the layers in case the Elephant foot compensation is applied. std::vector lslices_elfoot_uncompensated; lslices_elfoot_uncompensated.resize(elephant_foot_compensation_scaled > 0 ? std::min(m_config.elefant_foot_compensation_layers.value, (int)m_layers.size()) : 0); //BBS: this part has been changed a lot to support seperated contour and hole size compensation tbb::parallel_for( tbb::blocked_range(0, m_layers.size()), [this, xy_hole_scaled, xy_contour_scaled, elephant_foot_compensation_scaled, &lslices_elfoot_uncompensated](const tbb::blocked_range& range) { for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id) { m_print->throw_if_canceled(); Layer *layer = m_layers[layer_id]; // Apply size compensation and perform clipping of multi-part objects. float elfoot = elephant_foot_compensation_scaled > 0 && layer_id < m_config.elefant_foot_compensation_layers.value ? elephant_foot_compensation_scaled - (elephant_foot_compensation_scaled / m_config.elefant_foot_compensation_layers.value) * layer_id : 0.f; if (layer->m_regions.size() == 1) { // Optimized version for a single region layer. // Single region, growing or shrinking. LayerRegion *layerm = layer->m_regions.front(); if (elfoot > 0) { // Apply the elephant foot compensation and store the original layer slices without the Elephant foot compensation applied. ExPolygons expolygons_to_compensate = to_expolygons(std::move(layerm->slices.surfaces)); if (xy_contour_scaled > 0 || xy_hole_scaled > 0) { expolygons_to_compensate = _shrink_contour_holes(std::max(0.f, xy_contour_scaled), std::max(0.f, xy_hole_scaled), expolygons_to_compensate); } if (xy_contour_scaled < 0 || xy_hole_scaled < 0) { expolygons_to_compensate = _shrink_contour_holes(std::min(0.f, xy_contour_scaled), std::min(0.f, xy_hole_scaled), expolygons_to_compensate); } lslices_elfoot_uncompensated[layer_id] = expolygons_to_compensate; layerm->slices.set( union_ex( Slic3r::elephant_foot_compensation(expolygons_to_compensate, layerm->flow(frExternalPerimeter), unscale(elfoot))), stInternal); } else { // Apply the XY contour and hole size compensation. if (xy_contour_scaled != 0.0f || xy_hole_scaled != 0.0f) { ExPolygons expolygons = to_expolygons(std::move(layerm->slices.surfaces)); if (xy_contour_scaled > 0 || xy_hole_scaled > 0) { expolygons = _shrink_contour_holes(std::max(0.f, xy_contour_scaled), std::max(0.f, xy_hole_scaled), expolygons); } if (xy_contour_scaled < 0 || xy_hole_scaled < 0) { expolygons = _shrink_contour_holes(std::min(0.f, xy_contour_scaled), std::min(0.f, xy_hole_scaled), expolygons); } layerm->slices.set(std::move(expolygons), stInternal); } } } else { float max_growth = std::max(xy_hole_scaled, xy_contour_scaled); float min_growth = std::min(xy_hole_scaled, xy_contour_scaled); ExPolygons merged_poly_for_holes_growing; if (max_growth > 0) { //BBS: merge polygons because region can cut "holes". //Then, cut them to give them again later to their region merged_poly_for_holes_growing = layer->merged(float(SCALED_EPSILON)); merged_poly_for_holes_growing = _shrink_contour_holes(std::max(0.f, xy_contour_scaled), std::max(0.f, xy_hole_scaled), union_ex(merged_poly_for_holes_growing)); // BBS: clipping regions, priority is given to the first regions. Polygons processed; for (size_t region_id = 0; region_id < layer->regions().size(); ++region_id) { ExPolygons slices = to_expolygons(std::move(layer->m_regions[region_id]->slices.surfaces)); if (max_growth > 0.f) { slices = intersection_ex(offset_ex(slices, max_growth), merged_poly_for_holes_growing); } //BBS: Trim by the slices of already processed regions. if (region_id > 0) slices = diff_ex(to_polygons(std::move(slices)), processed); if (region_id + 1 < layer->regions().size()) // Collect the already processed regions to trim the to be processed regions. polygons_append(processed, slices); layer->m_regions[region_id]->slices.set(std::move(slices), stInternal); } } if (min_growth < 0.f || elfoot > 0.f) { // Apply the negative XY compensation. (the ones that is <0) ExPolygons trimming; static const float eps = float(scale_(m_config.slice_closing_radius.value) * 1.5); if (elfoot > 0.f) { ExPolygons expolygons_to_compensate = offset_ex(layer->merged(eps), -eps); lslices_elfoot_uncompensated[layer_id] = expolygons_to_compensate; trimming = Slic3r::elephant_foot_compensation(expolygons_to_compensate, layer->m_regions.front()->flow(frExternalPerimeter), unscale(elfoot)); } else { trimming = layer->merged(float(SCALED_EPSILON)); } if (min_growth < 0.0f) trimming = _shrink_contour_holes(std::min(0.f, xy_contour_scaled), std::min(0.f, xy_hole_scaled), trimming); //BBS: trim surfaces for (size_t region_id = 0; region_id < layer->regions().size(); ++region_id) { // BBS: split trimming result by region ExPolygons contour_exp = to_expolygons(std::move(layer->regions()[region_id]->slices.surfaces)); layer->regions()[region_id]->slices.set(intersection_ex(contour_exp, to_polygons(trimming)), stInternal); } } } // Merge all regions' slices to get islands, chain them by a shortest path. layer->make_slices(); } }); if (elephant_foot_compensation_scaled > 0.f && ! m_layers.empty()) { // The Elephant foot has been compensated, therefore the elefant_foot_compensation_layers layer's lslices are shrank with the Elephant foot compensation value. // Store the uncompensated value there. assert(m_layers.front()->id() == 0); //BBS: sort the lslices_elfoot_uncompensated according to shortest path before saving //Otherwise the travel of the layer layer would be mess. for (int i = 0; i < lslices_elfoot_uncompensated.size(); i++) { ExPolygons &expolygons_uncompensated = lslices_elfoot_uncompensated[i]; Points ordering_points; ordering_points.reserve(expolygons_uncompensated.size()); for (const ExPolygon &ex : expolygons_uncompensated) ordering_points.push_back(ex.contour.first_point()); std::vector order = chain_points(ordering_points); ExPolygons lslices_sorted; lslices_sorted.reserve(expolygons_uncompensated.size()); for (size_t i : order) lslices_sorted.emplace_back(std::move(expolygons_uncompensated[i])); m_layers[i]->lslices = std::move(lslices_sorted); } } } m_print->throw_if_canceled(); BOOST_LOG_TRIVIAL(debug) << "Slicing volumes - make_slices in parallel - end"; } void PrintObject::apply_conical_overhang() { BOOST_LOG_TRIVIAL(info) << "Make overhang printable..."; if (m_layers.empty()) { return; } const double conical_overhang_angle = this->config().make_overhang_printable_angle; if (conical_overhang_angle == 90.0) { return; } const double angle_radians = conical_overhang_angle * M_PI / 180.; const double max_hole_area = this->config().make_overhang_printable_hole_size; // in MM^2 const double tan_angle = tan(angle_radians); // the XY-component of the angle BOOST_LOG_TRIVIAL(info) << "angle " << angle_radians << " maxHoleArea " << max_hole_area << " tan_angle " << tan_angle; const coordf_t layer_thickness = m_config.layer_height.value; const coordf_t max_dist_from_lower_layer = tan_angle * layer_thickness; // max dist which can be bridged, in MM BOOST_LOG_TRIVIAL(info) << "layer_thickness " << layer_thickness << " max_dist_from_lower_layer " << max_dist_from_lower_layer; // Pre-scale config const coordf_t scaled_max_dist_from_lower_layer = -float(scale_(max_dist_from_lower_layer)); const coordf_t scaled_max_hole_area = float(scale_(scale_(max_hole_area))); for (auto i = m_layers.rbegin() + 1; i != m_layers.rend(); ++i) { m_print->throw_if_canceled(); Layer *layer = *i; Layer *upper_layer = layer->upper_layer; if (upper_layer->empty()) { continue; } // Skip if entire layer has this disabled if (std::all_of(layer->m_regions.begin(), layer->m_regions.end(), [](const LayerRegion *r) { return r->slices.empty() || !r->region().config().make_overhang_printable; })) { continue; } //layer->export_region_slices_to_svg_debug("layer_before_conical_overhang"); //upper_layer->export_region_slices_to_svg_debug("upper_layer_before_conical_overhang"); // Merge the upper layer because we want to offset the entire layer uniformly, otherwise // the model could break at the region boundary. auto upper_poly = upper_layer->merged(float(SCALED_EPSILON)); upper_poly = union_ex(upper_poly); // Merge layer for the same reason auto current_poly = layer->merged(float(SCALED_EPSILON)); current_poly = union_ex(current_poly); // Avoid closing up of recessed holes in the base of a model. // Detects when a hole is completely covered by the layer above and removes the hole from the layer above before // adding it in. // This should have no effect any time a hole in a layer interacts with any polygon in the layer above if (scaled_max_hole_area > 0.0) { // Now go through all the holes in the current layer and check if they intersect anything in the layer above // If not, then they're the top of a hole and should be cut from the layer above before the union for (auto layer_polygon : current_poly) { for (auto hole : layer_polygon.holes) { if (std::abs(hole.area()) < scaled_max_hole_area) { ExPolygon hole_poly(hole); auto hole_with_above = intersection_ex(upper_poly, hole_poly); if (!hole_with_above.empty()) { // The hole had some intersection with the above layer, check if it's a complete overlap auto hole_difference = xor_ex(hole_with_above, hole_poly); if (hole_difference.empty()) { // The layer above completely cover it, remove it from the layer above upper_poly = diff_ex(upper_poly, hole_poly); } } } } } } // Now offset the upper layer to be added into current layer upper_poly = offset_ex(upper_poly, scaled_max_dist_from_lower_layer); for (size_t region_id = 0; region_id < this->num_printing_regions(); ++region_id) { // export_to_svg(debug_out_path("Surface-obj-%d-layer-%d-region-%d.svg", id().id, layer->id(), region_id).c_str(), // layer->m_regions[region_id]->slices.surfaces); // Disable on given region if (!upper_layer->m_regions[region_id]->region().config().make_overhang_printable) { continue; } // Calculate the scaled upper poly that belongs to current region auto p = union_ex(intersection_ex(upper_layer->m_regions[region_id]->slices.surfaces, upper_poly)); // Remove all islands that have already been fully covered by current layer p.erase(std::remove_if(p.begin(), p.end(), [¤t_poly](const ExPolygon& ex) { return diff_ex(ex, current_poly).empty(); }), p.end()); // And now union it with current region ExPolygons layer_polygons = to_expolygons(layer->m_regions[region_id]->slices.surfaces); layer->m_regions[region_id]->slices.set(union_ex(layer_polygons, p), stInternal); // Then remove it from all other regions, to avoid overlapping regions for (size_t other_region = 0; other_region < this->num_printing_regions(); ++other_region) { if (other_region == region_id) { continue; } ExPolygons s = to_expolygons(layer->m_regions[other_region]->slices.surfaces); layer->m_regions[other_region]->slices.set(diff_ex(s, p, ApplySafetyOffset::Yes), stInternal); } } //layer->export_region_slices_to_svg_debug("layer_after_conical_overhang"); } } //BBS: this function is used to offset contour and holes of expolygons seperately by different value ExPolygons PrintObject::_shrink_contour_holes(double contour_delta, double hole_delta, const ExPolygons& polys) const { ExPolygons new_ex_polys; for (const ExPolygon& ex_poly : polys) { Polygons contours; Polygons holes; //BBS: modify hole for (const Polygon& hole : ex_poly.holes) { if (hole_delta != 0) { for (Polygon& newHole : offset(hole, -hole_delta)) { newHole.make_counter_clockwise(); holes.emplace_back(std::move(newHole)); } } else { holes.push_back(hole); holes.back().make_counter_clockwise(); } } //BBS: modify contour if (contour_delta != 0) { Polygons new_contours = offset(ex_poly.contour, contour_delta); if (new_contours.size() == 0) continue; contours.insert(contours.end(), std::make_move_iterator(new_contours.begin()), std::make_move_iterator(new_contours.end())); } else { contours.push_back(ex_poly.contour); } ExPolygons temp = diff_ex(union_(contours), union_(holes)); new_ex_polys.insert(new_ex_polys.end(), std::make_move_iterator(temp.begin()), std::make_move_iterator(temp.end())); } return union_ex(new_ex_polys); } std::vector PrintObject::slice_support_volumes(const ModelVolumeType model_volume_type) const { auto it_volume = this->model_object()->volumes.begin(); auto it_volume_end = this->model_object()->volumes.end(); for (; it_volume != it_volume_end && (*it_volume)->type() != model_volume_type; ++ it_volume) ; std::vector slices; if (it_volume != it_volume_end) { // Found at least a single support volume of model_volume_type. std::vector zs = zs_from_layers(this->layers()); std::vector merge_layers; bool merge = false; const Print *print = this->print(); auto throw_on_cancel_callback = std::function([print](){ print->throw_if_canceled(); }); MeshSlicingParamsEx params; params.trafo = this->trafo_centered(); for (; it_volume != it_volume_end; ++ it_volume) if ((*it_volume)->type() == model_volume_type) { std::vector slices2 = slice_volume(*(*it_volume), zs, params, throw_on_cancel_callback); if (slices.empty()) { slices.reserve(slices2.size()); for (ExPolygons &src : slices2) slices.emplace_back(to_polygons(std::move(src))); } else if (!slices2.empty()) { if (merge_layers.empty()) merge_layers.assign(zs.size(), false); for (size_t i = 0; i < zs.size(); ++ i) { if (slices[i].empty()) slices[i] = to_polygons(std::move(slices2[i])); else if (! slices2[i].empty()) { append(slices[i], to_polygons(std::move(slices2[i]))); merge_layers[i] = true; merge = true; } } } } if (merge) { std::vector to_merge; to_merge.reserve(zs.size()); for (size_t i = 0; i < zs.size(); ++ i) if (merge_layers[i]) to_merge.emplace_back(&slices[i]); tbb::parallel_for( tbb::blocked_range(0, to_merge.size()), [&to_merge](const tbb::blocked_range &range) { for (size_t i = range.begin(); i < range.end(); ++ i) *to_merge[i] = union_(*to_merge[i]); }); } } return slices; } } // namespace Slic3r