Preserve model textures during mesh boolean
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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_
|
||||
|
||||
@@ -1 +1 @@
|
||||
1.0.17
|
||||
1.0.18
|
||||
|
||||
Reference in New Issue
Block a user