diff --git a/sandboxes/its_neighbor_index/ItsNeighborIndex.cpp b/sandboxes/its_neighbor_index/ItsNeighborIndex.cpp index 92c0f57c3..84ada72ad 100644 --- a/sandboxes/its_neighbor_index/ItsNeighborIndex.cpp +++ b/sandboxes/its_neighbor_index/ItsNeighborIndex.cpp @@ -602,12 +602,12 @@ FaceNeighborIndex its_create_neighbors_index_8(const indexed_triangle_set &its) std::vector its_create_neighbors_index_9(const indexed_triangle_set &its) { - return create_neighbors_index(ex_seq, its); + return create_face_neighbors_index(ex_seq, its); } std::vector its_create_neighbors_index_10(const indexed_triangle_set &its) { - return create_neighbors_index(ex_tbb, its); + return create_face_neighbors_index(ex_tbb, its); } } // namespace Slic3r diff --git a/src/libslic3r/AppConfig.hpp b/src/libslic3r/AppConfig.hpp index c1bc0837c..540591f94 100644 --- a/src/libslic3r/AppConfig.hpp +++ b/src/libslic3r/AppConfig.hpp @@ -63,12 +63,14 @@ public: { std::string value; this->get("", key, value); return value; } void set(const std::string §ion, const std::string &key, const std::string &value) { -#ifndef _NDEBUG - std::string key_trimmed = key; - boost::trim_all(key_trimmed); - assert(key_trimmed == key); - assert(! key_trimmed.empty()); -#endif // _NDEBUG +#ifndef NDEBUG + { + std::string key_trimmed = key; + boost::trim_all(key_trimmed); + assert(key_trimmed == key); + assert(! key_trimmed.empty()); + } +#endif // NDEBUG std::string &old = m_storage[section][key]; if (old != value) { old = value; diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp index 44b1fcff1..06bbfb7ef 100644 --- a/src/libslic3r/GCode/SeamPlacer.cpp +++ b/src/libslic3r/GCode/SeamPlacer.cpp @@ -201,21 +201,24 @@ void SeamPlacer::init(const Print& print) std::vector temp_enf; std::vector temp_blk; + std::vector temp_polygons; for (const PrintObject* po : print.objects()) { - temp_enf.clear(); - temp_blk.clear(); - po->project_and_append_custom_facets(true, EnforcerBlockerType::ENFORCER, temp_enf); - po->project_and_append_custom_facets(true, EnforcerBlockerType::BLOCKER, temp_blk); - // Offset the triangles out slightly. - for (auto* custom_per_object : {&temp_enf, &temp_blk}) { + auto merge_and_offset = [po, &temp_polygons, max_nozzle_dmr](EnforcerBlockerType type, std::vector& out) { + // Offset the triangles out slightly. + temp_polygons.clear(); + po->project_and_append_custom_facets(true, type, temp_polygons); + out.clear(); + out.reserve(temp_polygons.size()); float offset = max_nozzle_dmr + po->config().elefant_foot_compensation; - for (ExPolygons& explgs : *custom_per_object) { - explgs = Slic3r::offset_ex(explgs, scale_(offset)); + for (const Polygons &src : temp_polygons) { + out.emplace_back(Slic3r::offset_ex(src, scale_(offset))); offset = max_nozzle_dmr; } - } + }; + merge_and_offset(EnforcerBlockerType::BLOCKER, temp_blk); + merge_and_offset(EnforcerBlockerType::ENFORCER, temp_enf); // FIXME: Offsetting should be done somehow cheaper, but following does not work // for (auto* custom_per_object : {&temp_enf, &temp_blk}) { diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index a7c0e0d7b..6cabdeb40 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -201,6 +201,16 @@ protected: virtual ~SupportLayer() = default; }; +template +inline std::vector zs_from_layers(const LayerContainer &layers) +{ + std::vector zs; + zs.reserve(layers.size()); + for (const Layer *l : layers) + zs.emplace_back((float)l->slice_z); + return zs; +} + } #endif diff --git a/src/libslic3r/MeshSplitImpl.hpp b/src/libslic3r/MeshSplitImpl.hpp index 322fc82f8..7946411aa 100644 --- a/src/libslic3r/MeshSplitImpl.hpp +++ b/src/libslic3r/MeshSplitImpl.hpp @@ -8,7 +8,7 @@ namespace Slic3r { template -std::vector create_neighbors_index(ExPolicy &&ex, const indexed_triangle_set &its); +std::vector create_face_neighbors_index(ExPolicy &&ex, const indexed_triangle_set &its); namespace meshsplit_detail { @@ -24,7 +24,7 @@ template<> struct ItsWithNeighborsIndex_ { static const indexed_triangle_set &get_its(const indexed_triangle_set &its) noexcept { return its; } static Index get_index(const indexed_triangle_set &its) noexcept { - return create_neighbors_index(ex_tbb, its); + return create_face_neighbors_index(ex_tbb, its); } }; @@ -162,28 +162,14 @@ template bool its_is_splittable(const Its &m) return !faces.empty(); } -inline int get_vertex_index(size_t vertex_index, const stl_triangle_vertex_indices &triangle_indices) { - if (int(vertex_index) == triangle_indices[0]) return 0; - if (int(vertex_index) == triangle_indices[1]) return 1; - if (int(vertex_index) == triangle_indices[2]) return 2; - return -1; -} - -inline Vec2crd get_edge_indices(int edge_index, const stl_triangle_vertex_indices &triangle_indices) -{ - int next_edge_index = (edge_index == 2) ? 0 : edge_index + 1; - int vi0 = triangle_indices[edge_index]; - int vi1 = triangle_indices[next_edge_index]; - return Vec2crd(vi0, vi1); -} - template -std::vector create_neighbors_index(ExPolicy &&ex, const indexed_triangle_set &its) +std::vector create_face_neighbors_index(ExPolicy &&ex, const indexed_triangle_set &its) { const std::vector &indices = its.indices; - size_t vertices_size = its.vertices.size(); - if (indices.empty() || vertices_size == 0) return {}; + if (indices.empty()) return {}; + + assert(! its.vertices.empty()); auto vertex_triangles = VertexFaceIndex{its}; static constexpr int no_value = -1; @@ -192,27 +178,28 @@ std::vector create_neighbors_index(ExPolicy &&ex, const indexed_triangle_ //for (const stl_triangle_vertex_indices& triangle_indices : indices) { execution::for_each(ex, size_t(0), indices.size(), - [&neighbors, &indices, &vertex_triangles] (size_t index) + [&neighbors, &indices, &vertex_triangles] (size_t face_idx) { - Vec3i& neighbor = neighbors[index]; - const stl_triangle_vertex_indices & triangle_indices = indices[index]; + Vec3i& neighbor = neighbors[face_idx]; + const stl_triangle_vertex_indices & triangle_indices = indices[face_idx]; for (int edge_index = 0; edge_index < 3; ++edge_index) { // check if done int& neighbor_edge = neighbor[edge_index]; - if (neighbor_edge != no_value) continue; - Vec2crd edge_indices = get_edge_indices(edge_index, triangle_indices); + if (neighbor_edge != no_value) + // This edge already has a neighbor assigned. + continue; + Vec2i edge_indices = its_triangle_edge(triangle_indices, edge_index); // IMPROVE: use same vector for 2 sides of triangle - const auto &faces_range = vertex_triangles[edge_indices[0]]; - for (const size_t &face : faces_range) { - if (face <= index) continue; - const stl_triangle_vertex_indices &face_indices = indices[face]; - int vertex_index = get_vertex_index(edge_indices[1], face_indices); + for (const size_t other_face : vertex_triangles[edge_indices[0]]) { + if (other_face <= face_idx) continue; + const stl_triangle_vertex_indices &face_indices = indices[other_face]; + int vertex_index = its_triangle_vertex_index(face_indices, edge_indices[1]); // NOT Contain second vertex? if (vertex_index < 0) continue; - // Has NOT oposit direction? + // Has NOT oposite direction? if (edge_indices[0] != face_indices[(vertex_index + 1) % 3]) continue; - neighbor_edge = face; - neighbors[face][vertex_index] = index; + neighbor_edge = other_face; + neighbors[other_face][vertex_index] = face_idx; break; } } diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 84566f4b1..33e01e03c 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1947,8 +1947,19 @@ indexed_triangle_set FacetsAnnotation::get_facets(const ModelVolume& mv, Enforce { TriangleSelector selector(mv.mesh()); selector.deserialize(m_data); - indexed_triangle_set out = selector.get_facets(type); - return out; + return selector.get_facets(type); +} + +indexed_triangle_set FacetsAnnotation::get_facets_strict(const ModelVolume& mv, EnforcerBlockerType type) const +{ + TriangleSelector selector(mv.mesh()); + selector.deserialize(m_data); + return selector.get_facets_strict(type); +} + +bool FacetsAnnotation::has_facets(const ModelVolume& mv, EnforcerBlockerType type) const +{ + return TriangleSelector::has_facets(m_data, type); } bool FacetsAnnotation::set(const TriangleSelector& selector) diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index c6a54d5c6..615a075d0 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -494,10 +494,26 @@ private: }; enum class EnforcerBlockerType : int8_t { - // Maximum is 3. The value is serialized in TriangleSelector into 2 bits! + // Maximum is 3. The value is serialized in TriangleSelector into 2 bits. NONE = 0, ENFORCER = 1, - BLOCKER = 2 + BLOCKER = 2, + // Maximum is 15. The value is serialized in TriangleSelector into 6 bits using a 2 bit prefix code. + Extruder1 = ENFORCER, + Extruder2 = BLOCKER, + Extruder3, + Extruder4, + Extruder5, + Extruder6, + Extruder7, + Extruder8, + Extruder9, + Extruder10, + Extruder11, + Extruder12, + Extruder13, + Extruder14, + Extruder15, }; enum class ConversionType : int { @@ -515,6 +531,8 @@ public: const std::pair>, std::vector>& get_data() const throw() { return m_data; } bool set(const TriangleSelector& selector); indexed_triangle_set get_facets(const ModelVolume& mv, EnforcerBlockerType type) const; + indexed_triangle_set get_facets_strict(const ModelVolume& mv, EnforcerBlockerType type) const; + bool has_facets(const ModelVolume& mv, EnforcerBlockerType type) const; bool empty() const { return m_data.first.empty(); } void clear(); diff --git a/src/libslic3r/MultiMaterialSegmentation.cpp b/src/libslic3r/MultiMaterialSegmentation.cpp index 2fbdf85bc..6f27468cc 100644 --- a/src/libslic3r/MultiMaterialSegmentation.cpp +++ b/src/libslic3r/MultiMaterialSegmentation.cpp @@ -1151,67 +1151,16 @@ static void cut_segmented_layers(const std::vector BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - cutting segmented layers in parallel - end"; } +// #define MMU_SEGMENTATION_DEBUG_TOP_BOTTOM + // Returns MMU segmentation of top and bottom layers based on painting in MMU segmentation gizmo static inline std::vector> mmu_segmentation_top_and_bottom_layers(const PrintObject &print_object, const std::vector &input_expolygons, const std::function &throw_on_cancel_callback) { const size_t num_extruders = print_object.print()->config().nozzle_diameter.size() + 1; + const size_t num_layers = input_expolygons.size(); const ConstLayerPtrsAdaptor layers = print_object.layers(); - std::vector> triangles_by_color(num_extruders); - triangles_by_color.assign(num_extruders, std::vector(layers.size())); - - BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - top and bottom layers - projection of painted triangles - begin"; - for (const ModelVolume *mv : print_object.model_object()->volumes) { - for (size_t extruder_idx = 0; extruder_idx < num_extruders; ++extruder_idx) { - throw_on_cancel_callback(); - const indexed_triangle_set custom_facets = mv->mmu_segmentation_facets.get_facets(*mv, EnforcerBlockerType(extruder_idx)); - if (!mv->is_model_part() || custom_facets.indices.empty()) - continue; - - const Transform3f tr = print_object.trafo().cast() * mv->get_matrix().cast(); - for (size_t facet_idx = 0; facet_idx < custom_facets.indices.size(); ++facet_idx) { - float min_z = std::numeric_limits::max(); - float max_z = std::numeric_limits::lowest(); - - std::array facet; - Points projected_facet(3); - for (int p_idx = 0; p_idx < 3; ++p_idx) { - facet[p_idx] = tr * custom_facets.vertices[custom_facets.indices[facet_idx](p_idx)]; - max_z = std::max(max_z, facet[p_idx].z()); - min_z = std::min(min_z, facet[p_idx].z()); - } - - // Sort the vertices by z-axis for simplification of projected_facet on slices - std::sort(facet.begin(), facet.end(), [](const Vec3f &p1, const Vec3f &p2) { return p1.z() < p2.z(); }); - - for (int p_idx = 0; p_idx < 3; ++p_idx) { - projected_facet[p_idx] = Point(scale_(facet[p_idx].x()), scale_(facet[p_idx].y())); - projected_facet[p_idx] = projected_facet[p_idx] - print_object.center_offset(); - } - - ExPolygon triangle = ExPolygon(projected_facet); - - // Find lowest slice not below the triangle. - auto first_layer = std::upper_bound(layers.begin(), layers.end(), float(min_z - EPSILON), - [](float z, const Layer *l1) { return z < l1->slice_z + l1->height * 0.5; }); - auto last_layer = std::upper_bound(layers.begin(), layers.end(), float(max_z - EPSILON), - [](float z, const Layer *l1) { return z < l1->slice_z + l1->height * 0.5; }); - - if (last_layer == layers.end()) - --last_layer; - - if (first_layer == layers.end() || (first_layer != layers.begin() && facet[0].z() < (*first_layer)->print_z - EPSILON)) - --first_layer; - - for (auto layer_it = first_layer; (layer_it != (last_layer + 1) && layer_it != layers.end()); ++layer_it) { - size_t layer_idx = layer_it - layers.begin(); - triangles_by_color[extruder_idx][layer_idx].emplace_back(triangle); - } - } - } - } - BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - top and bottom layers - projection of painted triangles - end"; auto get_extrusion_width = [&layers = std::as_const(layers)](const size_t layer_idx) -> float { auto extrusion_width_it = std::max_element(layers[layer_idx]->regions().begin(), layers[layer_idx]->regions().end(), @@ -1241,9 +1190,10 @@ static inline std::vector> mmu_segmentation_top_and_bott return (*top_bottom_layer_it)->region().config().bottom_solid_layers; }; - std::vector top_layers(input_expolygons.size()); +#if 0 + std::vector top_layers(num_layers); top_layers.back() = input_expolygons.back(); - tbb::parallel_for(tbb::blocked_range(1, input_expolygons.size()), [&](const tbb::blocked_range &range) { + tbb::parallel_for(tbb::blocked_range(1, num_layers), [&](const tbb::blocked_range &range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { throw_on_cancel_callback(); float extrusion_width = 0.1f * float(scale_(get_extrusion_width(layer_idx))); @@ -1251,9 +1201,9 @@ static inline std::vector> mmu_segmentation_top_and_bott } }); // end of parallel_for - std::vector bottom_layers(input_expolygons.size()); + std::vector bottom_layers(num_layers); bottom_layers.front() = input_expolygons.front(); - tbb::parallel_for(tbb::blocked_range(0, input_expolygons.size() - 1), [&](const tbb::blocked_range &range) { + tbb::parallel_for(tbb::blocked_range(0, num_layers - 1), [&](const tbb::blocked_range &range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { throw_on_cancel_callback(); float extrusion_width = 0.1f * float(scale_(get_extrusion_width(layer_idx))); @@ -1261,28 +1211,84 @@ static inline std::vector> mmu_segmentation_top_and_bott } }); // end of parallel_for - tbb::parallel_for(tbb::blocked_range(0, input_expolygons.size()), [&](const tbb::blocked_range &range) { - for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { - throw_on_cancel_callback(); - float extrusion_width = 0.1f * float(scale_(get_extrusion_width(layer_idx))); - for (std::vector &triangles : triangles_by_color) { - if (!triangles[layer_idx].empty() && (!top_layers[layer_idx].empty() || !bottom_layers[layer_idx].empty())) { - ExPolygons connected = union_ex(offset_ex(triangles[layer_idx], float(10 * SCALED_EPSILON))); - triangles[layer_idx] = union_ex(offset_ex(offset_ex(connected, -extrusion_width / 1), extrusion_width / 1)); - } else { - triangles[layer_idx].clear(); + std::vector> triangles_by_color_raw(num_extruders, std::vector(layers.size())); + BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - top and bottom layers - projection of painted triangles - begin"; + { + auto delta = float(10 * SCALED_EPSILON); + std::vector deltas { delta, delta, delta }; + Points projected_facet; + for (const ModelVolume *mv : print_object.model_object()->volumes) + if (mv->is_model_part()) { + const Transform3f tr = print_object.trafo().cast() * mv->get_matrix().cast(); + + for (size_t extruder_idx = 0; extruder_idx < num_extruders; ++extruder_idx) { + const indexed_triangle_set custom_facets = mv->mmu_segmentation_facets.get_facets(*mv, EnforcerBlockerType(extruder_idx)); + if (custom_facets.indices.empty()) + continue; + + throw_on_cancel_callback(); + for (size_t facet_idx = 0; facet_idx < custom_facets.indices.size(); ++facet_idx) { + float min_z = std::numeric_limits::max(); + float max_z = std::numeric_limits::lowest(); + + std::array facet; + for (int p_idx = 0; p_idx < 3; ++p_idx) { + facet[p_idx] = tr * custom_facets.vertices[custom_facets.indices[facet_idx](p_idx)]; + max_z = std::max(max_z, facet[p_idx].z()); + min_z = std::min(min_z, facet[p_idx].z()); + } + + // Sort the vertices by z-axis for simplification of projected_facet on slices + std::sort(facet.begin(), facet.end(), [](const Vec3f &p1, const Vec3f &p2) { return p1.z() < p2.z(); }); + projected_facet.clear(); + projected_facet.reserve(3); + for (int p_idx = 0; p_idx < 3; ++p_idx) + projected_facet.emplace_back(Point(scale_(facet[p_idx].x()), scale_(facet[p_idx].y())) - print_object.center_offset()); + if (cross2((projected_facet[1] - projected_facet[0]).cast(), (projected_facet[2] - projected_facet[1]).cast()) < 0) + // Make CCW. + std::swap(projected_facet[1], projected_facet[2]); + ClipperLib::Path offsetted = mittered_offset_path_scaled(projected_facet, deltas, 3.); + + // Find lowest slice not below the triangle. + auto first_layer = std::upper_bound(layers.begin(), layers.end(), float(min_z - EPSILON), + [](float z, const Layer *l1) { return z < l1->slice_z + l1->height * 0.5; }); + auto last_layer = std::upper_bound(layers.begin(), layers.end(), float(max_z - EPSILON), + [](float z, const Layer *l1) { return z < l1->slice_z + l1->height * 0.5; }); + + if (last_layer == layers.end()) + --last_layer; + + if (first_layer == layers.end() || (first_layer != layers.begin() && facet[0].z() < (*first_layer)->print_z - EPSILON)) + --first_layer; + + for (auto layer_it = first_layer; (layer_it != (last_layer + 1) && layer_it != layers.end()); ++layer_it) + if (size_t layer_idx = layer_it - layers.begin(); ! top_layers[layer_idx].empty() || ! bottom_layers[layer_idx].empty()) + triangles_by_color_raw[extruder_idx][layer_idx].emplace_back(offsetted); + } } } + } + BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - top and bottom layers - projection of painted triangles - end"; + + std::vector> triangles_by_color(num_extruders, std::vector(layers.size())); + tbb::parallel_for(tbb::blocked_range(0, num_layers), [&](const tbb::blocked_range &range) { + for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { + throw_on_cancel_callback(); + float offset_factor = 0.1f * float(scale_(get_extrusion_width(layer_idx))); + for (size_t extruder_id = 0; extruder_id < num_extruders; ++ extruder_id) + if (ClipperLib::Paths &src_paths = triangles_by_color_raw[extruder_id][layer_idx]; !src_paths.empty()) + triangles_by_color[extruder_id][layer_idx] = offset_ex(offset_ex(ClipperPaths_to_Slic3rExPolygons(src_paths), -offset_factor), offset_factor); } }); // end of parallel_for + triangles_by_color_raw.clear(); std::vector> triangles_by_color_bottom(num_extruders); std::vector> triangles_by_color_top(num_extruders); - triangles_by_color_bottom.assign(num_extruders, std::vector(input_expolygons.size())); - triangles_by_color_top.assign(num_extruders, std::vector(input_expolygons.size())); + triangles_by_color_bottom.assign(num_extruders, std::vector(num_layers)); + triangles_by_color_top.assign(num_extruders, std::vector(num_layers)); BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - segmentation of top layer - begin"; - for (size_t layer_idx = 0; layer_idx < input_expolygons.size(); ++layer_idx) { + for (size_t layer_idx = 0; layer_idx < num_layers; ++layer_idx) { float extrusion_width = scale_(get_extrusion_width(layer_idx)); int top_solid_layers = get_top_solid_layers(layer_idx); ExPolygons top_expolygon = top_layers[layer_idx]; @@ -1318,7 +1324,7 @@ static inline std::vector> mmu_segmentation_top_and_bott BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - segmentation of top layer - end"; BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - segmentation of bottom layer - begin"; - for (size_t layer_idx = 0; layer_idx < input_expolygons.size(); ++layer_idx) { + for (size_t layer_idx = 0; layer_idx < num_layers; ++layer_idx) { float extrusion_width = scale_(get_extrusion_width(layer_idx)); int bottom_solid_layers = get_bottom_solid_layers(layer_idx); const ExPolygons &bottom_expolygon = bottom_layers[layer_idx]; @@ -1334,7 +1340,7 @@ static inline std::vector> mmu_segmentation_top_and_bott if (!intersection_poly.empty()) { triangles_by_color_bottom[color_idx][layer_idx].insert(triangles_by_color_bottom[color_idx][layer_idx].end(), intersection_poly.begin(), intersection_poly.end()); - for (size_t last_idx = layer_idx + 1; last_idx < std::min(layer_idx + bottom_solid_layers, input_expolygons.size()); ++last_idx) { + for (size_t last_idx = layer_idx + 1; last_idx < std::min(layer_idx + bottom_solid_layers, num_layers); ++last_idx) { float offset_value = float(last_idx - layer_idx) * (-1.0f) * extrusion_width; if (offset_ex(bottom_expolygon, offset_value).empty()) continue; @@ -1353,8 +1359,8 @@ static inline std::vector> mmu_segmentation_top_and_bott BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - segmentation of bottom layer - end"; std::vector> triangles_by_color_merged(num_extruders); - triangles_by_color_merged.assign(num_extruders, std::vector(input_expolygons.size())); - for (size_t layer_idx = 0; layer_idx < input_expolygons.size(); ++layer_idx) { + triangles_by_color_merged.assign(num_extruders, std::vector(num_layers)); + for (size_t layer_idx = 0; layer_idx < num_layers; ++layer_idx) { throw_on_cancel_callback(); for (size_t color_idx = 0; color_idx < triangles_by_color_merged.size(); ++color_idx) { auto &self = triangles_by_color_merged[color_idx][layer_idx]; @@ -1369,6 +1375,169 @@ static inline std::vector> mmu_segmentation_top_and_bott triangles_by_color_merged[color_idx - 1][layer_idx]); } } +#else + + // Maximum number of top / bottom layers accounts for maximum overlap of one thread group into a neighbor thread group. + int max_top_layers = 0; + int max_bottom_layers = 0; + int granularity = 1; + for (size_t i = 0; i < print_object.num_printing_regions(); ++ i) { + const PrintRegionConfig &config = print_object.printing_region(i).config(); + max_top_layers = std::max(max_top_layers, config.top_solid_layers.value); + max_bottom_layers = std::max(max_bottom_layers, config.bottom_solid_layers.value); + granularity = std::max(granularity, std::max(config.top_solid_layers.value, config.bottom_solid_layers.value) - 1); + } + + // Project upwards pointing painted triangles over top surfaces, + // project downards pointing painted triangles over bottom surfaces. + std::vector> top_raw(num_extruders), bottom_raw(num_extruders); + std::vector zs = zs_from_layers(print_object.layers()); + Transform3d object_trafo = print_object.trafo(); + object_trafo.pretranslate(Vec3d(- unscale(print_object.center_offset().x()), - unscale(print_object.center_offset().y()), 0)); + +#ifdef MMU_SEGMENTATION_DEBUG_TOP_BOTTOM + static int iRun = 0; +#endif // NDEBUG + + if (max_top_layers > 0 || max_bottom_layers > 0) { + for (const ModelVolume *mv : print_object.model_object()->volumes) + if (mv->is_model_part()) { + const Transform3d volume_trafo = object_trafo * mv->get_matrix(); + for (size_t extruder_idx = 0; extruder_idx < num_extruders; ++ extruder_idx) { + const indexed_triangle_set painted = mv->mmu_segmentation_facets.get_facets_strict(*mv, EnforcerBlockerType(extruder_idx)); +#ifdef MMU_SEGMENTATION_DEBUG_TOP_BOTTOM + { + static int iRun = 0; + its_write_obj(painted, debug_out_path("mm-painted-patch-%d-%d.obj", iRun ++, extruder_idx).c_str()); + } +#endif // MMU_SEGMENTATION_DEBUG_TOP_BOTTOM + if (! painted.indices.empty()) { + std::vector top, bottom; + slice_mesh_slabs(painted, zs, volume_trafo, max_top_layers > 0 ? &top : nullptr, max_bottom_layers > 0 ? &bottom : nullptr, throw_on_cancel_callback); + auto merge = [](std::vector &&src, std::vector &dst) { + auto it_src = find_if(src.begin(), src.end(), [](const Polygons &p){ return ! p.empty(); }); + if (it_src != src.end()) { + if (dst.empty()) { + dst = std::move(src); + } else { + assert(src.size() == dst.size()); + auto it_dst = dst.begin() + (it_src - src.begin()); + for (; it_src != src.end(); ++ it_src, ++ it_dst) + if (! it_src->empty()) { + if (it_dst->empty()) + *it_dst = std::move(*it_src); + else + append(*it_dst, std::move(*it_src)); + } + } + } + }; + merge(std::move(top), top_raw[extruder_idx]); + merge(std::move(bottom), bottom_raw[extruder_idx]); + } + } + } + } + +#ifdef MMU_SEGMENTATION_DEBUG_TOP_BOTTOM + { + const char* colors[] = { "aqua", "black", "blue", "fuchsia", "gray", "green", "lime", "maroon", "navy", "olive", "purple", "red", "silver", "teal", "yellow" }; + static int iRun = 0; + for (size_t layer_id = 0; layer_id < zs.size(); ++layer_id) { + std::vector> svg; + for (size_t extruder_idx = 0; extruder_idx < num_extruders; ++ extruder_idx) { + if (! top_raw[extruder_idx].empty() && ! top_raw[extruder_idx][layer_id].empty()) + if (ExPolygons expoly = union_ex(top_raw[extruder_idx][layer_id]); ! expoly.empty()) { + const char *color = colors[extruder_idx]; + svg.emplace_back(expoly, SVG::ExPolygonAttributes{ format("top%d", extruder_idx), color, color, color }); + } + if (! bottom_raw[extruder_idx].empty() && ! bottom_raw[extruder_idx][layer_id].empty()) + if (ExPolygons expoly = union_ex(bottom_raw[extruder_idx][layer_id]); ! expoly.empty()) { + const char *color = colors[extruder_idx + 8]; + svg.emplace_back(expoly, SVG::ExPolygonAttributes{ format("bottom%d", extruder_idx), color, color, color }); + } + } + SVG::export_expolygons(debug_out_path("mm-segmentation-top-bottom-%d-%d-%lf.svg", iRun, layer_id, zs[layer_id]), svg); + } + ++ iRun; + } +#endif // MMU_SEGMENTATION_DEBUG_TOP_BOTTOM + + std::vector> triangles_by_color_bottom(num_extruders); + std::vector> triangles_by_color_top(num_extruders); + triangles_by_color_bottom.assign(num_extruders, std::vector(num_layers * 2)); + triangles_by_color_top.assign(num_extruders, std::vector(num_layers * 2)); + + tbb::parallel_for(tbb::blocked_range(0, num_layers, granularity), [&](const tbb::blocked_range &range) { + size_t group_idx = range.begin() / granularity; + size_t layer_idx_offset = (group_idx & 1) * num_layers; + for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { + float extrusion_width = scale_(get_extrusion_width(layer_idx)); + int top_solid_layers = get_top_solid_layers(layer_idx); + int bottom_solid_layers = get_bottom_solid_layers(layer_idx); + float narrow_island_width = 0.1f * float(extrusion_width); + for (size_t color_idx = 0; color_idx < num_extruders; ++ color_idx) { + throw_on_cancel_callback(); + if (std::vector &top = top_raw[color_idx]; ! top.empty() && ! top[layer_idx].empty()) + if (ExPolygons top_ex = union_ex(top[layer_idx]); ! top_ex.empty()) { + // Clean up thin projections. They are not printable anyways. + top_ex = offset2_ex(top_ex, - narrow_island_width, + narrow_island_width); + if (! top_ex.empty()) { + append(triangles_by_color_top[color_idx][layer_idx + layer_idx_offset], top_ex); + float offset = 0.f; + ExPolygons layer_slices_trimmed = input_expolygons[layer_idx]; + for (int last_idx = int(layer_idx) - 1; last_idx >= std::max(int(layer_idx - top_solid_layers), int(0)); --last_idx) { + offset -= extrusion_width; + layer_slices_trimmed = intersection_ex(layer_slices_trimmed, input_expolygons[last_idx]); + ExPolygons last = intersection_ex(top_ex, offset_ex(layer_slices_trimmed, offset)); + if (last.empty()) + break; + append(triangles_by_color_top[color_idx][last_idx + layer_idx_offset], std::move(last)); + } + } + } + if (std::vector &bottom = bottom_raw[color_idx]; ! bottom.empty() && ! bottom[layer_idx].empty()) + if (ExPolygons bottom_ex = union_ex(bottom[layer_idx]); ! bottom_ex.empty()) { + // Clean up thin projections. They are not printable anyways. + bottom_ex = offset2_ex(bottom_ex, - narrow_island_width, + narrow_island_width); + if (! bottom_ex.empty()) { + append(triangles_by_color_bottom[color_idx][layer_idx + layer_idx_offset], bottom_ex); + float offset = 0.f; + ExPolygons layer_slices_trimmed = input_expolygons[layer_idx]; + for (size_t last_idx = layer_idx + 1; last_idx < std::min(layer_idx + bottom_solid_layers, num_layers); ++last_idx) { + offset -= extrusion_width; + layer_slices_trimmed = intersection_ex(layer_slices_trimmed, input_expolygons[last_idx]); + ExPolygons last = intersection_ex(bottom_ex, offset_ex(layer_slices_trimmed, offset)); + if (last.empty()) + break; + append(triangles_by_color_bottom[color_idx][last_idx + layer_idx_offset], std::move(last)); + } + } + } + } + } + }); + + std::vector> triangles_by_color_merged(num_extruders); + triangles_by_color_merged.assign(num_extruders, std::vector(num_layers)); + tbb::parallel_for(tbb::blocked_range(0, num_layers), [&](const tbb::blocked_range &range) { + for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { + throw_on_cancel_callback(); + for (size_t color_idx = 0; color_idx < triangles_by_color_merged.size(); ++color_idx) { + auto &self = triangles_by_color_merged[color_idx][layer_idx]; + append(self, std::move(triangles_by_color_bottom[color_idx][layer_idx])); + append(self, std::move(triangles_by_color_bottom[color_idx][layer_idx + num_layers])); + append(self, std::move(triangles_by_color_top[color_idx][layer_idx])); + append(self, std::move(triangles_by_color_top[color_idx][layer_idx + num_layers])); + self = union_ex(self); + } + // Trim one region by the other if some of the regions overlap. + for (size_t color_idx = 1; color_idx < triangles_by_color_merged.size(); ++ color_idx) + triangles_by_color_merged[color_idx][layer_idx] = diff_ex(triangles_by_color_merged[color_idx][layer_idx], + triangles_by_color_merged[color_idx - 1][layer_idx]); + } + }); +#endif return triangles_by_color_merged; } diff --git a/src/libslic3r/MutablePolygon.cpp b/src/libslic3r/MutablePolygon.cpp index dc1d47731..403d625bf 100644 --- a/src/libslic3r/MutablePolygon.cpp +++ b/src/libslic3r/MutablePolygon.cpp @@ -166,7 +166,7 @@ static bool clip_narrow_corner( assert(orient1 > 0 == blocked); assert(orient2 > 0 == blocked); } -#endif // _NDEBUG +#endif // NDEBUG if (polygon.size() < 3 || (forward == Far && backward == Far)) { polygon.clear(); } else { diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 4561de247..add16d905 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -655,10 +655,8 @@ std::string Print::validate(std::string* warning) const // Notify the user in that case. if (! object->has_support() && warning) { for (const ModelVolume* mv : object->model_object()->volumes) { - bool has_enforcers = mv->is_support_enforcer() - || (mv->is_model_part() - && ! mv->supported_facets.empty() - && ! mv->supported_facets.get_facets(*mv, EnforcerBlockerType::ENFORCER).indices.empty()); + bool has_enforcers = mv->is_support_enforcer() || + (mv->is_model_part() && mv->supported_facets.has_facets(*mv, EnforcerBlockerType::ENFORCER)); if (has_enforcers) { *warning = "_SUPPORTS_OFF"; break; diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 9ed3d4778..e486b2bc6 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -326,12 +326,12 @@ public: void slice(); // Helpers to slice support enforcer / blocker meshes by the support generator. - std::vector slice_support_volumes(const ModelVolumeType model_volume_type) const; - std::vector slice_support_blockers() const { return this->slice_support_volumes(ModelVolumeType::SUPPORT_BLOCKER); } - std::vector slice_support_enforcers() const { return this->slice_support_volumes(ModelVolumeType::SUPPORT_ENFORCER); } + std::vector slice_support_volumes(const ModelVolumeType model_volume_type) const; + std::vector slice_support_blockers() const { return this->slice_support_volumes(ModelVolumeType::SUPPORT_BLOCKER); } + std::vector slice_support_enforcers() const { return this->slice_support_volumes(ModelVolumeType::SUPPORT_ENFORCER); } // Helpers to project custom facets on slices - void project_and_append_custom_facets(bool seam, EnforcerBlockerType type, std::vector& expolys) const; + void project_and_append_custom_facets(bool seam, EnforcerBlockerType type, std::vector& expolys) const; private: // to be called from Print only. diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index eb46537ec..9471d98ee 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -403,10 +403,8 @@ void PrintObject::generate_support_material() // Notify the user in that case. if (! this->has_support()) { for (const ModelVolume* mv : this->model_object()->volumes) { - bool has_enforcers = mv->is_support_enforcer() - || (mv->is_model_part() - && ! mv->supported_facets.empty() - && ! mv->supported_facets.get_facets(*mv, EnforcerBlockerType::ENFORCER).indices.empty()); + bool has_enforcers = mv->is_support_enforcer() || + (mv->is_model_part() && mv->supported_facets.has_facets(*mv, EnforcerBlockerType::ENFORCER)); if (has_enforcers) { this->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL, L("An object has custom support enforcers which will not be used " @@ -2102,206 +2100,206 @@ void PrintObject::_generate_support_material() support_material.generate(*this); } - -void PrintObject::project_and_append_custom_facets( - bool seam, EnforcerBlockerType type, std::vector& expolys) const +static void project_triangles_to_slabs(ConstLayerPtrsAdaptor layers, const indexed_triangle_set &custom_facets, const Transform3f &tr, bool seam, std::vector &out) { - for (const ModelVolume* mv : this->model_object()->volumes) { - const indexed_triangle_set custom_facets = seam - ? mv->seam_facets.get_facets(*mv, type) - : mv->supported_facets.get_facets(*mv, type); - if (! mv->is_model_part() || custom_facets.indices.empty()) + if (custom_facets.indices.empty()) + return; + + const float tr_det_sign = (tr.matrix().determinant() > 0. ? 1.f : -1.f); + + // The projection will be at most a pentagon. Let's minimize heap + // reallocations by saving in in the following struct. + // Points are used so that scaling can be done in parallel + // and they can be moved from to create an ExPolygon later. + struct LightPolygon { + LightPolygon() { pts.reserve(5); } + LightPolygon(const std::array& tri) { + pts.reserve(3); + pts.emplace_back(scaled(tri.front())); + pts.emplace_back(scaled(tri[1])); + pts.emplace_back(scaled(tri.back())); + } + + Points pts; + + void add(const Vec2f& pt) { + pts.emplace_back(scaled(pt)); + assert(pts.size() <= 5); + } + }; + + // Structure to collect projected polygons. One element for each triangle. + // Saves vector of polygons and layer_id of the first one. + struct TriangleProjections { + size_t first_layer_id; + std::vector polygons; + }; + + // Vector to collect resulting projections from each triangle. + std::vector projections_of_triangles(custom_facets.indices.size()); + + // Iterate over all triangles. + tbb::parallel_for( + tbb::blocked_range(0, custom_facets.indices.size()), + [&custom_facets, &tr, tr_det_sign, seam, layers, &projections_of_triangles](const tbb::blocked_range& range) { + for (size_t idx = range.begin(); idx < range.end(); ++ idx) { + + std::array facet; + + // Transform the triangle into worlds coords. + for (int i=0; i<3; ++i) + facet[i] = tr * custom_facets.vertices[custom_facets.indices[idx](i)]; + + // Ignore triangles with upward-pointing normal. Don't forget about mirroring. + float z_comp = (facet[1]-facet[0]).cross(facet[2]-facet[0]).z(); + if (! seam && tr_det_sign * z_comp > 0.) continue; - const Transform3f& tr1 = mv->get_matrix().cast(); - const Transform3f& tr2 = this->trafo().cast(); - const Transform3f tr = tr2 * tr1; - const float tr_det_sign = (tr.matrix().determinant() > 0. ? 1.f : -1.f); - const Vec2f center = unscaled(this->center_offset()); - ConstLayerPtrsAdaptor layers = this->layers(); + // The algorithm does not process vertical triangles, but it should for seam. + // In that case, tilt the triangle a bit so the projection does not degenerate. + if (seam && z_comp == 0.f) + facet[0].x() += float(EPSILON); - // The projection will be at most a pentagon. Let's minimize heap - // reallocations by saving in in the following struct. - // Points are used so that scaling can be done in parallel - // and they can be moved from to create an ExPolygon later. - struct LightPolygon { - LightPolygon() { pts.reserve(5); } - LightPolygon(const std::array& tri) { - pts.reserve(3); - pts.emplace_back(scaled(tri.front())); - pts.emplace_back(scaled(tri[1])); - pts.emplace_back(scaled(tri.back())); - } + // Sort the three vertices according to z-coordinate. + std::sort(facet.begin(), facet.end(), + [](const Vec3f& pt1, const Vec3f&pt2) { + return pt1.z() < pt2.z(); + }); - Points pts; + std::array trianglef; + for (int i=0; i<3; ++i) + trianglef[i] = to_2d(facet[i]); - void add(const Vec2f& pt) { - pts.emplace_back(scaled(pt)); - assert(pts.size() <= 5); - } - }; - - // Structure to collect projected polygons. One element for each triangle. - // Saves vector of polygons and layer_id of the first one. - struct TriangleProjections { - size_t first_layer_id; - std::vector polygons; - }; - - // Vector to collect resulting projections from each triangle. - std::vector projections_of_triangles(custom_facets.indices.size()); - - // Iterate over all triangles. - tbb::parallel_for( - tbb::blocked_range(0, custom_facets.indices.size()), - [center, &custom_facets, &tr, tr_det_sign, seam, layers, &projections_of_triangles](const tbb::blocked_range& range) { - for (size_t idx = range.begin(); idx < range.end(); ++ idx) { - - std::array facet; - - // Transform the triangle into worlds coords. - for (int i=0; i<3; ++i) - facet[i] = tr * custom_facets.vertices[custom_facets.indices[idx](i)]; - - // Ignore triangles with upward-pointing normal. Don't forget about mirroring. - float z_comp = (facet[1]-facet[0]).cross(facet[2]-facet[0]).z(); - if (! seam && tr_det_sign * z_comp > 0.) - continue; - - // The algorithm does not process vertical triangles, but it should for seam. - // In that case, tilt the triangle a bit so the projection does not degenerate. - if (seam && z_comp == 0.f) - facet[0].x() += float(EPSILON); - - // Sort the three vertices according to z-coordinate. - std::sort(facet.begin(), facet.end(), - [](const Vec3f& pt1, const Vec3f&pt2) { - return pt1.z() < pt2.z(); + // Find lowest slice not below the triangle. + auto it = std::lower_bound(layers.begin(), layers.end(), facet[0].z()+EPSILON, + [](const Layer* l1, float z) { + return l1->slice_z < z; }); - std::array trianglef; - for (int i=0; i<3; ++i) - trianglef[i] = to_2d(facet[i]) - center; + // Count how many projections will be generated for this triangle + // and allocate respective amount in projections_of_triangles. + size_t first_layer_id = projections_of_triangles[idx].first_layer_id = it - layers.begin(); + size_t last_layer_id = first_layer_id; + // The cast in the condition below is important. The comparison must + // be an exact opposite of the one lower in the code where + // the polygons are appended. And that one is on floats. + while (last_layer_id + 1 < layers.size() + && float(layers[last_layer_id]->slice_z) <= facet[2].z()) + ++last_layer_id; - // Find lowest slice not below the triangle. - auto it = std::lower_bound(layers.begin(), layers.end(), facet[0].z()+EPSILON, - [](const Layer* l1, float z) { - return l1->slice_z < z; - }); - - // Count how many projections will be generated for this triangle - // and allocate respective amount in projections_of_triangles. - size_t first_layer_id = projections_of_triangles[idx].first_layer_id = it - layers.begin(); - size_t last_layer_id = first_layer_id; - // The cast in the condition below is important. The comparison must - // be an exact opposite of the one lower in the code where - // the polygons are appended. And that one is on floats. - while (last_layer_id + 1 < layers.size() - && float(layers[last_layer_id]->slice_z) <= facet[2].z()) - ++last_layer_id; - - if (first_layer_id == last_layer_id) { - // The triangle fits just a single slab, just project it. This also avoids division by zero for horizontal triangles. - float dz = facet[2].z() - facet[0].z(); - assert(dz >= 0); - // The face is nearly horizontal and it crosses the slicing plane at first_layer_id - 1. - // Rather add this face to both the planes. - bool add_below = dz < float(2. * EPSILON) && first_layer_id > 0 && layers[first_layer_id - 1]->slice_z > facet[0].z() - EPSILON; - projections_of_triangles[idx].polygons.reserve(add_below ? 2 : 1); + if (first_layer_id == last_layer_id) { + // The triangle fits just a single slab, just project it. This also avoids division by zero for horizontal triangles. + float dz = facet[2].z() - facet[0].z(); + assert(dz >= 0); + // The face is nearly horizontal and it crosses the slicing plane at first_layer_id - 1. + // Rather add this face to both the planes. + bool add_below = dz < float(2. * EPSILON) && first_layer_id > 0 && layers[first_layer_id - 1]->slice_z > facet[0].z() - EPSILON; + projections_of_triangles[idx].polygons.reserve(add_below ? 2 : 1); + projections_of_triangles[idx].polygons.emplace_back(trianglef); + if (add_below) { + -- projections_of_triangles[idx].first_layer_id; projections_of_triangles[idx].polygons.emplace_back(trianglef); - if (add_below) { - -- projections_of_triangles[idx].first_layer_id; - projections_of_triangles[idx].polygons.emplace_back(trianglef); - } - continue; - } - - projections_of_triangles[idx].polygons.resize(last_layer_id - first_layer_id + 1); - - // Calculate how to move points on triangle sides per unit z increment. - Vec2f ta(trianglef[1] - trianglef[0]); - Vec2f tb(trianglef[2] - trianglef[0]); - ta *= 1.f/(facet[1].z() - facet[0].z()); - tb *= 1.f/(facet[2].z() - facet[0].z()); - - // Projection on current slice will be build directly in place. - LightPolygon* proj = &projections_of_triangles[idx].polygons[0]; - proj->add(trianglef[0]); - - bool passed_first = false; - bool stop = false; - - // Project a sub-polygon on all slices intersecting the triangle. - while (it != layers.end()) { - const float z = float((*it)->slice_z); - - // Projections of triangle sides intersections with slices. - // a moves along one side, b tracks the other. - Vec2f a; - Vec2f b; - - // If the middle vertex was already passed, append the vertex - // and use ta for tracking the remaining side. - if (z > facet[1].z() && ! passed_first) { - proj->add(trianglef[1]); - ta = trianglef[2]-trianglef[1]; - ta *= 1.f/(facet[2].z() - facet[1].z()); - passed_first = true; - } - - // This slice is above the triangle already. - if (z > facet[2].z() || it+1 == layers.end()) { - proj->add(trianglef[2]); - stop = true; - } - else { - // Move a, b along the side it currently tracks to get - // projected intersection with current slice. - a = passed_first ? (trianglef[1]+ta*(z-facet[1].z())) - : (trianglef[0]+ta*(z-facet[0].z())); - b = trianglef[0]+tb*(z-facet[0].z()); - proj->add(a); - proj->add(b); - } - - if (stop) - break; - - // Advance to the next layer. - ++it; - ++proj; - assert(proj <= &projections_of_triangles[idx].polygons.back() ); - - // a, b are first two points of the polygon for the next layer. - proj->add(b); - proj->add(a); } + continue; } - }); // end of parallel_for - // Make sure that the output vector can be used. - expolys.resize(layers.size()); + projections_of_triangles[idx].polygons.resize(last_layer_id - first_layer_id + 1); - // Now append the collected polygons to respective layers. - for (auto& trg : projections_of_triangles) { - int layer_id = int(trg.first_layer_id); - for (LightPolygon &poly : trg.polygons) { - if (layer_id >= int(expolys.size())) - break; // part of triangle could be projected above top layer - assert(! poly.pts.empty()); - // The resulting triangles are fed to the Clipper library, which seem to handle flipped triangles well. + // Calculate how to move points on triangle sides per unit z increment. + Vec2f ta(trianglef[1] - trianglef[0]); + Vec2f tb(trianglef[2] - trianglef[0]); + ta *= 1.f/(facet[1].z() - facet[0].z()); + tb *= 1.f/(facet[2].z() - facet[0].z()); + + // Projection on current slice will be built directly in place. + LightPolygon* proj = &projections_of_triangles[idx].polygons[0]; + proj->add(trianglef[0]); + + bool passed_first = false; + bool stop = false; + + // Project a sub-polygon on all slices intersecting the triangle. + while (it != layers.end()) { + const float z = float((*it)->slice_z); + + // Projections of triangle sides intersections with slices. + // a moves along one side, b tracks the other. + Vec2f a; + Vec2f b; + + // If the middle vertex was already passed, append the vertex + // and use ta for tracking the remaining side. + if (z > facet[1].z() && ! passed_first) { + proj->add(trianglef[1]); + ta = trianglef[2]-trianglef[1]; + ta *= 1.f/(facet[2].z() - facet[1].z()); + passed_first = true; + } + + // This slice is above the triangle already. + if (z > facet[2].z() || it+1 == layers.end()) { + proj->add(trianglef[2]); + stop = true; + } + else { + // Move a, b along the side it currently tracks to get + // projected intersection with current slice. + a = passed_first ? (trianglef[1]+ta*(z-facet[1].z())) + : (trianglef[0]+ta*(z-facet[0].z())); + b = trianglef[0]+tb*(z-facet[0].z()); + proj->add(a); + proj->add(b); + } + + if (stop) + break; + + // Advance to the next layer. + ++it; + ++proj; + assert(proj <= &projections_of_triangles[idx].polygons.back() ); + + // a, b are first two points of the polygon for the next layer. + proj->add(b); + proj->add(a); + } + } + }); // end of parallel_for + + // Make sure that the output vector can be used. + out.resize(layers.size()); + + // Now append the collected polygons to respective layers. + for (auto& trg : projections_of_triangles) { + int layer_id = int(trg.first_layer_id); + for (LightPolygon &poly : trg.polygons) { + if (layer_id >= int(out.size())) + break; // part of triangle could be projected above top layer + assert(! poly.pts.empty()); + // The resulting triangles are fed to the Clipper library, which seem to handle flipped triangles well. // if (cross2(Vec2d((poly.pts[1] - poly.pts[0]).cast()), Vec2d((poly.pts[2] - poly.pts[1]).cast())) < 0) // std::swap(poly.pts.front(), poly.pts.back()); - - expolys[layer_id].emplace_back(std::move(poly.pts)); - ++layer_id; - } + + out[layer_id].emplace_back(std::move(poly.pts)); + ++layer_id; } - - } // loop over ModelVolumes + } } - +void PrintObject::project_and_append_custom_facets( + bool seam, EnforcerBlockerType type, std::vector& out) const +{ + for (const ModelVolume* mv : this->model_object()->volumes) + if (mv->is_model_part()) { + const indexed_triangle_set custom_facets = seam + ? mv->seam_facets.get_facets_strict(*mv, type) + : mv->supported_facets.get_facets_strict(*mv, type); + if (! custom_facets.indices.empty()) + project_triangles_to_slabs(this->layers(), custom_facets, + (Eigen::Translation3d(to_3d(unscaled(this->center_offset()), 0.)) * this->trafo() * mv->get_matrix()).cast(), + seam, out); + } +} const Layer* PrintObject::get_layer_at_printz(coordf_t print_z) const { auto it = Slic3r::lower_bound_by_predicate(m_layers.begin(), m_layers.end(), [print_z](const Layer *layer) { return layer->print_z < print_z; }); diff --git a/src/libslic3r/PrintObjectSlice.cpp b/src/libslic3r/PrintObjectSlice.cpp index 6ec37ce24..82fd04bce 100644 --- a/src/libslic3r/PrintObjectSlice.cpp +++ b/src/libslic3r/PrintObjectSlice.cpp @@ -39,16 +39,6 @@ LayerPtrs new_layers( return out; } -template -static inline std::vector zs_from_layers(const LayerContainer &layers) -{ - std::vector zs; - zs.reserve(layers.size()); - for (const Layer *l : layers) - zs.emplace_back((float)l->slice_z); - return zs; -} - //FIXME The admesh repair function may break the face connectivity, rather refresh it here as the slicing code relies on it. // This function will go away once we get rid of admesh from ModelVolume. static indexed_triangle_set get_mesh_its_fix_mesh_connectivity(TriangleMesh mesh) @@ -604,14 +594,18 @@ static inline void apply_mm_segmentation(PrintObject &print_object, ThrowOnCance 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 = layer_range.painted_regions.begin(); for (int region_id = 0; region_id < int(layer->region_count()); ++ region_id) if (LayerRegion &layerm = *layer->get_region(region_id); ! layerm.slices.surfaces.empty()) { + assert(layerm.region().print_object_region_id() == region_id); const BoundingBox bbox = get_extents(layerm.slices.surfaces); assert(it_painted_region < layer_range.painted_regions.end()); + // Find the first it_painted_region which overrides this region. for (; layer_range.volume_regions[it_painted_region->parent].region->print_object_region_id() < region_id; ++ it_painted_region) - assert(it_painted_region < layer_range.painted_regions.end()); - assert(&layerm.region() == it_painted_region->region && layerm.region().print_object_region_id() == region_id); + assert(it_painted_region != layer_range.painted_regions.end()); + assert(it_painted_region != layer_range.painted_regions.end()); + assert(layer_range.volume_regions[it_painted_region->parent].region == &layerm.region()); // 1-based extruder ID bool self_trimmed = false; int self_extruder_id = -1; @@ -619,7 +613,7 @@ static inline void apply_mm_segmentation(PrintObject &print_object, ThrowOnCance if (ByExtruder &segmented = by_extruder[extruder_id - 1]; segmented.bbox.defined && bbox.overlap(segmented.bbox)) { // Find the target region. for (; int(it_painted_region->extruder_id) < extruder_id; ++ it_painted_region) - assert(it_painted_region < layer_range.painted_regions.end()); + assert(it_painted_region != layer_range.painted_regions.end()); assert(layer_range.volume_regions[it_painted_region->parent].region == &layerm.region() && int(it_painted_region->extruder_id) == extruder_id); //FIXME Don't trim by self, it is not reliable. if (&layerm.region() == it_painted_region->region) { @@ -669,7 +663,7 @@ static inline void apply_mm_segmentation(PrintObject &print_object, ThrowOnCance ByRegion &src = by_region[region_id]; if (src.needs_merge) // Multiple regions were merged into one. - src.expolygons = offset2_ex(src.expolygons, float(scale_(EPSILON)), - float(scale_(EPSILON))); + src.expolygons = offset2_ex(src.expolygons, float(scale_(10 * EPSILON)), - float(scale_(10 * EPSILON))); layer->get_region(region_id)->slices.set(std::move(src.expolygons), stInternal); } } @@ -701,7 +695,7 @@ void PrintObject::slice_volumes() std::vector slice_zs = zs_from_layers(m_layers); Transform3d trafo = this->trafo(); - trafo.pretranslate(Vec3d(- unscale(m_center_offset.x()), - unscale(m_center_offset.y()), 0)); + trafo.pretranslate(Vec3d(- unscale(m_center_offset.x()), - unscale(m_center_offset.y()), 0)); std::vector> region_slices = slices_to_regions(this->model_object()->volumes, *m_shared_regions, slice_zs, slice_volumes_inner( print->config(), this->config(), trafo, @@ -812,12 +806,12 @@ void PrintObject::slice_volumes() BOOST_LOG_TRIVIAL(debug) << "Slicing volumes - make_slices in parallel - end"; } -std::vector PrintObject::slice_support_volumes(const ModelVolumeType model_volume_type) const +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; + 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()); @@ -831,16 +825,18 @@ std::vector PrintObject::slice_support_volumes(const ModelVolumeType 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 = std::move(slices2); - else if (! slices2.empty()) { + 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] = std::move(slices2[i]); + slices[i] = to_polygons(std::move(slices2[i])); else if (! slices2[i].empty()) { - append(slices[i], std::move(slices2[i])); + append(slices[i], to_polygons(std::move(slices2[i]))); merge_layers[i] = true; merge = true; } @@ -848,7 +844,7 @@ std::vector PrintObject::slice_support_volumes(const ModelVolumeType } } if (merge) { - std::vector to_merge; + std::vector to_merge; to_merge.reserve(zs.size()); for (size_t i = 0; i < zs.size(); ++ i) if (merge_layers[i]) @@ -857,7 +853,7 @@ std::vector PrintObject::slice_support_volumes(const ModelVolumeType 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_ex(*to_merge[i]); + *to_merge[i] = union_(*to_merge[i]); }); } } diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index 5eb9c9433..e3f7da9b9 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -1354,9 +1354,9 @@ struct SupportAnnotations object.project_and_append_custom_facets(false, EnforcerBlockerType::BLOCKER, blockers_layers); } - std::vector enforcers_layers; - std::vector blockers_layers; - const std::vector& buildplate_covered; + std::vector enforcers_layers; + std::vector blockers_layers; + const std::vector& buildplate_covered; }; struct SlicesMarginCache diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index 93276d600..d709a67cd 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -728,7 +728,7 @@ static std::vector create_edge_map( // Map from a face edge to a unique edge identifier or -1 if no neighbor exists. // Two neighbor faces share a unique edge identifier even if they are flipped. template -static inline std::vector create_face_neighbors_index_impl(const indexed_triangle_set &its, ThrowOnCancelCallback throw_on_cancel) +static inline std::vector its_face_edge_ids_impl(const indexed_triangle_set &its, ThrowOnCancelCallback throw_on_cancel) { std::vector out(its.indices.size(), Vec3i(-1, -1, -1)); @@ -778,14 +778,56 @@ static inline std::vector create_face_neighbors_index_impl(const indexed_ return out; } -std::vector create_face_neighbors_index(const indexed_triangle_set &its) +std::vector its_face_edge_ids(const indexed_triangle_set &its) { - return create_face_neighbors_index_impl(its, [](){}); + return its_face_edge_ids_impl(its, [](){}); } -std::vector create_face_neighbors_index(const indexed_triangle_set &its, std::function throw_on_cancel_callback) +std::vector its_face_edge_ids(const indexed_triangle_set &its, std::function throw_on_cancel_callback) { - return create_face_neighbors_index_impl(its, throw_on_cancel_callback); + return its_face_edge_ids_impl(its, throw_on_cancel_callback); +} + +// Having the face neighbors available, assign unique edge IDs to face edges for chaining of polygons over slices. +std::vector its_face_edge_ids(const indexed_triangle_set &its, std::vector &face_neighbors, bool assign_unbound_edges, int *num_edges) +{ + // out elements are not initialized! + std::vector out(face_neighbors.size()); + int last_edge_id = 0; + for (int i = 0; i < int(face_neighbors.size()); ++ i) { + const stl_triangle_vertex_indices &triangle = its.indices[i]; + const Vec3i &neighbors = face_neighbors[i]; + for (int j = 0; j < 3; ++ j) { + int n = neighbors[j]; + if (n > i) { + const stl_triangle_vertex_indices &triangle2 = its.indices[n]; + int edge_id = last_edge_id ++; + Vec2i edge = its_triangle_edge(triangle, j); + // First find an edge with opposite orientation. + std::swap(edge(0), edge(1)); + int k = its_triangle_edge_index(triangle2, edge); + //FIXME is the following realistic? Could face_neighbors contain such faces? + // And if it does, do we want to produce the same edge ID for those mutually incorrectly oriented edges? + if (k == -1) { + // Second find an edge with the same orientation (the neighbor triangle may be flipped). + std::swap(edge(0), edge(1)); + k = its_triangle_edge_index(triangle2, edge); + } + assert(k >= 0); + out[i](j) = edge_id; + out[n](k) = edge_id; + } else if (n == -1) { + out[i](j) = assign_unbound_edges ? last_edge_id ++ : -1; + } else { + // Triangle shall never be neighbor of itself. + assert(n < i); + // Don't do anything, the neighbor will assign us an edge ID in later iterations. + } + } + } + if (num_edges) + *num_edges = last_edge_id; + return out; } // Merge duplicate vertices, return number of vertices removed. @@ -1219,14 +1261,14 @@ void VertexFaceIndex::create(const indexed_triangle_set &its) m_vertex_to_face_start.front() = 0; } -std::vector its_create_neighbors_index(const indexed_triangle_set &its) +std::vector its_face_neighbors(const indexed_triangle_set &its) { - return create_neighbors_index(ex_seq, its); + return create_face_neighbors_index(ex_seq, its); } -std::vector its_create_neighbors_index_par(const indexed_triangle_set &its) +std::vector its_face_neighbors_par(const indexed_triangle_set &its) { - return create_neighbors_index(ex_tbb, its); + return create_face_neighbors_index(ex_tbb, its); } } // namespace Slic3r diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index 8ac0e9871..7aef3055a 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -116,13 +116,14 @@ private: // Map from a face edge to a unique edge identifier or -1 if no neighbor exists. // Two neighbor faces share a unique edge identifier even if they are flipped. // Used for chaining slice lines into polygons. -std::vector create_face_neighbors_index(const indexed_triangle_set &its); -std::vector create_face_neighbors_index(const indexed_triangle_set &its, std::function throw_on_cancel_callback); +std::vector its_face_edge_ids(const indexed_triangle_set &its); +std::vector its_face_edge_ids(const indexed_triangle_set &its, std::function throw_on_cancel_callback); +// Having the face neighbors available, assign unique edge IDs to face edges for chaining of polygons over slices. +std::vector its_face_edge_ids(const indexed_triangle_set &its, std::vector &face_neighbors, bool assign_unbound_edges = false, int *num_edges = nullptr); // Create index that gives neighbor faces for each face. Ignores face orientations. -// TODO: naming... -std::vector its_create_neighbors_index(const indexed_triangle_set &its); -std::vector its_create_neighbors_index_par(const indexed_triangle_set &its); +std::vector its_face_neighbors(const indexed_triangle_set &its); +std::vector its_face_neighbors_par(const indexed_triangle_set &its); // After applying a transformation with negative determinant, flip the faces to keep the transformed mesh volume positive. void its_flip_triangles(indexed_triangle_set &its); @@ -153,6 +154,28 @@ void its_collect_mesh_projection_points_above(const indexed_triangle_set &its, c Polygon its_convex_hull_2d_above(const indexed_triangle_set &its, const Matrix3f &m, const float z); Polygon its_convex_hull_2d_above(const indexed_triangle_set &its, const Transform3f &t, const float z); +// Index of a vertex inside triangle_indices. +inline int its_triangle_vertex_index(const stl_triangle_vertex_indices &triangle_indices, int vertex_idx) +{ + return vertex_idx == triangle_indices[0] ? 0 : + vertex_idx == triangle_indices[1] ? 1 : + vertex_idx == triangle_indices[2] ? 2 : -1; +} + +inline Vec2i its_triangle_edge(const stl_triangle_vertex_indices &triangle_indices, int edge_idx) +{ + int next_edge_idx = (edge_idx == 2) ? 0 : edge_idx + 1; + return { triangle_indices[edge_idx], triangle_indices[next_edge_idx] }; +} + +// Index of an edge inside triangle. +inline int its_triangle_edge_index(const stl_triangle_vertex_indices &triangle_indices, const Vec2i &triangle_edge) +{ + return triangle_edge(0) == triangle_indices[0] && triangle_edge(1) == triangle_indices[1] ? 0 : + triangle_edge(0) == triangle_indices[1] && triangle_edge(1) == triangle_indices[2] ? 1 : + triangle_edge(0) == triangle_indices[2] && triangle_edge(1) == triangle_indices[0] ? 2 : -1; +} + using its_triangle = std::array; inline its_triangle its_triangle_vertices(const indexed_triangle_set &its, diff --git a/src/libslic3r/TriangleMeshSlicer.cpp b/src/libslic3r/TriangleMeshSlicer.cpp index f8b7d97c4..7779278fd 100644 --- a/src/libslic3r/TriangleMeshSlicer.cpp +++ b/src/libslic3r/TriangleMeshSlicer.cpp @@ -3,6 +3,7 @@ #include "Tesselate.hpp" #include "TriangleMesh.hpp" #include "TriangleMeshSlicer.hpp" +#include "Utils.hpp" #include #include @@ -14,6 +15,10 @@ #include +#ifndef NDEBUG +// #define EXPENSIVE_DEBUG_CHECKS +#endif // NDEBUG + #if 0 #define DEBUG #define _DEBUG @@ -64,6 +69,8 @@ public: bool is_seed_candidate() const { return (this->flags & NO_SEED) == 0 && ! this->skip(); } void set_no_seed(bool set) { if (set) this->flags |= NO_SEED; else this->flags &= ~NO_SEED; } + + void reverse() { std::swap(a, b); std::swap(a_id, b_id); std::swap(edge_a_id, edge_b_id); } // Inherits Point a, b // For each line end point, either {a,b}_id or {a,b}edge_a_id is set, the other is left to -1. @@ -82,7 +89,9 @@ public: // Two vertices are aligned with the cutting plane, the third vertex is above the cutting plane. Bottom, // All three vertices of a face are aligned with the cutting plane. - Horizontal + Horizontal, + // Edge + Slab, }; // feGeneral, feTop, feBottom, feHorizontal @@ -102,6 +111,15 @@ public: SKIP = 0x200, }; uint32_t flags { 0 }; + +#ifndef NDEBUG + enum class Source { + BottomPlane, + TopPlane, + Slab, + }; + Source source { Source::BottomPlane }; +#endif // NDEBUG }; using IntersectionLines = std::vector; @@ -119,7 +137,7 @@ static FacetSliceType slice_facet( // 3 vertices of the triangle, XY scaled. Z scaled or unscaled (same as slice_z). const stl_vertex *vertices, const stl_triangle_vertex_indices &indices, - const Vec3i &edge_neighbor, + const Vec3i &edge_ids, const int idx_vertex_lowest, const bool horizontal, IntersectionLine &line_out) @@ -138,7 +156,7 @@ static FacetSliceType slice_facet( { int k = (idx_vertex_lowest + j) % 3; int l = (k + 1) % 3; - edge_id = edge_neighbor(k); + edge_id = edge_ids(k); a_id = indices[k]; a = vertices + k; b_id = indices[l]; @@ -284,11 +302,11 @@ void slice_facet_at_zs( const std::vector &mesh_vertices, const TransformVertex &transform_vertex_fn, const stl_triangle_vertex_indices &indices, - const Vec3i &facet_neighbors, + const Vec3i &edge_ids, // Scaled or unscaled zs. If vertices have their zs scaled or transform_vertex_fn scales them, then zs have to be scaled as well. const std::vector &zs, std::vector &lines, - boost::mutex &lines_mutex) + std::array &lines_mutex) { stl_vertex vertices[3] { transform_vertex_fn(mesh_vertices[indices(0)]), transform_vertex_fn(mesh_vertices[indices(1)]), transform_vertex_fn(mesh_vertices[indices(2)]) }; @@ -299,43 +317,376 @@ void slice_facet_at_zs( // find layer extents auto min_layer = std::lower_bound(zs.begin(), zs.end(), min_z); // first layer whose slice_z is >= min_z auto max_layer = std::upper_bound(min_layer, zs.end(), max_z); // first layer whose slice_z is > max_z + int idx_vertex_lowest = (vertices[1].z() == min_z) ? 1 : ((vertices[2].z() == min_z) ? 2 : 0); for (auto it = min_layer; it != max_layer; ++ it) { IntersectionLine il; - int idx_vertex_lowest = (vertices[1].z() == min_z) ? 1 : ((vertices[2].z() == min_z) ? 2 : 0); - if (slice_facet(*it, vertices, indices, facet_neighbors, idx_vertex_lowest, min_z == max_z, il) == FacetSliceType::Slicing && - il.edge_type != IntersectionLine::FacetEdgeType::Horizontal) { - // Ignore horizontal triangles. Any valid horizontal triangle must have a vertical triangle connected, otherwise the part has zero volume. - boost::lock_guard l(lines_mutex); - lines[it - zs.begin()].emplace_back(il); + // Ignore horizontal triangles. Any valid horizontal triangle must have a vertical triangle connected, otherwise the part has zero volume. + if (min_z != max_z && slice_facet(*it, vertices, indices, edge_ids, idx_vertex_lowest, false, il) == FacetSliceType::Slicing) { + assert(il.edge_type != IntersectionLine::FacetEdgeType::Horizontal); + size_t slice_id = it - zs.begin(); + boost::lock_guard l(lines_mutex[slice_id >> 6]); + lines[slice_id].emplace_back(il); } } } template -inline std::vector slice_make_lines( +static inline std::vector slice_make_lines( const std::vector &vertices, const TransformVertex &transform_vertex_fn, const std::vector &indices, - const std::vector &face_neighbors, + const std::vector &face_edge_ids, const std::vector &zs, const ThrowOnCancel throw_on_cancel_fn) { std::vector lines(zs.size(), IntersectionLines()); - boost::mutex lines_mutex; + std::array lines_mutex; tbb::parallel_for( tbb::blocked_range(0, int(indices.size())), - [&vertices, &transform_vertex_fn, &indices, &face_neighbors, &zs, &lines, &lines_mutex, throw_on_cancel_fn](const tbb::blocked_range &range) { + [&vertices, &transform_vertex_fn, &indices, &face_edge_ids, &zs, &lines, &lines_mutex, throw_on_cancel_fn](const tbb::blocked_range &range) { for (int face_idx = range.begin(); face_idx < range.end(); ++ face_idx) { if ((face_idx & 0x0ffff) == 0) throw_on_cancel_fn(); - slice_facet_at_zs(vertices, transform_vertex_fn, indices[face_idx], face_neighbors[face_idx], zs, lines, lines_mutex); + slice_facet_at_zs(vertices, transform_vertex_fn, indices[face_idx], face_edge_ids[face_idx], zs, lines, lines_mutex); } } ); return lines; } +// For projecting triangle sets onto slice slabs. +struct SlabLines { + // Intersection lines of a slice with a triangle set, CCW oriented. + std::vector at_slice; + // Projections of triangle set boundary lines into layer below (for projection from the top) + // or into layer above (for projection from the bottom). + // In both cases the intersection liens are CCW oriented. + std::vector between_slices; +}; + +// Orientation of the face normal in regard to a XY plane pointing upwards. +enum class FaceOrientation : char { + // Z component of the normal is positive. + Up, + // Z component of the normal is negative. + Down, + // Z component of the normal is zero. + Vertical, + // Triangle is degenerate, thus its normal is undefined. We may want to slice the degenerate triangles + // because of the connectivity information they carry. + Degenerate +}; + +template +void slice_facet_with_slabs( + // Scaled or unscaled vertices. transform_vertex_fn may scale zs. + const std::vector &mesh_vertices, + const stl_triangle_vertex_indices &indices, + const Vec3i &facet_neighbors, + const Vec3i &facet_edge_ids, + // Increase edge_ids at the top plane of the slab edges by num_edges to allow chaining + // from bottom plane of the slab to the top plane of the slab and vice versa. + const int num_edges, + const std::vector &zs, + SlabLines &lines, + std::array &lines_mutex) +{ + stl_vertex vertices[3] { mesh_vertices[indices(0)], mesh_vertices[indices(1)], mesh_vertices[indices(2)] }; + + // find facet extents + const float min_z = fminf(vertices[0].z(), fminf(vertices[1].z(), vertices[2].z())); + const float max_z = fmaxf(vertices[0].z(), fmaxf(vertices[1].z(), vertices[2].z())); + const bool horizontal = min_z == max_z; + + // find layer extents + auto min_layer = std::lower_bound(zs.begin(), zs.end(), min_z); // first layer whose slice_z is >= min_z + auto max_layer = std::upper_bound(min_layer, zs.end(), max_z); // first layer whose slice_z is > max_z + assert(min_layer == zs.end() ? max_layer == zs.end() : *min_layer >= min_z); + assert(max_layer == zs.end() || *max_layer > max_z); + + auto emit_slab_edge = [&lines, &lines_mutex, num_edges](IntersectionLine il, size_t slab_id, bool reverse) { + if (reverse) + il.reverse(); + boost::lock_guard l(lines_mutex[(slab_id + 32) >> 6]); + lines.between_slices[slab_id].emplace_back(il); + }; + + if (min_layer == max_layer || horizontal) { + // Horizontal face or a nearly horizontal face that fits between two layers or below the bottom most or above the top most layer. + assert(horizontal || zs.empty() || max_z < zs.front() || min_z > zs.back() || + (min_layer == max_layer && min_layer != zs.end() && min_layer != zs.begin() && *(min_layer - 1) < min_z && *min_layer > max_z)); + size_t slab_id; + if (horizontal && min_layer != zs.end() && *min_layer == min_z) { + // slicing the triangle. + assert(min_layer != max_layer); + slab_id = min_layer - zs.begin(); + } else { + if (ProjectionFromTop) { + if (max_layer == zs.begin()) { + // Not slicing the triangle and it is below the lowest layer. + return; + } else { + // Not slicing the triangle and it could be projected into a slab. + slab_id = max_layer - zs.begin(); + } + } else { + // projection from bottom + if (min_layer == zs.end()) { + // Not slicing the triangle and it is above the highest layer. + return; + } else { + // Not slicing the triangle and it could be projected into a slab. + slab_id = min_layer - zs.begin(); + } + } + } + if (ProjectionFromTop) + -- slab_id; + for (int iedge = 0; iedge < 3; ++ iedge) + if (facet_neighbors(iedge) == -1) { + int i = iedge; + int j = next_idx_modulo(i, 3); + assert(ProjectionFromTop ? vertices[i].z() >= zs[slab_id] : vertices[i].z() <= zs[slab_id]); + assert(ProjectionFromTop ? vertices[j].z() >= zs[slab_id] : vertices[j].z() <= zs[slab_id]); + emit_slab_edge( + IntersectionLine { + { to_2d(vertices[i]).cast(), to_2d(vertices[j]).cast() }, + indices(i), indices(j), -1, -1, IntersectionLine::FacetEdgeType::Slab + }, + slab_id, ! ProjectionFromTop); + } + } else { + int idx_vertex_lowest = (vertices[1].z() == min_z) ? 1 : ((vertices[2].z() == min_z) ? 2 : 0); + IntersectionLine il_prev; + for (auto it = min_layer; it != max_layer; ++ it) { + IntersectionLine il; + auto type = slice_facet(*it, vertices, indices, facet_edge_ids, idx_vertex_lowest, false, il); + if (type == FacetSliceType::Slicing) { + if (! ProjectionFromTop) + il.reverse(); + size_t line_id = it - zs.begin(); + boost::lock_guard l(lines_mutex[line_id >> 6]); + lines.at_slice[line_id].emplace_back(il); + } else if (type == FacetSliceType::Cutting) { + // One edge is in plane, the 3rd vertex is above the plane. In case this edge has a neighbor, + // its opposite edge is added by slicing the neighboring triangle. Only if this edge has no neighbor, + // add this edge to lines. + assert(il.a_id != -1 && il.b_id != -1); + assert(il.edge_a_id == -1 && il.edge_b_id == -1); + // Identify edge ID from the edge vertices. + int edge_id = il.a_id == indices(0) ? 0 : il.a_id == indices(1) ? 1 : 2; + assert(il.a_id == indices(edge_id)); + assert(il.b_id == indices(next_idx_modulo(edge_id, 3))); + if (facet_neighbors(edge_id) == -1) { + // Open edge. + if (! ProjectionFromTop) + il.reverse(); + size_t line_id = it - zs.begin(); + boost::lock_guard l(lines_mutex[line_id >> 6]); + lines.at_slice[line_id].emplace_back(il); + } + } + if (! ProjectionFromTop || it != zs.begin()) { + size_t slab_id = it - zs.begin(); + if (ProjectionFromTop) + -- slab_id; + // Try to project unbound edges. + for (int iedge = 0; iedge < 3; ++ iedge) + if (facet_neighbors(iedge) == -1) { + // Unbound edge. + int edge_id = facet_edge_ids(iedge); + bool intersects_this = il.edge_a_id == edge_id || il.edge_b_id == edge_id; + bool intersects_prev = il_prev.edge_a_id == edge_id || il_prev.edge_b_id == edge_id; + int i = iedge; + int j = next_idx_modulo(i, 3); + assert((! intersects_this && ! intersects_prev) || vertices[j].z() != vertices[i].z()); + bool edge_up = vertices[j].z() > vertices[i].z(); + if (intersects_this && intersects_prev) { + // Intersects both, emit the segment between these intersections. + Line l(il_prev.edge_a_id == edge_id ? il_prev.a : il_prev.b, + il.edge_a_id == edge_id ? il.a : il.b); + emit_slab_edge( + IntersectionLine { l, -1, -1, edge_id, edge_id + num_edges, IntersectionLine::FacetEdgeType::Slab }, + slab_id, ProjectionFromTop != edge_up); + } else if (intersects_this) { + // Intersects just the top plane, may touch the bottom plane. + assert((vertices[i].z() > *it && vertices[j].z() < *it) || (vertices[i].z() < *it && vertices[j].z() > *it)); + assert(il.edge_a_id == edge_id || il.edge_b_id == edge_id); + emit_slab_edge( + IntersectionLine { { + to_2d(edge_up ? vertices[i] : vertices[j]).cast(), + il.edge_a_id == edge_id ? il.a : il.b + }, + edge_up ? indices(i) : indices(j), -1, -1, edge_id + num_edges, IntersectionLine::FacetEdgeType::Slab + }, + slab_id, ProjectionFromTop != edge_up); + } else if (intersects_prev) { + // Intersects just the bottom plane, may touch the top vertex. + assert(*it <= max_z); +#ifndef NDEBUG + { + auto it_prev = it; + -- it_prev; + assert((vertices[i].z() > *it_prev && vertices[j].z() < *it_prev) || (vertices[i].z() < *it_prev && vertices[j].z() > *it_prev)); + } +#endif // NDEBUG + emit_slab_edge( + IntersectionLine { { + il_prev.edge_a_id == edge_id ? il_prev.a : il_prev.b, + to_2d(edge_up ? vertices[j] : vertices[i]).cast() + }, + -1, edge_up ? indices(j) : indices(i), edge_id, -1, IntersectionLine::FacetEdgeType::Slab + }, + slab_id, ProjectionFromTop != edge_up); + } else if (float zi = vertices[i].z(), zj = vertices[j].z(); zi < *it || zj < *it) { + // The edge does not intersect the current plane and it does not intersect the previous plane either. + // Both points have to be inside the slab. + assert(zi <= *it && zj <= *it); +#ifndef NDEBUG + if (type == FacetSliceType::Slicing || type == FacetSliceType::Cutting) { + // Such edge should already be processed in the code above, it shall be skipped here. + assert(indices(i) != il.b_id || indices(j) != il.a_id); + assert(indices(i) != il.a_id || indices(j) != il.b_id); + } +#endif // NDEBUG + // Is it inside the slab? + bool inside_slab = true; + if (it != min_layer) { + auto it_prev = it; + -- it_prev; + assert(*it_prev >= *min_layer && *it_prev < *it); + // One point may touch the plane below, the other must not. + inside_slab = zi > *it_prev || zj > *it_prev; + // Both points have to be inside the slab. + assert(! inside_slab || (zi >= *it_prev && zj >= *it_prev)); + } + if (inside_slab) { + assert(ProjectionFromTop ? vertices[i].z() >= zs[slab_id] : vertices[i].z() <= zs[slab_id]); + assert(ProjectionFromTop ? vertices[j].z() >= zs[slab_id] : vertices[j].z() <= zs[slab_id]); + emit_slab_edge( + IntersectionLine { + { to_2d(vertices[i]).cast(), to_2d(vertices[j]).cast() }, + indices(i), indices(j), -1, -1, IntersectionLine::FacetEdgeType::Slab + }, + slab_id, ! ProjectionFromTop); + } + } + } + } + il_prev = il; + } + if (ProjectionFromTop || max_layer != zs.end()) { + // Try to project unbound edges above the last slicing plane to the last slab. + // Last layer slicing this triangle. + auto it = max_layer - 1; + size_t slab_id = max_layer - zs.begin(); + if (ProjectionFromTop) + -- slab_id; + for (int iedge = 0; iedge < 3; ++ iedge) + if (facet_neighbors(iedge) == -1) { + // Unbound edge. + int edge_id = facet_edge_ids(iedge); + int i = iedge; + int j = next_idx_modulo(i, 3); + if (il_prev.edge_a_id == edge_id || il_prev.edge_b_id == edge_id) { + // Intersects just the bottom plane, may touch the top vertex. + assert((vertices[i].z() > *it && vertices[j].z() < *it) || (vertices[i].z() < *it && vertices[j].z() > *it)); + bool edge_up = vertices[j].z() > vertices[i].z(); + emit_slab_edge( + IntersectionLine{ { + il_prev.edge_a_id == edge_id ? il_prev.a : il_prev.b, + to_2d(edge_up ? vertices[j] : vertices[i]).cast() + }, + -1, edge_up ? indices(j) : indices(i), edge_id, -1, IntersectionLine::FacetEdgeType::Slab + }, + slab_id, ProjectionFromTop != edge_up); + } else if (float zi = vertices[i].z(), zj = vertices[j].z(); zi > *it || zj > *it) { + // The edge does not intersect the current plane and it does not intersect the previous plane either. + // Both points have to be inside the slab. + assert(zi >= *it && zj >= *it); + assert(max_layer == zs.end() || (zi < *max_layer && zj < *max_layer)); + emit_slab_edge( + IntersectionLine{ + { to_2d(vertices[i]).cast(), to_2d(vertices[j]).cast() }, + indices(i), indices(j), -1, -1, IntersectionLine::FacetEdgeType::Slab + }, + slab_id, ! ProjectionFromTop); + } + } + } + } +} + +// used by slice_mesh_slabs() to produce on-slice lines and between-slices lines. +// Returning top / bottom SlabLines. +template +inline std::pair slice_slabs_make_lines( + const std::vector &vertices, + const std::vector &indices, + const std::vector &face_neighbors, + const std::vector &face_edge_ids, + // Total number of edges. All face_edge_ids are lower than num_edges. + // num_edges will be used to distinguish between intersections with the top and bottom plane. + const int num_edges, + const std::vector &face_orientation, + const std::vector &zs, + bool top, + bool bottom, + const ThrowOnCancel throw_on_cancel_fn) +{ + std::pair out; + SlabLines &lines_top = out.first; + SlabLines &lines_bottom = out.second; + std::array lines_mutex_top; + std::array lines_mutex_bottom; + + if (top) { + lines_top.at_slice.assign(zs.size(), IntersectionLines()); + lines_top.between_slices.assign(zs.size(), IntersectionLines()); + } + if (bottom) { + lines_bottom.at_slice.assign(zs.size(), IntersectionLines()); + lines_bottom.between_slices.assign(zs.size(), IntersectionLines()); + } + + tbb::parallel_for( + tbb::blocked_range(0, int(indices.size())), + [&vertices, &indices, &face_neighbors, &face_edge_ids, num_edges, &face_orientation, &zs, top, bottom, &lines_top, &lines_bottom, &lines_mutex_top, &lines_mutex_bottom, throw_on_cancel_fn] + (const tbb::blocked_range &range) { + for (int face_idx = range.begin(); face_idx < range.end(); ++ face_idx) { + if ((face_idx & 0x0ffff) == 0) + throw_on_cancel_fn(); + FaceOrientation fo = face_orientation[face_idx]; + Vec3i edge_ids = face_edge_ids[face_idx]; + if (top && (fo == FaceOrientation::Up || fo == FaceOrientation::Degenerate)) { + Vec3i neighbors = face_neighbors[face_idx]; + // Reset neighborship of this triangle in case the other triangle is oriented backwards from this one. + for (int i = 0; i < 3; ++ i) + if (neighbors(i) != -1) { + FaceOrientation fo2 = face_orientation[neighbors(i)]; + if (fo2 != FaceOrientation::Up && fo2 != FaceOrientation::Degenerate) + neighbors(i) = -1; + } + slice_facet_with_slabs(vertices, indices[face_idx], neighbors, edge_ids, num_edges, zs, lines_top, lines_mutex_top); + } + if (bottom && (fo == FaceOrientation::Down || fo == FaceOrientation::Degenerate)) { + Vec3i neighbors = face_neighbors[face_idx]; + // Reset neighborship of this triangle in case the other triangle is oriented backwards from this one. + for (int i = 0; i < 3; ++ i) + if (neighbors(i) != -1) { + FaceOrientation fo2 = face_orientation[neighbors(i)]; + if (fo2 != FaceOrientation::Down && fo2 != FaceOrientation::Degenerate) + neighbors(i) = -1; + } + slice_facet_with_slabs(vertices, indices[face_idx], neighbors, edge_ids, num_edges, zs, lines_bottom, lines_mutex_bottom); + } + } + } + ); + return out; +} + #if 0 //FIXME Should this go away? For valid meshes the function slice_facet() returns Slicing // and sets edges of vertical triangles to produce only a single edge per pair of neighbor faces. @@ -495,6 +846,7 @@ static void chain_lines_by_triangle_connectivity(IntersectionLines &lines, Polyg if ((first_line->edge_a_id != -1 && first_line->edge_a_id == last_line->edge_b_id) || (first_line->a_id != -1 && first_line->a_id == last_line->b_id)) { // The current loop is complete. Add it to the output. + assert(first_line->a == last_line->b); loops.emplace_back(std::move(loop_pts)); #ifdef SLIC3R_TRIANGLEMESH_DEBUG printf(" Discovered %s polygon of %d points\n", (p.is_counter_clockwise() ? "ccw" : "cw"), (int)p.points.size()); @@ -513,6 +865,7 @@ static void chain_lines_by_triangle_connectivity(IntersectionLines &lines, Polyg next_line->edge_a_id, next_line->edge_b_id, next_line->a_id, next_line->b_id, next_line->a.x, next_line->a.y, next_line->b.x, next_line->b.y); */ + assert(last_line->b == next_line->a); loop_pts.emplace_back(next_line->a); last_line = next_line; next_line->set_skip(); @@ -892,6 +1245,106 @@ static std::vector make_loops( return layers; } +// used by slice_mesh_slabs() to produce loops from on-slice lines and between-slices lines. +template +static std::vector make_slab_loops( + // Lines will have their flags modified. + SlabLines &lines, + // To differentiate edge IDs of the top plane from the edge IDs of the bottom plane for chaining. + int num_edges, + ThrowOnCancel throw_on_cancel) +{ + assert(! lines.at_slice.empty() && lines.at_slice.size() == lines.between_slices.size()); + std::vector layers; + layers.resize(lines.at_slice.size()); + tbb::parallel_for( + tbb::blocked_range(0, int(lines.at_slice.size())), + [&lines, num_edges, &layers, throw_on_cancel](const tbb::blocked_range &range) { + for (int line_idx = range.begin(); line_idx < range.end(); ++ line_idx) { + if ((line_idx & 0x0ffff) == 0) + throw_on_cancel(); + IntersectionLines in; + size_t nlines = lines.between_slices[line_idx].size(); + int slice_below = ProjectionFromTop ? line_idx : line_idx - 1; + int slice_above = ProjectionFromTop ? line_idx + 1 : line_idx; + bool has_slice_below = ProjectionFromTop || line_idx > 0; + bool has_slice_above = ! ProjectionFromTop || line_idx + 1 < int(lines.at_slice.size()); + if (has_slice_below) + nlines += lines.at_slice[slice_below].size(); + if (has_slice_above) + nlines += lines.at_slice[slice_above].size(); + if (nlines) { + in.reserve(nlines); + if (has_slice_below) { + for (const IntersectionLine &l : lines.at_slice[slice_below]) + if (l.edge_type != IntersectionLine::FacetEdgeType::Top) { + in.emplace_back(l); +#ifndef NDEBUG + in.back().source = IntersectionLine::Source::BottomPlane; +#endif // NDEBUG + } + } + { + // Edges in between slice_below and slice_above. +#ifndef NDEBUG + size_t old_size = in.size(); +#endif // NDEBUG + // Edge IDs of end points on in-between lines that touch the layer above are already increased with num_edges. + append(in, lines.between_slices[line_idx]); +#ifndef NDEBUG + for (auto it = in.begin() + old_size; it != in.end(); ++ it) { + assert(it->edge_type == IntersectionLine::FacetEdgeType::Slab); + it->source = IntersectionLine::Source::Slab; + } +#endif // NDEBUG + } + if (has_slice_above) { + for (const IntersectionLine &lsrc : lines.at_slice[slice_above]) + if (lsrc.edge_type != IntersectionLine::FacetEdgeType::Bottom) { + in.emplace_back(lsrc); + auto &l = in.back(); + l.reverse(); + // Differentiate edge IDs of the top plane from the edge IDs of the bottom plane for chaining. + if (l.edge_a_id >= 0) + l.edge_a_id += num_edges; + if (l.edge_b_id >= 0) + l.edge_b_id += num_edges; +#ifndef NDEBUG + l.source = IntersectionLine::Source::TopPlane; +#endif // NDEBUG + } + } + if (! in.empty()) { +#ifdef SLIC3R_DEBUG_SLICE_PROCESSING + BoundingBox bbox_svg; + { + static int iRun = 0; + for (const IntersectionLine &line : in) { + bbox_svg.merge(line.a); + bbox_svg.merge(line.b); + } + SVG svg(debug_out_path("make_slab_loops-%d.svg", iRun++).c_str(), bbox_svg); + for (const IntersectionLine& line : in) { + const char* color = line.source == IntersectionLine::Source::BottomPlane ? "red" : line.source == IntersectionLine::Source::TopPlane ? "blue" : "green"; + svg.draw(line, color, scaled(0.1)); + } + svg.Close(); + } +#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ + Polygons &loops = layers[line_idx]; + std::vector open_polylines; + chain_lines_by_triangle_connectivity(in, loops, open_polylines); + assert(! loops.empty()); + assert(open_polylines.empty()); + } + } + } + } + ); + + return layers; +} + // Used to cut the mesh into two halves. static ExPolygons make_expolygons_simple(std::vector &lines) { @@ -1055,6 +1508,44 @@ static void make_expolygons(const Polygons &loops, const float closing_radius, c union_ex(loops, fill_type)); } +// Make a trafo for transforming the vertices. Scale up in XY, not in Z. +static inline Transform3f make_trafo_for_slicing(const Transform3d &trafo) +{ + auto t = trafo; + static constexpr const double s = 1. / SCALING_FACTOR; + t.prescale(Vec3d(s, s, 1.)); + return t.cast(); +} + +static inline bool is_identity(const Transform3d &trafo) +{ + return trafo.matrix() == Transform3d::Identity().matrix(); +} + +static std::vector transform_mesh_vertices_for_slicing(const indexed_triangle_set &mesh, const Transform3d &trafo) +{ + // Copy and scale vertices in XY, don't scale in Z. + // Possibly apply the transformation. + static constexpr const double s = 1. / SCALING_FACTOR; + std::vector out(mesh.vertices); + if (is_identity(trafo)) { + // Identity. + for (stl_vertex &v : out) { + // Scale just XY, leave Z unscaled. + v.x() *= float(s); + v.y() *= float(s); + } + } else { + // Transform the vertices, scale up in XY, not in Y. + auto t = trafo; + t.prescale(Vec3d(s, s, 1.)); + auto tf = t.cast(); + for (stl_vertex &v : out) + v = tf * v; + } + return out; +} + std::vector slice_mesh( const indexed_triangle_set &mesh, // Unscaled Zs @@ -1071,41 +1562,23 @@ std::vector slice_mesh( // Instead of edge identifiers, one shall use a sorted pair of edge vertex indices. // However facets_edges assigns a single edge ID to two triangles only, thus when factoring facets_edges out, one will have // to make sure that no code relies on it. - std::vector facets_edges = create_face_neighbors_index(mesh); - const bool identity = params.trafo.matrix() == Transform3d::Identity().matrix(); - static constexpr const double s = 1. / SCALING_FACTOR; + std::vector face_edge_ids = its_face_edge_ids(mesh); if (zs.size() <= 1) { // It likely is not worthwile to copy the vertices. Apply the transformation in place. - if (identity) + if (is_identity(params.trafo)) { lines = slice_make_lines( mesh.vertices, [](const Vec3f &p) { return Vec3f(scaled(p.x()), scaled(p.y()), p.z()); }, - mesh.indices, facets_edges, zs, throw_on_cancel); - else { - // Transform the vertices, scale up in XY, not in Y. - auto t = params.trafo; - t.prescale(Vec3d(s, s, 1.)); - auto tf = t.cast(); - lines = slice_make_lines(mesh.vertices, [tf](const Vec3f &p) { return tf * p; }, mesh.indices, facets_edges, zs, throw_on_cancel); + mesh.indices, face_edge_ids, zs, throw_on_cancel); + } else { + // Transform the vertices, scale up in XY, not in Z. + Transform3f tf = make_trafo_for_slicing(params.trafo); + lines = slice_make_lines(mesh.vertices, [tf](const Vec3f &p) { return tf * p; }, mesh.indices, face_edge_ids, zs, throw_on_cancel); } } else { - // Copy and scale vertices in XY, don't scale in Z. - // Possibly apply the transformation. - std::vector vertices(mesh.vertices); - if (identity) { - for (stl_vertex &v : vertices) { - // Scale just XY, leave Z unscaled. - v.x() *= float(s); - v.y() *= float(s); - } - } else { - // Transform the vertices, scale up in XY, not in Y. - auto t = params.trafo; - t.prescale(Vec3d(s, s, 1.)); - auto tf = t.cast(); - for (stl_vertex &v : vertices) - v = tf * v; - } - lines = slice_make_lines(vertices, [](const Vec3f &p) { return p; }, mesh.indices, facets_edges, zs, throw_on_cancel); + // Copy and scale vertices in XY, don't scale in Z. Possibly apply the transformation. + lines = slice_make_lines( + transform_mesh_vertices_for_slicing(mesh, params.trafo), + [](const Vec3f &p) { return p; }, mesh.indices, face_edge_ids, zs, throw_on_cancel); } } @@ -1194,6 +1667,105 @@ std::vector slice_mesh_ex( return layers; } +// Slice a triangle set with a set of Z slabs (thick layers). +// The effect is similar to producing the usual top / bottom layers from a sliced mesh by +// subtracting layer[i] from layer[i - 1] for the top surfaces resp. +// subtracting layer[i] from layer[i + 1] for the bottom surfaces, +// with the exception that the triangle set this function processes may not cover the whole top resp. bottom surface. +// top resp. bottom surfaces are calculated only if out_top resp. out_bottom is not null. +void slice_mesh_slabs( + const indexed_triangle_set &mesh, + // Unscaled Zs + const std::vector &zs, + const Transform3d &trafo, + std::vector *out_top, + std::vector *out_bottom, + std::function throw_on_cancel) +{ + BOOST_LOG_TRIVIAL(debug) << "slice_mesh_slabs to polygons"; + +#ifdef EXPENSIVE_DEBUG_CHECKS + { + // Verify that the vertices are unique. + auto v = mesh.vertices; + std::sort(v.begin(), v.end(), [](auto &l, auto &r) { + return l.x() < r.x() || (l.x() == r.x() && (l.y() < r.y() || (l.y() == r.y() && l.z() < r.z()))); + }); + size_t num_duplicates = v.end() - std::unique(v.begin(), v.end()); + assert(num_duplicates == 0); + } + { + // Verify that there are no T-joints. + for (const auto &tri : mesh.indices) + for (int i = 0; i < 3; ++ i) { + int j = next_idx_modulo(i, 3); + int k = next_idx_modulo(j, 3); + auto &v1 = mesh.vertices[tri(i)]; + auto &v2 = mesh.vertices[tri(j)]; + auto &v3 = mesh.vertices[tri(k)]; + for (auto &pt : mesh.vertices) + if (&pt != &v1 && &pt != &v2) { + assert(pt != v1 && pt != v2); + assert((pt - v1).norm() > EPSILON); + assert((pt - v2).norm() > EPSILON); + auto l2 = (v2 - v1).squaredNorm(); + assert(l2 > 0); + auto t = (pt - v1).dot(v2 - v1); + if (t > 0 && t < l2) { + auto d2 = (pt - v1).squaredNorm() - sqr(t) / l2; + auto d = sqrt(std::max(d2, 0.f)); + if (&pt == &v3) { + if (d < EPSILON) + printf("Degenerate triangle!\n"); + } else { + assert(d > EPSILON); + } + } + } + } + } +#endif // EXPENSIVE_DEBUG_CHECKS + + std::vector vertices_transformed = transform_mesh_vertices_for_slicing(mesh, trafo); + + std::vector face_orientation(mesh.indices.size(), FaceOrientation::Up); + for (const stl_triangle_vertex_indices &tri : mesh.indices) { + const Vec3f fa = vertices_transformed[tri(0)]; + const Vec3f fb = vertices_transformed[tri(1)]; + const Vec3f fc = vertices_transformed[tri(2)]; + assert(fa != fb && fa != fc && fb != fc); + const Point a = to_2d(fa).cast(); + const Point b = to_2d(fb).cast(); + const Point c = to_2d(fc).cast(); + const int64_t d = cross2((b - a).cast(), (c - b).cast()); + FaceOrientation fo = FaceOrientation::Vertical; + if (d > 0) + fo = FaceOrientation::Up; + else if (d < 0) + fo = FaceOrientation::Down; + else { + // Is the triangle vertical or degenerate? + assert(d == 0); + fo = fa == fb || fa == fc || fb == fc ? FaceOrientation::Degenerate : FaceOrientation::Vertical; + } + face_orientation[&tri - mesh.indices.data()] = fo; + } + + std::vector face_neighbors = its_face_neighbors_par(mesh); + int num_edges; + std::vector face_edge_ids = its_face_edge_ids(mesh, face_neighbors, true, &num_edges); + std::pair lines = slice_slabs_make_lines( + vertices_transformed, mesh.indices, face_neighbors, face_edge_ids, num_edges, face_orientation, zs, + out_top != nullptr, out_bottom != nullptr, throw_on_cancel); + + throw_on_cancel(); + + if (out_top) + *out_top = make_slab_loops(lines.first, num_edges, throw_on_cancel); + if (out_bottom) + *out_bottom = make_slab_loops(lines.second, num_edges, throw_on_cancel); +} + // Remove duplicates of slice_vertices, optionally triangulate the cut. static void triangulate_slice( indexed_triangle_set &its, @@ -1308,7 +1880,7 @@ void cut_mesh(const indexed_triangle_set &mesh, float z, indexed_triangle_set *u // To triangulate the caps after slicing. IntersectionLines upper_lines, lower_lines; std::vector upper_slice_vertices, lower_slice_vertices; - std::vector facets_edges = create_face_neighbors_index(mesh); + std::vector facets_edge_ids = its_face_edge_ids(mesh); for (int facet_idx = 0; facet_idx < int(mesh.indices.size()); ++ facet_idx) { const stl_triangle_vertex_indices &facet = mesh.indices[facet_idx]; @@ -1329,7 +1901,7 @@ void cut_mesh(const indexed_triangle_set &mesh, float z, indexed_triangle_set *u dst.y() = scale_(src.y()); dst.z() = src.z(); } - slice_type = slice_facet(z, vertices_scaled, mesh.indices[facet_idx], facets_edges[facet_idx], idx_vertex_lowest, min_z == max_z, line); + slice_type = slice_facet(z, vertices_scaled, mesh.indices[facet_idx], facets_edge_ids[facet_idx], idx_vertex_lowest, min_z == max_z, line); } if (slice_type != FacetSliceType::NoSlice) { @@ -1371,8 +1943,8 @@ void cut_mesh(const indexed_triangle_set &mesh, float z, indexed_triangle_set *u // get vertices starting from the isolated one int iv = isolated_vertex; stl_vertex v0v1, v2v0; - assert(facets_edges[facet_idx](iv) == line.edge_a_id ||facets_edges[facet_idx](iv) == line.edge_b_id); - if (facets_edges[facet_idx](iv) == line.edge_a_id) { + assert(facets_edge_ids[facet_idx](iv) == line.edge_a_id ||facets_edge_ids[facet_idx](iv) == line.edge_b_id); + if (facets_edge_ids[facet_idx](iv) == line.edge_a_id) { v0v1 = to_3d(unscaled(line.a), z); v2v0 = to_3d(unscaled(line.b), z); } else { diff --git a/src/libslic3r/TriangleMeshSlicer.hpp b/src/libslic3r/TriangleMeshSlicer.hpp index ad0fd42da..9b02f5573 100644 --- a/src/libslic3r/TriangleMeshSlicer.hpp +++ b/src/libslic3r/TriangleMeshSlicer.hpp @@ -77,6 +77,21 @@ inline std::vector slice_mesh_ex( return slice_mesh_ex(mesh, zs, params, throw_on_cancel); } +// Slice a triangle set with a set of Z slabs (thick layers). +// The effect is similar to producing the usual top / bottom layers from a sliced mesh by +// subtracting layer[i] from layer[i - 1] for the top surfaces resp. +// subtracting layer[i] from layer[i + 1] for the bottom surfaces, +// with the exception that the triangle set this function processes may not cover the whole top resp. bottom surface. +// top resp. bottom surfaces are calculated only if out_top resp. out_bottom is not null. +void slice_mesh_slabs( + const indexed_triangle_set &mesh, + // Unscaled Zs + const std::vector &zs, + const Transform3d &trafo, + std::vector *out_top, + std::vector *out_bottom, + std::function throw_on_cancel); + void cut_mesh( const indexed_triangle_set &mesh, float z, diff --git a/src/libslic3r/TriangleSelector.cpp b/src/libslic3r/TriangleSelector.cpp index d7efd09b1..24fac0b7b 100644 --- a/src/libslic3r/TriangleSelector.cpp +++ b/src/libslic3r/TriangleSelector.cpp @@ -3,9 +3,9 @@ #include -#ifndef _NDEBUG +#ifndef NDEBUG #define EXPENSIVE_DEBUG_CHECKS -#endif // _NDEBUG +#endif // NDEBUG namespace Slic3r { @@ -19,7 +19,7 @@ static inline Vec3i root_neighbors(const TriangleMesh &mesh, int triangle_id) return neighbors; } -#ifndef _NDEBUG +#ifndef NDEBUG bool TriangleSelector::verify_triangle_midpoints(const Triangle &tr) const { for (int i = 0; i < 3; ++ i) { @@ -57,7 +57,7 @@ bool TriangleSelector::verify_triangle_neighbors(const Triangle &tr, const Vec3i } return true; } -#endif // _NDEBUG +#endif // NDEBUG // sides_to_split==-1 : just restore previous split void TriangleSelector::Triangle::set_division(int sides_to_split, int special_side_idx) @@ -308,12 +308,12 @@ int TriangleSelector::triangle_midpoint_or_allocate(int itriangle, int vertexi, } assert(m_vertices[midpoint].ref_cnt == 0); } else { -#ifndef _NDEBUG +#ifndef NDEBUG Vec3f c1 = 0.5f * (m_vertices[vertexi].v + m_vertices[vertexj].v); Vec3f c2 = m_vertices[midpoint].v; float d = (c2 - c1).norm(); assert(std::abs(d) < EPSILON); -#endif // _NDEBUG +#endif // NDEBUG assert(m_vertices[midpoint].ref_cnt > 0); } return midpoint; @@ -810,19 +810,23 @@ int TriangleSelector::push_triangle(int a, int b, int c, int source_triangle, co void TriangleSelector::perform_split(int facet_idx, const Vec3i &neighbors, EnforcerBlockerType old_state) { // Reserve space for the new triangles upfront, so that the reference to this triangle will not change. - m_triangles.reserve(m_triangles.size() + m_triangles[facet_idx].number_of_split_sides() + 1); + { + size_t num_triangles_new = m_triangles.size() + m_triangles[facet_idx].number_of_split_sides() + 1; + if (m_triangles.capacity() < num_triangles_new) + m_triangles.reserve(next_highest_power_of_2(num_triangles_new)); + } Triangle &tr = m_triangles[facet_idx]; assert(tr.is_split()); // indices of triangle vertices -#ifdef _NDEBUG +#ifdef NDEBUG boost::container::small_vector verts_idxs; -#else // _NDEBUG +#else // NDEBUG // For easier debugging. std::vector verts_idxs; verts_idxs.reserve(6); -#endif // _NDEBUG +#endif // NDEBUG for (int j=0, idx = tr.special_side(); j<3; ++j, idx = next_idx_modulo(idx, 3)) verts_idxs.push_back(tr.verts_idxs[idx]); @@ -861,13 +865,13 @@ void TriangleSelector::perform_split(int facet_idx, const Vec3i &neighbors, Enfo break; } -#ifndef _NDEBUG +#ifndef NDEBUG assert(this->verify_triangle_neighbors(tr, neighbors)); for (int i = 0; i <= tr.number_of_split_sides(); ++i) { Vec3i n = this->child_neighbors(tr, neighbors, i); assert(this->verify_triangle_neighbors(m_triangles[tr.children[i]], n)); } -#endif // _NDEBUG +#endif // NDEBUG } bool TriangleSelector::has_facets(EnforcerBlockerType state) const @@ -918,7 +922,7 @@ indexed_triangle_set TriangleSelector::get_facets_strict(EnforcerBlockerType sta ++ num_vertices; out.vertices.reserve(num_vertices); std::vector vertex_map(m_vertices.size(), -1); - for (int i = 0; i < m_vertices.size(); ++ i) + for (size_t i = 0; i < m_vertices.size(); ++ i) if (const Vertex &v = m_vertices[i]; v.ref_cnt > 0) { vertex_map[i] = int(out.vertices.size()); out.vertices.emplace_back(v.v); @@ -958,10 +962,13 @@ void TriangleSelector::get_facets_split_by_tjoints(const Vec3i vertices, const V this->triangle_midpoint(neighbors(1), vertices(2), vertices(1)), this->triangle_midpoint(neighbors(2), vertices(0), vertices(2))); int splits = (midpoints(0) != -1) + (midpoints(1) != -1) + (midpoints(2) != -1); - if (splits == 0) { + switch (splits) { + case 0: // Just emit this triangle. - out_triangles.emplace_back(vertices(0), midpoints(0), midpoints(2)); - } else if (splits == 1) { + out_triangles.emplace_back(vertices(0), vertices(1), vertices(2)); + break; + case 1: + { // Split to two triangles int i = midpoints(0) != -1 ? 2 : midpoints(1) != -1 ? 0 : 1; int j = next_idx_modulo(i, 3); @@ -969,16 +976,19 @@ void TriangleSelector::get_facets_split_by_tjoints(const Vec3i vertices, const V this->get_facets_split_by_tjoints( { vertices(i), vertices(j), midpoints(j) }, { neighbors(i), - this->neighbor_child(neighbors(j), vertices(j), vertices(k), Partition::Second), + this->neighbor_child(neighbors(j), vertices(k), vertices(j), Partition::Second), -1 }, out_triangles); this->get_facets_split_by_tjoints( - { midpoints(j), vertices(j), vertices(k) }, - { this->neighbor_child(neighbors(j), vertices(j), vertices(k), Partition::First), + { midpoints(j), vertices(k), vertices(i) }, + { this->neighbor_child(neighbors(j), vertices(k), vertices(j), Partition::First), neighbors(k), -1 }, out_triangles); - } else if (splits == 2) { + break; + } + case 2: + { // Split to three triangles. int i = midpoints(0) == -1 ? 2 : midpoints(1) == -1 ? 0 : 1; int j = next_idx_modulo(i, 3); @@ -1000,7 +1010,10 @@ void TriangleSelector::get_facets_split_by_tjoints(const Vec3i vertices, const V this->neighbor_child(neighbors(k), vertices(i), vertices(k), Partition::Second), -1 }, out_triangles); - } else if (splits == 4) { + break; + } + default: + assert(splits == 3); // Split to 4 triangles. this->get_facets_split_by_tjoints( { vertices(0), midpoints(0), midpoints(2) }, @@ -1021,6 +1034,7 @@ void TriangleSelector::get_facets_split_by_tjoints(const Vec3i vertices, const V -1 }, out_triangles); out_triangles.emplace_back(midpoints); + break; } } @@ -1106,6 +1120,13 @@ void TriangleSelector::deserialize(const std::pairits.indices.size(), data.second.size() / 4)); + // Number of triangles is twice the number of vertices on a large manifold mesh of genus zero. + // Here the triangles count account for both the nodes and leaves, thus the following line may overestimate. + m_vertices.reserve(std::max(m_mesh->its.vertices.size(), m_triangles.size() / 2)); + // Vector to store all parents that have offsprings. struct ProcessingInfo { int facet_id = 0; diff --git a/src/libslic3r/TriangleSelector.hpp b/src/libslic3r/TriangleSelector.hpp index 5115bb02a..6efae2150 100644 --- a/src/libslic3r/TriangleSelector.hpp +++ b/src/libslic3r/TriangleSelector.hpp @@ -204,10 +204,10 @@ private: int triangle_midpoint(int itriangle, int vertexi, int vertexj) const; int triangle_midpoint_or_allocate(int itriangle, int vertexi, int vertexj); -#ifndef _NDEBUG +#ifndef NDEBUG bool verify_triangle_neighbors(const Triangle& tr, const Vec3i& neighbors) const; bool verify_triangle_midpoints(const Triangle& tr) const; -#endif // _NDEBUG +#endif // NDEBUG void get_facets_strict_recursive( const Triangle &tr, diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index d23e28437..c06fc598b 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -39,26 +39,22 @@ enum ModelInstanceEPrintVolumeState : unsigned char; // possibly indexed by triangles and / or quads. class GLIndexedVertexArray { public: - GLIndexedVertexArray() : - vertices_and_normals_interleaved_VBO_id(0), - triangle_indices_VBO_id(0), - quad_indices_VBO_id(0) - {} + // Only Eigen types of Nx16 size are vectorized. This bounding box will not be vectorized. + static_assert(sizeof(Eigen::AlignedBox) == 24, "Eigen::AlignedBox is not being vectorized, thus it does not need to be aligned"); + using BoundingBox = Eigen::AlignedBox; + + GLIndexedVertexArray() { m_bounding_box.setEmpty(); } GLIndexedVertexArray(const GLIndexedVertexArray &rhs) : vertices_and_normals_interleaved(rhs.vertices_and_normals_interleaved), triangle_indices(rhs.triangle_indices), quad_indices(rhs.quad_indices), - vertices_and_normals_interleaved_VBO_id(0), - triangle_indices_VBO_id(0), - quad_indices_VBO_id(0) - { assert(! rhs.has_VBOs()); } + m_bounding_box(rhs.m_bounding_box) + { assert(! rhs.has_VBOs()); m_bounding_box.setEmpty(); } GLIndexedVertexArray(GLIndexedVertexArray &&rhs) : vertices_and_normals_interleaved(std::move(rhs.vertices_and_normals_interleaved)), triangle_indices(std::move(rhs.triangle_indices)), quad_indices(std::move(rhs.quad_indices)), - vertices_and_normals_interleaved_VBO_id(0), - triangle_indices_VBO_id(0), - quad_indices_VBO_id(0) + m_bounding_box(rhs.m_bounding_box) { assert(! rhs.has_VBOs()); } ~GLIndexedVertexArray() { release_geometry(); } @@ -92,7 +88,7 @@ public: this->vertices_and_normals_interleaved = std::move(rhs.vertices_and_normals_interleaved); this->triangle_indices = std::move(rhs.triangle_indices); this->quad_indices = std::move(rhs.quad_indices); - this->m_bounding_box = std::move(rhs.m_bounding_box); + this->m_bounding_box = rhs.m_bounding_box; this->vertices_and_normals_interleaved_size = rhs.vertices_and_normals_interleaved_size; this->triangle_indices_size = rhs.triangle_indices_size; this->quad_indices_size = rhs.quad_indices_size; @@ -147,7 +143,7 @@ public: this->vertices_and_normals_interleaved.emplace_back(z); this->vertices_and_normals_interleaved_size = this->vertices_and_normals_interleaved.size(); - m_bounding_box.merge(Vec3f(x, y, z).cast()); + m_bounding_box.extend(Vec3f(x, y, z)); }; inline void push_geometry(double x, double y, double z, double nx, double ny, double nz) { @@ -203,10 +199,12 @@ public: this->vertices_and_normals_interleaved.clear(); this->triangle_indices.clear(); this->quad_indices.clear(); - this->m_bounding_box.reset(); vertices_and_normals_interleaved_size = 0; triangle_indices_size = 0; quad_indices_size = 0; + static const float min = std::numeric_limits::lowest(); + static const float max = std::numeric_limits::max(); + m_bounding_box.setEmpty(); } // Shrink the internal storage to tighly fit the data stored. @@ -216,7 +214,7 @@ public: this->quad_indices.shrink_to_fit(); } - const BoundingBoxf3& bounding_box() const { return m_bounding_box; } + const BoundingBox& bounding_box() const { return m_bounding_box; } // Return an estimate of the memory consumed by this class. size_t cpu_memory_used() const { return sizeof(*this) + vertices_and_normals_interleaved.capacity() * sizeof(float) + triangle_indices.capacity() * sizeof(int) + quad_indices.capacity() * sizeof(int); } @@ -235,7 +233,7 @@ public: size_t total_memory_used() const { return this->cpu_memory_used() + this->gpu_memory_used(); } private: - BoundingBoxf3 m_bounding_box; + BoundingBox m_bounding_box; }; class GLVolume { @@ -355,7 +353,14 @@ public: std::vector offsets; // Bounding box of this volume, in unscaled coordinates. - const BoundingBoxf3& bounding_box() const { return this->indexed_vertex_array.bounding_box(); } + BoundingBoxf3 bounding_box() const { + BoundingBoxf3 out; + if (! this->indexed_vertex_array.bounding_box().isEmpty()) { + out.min = this->indexed_vertex_array.bounding_box().min().cast(); + out.max = this->indexed_vertex_array.bounding_box().max().cast(); + }; + return out; + } void set_render_color(float r, float g, float b, float a); void set_render_color(const float* rgba, unsigned int size); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp index c6dced670..201a16618 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp @@ -449,52 +449,50 @@ void TriangleSelectorMmuGui::render(ImGuiWrapper *imgui) { static constexpr std::array seed_fill_color{0.f, 1.f, 0.44f, 1.f}; - std::vector color_cnt(m_iva_colors.size()); - int seed_fill_cnt = 0; for (auto &iva_color : m_iva_colors) iva_color.release_geometry(); m_iva_seed_fill.release_geometry(); - auto append_triangle = [this](GLIndexedVertexArray &iva, int &cnt, const Triangle &tr) -> void { - for (int i = 0; i < 3; ++i) - iva.push_geometry(m_vertices[tr.verts_idxs[i]].v, m_mesh->stl.facet_start[tr.source_triangle].normal); - iva.push_triangle(cnt, cnt + 1, cnt + 2); - cnt += 3; - }; - - for (size_t color_idx = 0; color_idx < m_iva_colors.size(); ++color_idx) { - for (const Triangle &tr : m_triangles) { - if (!tr.valid() || tr.is_split() || tr.is_selected_by_seed_fill() || tr.get_state() != EnforcerBlockerType(color_idx)) - continue; - append_triangle(m_iva_colors[color_idx], color_cnt[color_idx], tr); + for (const Triangle &tr : m_triangles) + if (tr.valid() && ! tr.is_split()) { + GLIndexedVertexArray *iva = nullptr; + if (tr.is_selected_by_seed_fill()) + iva = &m_iva_seed_fill; + else if (int color = int(tr.get_state()); color < m_iva_colors.size()) + iva = &m_iva_colors[color]; + if (iva) { + if (iva->vertices_and_normals_interleaved.size() + 18 > iva->vertices_and_normals_interleaved.capacity()) + iva->vertices_and_normals_interleaved.reserve(next_highest_power_of_2(iva->vertices_and_normals_interleaved.size() + 18)); + const Vec3f &n = m_mesh->stl.facet_start[tr.source_triangle].normal; + for (int i = 0; i < 3; ++ i) { + const Vec3f &v = m_vertices[tr.verts_idxs[i]].v; + iva->vertices_and_normals_interleaved.emplace_back(n.x()); + iva->vertices_and_normals_interleaved.emplace_back(n.y()); + iva->vertices_and_normals_interleaved.emplace_back(n.z()); + iva->vertices_and_normals_interleaved.emplace_back(v.x()); + iva->vertices_and_normals_interleaved.emplace_back(v.y()); + iva->vertices_and_normals_interleaved.emplace_back(v.z()); + } + } } - } - for (const Triangle &tr : m_triangles) { - if (!tr.valid() || tr.is_split() || !tr.is_selected_by_seed_fill()) - continue; - append_triangle(m_iva_seed_fill, seed_fill_cnt, tr); - } - - for (auto &iva_color : m_iva_colors) - iva_color.finalize_geometry(true); - m_iva_seed_fill.finalize_geometry(true); - - auto *shader = wxGetApp().get_current_shader(); + auto* shader = wxGetApp().get_current_shader(); if (!shader) return; assert(shader->get_name() == "gouraud"); - auto render = [&shader](const GLIndexedVertexArray &iva, const std::array &color) -> void { - if (iva.has_VBOs()) { - shader->set_uniform("uniform_color", color); + for (size_t i = 0; i <= m_iva_colors.size(); ++i) + if (GLIndexedVertexArray &iva = i == m_iva_colors.size() ? m_iva_seed_fill : m_iva_colors[i]; + ! iva.vertices_and_normals_interleaved.empty()) { + iva.vertices_and_normals_interleaved_size = iva.vertices_and_normals_interleaved.size(); + iva.triangle_indices.assign(iva.vertices_and_normals_interleaved_size / 6, 0); + std::iota(iva.triangle_indices.begin(), iva.triangle_indices.end(), 0); + iva.triangle_indices_size = iva.triangle_indices.size(); + iva.finalize_geometry(true); + shader->set_uniform("uniform_color", + (i == 0) ? m_default_volume_color : i == m_iva_colors.size() ? seed_fill_color : m_colors[i - 1]); iva.render(); } - }; - - for (size_t color_idx = 0; color_idx < m_iva_colors.size(); ++color_idx) - render(m_iva_colors[color_idx], (color_idx == 0) ? m_default_volume_color : m_colors[color_idx - 1]); - render(m_iva_seed_fill, seed_fill_color); } wxString GLGizmoMmuSegmentation::handle_snapshot_action_name(bool shift_down, GLGizmoPainterBase::Button button_down) const diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp index 9f0c108be..f672471d6 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -685,7 +685,7 @@ void TriangleSelectorGUI::render_debug(ImGuiWrapper* imgui) va = &m_varrays[ORIGINAL]; cnt = &cnts[ORIGINAL]; } - else if (tr.valid) { + else if (tr.valid()) { va = &m_varrays[SPLIT]; cnt = &cnts[SPLIT]; }