From 06e7a6d6112d93238e954508a2242eeae7c863c5 Mon Sep 17 00:00:00 2001 From: sentientstardust Date: Thu, 21 May 2026 19:32:21 +0100 Subject: [PATCH] Preserve model textures during mesh boolean --- src/libslic3r/MeshBoolean.cpp | 699 ++++++++++++++++++- src/libslic3r/MeshBoolean.hpp | 28 + src/libslic3r/ModelTextureDataRemap.cpp | 541 ++++++++++++++ src/libslic3r/ModelTextureDataRemap.hpp | 22 + src/slic3r/GUI/GUI_ObjectList.cpp | 301 +++++++- src/slic3r/GUI/Gizmos/GLGizmoMeshBoolean.cpp | 218 ++++-- src/slic3r/GUI/Gizmos/GLGizmoMeshBoolean.hpp | 12 +- version_number.txt | 2 +- 8 files changed, 1752 insertions(+), 71 deletions(-) diff --git a/src/libslic3r/MeshBoolean.cpp b/src/libslic3r/MeshBoolean.cpp index 8b506813708..7eeb27d0197 100644 --- a/src/libslic3r/MeshBoolean.cpp +++ b/src/libslic3r/MeshBoolean.cpp @@ -1,5 +1,6 @@ #include "Exception.hpp" #include "MeshBoolean.hpp" +#include "libslic3r/MeshSplitImpl.hpp" #include "libslic3r/TriangleMesh.hpp" #include "libslic3r/TryCatchSignal.hpp" #include "libslic3r/format.hpp" @@ -29,6 +30,11 @@ // BBS: for boolean using mcut #include "mcut/include/mcut/mcut.h" +#include +#include +#include +#include + namespace Slic3r { namespace MeshBoolean { @@ -583,6 +589,248 @@ struct McutMesh void McutMeshDeleter::operator()(McutMesh *ptr) { delete ptr; } bool empty(const McutMesh &mesh) { return mesh.vertexCoordsArray.empty() || mesh.faceIndicesArray.empty(); } + +static Vec3f mcut_vertex(const McutMesh &mesh, uint32_t vertex_idx) +{ + const size_t offset = size_t(vertex_idx) * 3; + if (offset + 2 >= mesh.vertexCoordsArray.size()) + return Vec3f::Zero(); + return Vec3f(float(mesh.vertexCoordsArray[offset + 0]), float(mesh.vertexCoordsArray[offset + 1]), float(mesh.vertexCoordsArray[offset + 2])); +} + +static size_t mcut_face_offset(const McutMesh &mesh, size_t face_idx) +{ + size_t offset = 0; + for (size_t idx = 0; idx < face_idx && idx < mesh.faceSizesArray.size(); ++idx) + offset += size_t(mesh.faceSizesArray[idx]); + return offset; +} + +static bool mcut_face_vertices(const McutMesh &mesh, size_t face_idx, std::array &vertices) +{ + if (face_idx >= mesh.faceSizesArray.size() || mesh.faceSizesArray[face_idx] != 3) + return false; + const size_t offset = mcut_face_offset(mesh, face_idx); + if (offset + 2 >= mesh.faceIndicesArray.size()) + return false; + vertices = { + mcut_vertex(mesh, mesh.faceIndicesArray[offset + 0]), + mcut_vertex(mesh, mesh.faceIndicesArray[offset + 1]), + mcut_vertex(mesh, mesh.faceIndicesArray[offset + 2]) + }; + return true; +} + +static Vec3f normalized_nonnegative_barycentric_mcut(Vec3f weights) +{ + weights.x() = std::max(weights.x(), 0.f); + weights.y() = std::max(weights.y(), 0.f); + weights.z() = std::max(weights.z(), 0.f); + const float sum = weights.x() + weights.y() + weights.z(); + if (sum <= 1e-6f) + return Vec3f(1.f / 3.f, 1.f / 3.f, 1.f / 3.f); + weights /= sum; + return weights; +} + +static Vec3f barycentric_weights_3d_mcut(const Vec3f &point, const std::array &vertices) +{ + const Vec3f v0 = vertices[1] - vertices[0]; + const Vec3f v1 = vertices[2] - vertices[0]; + const Vec3f v2 = point - vertices[0]; + const float d00 = v0.dot(v0); + const float d01 = v0.dot(v1); + const float d11 = v1.dot(v1); + const float d20 = v2.dot(v0); + const float d21 = v2.dot(v1); + const float denom = d00 * d11 - d01 * d01; + if (!std::isfinite(denom) || std::abs(denom) <= 1e-6f) + return Vec3f(1.f / 3.f, 1.f / 3.f, 1.f / 3.f); + const float v = (d11 * d20 - d01 * d21) / denom; + const float w = (d00 * d21 - d01 * d20) / denom; + return Vec3f(1.f - v - w, v, w); +} + +static Vec3f interpolate_source_barycentric(const MeshFaceProvenance &provenance, const Vec3f &weights) +{ + return normalized_nonnegative_barycentric_mcut(provenance.source_barycentric[0] * weights.x() + + provenance.source_barycentric[1] * weights.y() + + provenance.source_barycentric[2] * weights.z()); +} + +std::vector identity_provenance(const indexed_triangle_set &its, size_t source_index) +{ + std::vector provenance(its.indices.size()); + for (size_t tri_idx = 0; tri_idx < provenance.size(); ++tri_idx) { + MeshFaceProvenance &entry = provenance[tri_idx]; + entry.valid = true; + entry.source_index = source_index; + entry.source_triangle = tri_idx; + entry.source_barycentric = { + Vec3f(1.f, 0.f, 0.f), + Vec3f(0.f, 1.f, 0.f), + Vec3f(0.f, 0.f, 1.f) + }; + } + return provenance; +} + +struct ProvenancedIts +{ + indexed_triangle_set its; + std::vector provenance; +}; + +enum class McutBooleanStatus +{ + Success, + NoOutput, + Failure +}; + +static bool its_bounds_overlap_mcut(const indexed_triangle_set &a, const indexed_triangle_set &b) +{ + if (a.vertices.empty() || b.vertices.empty()) + return false; + + Vec3f a_min = Vec3f::Constant(std::numeric_limits::max()); + Vec3f a_max = Vec3f::Constant(std::numeric_limits::lowest()); + for (const Vec3f &v : a.vertices) { + a_min = a_min.cwiseMin(v); + a_max = a_max.cwiseMax(v); + } + + Vec3f b_min = Vec3f::Constant(std::numeric_limits::max()); + Vec3f b_max = Vec3f::Constant(std::numeric_limits::lowest()); + for (const Vec3f &v : b.vertices) { + b_min = b_min.cwiseMin(v); + b_max = b_max.cwiseMax(v); + } + + constexpr float eps = 1e-5f; + return a_min.x() <= b_max.x() + eps && a_max.x() + eps >= b_min.x() && + a_min.y() <= b_max.y() + eps && a_max.y() + eps >= b_min.y() && + a_min.z() <= b_max.z() + eps && a_max.z() + eps >= b_min.z(); +} + +static std::vector split_with_provenance(const indexed_triangle_set &its, const std::vector &provenance) +{ + std::vector ret; + if (provenance.size() != its.indices.size()) + return ret; + + struct VertexConv { + size_t part_id = std::numeric_limits::max(); + size_t vertex_image = 0; + }; + std::vector vidx_conv(its.vertices.size()); + meshsplit_detail::NeighborVisitor visitor(its, meshsplit_detail::ItsWithNeighborsIndex_::get_index(its)); + + std::vector facets; + for (size_t part_id = 0;; ++part_id) { + facets.clear(); + visitor.visit([&facets](size_t idx) { facets.emplace_back(idx); return true; }); + if (facets.empty()) + break; + + ProvenancedIts part; + part.its.indices.reserve(facets.size()); + part.its.vertices.reserve(std::min(facets.size() * 3, its.vertices.size())); + part.provenance.reserve(facets.size()); + + for (size_t face_id : facets) { + const auto &face = its.indices[face_id]; + Vec3i32 new_face; + for (size_t v = 0; v < 3; ++v) { + auto vi = face(v); + if (vidx_conv[vi].part_id != part_id) { + vidx_conv[vi] = { part_id, part.its.vertices.size() }; + part.its.vertices.emplace_back(its.vertices[size_t(vi)]); + } + new_face(v) = int(vidx_conv[vi].vertex_image); + } + part.its.indices.emplace_back(new_face); + part.provenance.emplace_back(provenance[face_id]); + } + + ret.emplace_back(std::move(part)); + } + + return ret; +} + +static void append_with_provenance(indexed_triangle_set &dst, + std::vector &dst_provenance, + const indexed_triangle_set &src, + const std::vector &src_provenance) +{ + if (src.indices.size() != src_provenance.size()) + return; + const int vertex_offset = int(dst.vertices.size()); + dst.vertices.insert(dst.vertices.end(), src.vertices.begin(), src.vertices.end()); + for (const Vec3i32 &face : src.indices) + dst.indices.emplace_back(Vec3i32(face.x() + vertex_offset, face.y() + vertex_offset, face.z() + vertex_offset)); + dst_provenance.insert(dst_provenance.end(), src_provenance.begin(), src_provenance.end()); +} + +static void merge_mcut_meshes_with_provenance(McutMesh &srcMesh, + std::vector &src_provenance, + const McutMesh &cutMesh, + const std::vector &cut_provenance) +{ + TriangleMesh tri_src = mcut_to_triangle_mesh(srcMesh); + TriangleMesh tri_cut = mcut_to_triangle_mesh(cutMesh); + indexed_triangle_set all_its; + std::vector all_provenance; + append_with_provenance(all_its, all_provenance, tri_src.its, src_provenance); + append_with_provenance(all_its, all_provenance, tri_cut.its, cut_provenance); + srcMesh = *triangle_mesh_to_mcut(all_its); + src_provenance = std::move(all_provenance); +} + +static bool mapped_output_face_provenance(const McutMesh &srcMesh, + const std::vector &src_provenance, + const McutMesh &cutMesh, + const std::vector &cut_provenance, + uint32_t face_map, + const std::vector &ccVertices, + const std::vector &ccFaceIndices, + uint32_t face_offset, + int vertex_index_offset, + MeshFaceProvenance &out) +{ + const uint32_t src_face_count = uint32_t(srcMesh.faceSizesArray.size()); + const bool from_cut = face_map >= src_face_count; + const McutMesh &input_mesh = from_cut ? cutMesh : srcMesh; + const std::vector &input_provenance = from_cut ? cut_provenance : src_provenance; + const size_t input_face = size_t(from_cut ? face_map - src_face_count : face_map); + if (input_face >= input_provenance.size() || !input_provenance[input_face].valid) + return false; + + std::array input_vertices; + if (!mcut_face_vertices(input_mesh, input_face, input_vertices)) + return false; + + const MeshFaceProvenance &input = input_provenance[input_face]; + out.valid = true; + out.source_index = input.source_index; + out.source_triangle = input.source_triangle; + for (size_t corner = 0; corner < 3; ++corner) { + const uint32_t global_vertex_idx = ccFaceIndices[size_t(face_offset) + corner]; + if (int(global_vertex_idx) < vertex_index_offset) + return false; + const size_t local_vertex_idx = size_t(int(global_vertex_idx) - vertex_index_offset); + const size_t vertex_offset = local_vertex_idx * 3; + if (vertex_offset + 2 >= ccVertices.size()) + return false; + const Vec3f point = Vec3f(float(ccVertices[vertex_offset + 0]), + float(ccVertices[vertex_offset + 1]), + float(ccVertices[vertex_offset + 2])); + out.source_barycentric[corner] = interpolate_source_barycentric(input, barycentric_weights_3d_mcut(point, input_vertices)); + } + return true; +} + void triangle_mesh_to_mcut(const TriangleMesh &src_mesh, McutMesh &srcMesh, const Transform3d &src_nm = Transform3d::Identity()) { // vertices precision convention and copy @@ -845,6 +1093,327 @@ bool do_boolean_single(McutMesh &srcMesh, const McutMesh &cutMesh, const std::st return true; } +static bool do_boolean_single_with_cgal_fallback(McutMesh &srcMesh, const McutMesh &cutMesh, const std::string &boolean_opts) +{ + McutMesh original = srcMesh; + if (boolean_opts != "INTERSECTION" && do_boolean_single(srcMesh, cutMesh, boolean_opts)) + return true; + + TriangleMesh tri_src = mcut_to_triangle_mesh(original); + TriangleMesh tri_cut = mcut_to_triangle_mesh(cutMesh); + try { + if (boolean_opts == "UNION") + MeshBoolean::cgal::plus(tri_src, tri_cut); + else if (boolean_opts == "A_NOT_B") + MeshBoolean::cgal::minus(tri_src, tri_cut); + else if (boolean_opts == "INTERSECTION") + MeshBoolean::cgal::intersect(tri_src, tri_cut); + else { + srcMesh = std::move(original); + return false; + } + } catch (...) { + srcMesh = std::move(original); + return false; + } + + srcMesh = *triangle_mesh_to_mcut(tri_src.its); + return true; +} + +static McutBooleanStatus do_boolean_single_with_igl_provenance(McutMesh &srcMesh, + std::vector &src_provenance, + const McutMesh &cutMesh, + const std::vector &cut_provenance, + const std::string &boolean_opts) +{ + TriangleMesh tri_src = mcut_to_triangle_mesh(srcMesh); + TriangleMesh tri_cut = mcut_to_triangle_mesh(cutMesh); + if (tri_src.its.indices.size() != src_provenance.size() || tri_cut.its.indices.size() != cut_provenance.size()) + return McutBooleanStatus::Failure; + if (tri_src.empty()) { + if (boolean_opts == "UNION") { + srcMesh = cutMesh; + src_provenance = cut_provenance; + } else + src_provenance.clear(); + return McutBooleanStatus::Success; + } + if (tri_cut.empty()) { + if (boolean_opts == "INTERSECTION") { + srcMesh = *triangle_mesh_to_mcut(indexed_triangle_set{}); + src_provenance.clear(); + } + return McutBooleanStatus::Success; + } + + igl::MeshBooleanType boolean_type; + if (boolean_opts == "UNION") + boolean_type = igl::MESH_BOOLEAN_TYPE_UNION; + else if (boolean_opts == "A_NOT_B") + boolean_type = igl::MESH_BOOLEAN_TYPE_MINUS; + else if (boolean_opts == "INTERSECTION") + boolean_type = igl::MESH_BOOLEAN_TYPE_INTERSECT; + else + return McutBooleanStatus::Failure; + + EigenMesh src_eigen = triangle_mesh_to_eigen(tri_src); + EigenMesh cut_eigen = triangle_mesh_to_eigen(tri_cut); + Eigen::MatrixXd vertices; + Eigen::MatrixXi faces; + Eigen::VectorXi face_birth; + try { + igl::copyleft::cgal::mesh_boolean(src_eigen.first, + src_eigen.second, + cut_eigen.first, + cut_eigen.second, + boolean_type, + vertices, + faces, + face_birth); + } catch (...) { + return McutBooleanStatus::Failure; + } + + if (faces.rows() != face_birth.rows()) + return McutBooleanStatus::Failure; + + TriangleMesh out_mesh = eigen_to_triangle_mesh({ std::move(vertices), std::move(faces) }); + std::vector out_provenance; + out_provenance.reserve(out_mesh.its.indices.size()); + const int src_face_count = int(tri_src.its.indices.size()); + for (size_t face_idx = 0; face_idx < out_mesh.its.indices.size(); ++face_idx) { + const int birth = face_birth[int(face_idx)]; + if (birth < 0) + return McutBooleanStatus::Failure; + const bool from_cut = birth >= src_face_count; + const indexed_triangle_set &input_its = from_cut ? tri_cut.its : tri_src.its; + const std::vector &input_provenance = from_cut ? cut_provenance : src_provenance; + const size_t input_face = size_t(from_cut ? birth - src_face_count : birth); + if (input_face >= input_its.indices.size() || input_face >= input_provenance.size() || !input_provenance[input_face].valid) + return McutBooleanStatus::Failure; + + const Vec3i32 &input_face_indices = input_its.indices[input_face]; + std::array input_vertices = { + input_its.vertices[size_t(input_face_indices.x())], + input_its.vertices[size_t(input_face_indices.y())], + input_its.vertices[size_t(input_face_indices.z())] + }; + const MeshFaceProvenance &input = input_provenance[input_face]; + MeshFaceProvenance output; + output.valid = true; + output.source_index = input.source_index; + output.source_triangle = input.source_triangle; + const Vec3i32 &output_face_indices = out_mesh.its.indices[face_idx]; + for (size_t corner = 0; corner < 3; ++corner) { + const Vec3f &point = out_mesh.its.vertices[size_t(output_face_indices[int(corner)])]; + output.source_barycentric[corner] = interpolate_source_barycentric(input, barycentric_weights_3d_mcut(point, input_vertices)); + } + out_provenance.emplace_back(output); + } + + srcMesh = *triangle_mesh_to_mcut(out_mesh.its); + src_provenance = std::move(out_provenance); + return McutBooleanStatus::Success; +} + +static McutBooleanStatus do_boolean_single_with_provenance(McutMesh &srcMesh, + std::vector &src_provenance, + const McutMesh &cutMesh, + const std::vector &cut_provenance, + const std::string &boolean_opts) +{ + if (src_provenance.size() != srcMesh.faceSizesArray.size() || cut_provenance.size() != cutMesh.faceSizesArray.size()) + return McutBooleanStatus::Failure; + if (boolean_opts == "INTERSECTION") + return do_boolean_single_with_igl_provenance(srcMesh, src_provenance, cutMesh, cut_provenance, boolean_opts); + + McContext context = MC_NULL_HANDLE; + McResult err = mcCreateContext(&context, 0); + if (err != MC_NO_ERROR) + return McutBooleanStatus::Failure; + mcDebugMessageCallback(context, mcDebugOutput, nullptr); + mcDebugMessageControl(context, MC_DEBUG_SOURCE_ALL, MC_DEBUG_TYPE_ERROR, MC_DEBUG_SEVERITY_MEDIUM, true); + + const std::map booleanOpts = { + {"A_NOT_B", MC_DISPATCH_FILTER_FRAGMENT_SEALING_INSIDE | MC_DISPATCH_FILTER_FRAGMENT_LOCATION_ABOVE}, + {"B_NOT_A", MC_DISPATCH_FILTER_FRAGMENT_SEALING_OUTSIDE | MC_DISPATCH_FILTER_FRAGMENT_LOCATION_BELOW}, + {"UNION", MC_DISPATCH_FILTER_FRAGMENT_SEALING_OUTSIDE | MC_DISPATCH_FILTER_FRAGMENT_LOCATION_ABOVE}, + {"INTERSECTION", MC_DISPATCH_FILTER_FRAGMENT_SEALING_INSIDE | MC_DISPATCH_FILTER_FRAGMENT_LOCATION_BELOW}, + }; + + auto it = booleanOpts.find(boolean_opts); + if (it == booleanOpts.end()) { + mcReleaseContext(context); + return McutBooleanStatus::Failure; + } + + if (srcMesh.vertexCoordsArray.empty() && (boolean_opts == "UNION" || boolean_opts == "B_NOT_A")) { + srcMesh = cutMesh; + src_provenance = cut_provenance; + mcReleaseContext(context); + return McutBooleanStatus::Success; + } + + err = mcDispatch(context, + MC_DISPATCH_VERTEX_ARRAY_DOUBLE | + MC_DISPATCH_ENFORCE_GENERAL_POSITION | + MC_DISPATCH_INCLUDE_FACE_MAP | + it->second, + reinterpret_cast(srcMesh.vertexCoordsArray.data()), + reinterpret_cast(srcMesh.faceIndicesArray.data()), + srcMesh.faceSizesArray.data(), + static_cast(srcMesh.vertexCoordsArray.size() / 3), + static_cast(srcMesh.faceSizesArray.size()), + reinterpret_cast(cutMesh.vertexCoordsArray.data()), + cutMesh.faceIndicesArray.data(), + cutMesh.faceSizesArray.data(), + static_cast(cutMesh.vertexCoordsArray.size() / 3), + static_cast(cutMesh.faceSizesArray.size())); + if (err != MC_NO_ERROR) { + BOOST_LOG_TRIVIAL(debug) << "MCUT mcDispatch provenance fails! err=" << err; + mcReleaseContext(context); + if (boolean_opts == "UNION") { + merge_mcut_meshes_with_provenance(srcMesh, src_provenance, cutMesh, cut_provenance); + return McutBooleanStatus::Success; + } + return do_boolean_single_with_igl_provenance(srcMesh, src_provenance, cutMesh, cut_provenance, boolean_opts); + } + + uint32_t numConnComps = 0; + err = mcGetConnectedComponents(context, MC_CONNECTED_COMPONENT_TYPE_FRAGMENT, 0, NULL, &numConnComps); + if (err != MC_NO_ERROR || numConnComps == 0) { + BOOST_LOG_TRIVIAL(debug) << "MCUT mcGetConnectedComponents provenance fails! err=" << err << ", numConnComps" << numConnComps; + mcReleaseContext(context); + if (numConnComps == 0 && boolean_opts == "UNION") { + merge_mcut_meshes_with_provenance(srcMesh, src_provenance, cutMesh, cut_provenance); + return McutBooleanStatus::Success; + } + return do_boolean_single_with_igl_provenance(srcMesh, src_provenance, cutMesh, cut_provenance, boolean_opts); + } + + std::vector connectedComponents(numConnComps, MC_NULL_HANDLE); + err = mcGetConnectedComponents(context, MC_CONNECTED_COMPONENT_TYPE_FRAGMENT, uint32_t(connectedComponents.size()), connectedComponents.data(), NULL); + if (err != MC_NO_ERROR) { + mcReleaseContext(context); + return do_boolean_single_with_igl_provenance(srcMesh, src_provenance, cutMesh, cut_provenance, boolean_opts); + } + + McutMesh outMesh; + std::vector out_provenance; + int vertex_index_offset = 0; + bool ok = true; + for (int n = 0; n < int(numConnComps) && ok; ++n) { + McConnectedComponent connComp = connectedComponents[n]; + + McSize numBytes = 0; + err = mcGetConnectedComponentData(context, connComp, MC_CONNECTED_COMPONENT_DATA_VERTEX_DOUBLE, 0, NULL, &numBytes); + if (err != MC_NO_ERROR) { + ok = false; + break; + } + uint32_t ccVertexCount = uint32_t(numBytes / (sizeof(double) * 3)); + std::vector ccVertices(size_t(ccVertexCount) * 3u, 0); + err = mcGetConnectedComponentData(context, connComp, MC_CONNECTED_COMPONENT_DATA_VERTEX_DOUBLE, numBytes, ccVertices.data(), NULL); + if (err != MC_NO_ERROR) { + ok = false; + break; + } + + numBytes = 0; + err = mcGetConnectedComponentData(context, connComp, MC_CONNECTED_COMPONENT_DATA_FACE_TRIANGULATION, 0, NULL, &numBytes); + if (err != MC_NO_ERROR) { + ok = false; + break; + } + std::vector ccFaceIndices(numBytes / sizeof(uint32_t), 0); + err = mcGetConnectedComponentData(context, connComp, MC_CONNECTED_COMPONENT_DATA_FACE_TRIANGULATION, numBytes, ccFaceIndices.data(), NULL); + if (err != MC_NO_ERROR) { + ok = false; + break; + } + std::vector faceSizes(ccFaceIndices.size() / 3, 3); + const uint32_t ccFaceCount = uint32_t(faceSizes.size()); + + numBytes = 0; + err = mcGetConnectedComponentData(context, connComp, MC_CONNECTED_COMPONENT_DATA_FACE_TRIANGULATION_MAP, 0, NULL, &numBytes); + if (err != MC_NO_ERROR) { + ok = false; + break; + } + std::vector ccFaceMap(numBytes / sizeof(uint32_t), 0); + err = mcGetConnectedComponentData(context, connComp, MC_CONNECTED_COMPONENT_DATA_FACE_TRIANGULATION_MAP, numBytes, ccFaceMap.data(), NULL); + if (err != MC_NO_ERROR || ccFaceMap.size() != ccFaceCount) { + ok = false; + break; + } + + McPatchLocation patchLocation = (McPatchLocation) 0; + err = mcGetConnectedComponentData(context, connComp, MC_CONNECTED_COMPONENT_DATA_PATCH_LOCATION, sizeof(McPatchLocation), &patchLocation, NULL); + if (err != MC_NO_ERROR) { + ok = false; + break; + } + McFragmentLocation fragmentLocation = (McFragmentLocation) 0; + err = mcGetConnectedComponentData(context, connComp, MC_CONNECTED_COMPONENT_DATA_FRAGMENT_LOCATION, sizeof(McFragmentLocation), &fragmentLocation, NULL); + if (err != MC_NO_ERROR) { + ok = false; + break; + } + + outMesh.vertexCoordsArray.insert(outMesh.vertexCoordsArray.end(), ccVertices.begin(), ccVertices.end()); + for (size_t i = 0; i < ccFaceIndices.size(); ++i) + ccFaceIndices[i] += uint32_t(vertex_index_offset); + + int faceVertexOffsetBase = 0; + for (uint32_t f = 0; f < ccFaceCount; ++f) { + const bool reverseWindingOrder = (fragmentLocation == MC_FRAGMENT_LOCATION_BELOW) && (patchLocation == MC_PATCH_LOCATION_OUTSIDE); + const int faceSize = int(faceSizes[f]); + if (faceSize != 3) { + ok = false; + break; + } + if (reverseWindingOrder) { + std::vector faceIndex(static_cast(faceSize)); + for (int v = faceSize - 1; v >= 0; --v) + faceIndex[size_t(v)] = ccFaceIndices[size_t(faceVertexOffsetBase) + size_t(v)]; + std::copy(faceIndex.begin(), faceIndex.end(), ccFaceIndices.begin() + faceVertexOffsetBase); + } + MeshFaceProvenance face_provenance; + if (!mapped_output_face_provenance(srcMesh, + src_provenance, + cutMesh, + cut_provenance, + ccFaceMap[f], + ccVertices, + ccFaceIndices, + uint32_t(faceVertexOffsetBase), + vertex_index_offset, + face_provenance)) { + ok = false; + break; + } + out_provenance.emplace_back(face_provenance); + faceVertexOffsetBase += faceSize; + } + if (!ok) + break; + + outMesh.faceIndicesArray.insert(outMesh.faceIndicesArray.end(), ccFaceIndices.begin(), ccFaceIndices.end()); + outMesh.faceSizesArray.insert(outMesh.faceSizesArray.end(), faceSizes.begin(), faceSizes.end()); + vertex_index_offset += int(ccVertexCount); + } + + mcReleaseConnectedComponents(context, 0, NULL); + mcReleaseContext(context); + if (!ok || out_provenance.size() != outMesh.faceSizesArray.size()) + return do_boolean_single_with_igl_provenance(srcMesh, src_provenance, cutMesh, cut_provenance, boolean_opts); + + srcMesh = std::move(outMesh); + src_provenance = std::move(out_provenance); + return McutBooleanStatus::Success; +} + void do_boolean(McutMesh& srcMesh, const McutMesh& cutMesh, const std::string& boolean_opts) { TriangleMesh tri_src = mcut_to_triangle_mesh(srcMesh); @@ -863,33 +1432,124 @@ void do_boolean(McutMesh& srcMesh, const McutMesh& cutMesh, const std::string& b // But we can force it to work by spliting the src mesh into disconnected components, // and do booleans seperately, then merge all the results. indexed_triangle_set all_its; + bool ok = true; if (boolean_opts == "UNION" || boolean_opts == "A_NOT_B") { - for (size_t i = 0; i < src_parts.size(); i++) { + for (size_t i = 0; i < src_parts.size() && ok; i++) { auto src_part = triangle_mesh_to_mcut(src_parts[i]); for (size_t j = 0; j < cut_parts.size(); j++) { + if (boolean_opts == "A_NOT_B") { + TriangleMesh current_src_part = mcut_to_triangle_mesh(*src_part); + if (!its_bounds_overlap_mcut(current_src_part.its, cut_parts[j])) + continue; + } auto cut_part = triangle_mesh_to_mcut(cut_parts[j]); - do_boolean_single(*src_part, *cut_part, boolean_opts); + if (!do_boolean_single_with_cgal_fallback(*src_part, *cut_part, boolean_opts)) { + ok = false; + break; + } } + if (!ok) + break; TriangleMesh tri_part = mcut_to_triangle_mesh(*src_part); its_merge(all_its, tri_part.its); } } else if (boolean_opts == "INTERSECTION") { - for (size_t i = 0; i < src_parts.size(); i++) { + for (size_t i = 0; i < src_parts.size() && ok; i++) { for (size_t j = 0; j < cut_parts.size(); j++) { + if (!its_bounds_overlap_mcut(src_parts[i], cut_parts[j])) + continue; auto src_part = triangle_mesh_to_mcut(src_parts[i]); auto cut_part = triangle_mesh_to_mcut(cut_parts[j]); - bool success = do_boolean_single(*src_part, *cut_part, boolean_opts); - if (success) { - TriangleMesh tri_part = mcut_to_triangle_mesh(*src_part); - its_merge(all_its, tri_part.its); + if (!do_boolean_single_with_cgal_fallback(*src_part, *cut_part, boolean_opts)) { + ok = false; + break; } + TriangleMesh tri_part = mcut_to_triangle_mesh(*src_part); + its_merge(all_its, tri_part.its); } } } + if (!ok) { + srcMesh = *triangle_mesh_to_mcut(indexed_triangle_set{}); + return; + } srcMesh = *triangle_mesh_to_mcut(all_its); } +bool do_boolean_with_provenance(McutMesh &srcMesh, + std::vector &src_provenance, + const McutMesh &cutMesh, + const std::vector &cut_provenance, + const std::string &boolean_opts) +{ + TriangleMesh tri_src = mcut_to_triangle_mesh(srcMesh); + TriangleMesh tri_cut = mcut_to_triangle_mesh(cutMesh); + if (src_provenance.size() != tri_src.its.indices.size() || cut_provenance.size() != tri_cut.its.indices.size()) + return false; + + std::vector src_parts = split_with_provenance(tri_src.its, src_provenance); + std::vector cut_parts = split_with_provenance(tri_cut.its, cut_provenance); + + if (src_parts.empty() && boolean_opts == "UNION") { + srcMesh = cutMesh; + src_provenance = cut_provenance; + return true; + } + if (cut_parts.empty()) + return true; + + indexed_triangle_set all_its; + std::vector all_provenance; + if (boolean_opts == "UNION" || boolean_opts == "A_NOT_B") { + for (size_t i = 0; i < src_parts.size(); ++i) { + auto src_part = triangle_mesh_to_mcut(src_parts[i].its); + std::vector src_part_provenance = src_parts[i].provenance; + for (size_t j = 0; j < cut_parts.size(); ++j) { + if (boolean_opts == "A_NOT_B") { + TriangleMesh current_src_part = mcut_to_triangle_mesh(*src_part); + if (!its_bounds_overlap_mcut(current_src_part.its, cut_parts[j].its)) + continue; + } + auto cut_part = triangle_mesh_to_mcut(cut_parts[j].its); + const McutBooleanStatus status = do_boolean_single_with_provenance(*src_part, src_part_provenance, *cut_part, cut_parts[j].provenance, boolean_opts); + if (status == McutBooleanStatus::Failure) + return false; + if (status == McutBooleanStatus::NoOutput && boolean_opts != "A_NOT_B") + return false; + } + TriangleMesh tri_part = mcut_to_triangle_mesh(*src_part); + if (tri_part.its.indices.size() != src_part_provenance.size()) + return false; + append_with_provenance(all_its, all_provenance, tri_part.its, src_part_provenance); + } + } else if (boolean_opts == "INTERSECTION") { + for (size_t i = 0; i < src_parts.size(); ++i) { + for (size_t j = 0; j < cut_parts.size(); ++j) { + if (!its_bounds_overlap_mcut(src_parts[i].its, cut_parts[j].its)) + continue; + auto src_part = triangle_mesh_to_mcut(src_parts[i].its); + auto cut_part = triangle_mesh_to_mcut(cut_parts[j].its); + std::vector src_part_provenance = src_parts[i].provenance; + const McutBooleanStatus status = do_boolean_single_with_provenance(*src_part, src_part_provenance, *cut_part, cut_parts[j].provenance, boolean_opts); + if (status == McutBooleanStatus::Failure) + return false; + if (status == McutBooleanStatus::NoOutput) + continue; + TriangleMesh tri_part = mcut_to_triangle_mesh(*src_part); + if (tri_part.its.indices.size() != src_part_provenance.size()) + return false; + append_with_provenance(all_its, all_provenance, tri_part.its, src_part_provenance); + } + } + } else + return false; + + srcMesh = *triangle_mesh_to_mcut(all_its); + src_provenance = std::move(all_provenance); + return true; +} + void make_boolean(const TriangleMesh &src_mesh, const TriangleMesh &cut_mesh, std::vector &dst_mesh, const std::string &boolean_opts) { McutMesh srcMesh, cutMesh; @@ -902,6 +1562,31 @@ void make_boolean(const TriangleMesh &src_mesh, const TriangleMesh &cut_mesh, st dst_mesh.push_back(std::move(tri_src)); } +bool make_boolean_with_provenance(const TriangleMesh &src_mesh, + size_t src_source_index, + const TriangleMesh &cut_mesh, + size_t cut_source_index, + std::vector &dst_mesh, + const std::string &boolean_opts) +{ + McutMesh srcMesh, cutMesh; + triangle_mesh_to_mcut(src_mesh, srcMesh); + triangle_mesh_to_mcut(cut_mesh, cutMesh); + std::vector src_provenance = identity_provenance(src_mesh.its, src_source_index); + std::vector cut_provenance = identity_provenance(cut_mesh.its, cut_source_index); + + if (!do_boolean_with_provenance(srcMesh, src_provenance, cutMesh, cut_provenance, boolean_opts)) + return false; + + TriangleMesh tri_src = mcut_to_triangle_mesh(srcMesh); + if (!tri_src.empty()) { + if (tri_src.its.indices.size() != src_provenance.size()) + return false; + dst_mesh.push_back({ std::move(tri_src), std::move(src_provenance) }); + } + return true; +} + } // namespace mcut diff --git a/src/libslic3r/MeshBoolean.hpp b/src/libslic3r/MeshBoolean.hpp index d9a5d196fbf..09a0911d154 100644 --- a/src/libslic3r/MeshBoolean.hpp +++ b/src/libslic3r/MeshBoolean.hpp @@ -1,8 +1,10 @@ #ifndef libslic3r_MeshBoolean_hpp_ #define libslic3r_MeshBoolean_hpp_ +#include #include #include +#include #include #include @@ -84,8 +86,23 @@ struct McutMeshDeleter using McutMeshPtr = std::unique_ptr; bool empty(const McutMesh &mesh); +struct MeshFaceProvenance +{ + bool valid { false }; + size_t source_index { 0 }; + size_t source_triangle { 0 }; + std::array source_barycentric; +}; + +struct ProvenancedMesh +{ + TriangleMesh mesh; + std::vector provenance; +}; + McutMeshPtr triangle_mesh_to_mcut(const indexed_triangle_set &M); TriangleMesh mcut_to_triangle_mesh(const McutMesh &mcutmesh); +std::vector identity_provenance(const indexed_triangle_set &its, size_t source_index); // do boolean and save result to srcMesh // return true if sucessful @@ -93,10 +110,21 @@ bool do_boolean_single(McutMesh& srcMesh, const McutMesh& cutMesh, const std::st // do boolean of mesh with multiple volumes and save result to srcMesh // Both srcMesh and cutMesh may have multiple volumes. void do_boolean(McutMesh &srcMesh, const McutMesh &cutMesh, const std::string &boolean_opts); +bool do_boolean_with_provenance(McutMesh &srcMesh, + std::vector &src_provenance, + const McutMesh &cutMesh, + const std::vector &cut_provenance, + const std::string &boolean_opts); // do boolean and convert result to TriangleMesh void make_boolean(const TriangleMesh &src_mesh, const TriangleMesh &cut_mesh, std::vector &dst_mesh, const std::string &boolean_opts); +bool make_boolean_with_provenance(const TriangleMesh &src_mesh, + size_t src_source_index, + const TriangleMesh &cut_mesh, + size_t cut_source_index, + std::vector &dst_mesh, + const std::string &boolean_opts); } // namespace mcut } // namespace MeshBoolean diff --git a/src/libslic3r/ModelTextureDataRemap.cpp b/src/libslic3r/ModelTextureDataRemap.cpp index f46742c9775..028bd831320 100644 --- a/src/libslic3r/ModelTextureDataRemap.cpp +++ b/src/libslic3r/ModelTextureDataRemap.cpp @@ -1445,6 +1445,436 @@ static SimplifyTextureDataResult remap_vertex_colors_from_snapshot(const Simplif return result; } +struct PreparedMultiSourceTextureDataSource +{ + MultiSourceTextureDataSource source; + RgbaFacetLookup rgba_lookup; + RegionFacetLookup region_lookup; +}; + +static bool snapshot_has_color_data(const SimplifyTextureDataSnapshot &snapshot) +{ + return snapshot.source != SimplifyColorSource::None; +} + +static ColorRGBA first_multi_source_fallback_color(const std::vector &sources) +{ + return sources.empty() ? ColorRGBA(1.f, 1.f, 1.f, 1.f) : sources.front().source.fallback_color; +} + +static bool multi_source_provenance_source(const std::vector &sources, + const std::vector &provenance, + size_t target_triangle, + size_t &source_index, + const MultiSourceTextureTriangleProvenance *&entry) +{ + if (target_triangle >= provenance.size()) + return false; + entry = &provenance[target_triangle]; + if (!entry->valid || entry->source_index >= sources.size()) + return false; + source_index = entry->source_index; + return true; +} + +static Vec3f multi_source_source_barycentric(const MultiSourceTextureTriangleProvenance &entry, const Vec3f &target_barycentric) +{ + return normalized_nonnegative_barycentric(entry.source_barycentric[0] * target_barycentric.x() + + entry.source_barycentric[1] * target_barycentric.y() + + entry.source_barycentric[2] * target_barycentric.z()); +} + +static bool multi_source_source_point(const SimplifyTextureDataSnapshot &snapshot, + size_t source_triangle, + const Vec3f &source_barycentric, + Vec3f &source_point) +{ + std::array source_vertices; + if (!valid_triangle_vertices(snapshot.source_mesh, source_triangle, source_vertices)) + return false; + source_point = source_vertices[0] * source_barycentric.x() + + source_vertices[1] * source_barycentric.y() + + source_vertices[2] * source_barycentric.z(); + return true; +} + +static bool ensure_snapshot_rgba_preview_from_raw(SimplifyTextureDataSnapshot &snapshot) +{ + if (snapshot_has_valid_rgba_texture(snapshot)) + return true; + if (!snapshot_has_valid_raw_atlas(snapshot)) + return false; + + ImageMapRawFilamentOffsetAtlas atlas; + atlas.width = snapshot.texture_width; + atlas.height = snapshot.texture_height; + atlas.channels = snapshot.texture_raw_channels; + atlas.offsets = snapshot.texture_raw_filament_offsets; + atlas.metadata_json = snapshot.texture_raw_metadata_json; + atlas.filaments = image_map_raw_filaments_from_metadata_json(atlas.metadata_json, atlas.channels); + snapshot.texture_rgba = image_map_raw_filament_offset_preview_rgba(atlas); + return snapshot_has_valid_rgba_texture(snapshot); +} + +static std::vector prepare_multi_source_texture_data_sources(const std::vector &sources) +{ + std::vector prepared; + prepared.reserve(sources.size()); + for (const MultiSourceTextureDataSource &source : sources) { + PreparedMultiSourceTextureDataSource item; + item.source = source; + if (item.source.snapshot.source == SimplifyColorSource::ImageTexture) + limit_snapshot_texture_resolution(item.source.snapshot); + if (item.source.snapshot.source == SimplifyColorSource::RgbaData) + item.rgba_lookup = make_rgba_facet_lookup(item.source.snapshot); + if (item.source.snapshot.region_painting_transfer_needed) + item.region_lookup = make_region_facet_lookup(item.source.snapshot); + prepared.emplace_back(std::move(item)); + } + return prepared; +} + +static ColorRGBA sample_multi_source_color_at_target(const std::vector &sources, + const std::vector &provenance, + size_t target_triangle, + const Vec3f &target_barycentric) +{ + size_t source_index = 0; + const MultiSourceTextureTriangleProvenance *entry = nullptr; + if (!multi_source_provenance_source(sources, provenance, target_triangle, source_index, entry)) + return first_multi_source_fallback_color(sources); + + const PreparedMultiSourceTextureDataSource &source = sources[source_index]; + const SimplifyTextureDataSnapshot &snapshot = source.source.snapshot; + if (snapshot.source == SimplifyColorSource::None) + return source.source.fallback_color; + + const Vec3f source_barycentric = multi_source_source_barycentric(*entry, target_barycentric); + switch (snapshot.source) { + case SimplifyColorSource::RgbaData: { + Vec3f source_point = Vec3f::Zero(); + if (!multi_source_source_point(snapshot, entry->source_triangle, source_barycentric, source_point)) + return source.source.fallback_color; + return sample_rgba_facets_at_source(snapshot, source.rgba_lookup, entry->source_triangle, source_point); + } + case SimplifyColorSource::ImageTexture: + return snapshot_has_valid_rgba_texture(snapshot) ? + sample_image_rgba_at_source(snapshot, entry->source_triangle, source_barycentric) : + source.source.fallback_color; + case SimplifyColorSource::VertexColors: + return sample_vertex_color_at_source(snapshot, entry->source_triangle, source_barycentric); + case SimplifyColorSource::None: + break; + } + return source.source.fallback_color; +} + +static std::vector sample_multi_source_raw_at_target(const std::vector &sources, + const std::vector &provenance, + size_t target_triangle, + const Vec3f &target_barycentric, + uint32_t channels) +{ + size_t source_index = 0; + const MultiSourceTextureTriangleProvenance *entry = nullptr; + if (!multi_source_provenance_source(sources, provenance, target_triangle, source_index, entry)) + return std::vector(size_t(channels), 0); + + const SimplifyTextureDataSnapshot &snapshot = sources[source_index].source.snapshot; + if (snapshot.source != SimplifyColorSource::ImageTexture || !snapshot_has_valid_raw_atlas(snapshot)) + return std::vector(size_t(channels), 0); + return sample_image_raw_at_source(snapshot, entry->source_triangle, multi_source_source_barycentric(*entry, target_barycentric)); +} + +static unsigned int sample_multi_source_region_at_target(const std::vector &sources, + const std::vector &provenance, + size_t target_triangle, + const Vec3f &target_barycentric) +{ + size_t source_index = 0; + const MultiSourceTextureTriangleProvenance *entry = nullptr; + if (!multi_source_provenance_source(sources, provenance, target_triangle, source_index, entry)) + return 0; + + const PreparedMultiSourceTextureDataSource &source = sources[source_index]; + const SimplifyTextureDataSnapshot &snapshot = source.source.snapshot; + if (!snapshot.region_painting_transfer_needed) + return 0; + + const Vec3f source_barycentric = multi_source_source_barycentric(*entry, target_barycentric); + Vec3f source_point = Vec3f::Zero(); + if (!multi_source_source_point(snapshot, entry->source_triangle, source_barycentric, source_point)) + return 0; + return sample_region_state_at_source(snapshot, source.region_lookup, entry->source_triangle, source_point); +} + +static bool multi_source_raw_compatible(const std::vector &sources, + const std::vector &participating, + uint32_t &channels, + std::string &metadata_json) +{ + channels = 0; + metadata_json.clear(); + bool found = false; + for (size_t source_idx = 0; source_idx < sources.size(); ++source_idx) { + if (source_idx >= participating.size() || participating[source_idx] == 0) + continue; + const SimplifyTextureDataSnapshot &snapshot = sources[source_idx].source.snapshot; + if (!snapshot_has_color_data(snapshot)) + continue; + if (snapshot.source != SimplifyColorSource::ImageTexture || !snapshot_has_valid_raw_atlas(snapshot)) + return false; + if (!found) { + channels = snapshot.texture_raw_channels; + metadata_json = snapshot.texture_raw_metadata_json; + found = true; + } else if (channels != snapshot.texture_raw_channels || metadata_json != snapshot.texture_raw_metadata_json) + return false; + } + return found; +} + +static SimplifyTextureDataResult remap_multi_source_rgba_from_provenance(const std::vector &sources, + const indexed_triangle_set &target_mesh, + const std::vector &provenance, + bool any_image_source, + const SimplifyTextureCancelFn &throw_on_cancel, + const SimplifyTextureProgressFn &status_fn) +{ + SimplifyTextureDataResult result; + result.source = SimplifyColorSource::RgbaData; + if (target_mesh.indices.empty() || provenance.size() != target_mesh.indices.size()) + return result; + + set_progress(status_fn, 0); + auto annotation = ColorFacetsAnnotation::make_temporary(); + annotation->set_metadata_json(rgb_metadata_json(ColorRGBA(1.f, 1.f, 1.f, 1.f))); + + const int max_depth = any_image_source ? 5 : 4; + const float target_span = std::max(mesh_max_axis_span(target_mesh) / 120.f, 0.3f); + TextureMappingColorSubdivisionDepths subdivision_depths = + [target_span, max_depth](size_t, const std::array &vertices) { + const int depth = texture_mapping_depth_from_span(triangle_max_edge_length(vertices), target_span, max_depth); + return std::make_pair(depth, max_depth); + }; + + size_t sample_counter = 0; + TextureMappingColorSampler sampler = [&](size_t target_triangle, const Vec3f &, const Vec3f &target_barycentric) { + if ((++sample_counter & 1023u) == 0) + check_cancel(throw_on_cancel); + return pack_rgba(sample_multi_source_color_at_target(sources, provenance, target_triangle, target_barycentric)); + }; + + const float split_threshold = any_image_source ? 0.025f : 0.04f; + TextureMappingColorProgressFn progress_fn = [&status_fn](size_t completed, size_t total) { + const int percent = total == 0 ? 100 : int((uint64_t(completed) * 100u) / uint64_t(total)); + set_progress(status_fn, percent); + }; + annotation->set_from_triangle_sampler(target_mesh, sampler, max_depth, split_threshold, subdivision_depths, nullptr, {}, progress_fn); + check_cancel(throw_on_cancel); + set_progress(status_fn, 100); + if (!annotation->empty()) + result.rgba_data = std::move(annotation); + return result; +} + +static SimplifyTextureDataResult remap_multi_source_image_from_provenance(const std::vector &sources, + const indexed_triangle_set &target_mesh, + const std::vector &provenance, + bool remap_raw, + uint32_t raw_channels, + const std::string &raw_metadata_json, + const SimplifyTextureCancelFn &throw_on_cancel, + const SimplifyTextureProgressFn &status_fn) +{ + SimplifyTextureDataResult result; + if (target_mesh.indices.empty() || provenance.size() != target_mesh.indices.size()) + return result; + + set_progress(status_fn, 0); + GeneratedImageTextureAtlas atlas; + if (!initialize_generated_image_texture(target_mesh, result, atlas, first_multi_source_fallback_color(sources))) + return result; + set_progress(status_fn, 8); + check_cancel(throw_on_cancel); + + if (remap_raw) { + result.texture_raw_channels = raw_channels; + result.texture_raw_metadata_json = raw_metadata_json; + result.texture_raw_filament_offsets.assign(size_t(result.texture_width) * size_t(result.texture_height) * size_t(result.texture_raw_channels), 0); + } + + auto island_pixel_count = [&result](const GeneratedImageTextureIsland &island) -> uint64_t { + const int x_begin = std::max(0, island.x); + const int y_begin = std::max(0, island.y); + const int x_end = std::min(island.x + island.rect_width, int(result.texture_width)); + const int y_end = std::min(island.y + island.rect_height, int(result.texture_height)); + if (x_end <= x_begin || y_end <= y_begin) + return 0; + return uint64_t(x_end - x_begin) * uint64_t(y_end - y_begin); + }; + + uint64_t total_pixels = 0; + for (const GeneratedImageTextureIsland &island : atlas.islands) + total_pixels += island_pixel_count(island); + + uint64_t processed_pixels = 0; + int last_raster_progress = -1; + auto report_raster_progress = [&status_fn, total_pixels, &processed_pixels, &last_raster_progress]() { + const int progress = total_pixels == 0 ? 96 : 8 + int((processed_pixels * 88u) / total_pixels); + if (progress != last_raster_progress) { + last_raster_progress = progress; + set_progress(status_fn, progress); + } + }; + report_raster_progress(); + + size_t pixel_counter = 0; + for (size_t island_idx = 0; island_idx < atlas.islands.size(); ++island_idx) { + if ((island_idx & 31u) == 0) + check_cancel(throw_on_cancel); + + const GeneratedImageTextureIsland &island = atlas.islands[island_idx]; + const uint64_t island_pixels = island_pixel_count(island); + const size_t uv_offset = island.tri_idx * 6; + if (uv_offset + 5 >= result.texture_uvs_per_face.size()) { + processed_pixels += island_pixels; + report_raster_progress(); + continue; + } + + const float texture_size = float(result.texture_width); + const std::array target_uv_pixels = { + Vec2f(result.texture_uvs_per_face[uv_offset + 0] * texture_size, result.texture_uvs_per_face[uv_offset + 1] * texture_size), + Vec2f(result.texture_uvs_per_face[uv_offset + 2] * texture_size, result.texture_uvs_per_face[uv_offset + 3] * texture_size), + Vec2f(result.texture_uvs_per_face[uv_offset + 4] * texture_size, result.texture_uvs_per_face[uv_offset + 5] * texture_size) + }; + + const int x_end = std::min(island.x + island.rect_width, int(result.texture_width)); + const int y_end = std::min(island.y + island.rect_height, int(result.texture_height)); + for (int y = std::max(0, island.y); y < y_end; ++y) { + for (int x = std::max(0, island.x); x < x_end; ++x) { + ++processed_pixels; + report_raster_progress(); + if ((++pixel_counter & 65535u) == 0) + check_cancel(throw_on_cancel); + + const Vec2f pixel(float(x) + 0.5f, float(y) + 0.5f); + const Vec3f raw_target_bary = barycentric_weights_2d(pixel, target_uv_pixels); + const Vec3f target_bary = normalized_nonnegative_barycentric(raw_target_bary); + if (std::min({ raw_target_bary.x(), raw_target_bary.y(), raw_target_bary.z() }) < -1e-4f) { + const Vec2f closest = target_uv_pixels[0] * target_bary.x() + + target_uv_pixels[1] * target_bary.y() + + target_uv_pixels[2] * target_bary.z(); + const float bleed_px = float(atlas.padding_px) + 1.5f; + if ((closest - pixel).squaredNorm() > bleed_px * bleed_px) + continue; + } + + if (remap_raw) { + write_raw_offset_pixel(result.texture_raw_filament_offsets, + result.texture_width, + result.texture_raw_channels, + uint32_t(x), + uint32_t(y), + sample_multi_source_raw_at_target(sources, provenance, island.tri_idx, target_bary, raw_channels)); + } else { + write_rgba_pixel(result.texture_rgba, + result.texture_width, + uint32_t(x), + uint32_t(y), + sample_multi_source_color_at_target(sources, provenance, island.tri_idx, target_bary)); + } + } + } + } + + if (remap_raw && !refresh_result_preview_from_raw(result)) + return SimplifyTextureDataResult(); + + set_progress(status_fn, 100); + result.source = SimplifyColorSource::ImageTexture; + return result; +} + +static void remap_multi_source_region_painting_from_provenance(const std::vector &sources, + const indexed_triangle_set &target_mesh, + const std::vector &provenance, + bool region_painting_present, + bool region_painting_transfer_needed, + const SimplifyTextureCancelFn &throw_on_cancel, + const SimplifyTextureProgressFn &status_fn, + SimplifyTextureDataResult &result) +{ + result.region_painting_touched = region_painting_present; + if (!region_painting_present) { + set_progress(status_fn, 100); + return; + } + + if (!region_painting_transfer_needed) { + set_progress(status_fn, 100); + return; + } + + if (target_mesh.indices.empty() || provenance.size() != target_mesh.indices.size()) { + result.region_painting_remap_failed = true; + set_progress(status_fn, 100); + return; + } + + set_progress(status_fn, 0); + const int max_depth = 4; + const float target_span = std::max(mesh_max_axis_span(target_mesh) / 120.f, 0.3f); + const std::array root_barycentrics = { + Vec3f(1.f, 0.f, 0.f), + Vec3f(0.f, 1.f, 0.f), + Vec3f(0.f, 0.f, 1.f) + }; + + RegionPaintingSampler sampler = [&](size_t target_triangle, const Vec3f &, const Vec3f &target_barycentric) { + return sample_multi_source_region_at_target(sources, provenance, target_triangle, target_barycentric); + }; + + TriangleSelector::TriangleSplittingData data; + data.triangles_to_split.reserve(target_mesh.indices.size()); + size_t sample_counter = 0; + for (size_t tri_idx = 0; tri_idx < target_mesh.indices.size(); ++tri_idx) { + if ((++sample_counter & 255u) == 0) { + check_cancel(throw_on_cancel); + set_progress(status_fn, int((uint64_t(tri_idx) * 100u) / std::max(target_mesh.indices.size(), 1))); + } + + std::array vertices; + if (!valid_triangle_vertices(target_mesh, tri_idx, vertices)) + continue; + + const size_t bitstream_start = data.bitstream.size(); + const int min_depth = texture_mapping_depth_from_span(triangle_max_edge_length(vertices), target_span, max_depth); + const bool has_non_base = region_append_sampled_triangle(data, + sampler, + tri_idx, + vertices, + root_barycentrics, + 0, + min_depth, + max_depth); + if (has_non_base) + data.triangles_to_split.emplace_back(int(tri_idx), int(bitstream_start)); + else + data.bitstream.resize(bitstream_start); + } + + check_cancel(throw_on_cancel); + set_progress(status_fn, 100); + data.triangles_to_split.shrink_to_fit(); + data.bitstream.shrink_to_fit(); + if (!data.triangles_to_split.empty()) { + result.region_painting_data = std::move(data); + result.region_painting_valid = true; + } +} + static void clear_image_texture(ModelVolume &volume) { volume.imported_texture_uvs_per_face.clear(); @@ -1650,6 +2080,117 @@ SimplifyTextureDataResult remap_simplify_texture_data(const SimplifyTextureDataS return result; } +SimplifyTextureDataResult remap_multi_source_texture_data(const std::vector &sources, + const indexed_triangle_set &target_mesh, + const std::vector &provenance, + const SimplifyTextureCancelFn &throw_on_cancel, + const SimplifyTextureProgressFn &status_fn, + const SimplifyTextureDataRemapOptions &options) +{ + std::vector prepared = prepare_multi_source_texture_data_sources(sources); + std::vector participating(prepared.size(), 0); + for (const MultiSourceTextureTriangleProvenance &entry : provenance) + if (entry.valid && entry.source_index < participating.size()) + participating[entry.source_index] = 1; + + bool has_color_data = false; + bool any_image_source = false; + bool region_painting_present = false; + bool region_painting_transfer_needed = false; + for (size_t source_idx = 0; source_idx < prepared.size(); ++source_idx) { + if (source_idx >= participating.size() || participating[source_idx] == 0) + continue; + const SimplifyTextureDataSnapshot &snapshot = prepared[source_idx].source.snapshot; + has_color_data = true; + any_image_source |= snapshot.source == SimplifyColorSource::ImageTexture; + region_painting_present |= snapshot.region_painting_present; + region_painting_transfer_needed |= snapshot.region_painting_transfer_needed; + } + + const bool remap_color = has_color_data && options.remap_color_data && !target_mesh.indices.empty(); + const bool remap_region = region_painting_present && + options.remap_region_painting && + region_painting_transfer_needed && + !target_mesh.indices.empty(); + if (!remap_color && !remap_region) { + set_progress(status_fn, 100); + SimplifyTextureDataResult result; + result.source = SimplifyColorSource::None; + result.region_painting_touched = region_painting_present; + return result; + } + + if (provenance.size() != target_mesh.indices.size()) { + SimplifyTextureDataResult result; + result.source = SimplifyColorSource::None; + result.remap_failed = remap_color; + result.region_painting_touched = region_painting_present; + result.region_painting_remap_failed = remap_region; + set_progress(status_fn, 100); + return result; + } + + uint32_t raw_channels = 0; + std::string raw_metadata_json; + const bool remap_raw = any_image_source && multi_source_raw_compatible(prepared, participating, raw_channels, raw_metadata_json); + if (any_image_source && !remap_raw) { + for (PreparedMultiSourceTextureDataSource &source : prepared) + if (source.source.snapshot.source == SimplifyColorSource::ImageTexture) + ensure_snapshot_rgba_preview_from_raw(source.source.snapshot); + } + + auto scaled_progress = [&status_fn](int offset, int span) { + return [status_fn, offset, span](int percent) { + set_progress(status_fn, offset + int(std::round(float(std::clamp(percent, 0, 100)) * float(span) / 100.f))); + }; + }; + + const bool split_progress = remap_color && region_painting_present; + SimplifyTextureDataResult result; + if (remap_color) { + const SimplifyTextureProgressFn color_progress = split_progress ? scaled_progress(0, 80) : status_fn; + if (any_image_source) { + result = remap_multi_source_image_from_provenance(prepared, + target_mesh, + provenance, + remap_raw, + raw_channels, + raw_metadata_json, + throw_on_cancel, + color_progress); + if (result.source != SimplifyColorSource::ImageTexture) { + result = remap_multi_source_rgba_from_provenance(prepared, target_mesh, provenance, true, throw_on_cancel, color_progress); + result.remap_failed = true; + result.used_fallback_rgba = result.source == SimplifyColorSource::RgbaData && result.rgba_data != nullptr; + } + } else + result = remap_multi_source_rgba_from_provenance(prepared, target_mesh, provenance, false, throw_on_cancel, color_progress); + } else + result.source = SimplifyColorSource::None; + + if (region_painting_present) { + SimplifyTextureProgressFn region_progress = status_fn; + if (split_progress) + region_progress = scaled_progress(80, 20); + if (options.remap_region_painting) + remap_multi_source_region_painting_from_provenance(prepared, + target_mesh, + provenance, + region_painting_present, + region_painting_transfer_needed, + throw_on_cancel, + region_progress, + result); + else { + result.region_painting_touched = true; + set_progress(region_progress, 100); + } + } + + set_progress(status_fn, 100); + return result; +} + void apply_simplify_texture_data_result(ModelVolume &volume, SimplifyTextureDataResult &&result) { switch (result.source) { diff --git a/src/libslic3r/ModelTextureDataRemap.hpp b/src/libslic3r/ModelTextureDataRemap.hpp index 16ba2aac4fc..2de457d3ad3 100644 --- a/src/libslic3r/ModelTextureDataRemap.hpp +++ b/src/libslic3r/ModelTextureDataRemap.hpp @@ -6,6 +6,7 @@ #include "Model.hpp" #include "TriangleMesh.hpp" +#include #include #include #include @@ -72,6 +73,20 @@ struct SimplifyTextureDataRemapOptions bool remap_region_painting { true }; }; +struct MultiSourceTextureDataSource +{ + SimplifyTextureDataSnapshot snapshot; + ColorRGBA fallback_color { 1.f, 1.f, 1.f, 1.f }; +}; + +struct MultiSourceTextureTriangleProvenance +{ + bool valid { false }; + size_t source_index { 0 }; + size_t source_triangle { 0 }; + std::array source_barycentric; +}; + using SimplifyTextureCancelFn = std::function; using SimplifyTextureProgressFn = std::function; @@ -87,6 +102,13 @@ SimplifyTextureDataResult remap_simplify_texture_data(const SimplifyTextureDataS const SimplifyTextureProgressFn &status_fn = {}, const SimplifyTextureDataRemapOptions &options = SimplifyTextureDataRemapOptions()); +SimplifyTextureDataResult remap_multi_source_texture_data(const std::vector &sources, + const indexed_triangle_set &target_mesh, + const std::vector &provenance, + const SimplifyTextureCancelFn &throw_on_cancel = {}, + const SimplifyTextureProgressFn &status_fn = {}, + const SimplifyTextureDataRemapOptions &options = SimplifyTextureDataRemapOptions()); + void apply_simplify_texture_data_result(ModelVolume &volume, SimplifyTextureDataResult &&result); } // namespace Slic3r diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index bf4bfecb0fb..81ad1ac26c8 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -4,6 +4,7 @@ #include "GUI_Factories.hpp" //#include "GUI_ObjectLayers.hpp" #include "GUI_App.hpp" +#include "GUI_Utils.hpp" #include "I18N.hpp" #include "Plater.hpp" #include "BitmapComboBox.hpp" @@ -14,6 +15,8 @@ #include "Tab.hpp" #include "wxExtensions.hpp" #include "libslic3r/Model.hpp" +#include "libslic3r/MeshBoolean.hpp" +#include "libslic3r/ModelTextureDataRemap.hpp" #include "GLCanvas3D.hpp" #include "Selection.hpp" #include "PartPlate.hpp" @@ -25,6 +28,7 @@ #include "StepMeshDialog.hpp" +#include #include #include #include @@ -143,6 +147,257 @@ static void take_snapshot(const std::string& snapshot_name) plater->take_snapshot(snapshot_name); } +static wxString mesh_boolean_color_transfer_warning_text() +{ + return _L("Color data could not be transferred because boolean face provenance was unavailable. The mesh boolean result was created without color data."); +} + +static wxString mesh_boolean_color_remap_warning_text() +{ + return _L("Color data could not be transferred. The mesh boolean result was created without color data."); +} + +static void show_mesh_boolean_color_transfer_warning_dialog() +{ + MessageDialog(wxGetApp().plater(), mesh_boolean_color_transfer_warning_text(), _L("Warning"), wxOK | wxICON_WARNING).ShowModal(); +} + +static void show_mesh_boolean_color_remap_warning_dialog() +{ + MessageDialog(wxGetApp().plater(), mesh_boolean_color_remap_warning_text(), _L("Warning"), wxOK | wxICON_WARNING).ShowModal(); +} + +static ColorRGBA mesh_boolean_volume_base_color(const ModelVolume &volume) +{ + std::vector colors; + if (wxGetApp().plater() != nullptr) + colors = wxGetApp().plater()->get_extruder_colors_from_plater_config(nullptr, true); + const int extruder_id = std::max(volume.extruder_id(), 1); + if (size_t(extruder_id - 1) < colors.size()) + return decode_color_to_float_array(colors[size_t(extruder_id - 1)]); + return ColorRGBA(0.15f, 0.65f, 0.6f, 1.f); +} + +static bool mesh_boolean_snapshot_has_transfer_data(const SimplifyTextureDataSnapshot &snapshot) +{ + return snapshot.source != SimplifyColorSource::None || snapshot.region_painting_present; +} + +static const ModelVolume *mesh_boolean_primary_positive_volume(const ModelObject &object) +{ + for (const ModelVolume *volume : object.volumes) + if (volume != nullptr && volume->is_model_part()) + return volume; + return nullptr; +} + +static bool mesh_boolean_texture_result_has_output(const SimplifyTextureDataResult &result) +{ + switch (result.source) { + case SimplifyColorSource::RgbaData: + return !result.remap_failed || (result.rgba_data != nullptr && !result.rgba_data->empty()); + case SimplifyColorSource::ImageTexture: + return result.texture_width > 0 && + result.texture_height > 0 && + !result.texture_uv_valid.empty() && + (!result.texture_rgba.empty() || !result.texture_raw_filament_offsets.empty()); + case SimplifyColorSource::VertexColors: + return !result.vertex_colors_rgba.empty(); + case SimplifyColorSource::None: + break; + } + return result.region_painting_valid; +} + +static void mesh_boolean_transform_snapshot(SimplifyTextureDataSnapshot &snapshot, const Transform3d &transform, bool fix_left_handed) +{ + transform_simplify_texture_data_snapshot(snapshot, transform); + if (!fix_left_handed) + return; + if (transform.matrix().block(0, 0, 3, 3).determinant() >= 0.) + return; + if (!snapshot.source_mesh.indices.empty()) + its_flip_triangles(snapshot.source_mesh); + for (size_t tri_idx = 0; tri_idx < snapshot.texture_uv_valid.size(); ++tri_idx) { + const size_t uv_offset = tri_idx * 6; + if (uv_offset + 5 >= snapshot.texture_uvs_per_face.size()) + break; + std::swap(snapshot.texture_uvs_per_face[uv_offset + 2], snapshot.texture_uvs_per_face[uv_offset + 4]); + std::swap(snapshot.texture_uvs_per_face[uv_offset + 3], snapshot.texture_uvs_per_face[uv_offset + 5]); + } +} + +static MultiSourceTextureTriangleProvenance mesh_boolean_remap_provenance_entry(const MeshBoolean::mcut::MeshFaceProvenance &entry) +{ + MultiSourceTextureTriangleProvenance out; + out.valid = entry.valid; + out.source_index = entry.source_index; + out.source_triangle = entry.source_triangle; + out.source_barycentric = entry.source_barycentric; + return out; +} + +static std::vector mesh_boolean_remap_provenance(const std::vector &provenance) +{ + std::vector out; + out.reserve(provenance.size()); + for (const MeshBoolean::mcut::MeshFaceProvenance &entry : provenance) + out.emplace_back(mesh_boolean_remap_provenance_entry(entry)); + return out; +} + +static void mesh_boolean_append_transformed_mesh(indexed_triangle_set &dst, + std::vector &dst_provenance, + const TriangleMesh &src, + const std::vector &src_provenance, + const Transform3d &transform) +{ + if (src.its.indices.size() != src_provenance.size()) + return; + + TriangleMesh transformed = src; + transformed.transform(transform, true); + const bool flipped = transform.matrix().block(0, 0, 3, 3).determinant() < 0.; + const int vertex_offset = int(dst.vertices.size()); + dst.vertices.insert(dst.vertices.end(), transformed.its.vertices.begin(), transformed.its.vertices.end()); + for (const Vec3i32 &face : transformed.its.indices) + dst.indices.emplace_back(Vec3i32(face.x() + vertex_offset, face.y() + vertex_offset, face.z() + vertex_offset)); + for (MeshBoolean::mcut::MeshFaceProvenance entry : src_provenance) { + if (flipped) + std::swap(entry.source_barycentric[1], entry.source_barycentric[2]); + dst_provenance.emplace_back(entry); + } +} + +static void mesh_boolean_append_transformed_mesh(indexed_triangle_set &dst, const TriangleMesh &src, const Transform3d &transform) +{ + TriangleMesh transformed = src; + transformed.transform(transform, true); + its_merge(dst, transformed.its); +} + +static bool mesh_boolean_object_with_provenance(const ModelObject &object, + TriangleMesh &mesh, + std::vector &provenance, + std::vector &sources, + bool &had_transfer_data) +{ + const ModelVolume *primary_positive_volume = mesh_boolean_primary_positive_volume(object); + ColorRGBA positive_base_color = primary_positive_volume != nullptr ? + mesh_boolean_volume_base_color(*primary_positive_volume) : + ColorRGBA(0.15f, 0.65f, 0.6f, 1.f); + + MeshBoolean::mcut::McutMeshPtr current_mesh; + std::vector current_provenance; + bool has_current_mesh = false; + + for (const ModelVolume *volume : object.volumes) { + if (volume == nullptr || volume->mesh_ptr() == nullptr || (!volume->is_model_part() && !volume->is_negative_volume())) + continue; + + TriangleMesh volume_mesh = volume->mesh(); + volume_mesh.transform(volume->get_matrix(), true); + if (volume_mesh.empty()) + continue; + + SimplifyTextureDataSnapshot snapshot = snapshot_simplify_texture_data(*volume); + mesh_boolean_transform_snapshot(snapshot, volume->get_matrix(), true); + had_transfer_data = true; + had_transfer_data |= mesh_boolean_snapshot_has_transfer_data(snapshot); + + const bool use_positive_fallback = volume->is_negative_volume() && snapshot.source == SimplifyColorSource::None; + MultiSourceTextureDataSource source; + source.snapshot = std::move(snapshot); + source.fallback_color = use_positive_fallback ? positive_base_color : mesh_boolean_volume_base_color(*volume); + const size_t source_index = sources.size(); + sources.emplace_back(std::move(source)); + + auto volume_mcut = MeshBoolean::mcut::triangle_mesh_to_mcut(volume_mesh.its); + std::vector volume_provenance = MeshBoolean::mcut::identity_provenance(volume_mesh.its, source_index); + if (volume->is_model_part()) { + if (!has_current_mesh) { + current_mesh = std::move(volume_mcut); + current_provenance = std::move(volume_provenance); + has_current_mesh = true; + } else if (!MeshBoolean::mcut::do_boolean_with_provenance(*current_mesh, current_provenance, *volume_mcut, volume_provenance, "UNION")) + return false; + } else if (has_current_mesh && !MeshBoolean::mcut::do_boolean_with_provenance(*current_mesh, current_provenance, *volume_mcut, volume_provenance, "A_NOT_B")) + return false; + } + + if (!has_current_mesh) + return false; + + TriangleMesh object_mesh = MeshBoolean::mcut::mcut_to_triangle_mesh(*current_mesh); + if (object_mesh.empty() || object_mesh.its.indices.size() != current_provenance.size()) + return false; + + indexed_triangle_set all_instances; + std::vector all_provenance; + if (object.instances.empty()) + mesh_boolean_append_transformed_mesh(all_instances, all_provenance, object_mesh, current_provenance, Transform3d::Identity()); + else { + for (const ModelInstance *instance : object.instances) + if (instance != nullptr) + mesh_boolean_append_transformed_mesh(all_instances, all_provenance, object_mesh, current_provenance, instance->get_matrix()); + } + + if (all_instances.indices.size() != all_provenance.size()) + return false; + + mesh = TriangleMesh(std::move(all_instances)); + provenance = std::move(all_provenance); + return true; +} + +static bool mesh_boolean_object_without_provenance(const ModelObject &object, TriangleMesh &mesh) +{ + MeshBoolean::mcut::McutMeshPtr current_mesh; + bool has_current_mesh = false; + + for (const ModelVolume *volume : object.volumes) { + if (volume == nullptr || volume->mesh_ptr() == nullptr || (!volume->is_model_part() && !volume->is_negative_volume())) + continue; + + TriangleMesh volume_mesh = volume->mesh(); + volume_mesh.transform(volume->get_matrix(), true); + if (volume_mesh.empty()) + continue; + + auto volume_mcut = MeshBoolean::mcut::triangle_mesh_to_mcut(volume_mesh.its); + if (volume->is_model_part()) { + if (!has_current_mesh) { + current_mesh = std::move(volume_mcut); + has_current_mesh = true; + } else + MeshBoolean::mcut::do_boolean(*current_mesh, *volume_mcut, "UNION"); + } else if (has_current_mesh) + MeshBoolean::mcut::do_boolean(*current_mesh, *volume_mcut, "A_NOT_B"); + } + + if (!has_current_mesh) + return false; + + TriangleMesh object_mesh = MeshBoolean::mcut::mcut_to_triangle_mesh(*current_mesh); + if (object_mesh.empty()) + return false; + + indexed_triangle_set all_instances; + if (object.instances.empty()) + mesh_boolean_append_transformed_mesh(all_instances, object_mesh, Transform3d::Identity()); + else { + for (const ModelInstance *instance : object.instances) + if (instance != nullptr) + mesh_boolean_append_transformed_mesh(all_instances, object_mesh, instance->get_matrix()); + } + + if (all_instances.indices.empty()) + return false; + + mesh = TriangleMesh(std::move(all_instances)); + return true; +} + class wxRenderer : public wxDelegateRendererNative { public: @@ -3118,11 +3373,9 @@ void ObjectList::merge(bool to_multipart_object) if (object->volumes.size() > 1){ new_volume->config.assign_config(volume->config); } - auto option = new_volume->config.option("extruder"); - if (!option) { - auto opt = object->config.option("extruder"); - if (opt) { new_volume->config.set_key_value("extruder", new ConfigOptionInt(opt->getInt())); } - } + const int source_extruder_id = volume->extruder_id(); + if (source_extruder_id > 0) + new_volume->config.set_key_value("extruder", new ConfigOptionInt(source_extruder_id)); new_volume->mmu_segmentation_facets.assign(std::move(volume->mmu_segmentation_facets)); } new_object->sort_volumes(true); @@ -3143,14 +3396,6 @@ void ObjectList::merge(bool to_multipart_object) config.set_key_value(opt_key, option->clone()); } } - // save extruder value if it was set - if (object->volumes.size() == 1 && find(opt_keys.begin(), opt_keys.end(), "extruder") != opt_keys.end()) { - ModelVolume* volume = new_object->volumes.back(); - const ConfigOption* option = from_config.option("extruder"); - if (option) - volume->config.set_key_value("extruder", option->clone()); - } - // merge printable and auto_drop values // non-default have priority -> if one object has printable == false, // then merged object will also have printable == false @@ -3308,7 +3553,15 @@ void ObjectList::boolean() Plater::TakeSnapshot snapshot(wxGetApp().plater(), "boolean"); ModelObject* object = (*m_objects)[obj_idxs.front()]; - TriangleMesh mesh = Plater::combine_mesh_fff(*object, -1, [this](const std::string& msg) {return wxGetApp().notification_manager()->push_plater_error_notification(msg); }); + TriangleMesh mesh; + std::vector provenance; + std::vector sources; + bool had_transfer_data = false; + bool provenance_available = mesh_boolean_object_with_provenance(*object, mesh, provenance, sources, had_transfer_data); + if (!provenance_available && !mesh_boolean_object_without_provenance(*object, mesh)) { + wxGetApp().notification_manager()->push_plater_error_notification(_u8L("Unable to perform boolean operation on model meshes.")); + return; + } // add mesh to model as a new object, keep the original object's name and config Model* model = object->get_model(); @@ -3318,6 +3571,26 @@ void ObjectList::boolean() if (new_object->instances.empty()) new_object->add_instance(); ModelVolume* new_volume = new_object->add_volume(mesh); + if (const ModelVolume *primary_positive_volume = mesh_boolean_primary_positive_volume(*object); primary_positive_volume != nullptr) + new_volume->config.apply(primary_positive_volume->config); + if (provenance_available && had_transfer_data && new_volume->mesh().its.indices.size() == provenance.size()) { + std::vector remap_provenance = mesh_boolean_remap_provenance(provenance); + ProgressDialog progress_dlg(_L("Transferring color data"), "", 100, find_toplevel_parent(wxGetApp().plater()), wxPD_AUTO_HIDE | wxPD_APP_MODAL); + progress_dlg.Update(0, _L("Transferring color data")); + SimplifyTextureDataResult result = remap_multi_source_texture_data(sources, + new_volume->mesh().its, + remap_provenance, + {}, + [&progress_dlg](int percent) { + progress_dlg.Update(std::clamp(percent, 0, 100), _L("Transferring color data")); + }); + progress_dlg.Update(100, _L("Transferring color data")); + if (mesh_boolean_texture_result_has_output(result) || result.region_painting_touched) + apply_simplify_texture_data_result(*new_volume, std::move(result)); + else + show_mesh_boolean_color_remap_warning_dialog(); + } else if (had_transfer_data) + show_mesh_boolean_color_transfer_warning_dialog(); // BBS: ensure on bed but no need to ensure locate in the center around origin new_object->ensure_on_bed(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeshBoolean.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMeshBoolean.cpp index bfff70d315a..19e461c55fb 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMeshBoolean.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMeshBoolean.cpp @@ -2,10 +2,13 @@ #include "slic3r/GUI/GLCanvas3D.hpp" #include "slic3r/GUI/ImGuiWrapper.hpp" #include "slic3r/GUI/GUI.hpp" +#include "slic3r/GUI/GUI_Utils.hpp" #include "libslic3r/MeshBoolean.hpp" +#include "libslic3r/ModelTextureDataRemap.hpp" #include "slic3r/GUI/GUI_ObjectList.hpp" #include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/Camera.hpp" +#include "slic3r/GUI/MsgDialog.hpp" #include "slic3r/GUI/NotificationManager.hpp" #ifndef IMGUI_DEFINE_MATH_OPERATORS #define IMGUI_DEFINE_MATH_OPERATORS @@ -16,6 +19,111 @@ namespace GUI { static const std::string warning_text = _u8L("Unable to perform boolean operation on selected parts"); +static wxString mesh_boolean_color_transfer_warning_text() +{ + return _L("Color data could not be transferred because boolean face provenance was unavailable. The mesh boolean result was created without color data."); +} + +static wxString mesh_boolean_color_remap_warning_text() +{ + return _L("Color data could not be transferred. The mesh boolean result was created without color data."); +} + +static void show_mesh_boolean_color_transfer_warning_dialog() +{ + MessageDialog(wxGetApp().plater(), mesh_boolean_color_transfer_warning_text(), _L("Warning"), wxOK | wxICON_WARNING).ShowModal(); +} + +static void show_mesh_boolean_color_remap_warning_dialog() +{ + MessageDialog(wxGetApp().plater(), mesh_boolean_color_remap_warning_text(), _L("Warning"), wxOK | wxICON_WARNING).ShowModal(); +} + +static ColorRGBA mesh_boolean_volume_base_color(const ModelVolume &volume) +{ + std::vector colors; + if (wxGetApp().plater() != nullptr) + colors = wxGetApp().plater()->get_extruder_colors_from_plater_config(nullptr, true); + const int extruder_id = std::max(volume.extruder_id(), 1); + if (size_t(extruder_id - 1) < colors.size()) + return decode_color_to_float_array(colors[size_t(extruder_id - 1)]); + return ColorRGBA(0.15f, 0.65f, 0.6f, 1.f); +} + +static bool mesh_boolean_snapshot_has_transfer_data(const SimplifyTextureDataSnapshot &snapshot) +{ + return snapshot.source != SimplifyColorSource::None || snapshot.region_painting_present; +} + +static bool mesh_boolean_texture_result_has_output(const SimplifyTextureDataResult &result) +{ + switch (result.source) { + case SimplifyColorSource::RgbaData: + return !result.remap_failed || (result.rgba_data != nullptr && !result.rgba_data->empty()); + case SimplifyColorSource::ImageTexture: + return result.texture_width > 0 && + result.texture_height > 0 && + !result.texture_uv_valid.empty() && + (!result.texture_rgba.empty() || !result.texture_raw_filament_offsets.empty()); + case SimplifyColorSource::VertexColors: + return !result.vertex_colors_rgba.empty(); + case SimplifyColorSource::None: + break; + } + return result.region_painting_valid; +} + +static void mesh_boolean_transform_snapshot(SimplifyTextureDataSnapshot &snapshot, const Transform3d &transform, bool fix_left_handed) +{ + transform_simplify_texture_data_snapshot(snapshot, transform); + if (!fix_left_handed) + return; + if (transform.matrix().block(0, 0, 3, 3).determinant() >= 0.) + return; + if (!snapshot.source_mesh.indices.empty()) + its_flip_triangles(snapshot.source_mesh); + for (size_t tri_idx = 0; tri_idx < snapshot.texture_uv_valid.size(); ++tri_idx) { + const size_t uv_offset = tri_idx * 6; + if (uv_offset + 5 >= snapshot.texture_uvs_per_face.size()) + break; + std::swap(snapshot.texture_uvs_per_face[uv_offset + 2], snapshot.texture_uvs_per_face[uv_offset + 4]); + std::swap(snapshot.texture_uvs_per_face[uv_offset + 3], snapshot.texture_uvs_per_face[uv_offset + 5]); + } +} + +static MultiSourceTextureDataSource mesh_boolean_gizmo_source(const ModelVolume &volume, + const Transform3d &transform, + const ColorRGBA &fallback_color, + bool &had_transfer_data) +{ + MultiSourceTextureDataSource source; + source.snapshot = snapshot_simplify_texture_data(volume); + mesh_boolean_transform_snapshot(source.snapshot, transform, true); + source.fallback_color = fallback_color; + had_transfer_data = true; + had_transfer_data |= mesh_boolean_snapshot_has_transfer_data(source.snapshot); + return source; +} + +static MultiSourceTextureTriangleProvenance mesh_boolean_remap_provenance_entry(const MeshBoolean::mcut::MeshFaceProvenance &entry) +{ + MultiSourceTextureTriangleProvenance out; + out.valid = entry.valid; + out.source_index = entry.source_index; + out.source_triangle = entry.source_triangle; + out.source_barycentric = entry.source_barycentric; + return out; +} + +static std::vector mesh_boolean_remap_provenance(const std::vector &provenance) +{ + std::vector out; + out.reserve(provenance.size()); + for (const MeshBoolean::mcut::MeshFaceProvenance &entry : provenance) + out.emplace_back(mesh_boolean_remap_provenance_entry(entry)); + return out; +} + GLGizmoMeshBoolean::GLGizmoMeshBoolean(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) { @@ -338,57 +446,18 @@ void GLGizmoMeshBoolean::on_render_input_window(float x, float y, float bottom_l bool enable_button = m_src.mv && m_tool.mv; if (m_operation_mode == MeshBooleanOperation::Union) { - if (operate_button(_L("Union") + "##btn", enable_button)) { - TriangleMesh temp_src_mesh = m_src.mv->mesh(); - temp_src_mesh.transform(m_src.trafo); - TriangleMesh temp_tool_mesh = m_tool.mv->mesh(); - temp_tool_mesh.transform(m_tool.trafo); - std::vector temp_mesh_resuls; - Slic3r::MeshBoolean::mcut::make_boolean(temp_src_mesh, temp_tool_mesh, temp_mesh_resuls, "UNION"); - if (temp_mesh_resuls.size() != 0) { - generate_new_volume(true, *temp_mesh_resuls.begin()); - wxGetApp().notification_manager()->close_plater_warning_notification(warning_text); - } - else { - wxGetApp().notification_manager()->push_plater_warning_notification(warning_text); - } - } + if (operate_button(_L("Union") + "##btn", enable_button)) + run_boolean_operation("UNION", true); } else if (m_operation_mode == MeshBooleanOperation::Difference) { m_imgui->bbl_checkbox(_L("Delete input"), m_diff_delete_input); - if (operate_button(_L("Difference") + "##btn", enable_button)) { - TriangleMesh temp_src_mesh = m_src.mv->mesh(); - temp_src_mesh.transform(m_src.trafo); - TriangleMesh temp_tool_mesh = m_tool.mv->mesh(); - temp_tool_mesh.transform(m_tool.trafo); - std::vector temp_mesh_resuls; - Slic3r::MeshBoolean::mcut::make_boolean(temp_src_mesh, temp_tool_mesh, temp_mesh_resuls, "A_NOT_B"); - if (temp_mesh_resuls.size() != 0) { - generate_new_volume(m_diff_delete_input, *temp_mesh_resuls.begin()); - wxGetApp().notification_manager()->close_plater_warning_notification(warning_text); - } - else { - wxGetApp().notification_manager()->push_plater_warning_notification(warning_text); - } - } + if (operate_button(_L("Difference") + "##btn", enable_button)) + run_boolean_operation("A_NOT_B", m_diff_delete_input); } else if (m_operation_mode == MeshBooleanOperation::Intersection){ m_imgui->bbl_checkbox(_L("Delete input"), m_inter_delete_input); - if (operate_button(_L("Intersection") + "##btn", enable_button)) { - TriangleMesh temp_src_mesh = m_src.mv->mesh(); - temp_src_mesh.transform(m_src.trafo); - TriangleMesh temp_tool_mesh = m_tool.mv->mesh(); - temp_tool_mesh.transform(m_tool.trafo); - std::vector temp_mesh_resuls; - Slic3r::MeshBoolean::mcut::make_boolean(temp_src_mesh, temp_tool_mesh, temp_mesh_resuls, "INTERSECTION"); - if (temp_mesh_resuls.size() != 0) { - generate_new_volume(m_inter_delete_input, *temp_mesh_resuls.begin()); - wxGetApp().notification_manager()->close_plater_warning_notification(warning_text); - } - else { - wxGetApp().notification_manager()->push_plater_warning_notification(warning_text); - } - } + if (operate_button(_L("Intersection") + "##btn", enable_button)) + run_boolean_operation("INTERSECTION", m_inter_delete_input); } float win_w = ImGui::GetWindowWidth(); @@ -420,7 +489,54 @@ void GLGizmoMeshBoolean::on_save(cereal::BinaryOutputArchive &ar) const ar(m_enable, m_operation_mode, m_selecting_state, m_diff_delete_input, m_inter_delete_input, m_src, m_tool); } -void GLGizmoMeshBoolean::generate_new_volume(bool delete_input, const TriangleMesh& mesh_result) { +void GLGizmoMeshBoolean::run_boolean_operation(const std::string &boolean_opts, bool delete_input) +{ + TriangleMesh temp_src_mesh = m_src.mv->mesh(); + temp_src_mesh.transform(m_src.trafo, true); + TriangleMesh temp_tool_mesh = m_tool.mv->mesh(); + temp_tool_mesh.transform(m_tool.trafo, true); + + const ColorRGBA source_base_color = mesh_boolean_volume_base_color(*m_src.mv); + const ColorRGBA tool_base_color = mesh_boolean_volume_base_color(*m_tool.mv); + bool had_transfer_data = false; + std::vector sources; + sources.reserve(2); + sources.emplace_back(mesh_boolean_gizmo_source(*m_src.mv, m_src.trafo, source_base_color, had_transfer_data)); + MultiSourceTextureDataSource tool_source = mesh_boolean_gizmo_source(*m_tool.mv, + m_tool.trafo, + tool_base_color, + had_transfer_data); + if (boolean_opts == "A_NOT_B" && tool_source.snapshot.source == SimplifyColorSource::None) + tool_source.fallback_color = source_base_color; + sources.emplace_back(std::move(tool_source)); + + if (had_transfer_data) { + std::vector provenanced_results; + const bool provenance_ok = MeshBoolean::mcut::make_boolean_with_provenance(temp_src_mesh, 0, temp_tool_mesh, 1, provenanced_results, boolean_opts); + if (provenance_ok && !provenanced_results.empty()) { + generate_new_volume(delete_input, provenanced_results.front().mesh, &sources, &provenanced_results.front().provenance); + wxGetApp().notification_manager()->close_plater_warning_notification(warning_text); + return; + } + } + + std::vector temp_mesh_resuls; + Slic3r::MeshBoolean::mcut::make_boolean(temp_src_mesh, temp_tool_mesh, temp_mesh_resuls, boolean_opts); + if (temp_mesh_resuls.size() != 0) { + if (had_transfer_data) + show_mesh_boolean_color_transfer_warning_dialog(); + generate_new_volume(delete_input, *temp_mesh_resuls.begin()); + wxGetApp().notification_manager()->close_plater_warning_notification(warning_text); + } + else { + wxGetApp().notification_manager()->push_plater_warning_notification(warning_text); + } +} + +void GLGizmoMeshBoolean::generate_new_volume(bool delete_input, + const TriangleMesh& mesh_result, + const std::vector* sources, + const std::vector* provenance) { wxGetApp().plater()->take_snapshot("Mesh Boolean"); @@ -449,7 +565,15 @@ void GLGizmoMeshBoolean::generate_new_volume(bool delete_input, const TriangleMe new_volume->config.apply(old_volume->config); new_volume->set_type(old_volume->type()); new_volume->set_material_id(old_volume->material_id()); - new_volume->set_offset(old_volume->get_transformation().get_offset()); + if (sources != nullptr && provenance != nullptr && new_volume->mesh().its.indices.size() == provenance->size()) { + std::vector remap_provenance = mesh_boolean_remap_provenance(*provenance); + SimplifyTextureDataResult result = remap_multi_source_texture_data(*sources, new_volume->mesh().its, remap_provenance); + if (mesh_boolean_texture_result_has_output(result) || result.region_painting_touched) + apply_simplify_texture_data_result(*new_volume, std::move(result)); + else + show_mesh_boolean_color_remap_warning_dialog(); + } else if (sources != nullptr && !sources->empty()) + show_mesh_boolean_color_transfer_warning_dialog(); //Vec3d translate_z = { 0,0, (new_volume->source.mesh_offset - old_volume->source.mesh_offset).z() }; //new_volume->translate(new_volume->get_transformation().get_matrix_no_offset() * translate_z); //new_volume->supported_facets.assign(old_volume->supported_facets); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeshBoolean.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMeshBoolean.hpp index c012a68dca8..de9126af4ab 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMeshBoolean.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMeshBoolean.hpp @@ -3,7 +3,11 @@ #include "GLGizmoBase.hpp" #include "GLGizmosCommon.hpp" +#include "libslic3r/MeshBoolean.hpp" #include "libslic3r/Model.hpp" +#include "libslic3r/ModelTextureDataRemap.hpp" + +#include namespace Slic3r { @@ -87,10 +91,14 @@ private: VolumeInfo m_src; VolumeInfo m_tool; - void generate_new_volume(bool delete_input, const TriangleMesh& mesh_result); + void run_boolean_operation(const std::string &boolean_opts, bool delete_input); + void generate_new_volume(bool delete_input, + const TriangleMesh& mesh_result, + const std::vector* sources = nullptr, + const std::vector* provenance = nullptr); }; } // namespace GUI } // namespace Slic3r -#endif // slic3r_GLGizmoMeshBoolean_hpp_ \ No newline at end of file +#endif // slic3r_GLGizmoMeshBoolean_hpp_ diff --git a/version_number.txt b/version_number.txt index 8fc77d0fab6..f8f3c08725d 100644 --- a/version_number.txt +++ b/version_number.txt @@ -1 +1 @@ -1.0.17 +1.0.18