From 266113d3a201424b82734846ccc27cec0cfa1bcb Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Thu, 7 Jul 2022 19:41:09 +0200 Subject: [PATCH] Work with multiple patches from one CutAOI - only area without outlines and selection of correct projection distance NOTE: One clip can create multiple patches - divide by connected triangles --- src/libslic3r/CutSurface.cpp | 333 ++++++++++++++++++++++++++--------- 1 file changed, 251 insertions(+), 82 deletions(-) diff --git a/src/libslic3r/CutSurface.cpp b/src/libslic3r/CutSurface.cpp index 5d0635232..6cc9dab51 100644 --- a/src/libslic3r/CutSurface.cpp +++ b/src/libslic3r/CutSurface.cpp @@ -640,19 +640,38 @@ SurfaceCut::CutContour create_cut(const std::vector &outlines, /// Merged all surface cuts into one SurfaceCut merge_intersections(SurfaceCuts &cuts, const CutAOIs& cutAOIs, const std::vector& use_cut); +// To track what was cutted of +struct SurfacePatch +{ + // converted cut to CGAL mesh + CutMesh mesh; + // converted source.second to mesh half edges + std::vector outline; + + BoundingBoxf3 bb; + + /// used during process + // flag that part will be deleted + bool full_inside = false; + // flag that Patch could contain more than one part + bool just_cliped = false; +}; +using SurfacePatches = std::vector; + /// /// Differenciate other models /// /// Patches from meshes -/// Define wanted cuts (addressing by m2i) /// Convert model_index and cut_index into one index /// Source points for Cutted AOIs /// Original models without cut modifications used for differenciate -void diff_models(VCutAOIs &cuts, - const std::vector &use_cut, - const ModelCut2index &m2i, - const CutMeshes &cut_models, - /*const*/ CutMeshes &models); +/// Define projection direction +/// Cuts differenciate by models - Patch +SurfacePatches diff_models(VCutAOIs &cuts, + const ModelCut2index &m2i, + const CutMeshes &cut_models, + /*const*/ CutMeshes &models, + const Emboss::IProject3f &projection); // keep CGAL Mesh for next processing struct SurfaceCutWithMesh : public SurfaceCut{ @@ -682,6 +701,7 @@ indexed_triangle_set create_indexed_triangle_set(const std::vector &faces, void store(CutMesh &mesh, const FaceTypeMap &face_type_map, const std::string &file); void store(CutMesh &mesh, const ReductionMap &reduction_map, const std::string &file); void store(const CutAOIs &aois, const CutMesh &mesh, const std::string &dir); +void store(const SurfacePatches &patches, const std::string &dir); void store(const Vec3f &vertex, const Vec3f &normal, const std::string &file, float size = 2.f); void store(const ProjectionDistances &pds, const VCutAOIs &aois, const CutMeshes &meshes, const std::string &file, float width = 0.2f/* [in mm] */); void store(const SurfaceCuts &cut, const std::string &dir); @@ -758,6 +778,12 @@ SurfaceCut Slic3r::cut_surface(const ExPolygons &shapes, model_cuts.push_back(std::move(cutAOIs)); } + priv::ModelCut2index m2i(model_cuts); + priv::SurfacePatches patches = priv::diff_models(model_cuts, m2i, cgal_models, cgal_neg_models, projection); +#ifdef DEBUG_OUTPUT_DIR + priv::store(patches, DEBUG_OUTPUT_DIR + "patches/"); +#endif // DEBUG_OUTPUT_DIR + // calc distance to projection for all outline points of cutAOI(shape) // it is used for distiguish the top one uint32_t shapes_points = s2i.get_count(); @@ -773,14 +799,12 @@ SurfaceCut Slic3r::cut_surface(const ExPolygons &shapes, store(best_projection, model_cuts, cgal_models, DEBUG_OUTPUT_DIR + "best_projection.obj"); // only debug #endif // DEBUG_OUTPUT_DIR - priv::ModelCut2index m2i(model_cuts); // Create mask for wanted AOIs std::vector is_best_cut(m2i.get_count(), {false}); for (const priv::ProjectionDistance& d : best_projection) if (d.model_index != std::numeric_limits::max()) is_best_cut[m2i.calc_index({d.model_index, d.aoi_index})] = true; - priv::diff_models(model_cuts, is_best_cut, m2i, cgal_models, cgal_neg_models); // IMPROVE: create reduce map on demand - may be model do not need it (when it is not used for result) // Reduction prepare @@ -2632,27 +2656,20 @@ void create_face_types(FaceTypeMap &map, const EcmType &ecm, const VertexSourceMap &sources); -// To track what was cutted of -struct ExtendAOI -{ - // source for extend - const CutAOI *source; - // converted cut to CGAL mesh - CutMesh mesh; -}; - /// /// Implement 'cut' Minus 'clipper', where clipper is reverse input Volume /// NOTE: clipper will be modified (corefined by cut) !!! /// /// differ from /// differ what -void clip_cut(ExtendAOI &cut, CutMesh clipper); +/// True on succes, otherwise FALSE +bool clip_cut(SurfacePatch &cut, CutMesh clipper); BoundingBoxf3 bounding_box(const CutAOI &cut, const CutMesh &mesh); -BoundingBoxf3 bounding_box(const ExtendAOI &ecut); +BoundingBoxf3 bounding_box(const CutMesh &mesh); +BoundingBoxf3 bounding_box(const SurfacePatch &ecut); -ExtendAOI create_extend_aoi(CutAOI &cut, const CutMesh &mesh); +SurfacePatch create_surface_patch(CutAOI &cut, const CutMesh &mesh); } // namespace priv @@ -2760,10 +2777,11 @@ void priv::create_face_types(FaceTypeMap &map, #include #include -void priv::clip_cut(ExtendAOI &cut, CutMesh clipper) +bool priv::clip_cut(SurfacePatch &cut, CutMesh clipper) { + CutMesh& tm = cut.mesh; // create backup for case that there is no intersection - CutMesh backup_copy = cut.mesh; + CutMesh backup_copy = tm; class ExistIntersectionClipVisitor: public CGAL::Polygon_mesh_processing::Corefinement::Default_visitor { @@ -2774,24 +2792,30 @@ void priv::clip_cut(ExtendAOI &cut, CutMesh clipper) { *exist_intersection = true;} }; bool exist_intersection = false; - ExistIntersectionClipVisitor visitor{&exist_intersection}; + ExistIntersectionClipVisitor visitor{&exist_intersection}; + + // bool map for affected edge + EcmType ecm = get(DynamicEdgeProperty(), tm); // namep parameters for model tm and function clip const auto &np_tm = CGAL::parameters::visitor(visitor) + .edge_is_constrained_map(ecm) .throw_on_self_intersection(false); // name parameters for model clipper and function clip - // Can't use 'do_not_modify' need clipper to be closed - //const auto &np_c = CGAL::parameters::do_not_modify(true); + const auto &np_c = CGAL::parameters::throw_on_self_intersection(false); + // Can't use 'do_not_modify', when Ture than clipper has to be closed !! + // .do_not_modify(true); // .throw_on_self_intersection(false); is set automaticaly by param 'do_not_modify' // .clip_volume(false); is set automaticaly by param 'do_not_modify' + std::string dir = "C:/data/temp/out/"; static int i = 0; - CGAL::IO::write_OFF(dir + "in_patch"+std::to_string(i)+".off", cut.mesh); + CGAL::IO::write_OFF(dir + "in_patch"+std::to_string(i)+".off", tm); CGAL::IO::write_OFF(dir + "in_model"+std::to_string(i)+".off", clipper); - bool suc = CGAL::Polygon_mesh_processing::clip(cut.mesh, clipper, np_tm); - CGAL::IO::write_OFF(dir + "out_patch"+std::to_string(i)+".off", cut.mesh); + bool suc = CGAL::Polygon_mesh_processing::clip(tm, clipper, np_tm, np_c); + CGAL::IO::write_OFF(dir + "out_patch"+std::to_string(i)+".off", tm); CGAL::IO::write_OFF(dir + "out_model"+std::to_string(i++)+".off", clipper); // true if the output surface mesh is manifold. @@ -2801,12 +2825,13 @@ void priv::clip_cut(ExtendAOI &cut, CutMesh clipper) if (!exist_intersection || !suc) { // TODO: test if cut is fully in or fully out!! cut.mesh = backup_copy; - return; + return false; } // TODO: fix outlines list + // Need to trace clip (corefine line) - return; + return true; //Store_VI_pairs::Pairs intersections; //std::string vertex_source_map_name = "v:source_intersections"; //VertexSourceMap vmap = tm1.add_property_map(vertex_source_map_name).first; @@ -2869,14 +2894,14 @@ BoundingBoxf3 priv::bounding_box(const CutAOI &cut, const CutMesh &mesh) { return BoundingBoxf3(min, max); } -BoundingBoxf3 priv::bounding_box(const ExtendAOI &ecut) { - const CutMesh& mesh = ecut.mesh; +BoundingBoxf3 priv::bounding_box(const CutMesh &mesh) +{ const P3 &p_from_cut = *mesh.points().begin(); - Vec3d min(p_from_cut.x(), p_from_cut.y(), p_from_cut.z()); - Vec3d max = min; - for (VI vi: mesh.vertices()) { - const P3& p = mesh.point(vi); - for (size_t i = 0; i < 3; ++i) { + Vec3d min(p_from_cut.x(), p_from_cut.y(), p_from_cut.z()); + Vec3d max = min; + for (VI vi : mesh.vertices()) { + const P3 &p = mesh.point(vi); + for (size_t i = 0; i < 3; ++i) { if (min[i] > p[i]) min[i] = p[i]; if (max[i] < p[i]) max[i] = p[i]; } @@ -2884,7 +2909,11 @@ BoundingBoxf3 priv::bounding_box(const ExtendAOI &ecut) { return BoundingBoxf3(min, max); } -priv::ExtendAOI priv::create_extend_aoi(CutAOI &cut, const CutMesh &mesh) +BoundingBoxf3 priv::bounding_box(const SurfacePatch &ecut) { + return bounding_box(ecut.mesh); +} + +priv::SurfacePatch priv::create_surface_patch(CutAOI &cut, const CutMesh &mesh) { std::vector is_counted(mesh.vertices().size(), {false}); uint32_t count_vertices = 0; @@ -2920,14 +2949,29 @@ priv::ExtendAOI priv::create_extend_aoi(CutAOI &cut, const CutMesh &mesh) } cm.add_face(t[0], t[1], t[2]); } - return {&cut, cm}; + + // converted source.second to mesh half edges + std::vector outline; + outline.reserve(cut.second.size()); + for (HI hi : cut.second) { + VI vi_s = mesh.source(hi); + VI vi_t = mesh.target(hi); + + VI vi_s2(v_cvt[vi_s.idx()]); + VI vi_t2(v_cvt[vi_t.idx()]); + // converted half edge + HI hi_cvt = cm.halfedge(vi_s2, vi_t2); + outline.push_back(hi_cvt); + } + + return {std::move(cm), std::move(outline)}; } -void priv::diff_models(VCutAOIs &cuts, - const std::vector &use_cut, - const ModelCut2index &m2i, - const CutMeshes &cut_models, - /*const*/ CutMeshes &models) +priv::SurfacePatches priv::diff_models(VCutAOIs &cuts, + const ModelCut2index &m2i, + const CutMeshes &cut_models, + /*const*/ CutMeshes &models, + const Emboss::IProject3f &projection) { // create bounding boxes for cuts std::vector bbs; @@ -2941,53 +2985,170 @@ void priv::diff_models(VCutAOIs &cuts, } } - // keep converted AOI to extend from - std::vector> ecuts(m2i.get_count()); + // check whether cut has intersection with model + auto has_bb_intersection = [&bbs, &m2i, &cuts] + (const BoundingBoxf3 &bb, size_t model_index) -> bool { + // for cut index with model_index2 + size_t start = m2i.calc_index({uint32_t(model_index), 0}); + size_t end = start + cuts[model_index].size(); + for (size_t bb_index = start; bb_index < end; bb_index++) + if (bb.intersects(bbs[bb_index])) return true; + return false; + }; - // NOTE: bad model could have self intersection but this can't solve it - // find intersection of cuts by Bounding boxes intersection + // Do not make Tree twice, when exist out of cut function + using Primitive = CGAL::AABB_face_graph_triangle_primitive; + using Traits = CGAL::AABB_traits; + using Ray = EpicKernel::Ray_3; + using Tree = CGAL::AABB_tree; + using Trees = std::vector; + Trees trees; + trees.reserve(models.size()); + for (CutMesh &model: models) { + Tree tree; + tree.insert(faces(model).first, faces(model).second, model); + tree.build(); + trees.emplace_back(std::move(tree)); + } + + // only for model without intersection + // use ray from any point in projection direction + auto is_patch_inside_of_model = [&trees, &projection] + (SurfacePatch &patch, size_t model_index) { + // TODO: Solve model with hole in projection direction !!! + const P3 &a = patch.mesh.point(VI(0)); + Vec3f a_(a.x(), a.y(), a.z()); + Vec3f b_ = projection.project(a_); + P3 b(b_.x(), b_.y(), b_.z()); + Tree &tree = trees[model_index]; + + Ray ray_query(a, b); + size_t count = tree.number_of_intersected_primitives(ray_query); + bool is_in = (count % 2) == 1; + + // try opposit direction result should be same, otherwise open model is used + //Vec3f c_ = a_ - (b_ - a_); // opposit direction + //P3 c(c_.x(), c_.y(), c_.z()); + //Ray ray_query2(a, b); + //size_t count2 = tree.number_of_intersected_primitives(ray_query2); + //bool is_in2 = (count2 % 2) == 1; + assert(((tree.number_of_intersected_primitives( + Ray(a, P3(2 * a.x() - b.x(), + 2 * a.y() - b.y(), + 2 * a.z() - b.z()))) % + 2) == 1) == is_in); + return is_in; + }; + + // separate connected triangles into it's own patches + // new patches are added to back of input patches + auto divide_patch = [](size_t i, SurfacePatches &patches) { + SurfacePatch &patch = patches[i]; + assert(patch.just_cliped); + patch.just_cliped = false; + + constexpr size_t def_value = std::numeric_limits::max(); + + CutMesh& cm = patch.mesh; + std::string patch_number_name = "f:patch_number"; + auto patch_number = cm.add_property_map(patch_number_name, {def_value}).first; + + size_t number = 0; + std::vector queue; + // IMPROVE: create groups around triangles and than connect groups + for (FI fi_cm : cm.faces()) { + if (patch_number[fi_cm] != def_value) continue; + assert(queue.empty()); + queue.push_back(fi_cm); + // flood fill from triangle fi_cm to surrounding + do { + FI fi_q = queue.back(); + queue.pop_back(); + if (patch_number[fi_q] != def_value) { + assert(patch_number[fi_q] == number); + continue; + } + patch_number[fi_q] = number; + HI hi = cm.halfedge(fi_q); + for (FI fi : cm.faces_around_face(hi)) { + // by documentation The face descriptor may be the null face, and it may be several times the same face descriptor. + if (!fi.is_valid()) continue; + if (patch_number[fi] == def_value) queue.push_back(fi); + } + } while (!queue.empty()); + ++number; + } + + // speed up for only one patch - no dividing (the most common) + if (number == 1) { + cm.remove_property_map(patch_number); + patch.bb = bounding_box(cm); + return; + } + + auto separate_patch = [&patch_number, &cm](size_t n) -> SurfacePatch { + CutAOI cut; + for (FI fi_cm : cm.faces()) + if (patch_number[fi_cm] == n) cut.first.push_back(fi_cm); + SurfacePatch patch = create_surface_patch(cut, cm); + patch.bb = bounding_box(patch.mesh); + return patch; + }; + + for (size_t n = 1; n < number; n++) + patches.push_back(separate_patch(n)); + patch = separate_patch(0); + }; + + std::vector removed(m2i.get_count(), {false}); + SurfacePatches patches; + // queue of patches for one AOI (permanent with respect to for loop) + SurfacePatches aoi_patches; + patches.reserve(m2i.get_count()); // only approximation of count size_t index = 0; for (size_t model_index = 0; model_index < models.size(); ++model_index) { CutAOIs &model_cuts = cuts[model_index]; - for (size_t cut_index = 0; cut_index < model_cuts.size(); ++cut_index, ++index) - { - if (!use_cut[index]) continue; - BoundingBoxf3 &result_bb = bbs[index]; - std::optional &ecut = ecuts[index]; - if (!ecut.has_value()) { - CutAOI &cut = model_cuts[cut_index]; - const CutMesh &cut_model = cut_models[model_index]; - ecut = create_extend_aoi(cut, cut_model); - } - - // all differenced models from this model - std::vector differenced(models.size(), {false}); - // do not merge itself - differenced[model_index] = true; - - // check when exist intersection with result_bb - size_t index2 = 0; + const CutMesh &cut_model = cut_models[model_index]; + for (size_t cut_index = 0; cut_index < model_cuts.size(); ++cut_index, ++index) { + CutAOI &cut = model_cuts[cut_index]; + SurfacePatch patch = create_surface_patch(cut, cut_model); + patch.bb = bbs[index]; + aoi_patches.clear(); + aoi_patches.push_back(patch); for (size_t model_index2 = 0; model_index2 < models.size(); ++model_index2) { - if (differenced[model_index2]) { - if ((model_index2+1) < models.size()) - index2 = m2i.calc_index({uint32_t(model_index2 + 1), 0}); - continue; + // do not clip source model itself + if (model_index == model_index2) continue; + for (SurfacePatch &patch : aoi_patches) { + if (has_bb_intersection(patch.bb, model_index2) && + clip_cut(patch, models[model_index2])){ + patch.just_cliped = true; + } else if (is_patch_inside_of_model(patch, model_index2)) + patch.full_inside = true; } - size_t count_cuts = cuts[model_index2].size(); - for (size_t cut_index2 = 0; cut_index2 < count_cuts; ++cut_index2, ++index2){ - const BoundingBoxf3 &bb = bbs[index2]; - if (!bb.intersects(result_bb)) continue; - priv::clip_cut(*ecut, models[model_index2]); - differenced[model_index2] = true; - if ((model_index2+1) < models.size()) - index2 = m2i.calc_index({uint32_t(model_index2 + 1), 0}); + // erase full inside + for (size_t i = aoi_patches.size(); i != 0; --i) { + auto it = aoi_patches.begin() + (i - 1); + if (it->full_inside) aoi_patches.erase(it); + } + if (aoi_patches.empty()) { + removed[index] = true; break; } + // divide cliped into parts + size_t end = aoi_patches.size(); + for (size_t i = 0; i < end; ++i) + if (aoi_patches[i].just_cliped) + divide_patch(i, aoi_patches); } - } - } - - // TODO: convert ecuts to CutSurface + if (!removed[index]) + patches.insert(patches.end(), + aoi_patches.begin(), + aoi_patches.end()); + } + } + return patches; + // TODO: reduce outlines + // TODO: add cutting edge // TODO: merge AOIs without intersection - only append @@ -3286,6 +3447,14 @@ void priv::store(const CutAOIs &aois, const CutMesh &mesh, const std::string &di } } +void priv::store(const SurfacePatches &patches, const std::string &dir) { + for (const priv::SurfacePatch &patch : patches) { + size_t index = &patch - &patches.front(); + if (patch.mesh.faces().empty()) continue; + CGAL::IO::write_OFF(dir + "patch" + std::to_string(index) + ".off", patch.mesh); + } +} + void priv::store(const ProjectionDistances &pds, const VCutAOIs &aois, const CutMeshes &meshes,