Preserve model textures during mesh boolean

This commit is contained in:
sentientstardust
2026-05-21 19:32:21 +01:00
parent 64d24c755f
commit 06e7a6d611
8 changed files with 1752 additions and 71 deletions

View File

@@ -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 <algorithm>
#include <cmath>
#include <limits>
#include <map>
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<Vec3f, 3> &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<Vec3f, 3> &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<MeshFaceProvenance> identity_provenance(const indexed_triangle_set &its, size_t source_index)
{
std::vector<MeshFaceProvenance> 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<MeshFaceProvenance> 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<float>::max());
Vec3f a_max = Vec3f::Constant(std::numeric_limits<float>::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<float>::max());
Vec3f b_max = Vec3f::Constant(std::numeric_limits<float>::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<ProvenancedIts> split_with_provenance(const indexed_triangle_set &its, const std::vector<MeshFaceProvenance> &provenance)
{
std::vector<ProvenancedIts> ret;
if (provenance.size() != its.indices.size())
return ret;
struct VertexConv {
size_t part_id = std::numeric_limits<size_t>::max();
size_t vertex_image = 0;
};
std::vector<VertexConv> vidx_conv(its.vertices.size());
meshsplit_detail::NeighborVisitor visitor(its, meshsplit_detail::ItsWithNeighborsIndex_<indexed_triangle_set>::get_index(its));
std::vector<size_t> 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<MeshFaceProvenance> &dst_provenance,
const indexed_triangle_set &src,
const std::vector<MeshFaceProvenance> &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<MeshFaceProvenance> &src_provenance,
const McutMesh &cutMesh,
const std::vector<MeshFaceProvenance> &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<MeshFaceProvenance> 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<MeshFaceProvenance> &src_provenance,
const McutMesh &cutMesh,
const std::vector<MeshFaceProvenance> &cut_provenance,
uint32_t face_map,
const std::vector<double> &ccVertices,
const std::vector<uint32_t> &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<MeshFaceProvenance> &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<Vec3f, 3> 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<MeshFaceProvenance> &src_provenance,
const McutMesh &cutMesh,
const std::vector<MeshFaceProvenance> &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<MeshFaceProvenance> 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<MeshFaceProvenance> &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<Vec3f, 3> 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<MeshFaceProvenance> &src_provenance,
const McutMesh &cutMesh,
const std::vector<MeshFaceProvenance> &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<std::string, McFlags> 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<const void *>(srcMesh.vertexCoordsArray.data()),
reinterpret_cast<const uint32_t *>(srcMesh.faceIndicesArray.data()),
srcMesh.faceSizesArray.data(),
static_cast<uint32_t>(srcMesh.vertexCoordsArray.size() / 3),
static_cast<uint32_t>(srcMesh.faceSizesArray.size()),
reinterpret_cast<const void *>(cutMesh.vertexCoordsArray.data()),
cutMesh.faceIndicesArray.data(),
cutMesh.faceSizesArray.data(),
static_cast<uint32_t>(cutMesh.vertexCoordsArray.size() / 3),
static_cast<uint32_t>(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<McConnectedComponent> 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<MeshFaceProvenance> 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<double> 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<uint32_t> 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<uint32_t> 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<uint32_t> 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<uint32_t> faceIndex(static_cast<size_t>(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<MeshFaceProvenance> &src_provenance,
const McutMesh &cutMesh,
const std::vector<MeshFaceProvenance> &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<ProvenancedIts> src_parts = split_with_provenance(tri_src.its, src_provenance);
std::vector<ProvenancedIts> 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<MeshFaceProvenance> 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<MeshFaceProvenance> 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<MeshFaceProvenance> 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<TriangleMesh> &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<ProvenancedMesh> &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<MeshFaceProvenance> src_provenance = identity_provenance(src_mesh.its, src_source_index);
std::vector<MeshFaceProvenance> 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

View File

@@ -1,8 +1,10 @@
#ifndef libslic3r_MeshBoolean_hpp_
#define libslic3r_MeshBoolean_hpp_
#include <array>
#include <memory>
#include <exception>
#include <vector>
#include <libslic3r/TriangleMesh.hpp>
#include <Eigen/Geometry>
@@ -84,8 +86,23 @@ struct McutMeshDeleter
using McutMeshPtr = std::unique_ptr<McutMesh, McutMeshDeleter>;
bool empty(const McutMesh &mesh);
struct MeshFaceProvenance
{
bool valid { false };
size_t source_index { 0 };
size_t source_triangle { 0 };
std::array<Vec3f, 3> source_barycentric;
};
struct ProvenancedMesh
{
TriangleMesh mesh;
std::vector<MeshFaceProvenance> provenance;
};
McutMeshPtr triangle_mesh_to_mcut(const indexed_triangle_set &M);
TriangleMesh mcut_to_triangle_mesh(const McutMesh &mcutmesh);
std::vector<MeshFaceProvenance> 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<MeshFaceProvenance> &src_provenance,
const McutMesh &cutMesh,
const std::vector<MeshFaceProvenance> &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<TriangleMesh> &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<ProvenancedMesh> &dst_mesh,
const std::string &boolean_opts);
} // namespace mcut
} // namespace MeshBoolean

View File

@@ -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<PreparedMultiSourceTextureDataSource> &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<PreparedMultiSourceTextureDataSource> &sources,
const std::vector<MultiSourceTextureTriangleProvenance> &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<Vec3f, 3> 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<PreparedMultiSourceTextureDataSource> prepare_multi_source_texture_data_sources(const std::vector<MultiSourceTextureDataSource> &sources)
{
std::vector<PreparedMultiSourceTextureDataSource> 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<PreparedMultiSourceTextureDataSource> &sources,
const std::vector<MultiSourceTextureTriangleProvenance> &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<uint8_t> sample_multi_source_raw_at_target(const std::vector<PreparedMultiSourceTextureDataSource> &sources,
const std::vector<MultiSourceTextureTriangleProvenance> &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<uint8_t>(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<uint8_t>(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<PreparedMultiSourceTextureDataSource> &sources,
const std::vector<MultiSourceTextureTriangleProvenance> &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<PreparedMultiSourceTextureDataSource> &sources,
const std::vector<uint8_t> &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<PreparedMultiSourceTextureDataSource> &sources,
const indexed_triangle_set &target_mesh,
const std::vector<MultiSourceTextureTriangleProvenance> &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<Vec3f, 3> &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<PreparedMultiSourceTextureDataSource> &sources,
const indexed_triangle_set &target_mesh,
const std::vector<MultiSourceTextureTriangleProvenance> &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<int>(island.x + island.rect_width, int(result.texture_width));
const int y_end = std::min<int>(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<Vec2f, 3> 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<int>(island.x + island.rect_width, int(result.texture_width));
const int y_end = std::min<int>(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<PreparedMultiSourceTextureDataSource> &sources,
const indexed_triangle_set &target_mesh,
const std::vector<MultiSourceTextureTriangleProvenance> &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<Vec3f, 3> 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<size_t>(target_mesh.indices.size(), 1)));
}
std::array<Vec3f, 3> 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<MultiSourceTextureDataSource> &sources,
const indexed_triangle_set &target_mesh,
const std::vector<MultiSourceTextureTriangleProvenance> &provenance,
const SimplifyTextureCancelFn &throw_on_cancel,
const SimplifyTextureProgressFn &status_fn,
const SimplifyTextureDataRemapOptions &options)
{
std::vector<PreparedMultiSourceTextureDataSource> prepared = prepare_multi_source_texture_data_sources(sources);
std::vector<uint8_t> 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) {

View File

@@ -6,6 +6,7 @@
#include "Model.hpp"
#include "TriangleMesh.hpp"
#include <array>
#include <cstdint>
#include <functional>
#include <memory>
@@ -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<Vec3f, 3> source_barycentric;
};
using SimplifyTextureCancelFn = std::function<void()>;
using SimplifyTextureProgressFn = std::function<void(int)>;
@@ -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<MultiSourceTextureDataSource> &sources,
const indexed_triangle_set &target_mesh,
const std::vector<MultiSourceTextureTriangleProvenance> &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

View File

@@ -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 <algorithm>
#include <vector>
#include <unordered_map>
#include <functional>
@@ -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<std::string> 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<MultiSourceTextureTriangleProvenance> mesh_boolean_remap_provenance(const std::vector<MeshBoolean::mcut::MeshFaceProvenance> &provenance)
{
std::vector<MultiSourceTextureTriangleProvenance> 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<MeshBoolean::mcut::MeshFaceProvenance> &dst_provenance,
const TriangleMesh &src,
const std::vector<MeshBoolean::mcut::MeshFaceProvenance> &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<MeshBoolean::mcut::MeshFaceProvenance> &provenance,
std::vector<MultiSourceTextureDataSource> &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<MeshBoolean::mcut::MeshFaceProvenance> 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<MeshBoolean::mcut::MeshFaceProvenance> 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<MeshBoolean::mcut::MeshFaceProvenance> 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<MeshBoolean::mcut::MeshFaceProvenance> provenance;
std::vector<MultiSourceTextureDataSource> 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<MultiSourceTextureTriangleProvenance> 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();

View File

@@ -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<std::string> 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<MultiSourceTextureTriangleProvenance> mesh_boolean_remap_provenance(const std::vector<MeshBoolean::mcut::MeshFaceProvenance> &provenance)
{
std::vector<MultiSourceTextureTriangleProvenance> 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<TriangleMesh> 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<TriangleMesh> 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<TriangleMesh> 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<MultiSourceTextureDataSource> 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<MeshBoolean::mcut::ProvenancedMesh> 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<TriangleMesh> 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<MultiSourceTextureDataSource>* sources,
const std::vector<MeshBoolean::mcut::MeshFaceProvenance>* 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<MultiSourceTextureTriangleProvenance> 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);

View File

@@ -3,7 +3,11 @@
#include "GLGizmoBase.hpp"
#include "GLGizmosCommon.hpp"
#include "libslic3r/MeshBoolean.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/ModelTextureDataRemap.hpp"
#include <vector>
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<MultiSourceTextureDataSource>* sources = nullptr,
const std::vector<MeshBoolean::mcut::MeshFaceProvenance>* provenance = nullptr);
};
} // namespace GUI
} // namespace Slic3r
#endif // slic3r_GLGizmoMeshBoolean_hpp_
#endif // slic3r_GLGizmoMeshBoolean_hpp_

View File

@@ -1 +1 @@
1.0.17
1.0.18