OrcaSlicer latest
OrcaSlicer
Loading...
Searching...
No Matches
Slic3r::tex2color Namespace Reference

Namespaces

namespace  cgalutils
namespace  color_utils

Classes

struct  AlgoProgress
struct  RepairSetting
struct  BoundaryEdgeStats
struct  TextureToColorSettings
struct  TriMesh

Typedefs

using AlgoProgressCallback = std::function<void(AlgoProgress)>
using AlgoCancelCallback = std::function<bool()>
using FaceUVArray = std::array<Vec2f, 3>
using VertexColor = std::array<float, 4>
using MeshRepairCallback
using TriVertex = stl_vertex
using TriVertices = std::vector<stl_vertex>
using TriFace = stl_triangle_vertex_indices
using TriFaces = std::vector<stl_triangle_vertex_indices>

Enumerations

enum class  MeshRepairDecision { Ask , ImportWithoutRepair , RepairAndImport }

Functions

BoundaryEdgeStats ComputeBoundaryEdgeStats (const cgalutils::CGALMesh &cgal_mesh)
void CloseBoundariesAndRepairManifoldness (cgalutils::CGALMesh &cgal_mesh)
bool RepairMesh (const TriMesh &mesh, std::shared_ptr< TriMesh > &out_mesh, AlgoProgressCallback progress_callback=nullptr, AlgoCancelCallback cancel_callback=nullptr, const RepairSetting &setting=RepairSetting{})
static void SaveToOFF (const std::string &path, const TriMesh &mesh, const std::vector< RGB > &face_colors)
static std::vector< std::size_t > count_cluster_label_usage (const std::vector< std::size_t > &face_labels, std::size_t cluster_count)
static bool discard_unused_cluster_centers (std::vector< RGB > &cluster_centers, std::vector< std::size_t > &face_labels, const char *stage_name)
static bool ensure_all_cluster_centers_used (const std::vector< RGB > &source_face_colors, const std::vector< RGB > &cluster_centers, std::vector< std::size_t > &face_labels, const char *stage_name)
static RGB get_pixel_color (float u, float v, const cv::Mat &texture)
static RGB sample_face_color (const std::array< Vec2f, 3 > &uvs, const cv::Mat &texture)
static bool linear_subdivision (TriMesh &mesh, std::vector< FaceUVArray > &uv_coords, const std::function< void(int)> &sub_progress=nullptr)
static bool quantize_vertex_colors (const std::vector< VertexColor > &vertex_colors, const TextureToColorSettings &settings, AlgoCancelCallback cancel_callback, std::vector< RGB > &out_centers, std::vector< std::size_t > &out_vertex_cluster_ids)
static bool adaptive_split_by_vertex_clusters (TriMesh &mesh, const std::vector< std::size_t > &vertex_cluster_ids, const std::vector< RGB > &cluster_centers, std::vector< RGB > &out_face_colors)
static bool repair_cluster_smooth (TriMesh &mesh, std::vector< RGB > &face_colors, std::vector< RGB > &out_clustered_face_colors, const TextureToColorSettings &settings, AlgoProgressCallback progress_callback, AlgoCancelCallback cancel_callback, const char *log_prefix)
bool TextureToColor (const TriMesh &texture_mesh, const std::vector< std::vector< Vec2f > > &uv_coords, const cv::Mat &texture, TriMesh &color_mesh, std::vector< std::array< std::size_t, 3 > > &face_colors, const TextureToColorSettings &settings=TextureToColorSettings(), AlgoProgressCallback progress_callback=nullptr, AlgoCancelCallback cancel_callback=nullptr)
 将纹理贴图转换为网格面片颜色, 并通过聚类和光顺生成可用于多色打印的着色网格
bool ClusterAndSmooth (const TriMesh &mesh, const std::vector< std::array< std::size_t, 3 > > &input_face_colors, TriMesh &out_mesh, std::vector< std::array< std::size_t, 3 > > &out_face_colors, const TextureToColorSettings &settings=TextureToColorSettings(), AlgoProgressCallback progress_callback=nullptr, AlgoCancelCallback cancel_callback=nullptr, const std::vector< std::array< float, 4 > > &vertex_colors={})
 Turn pre-computed per-face colors into a clustered color mesh (no texture/UV).

Variables

constexpr std::size_t MAX_REPAIRABLE_MESH_HOLE_EDGES = 500
constexpr std::size_t MAX_REPAIRABLE_MESH_BOUNDARY_EDGES = 5000
static constexpr std::array< std::array< float, 3 >, 7 > GAUSS_TRI_BARY
static constexpr std::array< float, 7 > GAUSS_TRI_WEIGHT = {0.225f, 0.132394152f, 0.132394152f, 0.132394152f, 0.125939181f, 0.125939181f, 0.125939181f}

Class Documentation

◆ Slic3r::tex2color::AlgoProgress

struct Slic3r::tex2color::AlgoProgress
Class Members
const char * message = ""
int percent = 0

◆ Slic3r::tex2color::RepairSetting

struct Slic3r::tex2color::RepairSetting
Class Members
size_t max_boundary_edges = MAX_REPAIRABLE_MESH_BOUNDARY_EDGES
size_t max_hole_edges = MAX_REPAIRABLE_MESH_HOLE_EDGES

◆ Slic3r::tex2color::BoundaryEdgeStats

struct Slic3r::tex2color::BoundaryEdgeStats
Class Members
size_t cycle_count = 0
size_t max_cycle_edges = 0
size_t total_boundary_edges = 0

◆ Slic3r::tex2color::TextureToColorSettings

struct Slic3r::tex2color::TextureToColorSettings
Class Members
size_t max_cluster_k = 32
double max_color_distance = 25.0
MeshRepairCallback mesh_repair_callback
MeshRepairDecision mesh_repair_decision = MeshRepairDecision::ImportWithoutRepair
bool * mesh_repair_decision_required = nullptr
size_t oversampling_iters = 0
size_t oversampling_max_face_count = 1000000
size_t oversampling_min_face_count = 10000
double smooth_weight = 0.5
size_t target_colors_num = 4

Typedef Documentation

◆ AlgoCancelCallback

using Slic3r::tex2color::AlgoCancelCallback = std::function<bool()>

◆ AlgoProgressCallback

◆ FaceUVArray

using Slic3r::tex2color::FaceUVArray = std::array<Vec2f, 3>

◆ MeshRepairCallback

Initial value:
std::function<bool(const indexed_triangle_set& mesh,
indexed_triangle_set& repaired_mesh,
std::function<void(const char* message, unsigned progress)> progress_callback,
std::function<bool()> cancel_callback,
std::string* error_message)>

◆ TriFace

using Slic3r::tex2color::TriFace = stl_triangle_vertex_indices

◆ TriFaces

using Slic3r::tex2color::TriFaces = std::vector<stl_triangle_vertex_indices>

◆ TriVertex

using Slic3r::tex2color::TriVertex = stl_vertex

◆ TriVertices

using Slic3r::tex2color::TriVertices = std::vector<stl_vertex>

◆ VertexColor

using Slic3r::tex2color::VertexColor = std::array<float, 4>

Enumeration Type Documentation

◆ MeshRepairDecision

Enumerator
Ask 
ImportWithoutRepair 
RepairAndImport 
18 {
19 Ask,
22};
@ RepairAndImport
Definition TextureToColor.hpp:21
@ ImportWithoutRepair
Definition TextureToColor.hpp:20
@ Ask
Definition TextureToColor.hpp:19

Function Documentation

◆ adaptive_split_by_vertex_clusters()

bool Slic3r::tex2color::adaptive_split_by_vertex_clusters ( TriMesh & mesh,
const std::vector< std::size_t > & vertex_cluster_ids,
const std::vector< RGB > & cluster_centers,
std::vector< RGB > & out_face_colors )
static
460{
461 const TriVertices original_vertices = mesh.vertices;
462 const TriFaces original_faces = mesh.indices;
463 if (original_vertices.empty() || original_faces.empty() || cluster_centers.empty())
464 return false;
465 if (vertex_cluster_ids.size() != original_vertices.size()) {
466 BOOST_LOG_TRIVIAL(warning) << "adaptive_split_by_vertex_clusters: cluster id count ("
467 << vertex_cluster_ids.size() << ") != vertex count ("
468 << original_vertices.size() << ").";
469 return false;
470 }
471 if (original_vertices.size() >= (1ULL << 32)) [[unlikely]] {
472 BOOST_LOG_TRIVIAL(warning) << "adaptive_split_by_vertex_clusters: vertex_count="
473 << original_vertices.size() << " exceeds 32-bit edge_key range.";
474 return false;
475 }
476
477 TriVertices out_vertices = original_vertices;
478 TriFaces out_faces;
479 out_faces.reserve(original_faces.size() * 5);
480 out_face_colors.clear();
481 out_face_colors.reserve(original_faces.size() * 5);
482
483 auto edge_key = [](std::size_t a, std::size_t b) -> uint64_t {
484 return a < b ? ((static_cast<uint64_t>(a) << 32) | b)
485 : ((static_cast<uint64_t>(b) << 32) | a);
486 };
487 std::unordered_map<uint64_t, std::size_t> edge_to_mid;
488 edge_to_mid.reserve(original_faces.size() * 3 / 2);
489
490 // Midpoints on shared edges must be deduplicated so that neighbouring faces
491 // reference the same vertex instead of coincident duplicates.
492 auto midpoint_of_edge = [&](std::size_t a, std::size_t b) -> std::size_t {
493 const uint64_t key = edge_key(a, b);
494 auto it = edge_to_mid.find(key);
495 if (it != edge_to_mid.end())
496 return it->second;
497 const std::size_t idx = out_vertices.size();
498 out_vertices.push_back((original_vertices[a] + original_vertices[b]) * 0.5f);
499 edge_to_mid.emplace(key, idx);
500 return idx;
501 };
502 // Points strictly inside an original face are never shared, so they skip the map.
503 // The midpoint is computed before push_back so a reallocation cannot dangle it.
504 auto append_interior_midpoint = [&](std::size_t a, std::size_t b) -> std::size_t {
505 const TriVertex mid = (out_vertices[a] + out_vertices[b]) * 0.5f;
506 const std::size_t idx = out_vertices.size();
507 out_vertices.push_back(mid);
508 return idx;
509 };
510 auto emit = [&](std::size_t a, std::size_t b, std::size_t c, std::size_t cluster_id) {
511 out_faces.push_back(Vec3i32(static_cast<int>(a), static_cast<int>(b), static_cast<int>(c)));
512 out_face_colors.push_back(cluster_centers[cluster_id]);
513 };
514
515 for (const auto& f : original_faces) {
516 const std::size_t v[3] = {static_cast<std::size_t>(f[0]), static_cast<std::size_t>(f[1]), static_cast<std::size_t>(f[2])};
517 const std::size_t c[3] = {vertex_cluster_ids[v[0]], vertex_cluster_ids[v[1]], vertex_cluster_ids[v[2]]};
518
519 // Case A: uniform cluster, keep the face untouched.
520 if (c[0] == c[1] && c[1] == c[2]) {
521 emit(v[0], v[1], v[2], c[0]);
522 continue;
523 }
524
525 // Case B: two vertices share a cluster and the third is isolated. Split the
526 // two edges incident to the isolated vertex, which are exactly the
527 // cross-cluster ones; the opposite edge stays intact.
528 int iso = -1;
529 if (c[1] == c[2]) iso = 0;
530 else if (c[2] == c[0]) iso = 1;
531 else if (c[0] == c[1]) iso = 2;
532 if (iso >= 0) {
533 const int i = iso, j = (iso + 1) % 3, k = (iso + 2) % 3;
534 const std::size_t m_ij = midpoint_of_edge(v[i], v[j]);
535 const std::size_t m_ki = midpoint_of_edge(v[k], v[i]);
536 emit(v[i], m_ij, m_ki, c[i]);
537 emit(m_ij, v[j], m_ki, c[j]);
538 emit(v[j], v[k], m_ki, c[j]);
539 continue;
540 }
541
542 // Case C: all three clusters differ. Split every edge, then cut the centre
543 // triangle once more. The centre is equidistant from all three clusters, so
544 // the legacy heuristic selects the cut by widest interior angle, which is
545 // the vertex opposite the longest edge.
546 const std::size_t m01 = midpoint_of_edge(v[0], v[1]);
547 const std::size_t m12 = midpoint_of_edge(v[1], v[2]);
548 const std::size_t m20 = midpoint_of_edge(v[2], v[0]);
549 emit(v[0], m01, m20, c[0]);
550 emit(m01, v[1], m12, c[1]);
551 emit(m12, v[2], m20, c[2]);
552
553 const TriVertex& p0 = original_vertices[v[0]];
554 const TriVertex& p1 = original_vertices[v[1]];
555 const TriVertex& p2 = original_vertices[v[2]];
556 const float sq_opposite_v0 = (p2 - p1).squaredNorm();
557 const float sq_opposite_v1 = (p0 - p2).squaredNorm();
558 const float sq_opposite_v2 = (p1 - p0).squaredNorm();
559 int widest = 0;
560 float widest_len = sq_opposite_v0;
561 if (sq_opposite_v1 > widest_len) { widest = 1; widest_len = sq_opposite_v1; }
562 if (sq_opposite_v2 > widest_len) { widest = 2; }
563
564 if (widest == 0) {
565 const std::size_t mc = append_interior_midpoint(m20, m01);
566 emit(m12, m20, mc, c[1]);
567 emit(mc, m01, m12, c[2]);
568 } else if (widest == 1) {
569 const std::size_t mc = append_interior_midpoint(m01, m12);
570 emit(m20, m01, mc, c[0]);
571 emit(mc, m12, m20, c[2]);
572 } else {
573 const std::size_t mc = append_interior_midpoint(m12, m20);
574 emit(m01, m12, mc, c[1]);
575 emit(mc, m20, m01, c[0]);
576 }
577 }
578
579 BOOST_LOG_TRIVIAL(info) << "adaptive_split_by_vertex_clusters: faces " << original_faces.size()
580 << " -> " << out_faces.size() << ", vertices " << original_vertices.size()
581 << " -> " << out_vertices.size();
582 mesh = TriMesh(out_faces, out_vertices);
583 return true;
584}
std::vector< stl_triangle_vertex_indices > TriFaces
Definition TriMesh.hpp:13
std::vector< stl_vertex > TriVertices
Definition TriMesh.hpp:11
stl_vertex TriVertex
Definition TriMesh.hpp:10
static double f(double x, double z_sin, double z_cos, bool vertical, bool flip)
Definition FillGyroid.cpp:124
Eigen::Matrix< int32_t, 3, 1, Eigen::DontAlign > Vec3i32
Definition Point.hpp:59
uint64_t edge_key(int a, int b)
Definition texture_unwrap_dump.cpp:37
Definition TriMesh.hpp:15

References Slic3r::f().

Referenced by ClusterAndSmooth().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ CloseBoundariesAndRepairManifoldness()

void Slic3r::tex2color::CloseBoundariesAndRepairManifoldness ( cgalutils::CGALMesh & cgal_mesh)
inline
88{
89 namespace PMP = CGAL::Polygon_mesh_processing;
91 using HalfedgeDescriptor = boost::graph_traits<CGALMesh>::halfedge_descriptor;
92 using FaceDescriptor = boost::graph_traits<CGALMesh>::face_descriptor;
93
94 PMP::stitch_borders(cgal_mesh);
95 PMP::duplicate_non_manifold_vertices(cgal_mesh);
96
97 std::vector<HalfedgeDescriptor> border_cycles;
98 CGAL::extract_boundary_cycles(cgal_mesh, std::back_inserter(border_cycles));
99
100 for (const HalfedgeDescriptor h : border_cycles) {
101 std::vector<FaceDescriptor> patch_faces;
102 PMP::triangulate_hole(cgal_mesh, h, CGAL::parameters::face_output_iterator(std::back_inserter(patch_faces)));
103 }
104
105 PMP::remove_degenerate_faces(cgal_mesh);
106 PMP::duplicate_non_manifold_vertices(cgal_mesh);
107}
CGAL::Surface_mesh< Kernel::Point_3 > CGALMesh
Definition CgalUtils.hpp:20

◆ ClusterAndSmooth()

bool Slic3r::tex2color::ClusterAndSmooth ( const TriMesh & mesh,
const std::vector< std::array< std::size_t, 3 > > & input_face_colors,
TriMesh & out_mesh,
std::vector< std::array< std::size_t, 3 > > & out_face_colors,
const TextureToColorSettings & settings = TextureToColorSettings(),
AlgoProgressCallback progress_callback = nullptr,
AlgoCancelCallback cancel_callback = nullptr,
const std::vector< std::array< float, 4 > > & vertex_colors = {} )

Turn pre-computed per-face colors into a clustered color mesh (no texture/UV).

Used for OBJ vertex colors and MTL face colors, which bypass texture sampling. Two routes are possible:

  • Low-poly meshes carrying per-vertex colors: the vertex colors are quantized into a small palette and the mesh is geometrically split along cluster boundaries, reproducing the split topology of the legacy OBJ vertex-color import. Output colors are then exact cluster centers, so mesh repair, re-clustering and smoothing are skipped.
  • Everything else: mesh repair, color clustering (K-Means or adaptive) and region smoothing, sharing the same pipeline as TextureToColor.
Parameters
[in]meshInput triangle mesh
[in]input_face_colorsPre-computed per-face RGB colors [0..255]
[out]out_meshOutput mesh. Geometry is subdivided on the vertex-color route, and may still be replaced by mesh repair on the generic route.
[out]out_face_colorsOutput per-face colors, one entry per out_mesh face
[in]settingsAlgorithm parameters (target_colors_num, smooth_weight; oversampling_min_face_count doubles as the low-poly threshold for the vertex-color route)
[in]progress_callbackProgress callback
[in]cancel_callbackCancel callback
[in]vertex_colorsOptional per-vertex RGBA [0..1]. Must match mesh.vertices in size to enable the vertex-color route; otherwise it is ignored.
Returns
true on success, false on failure or cancellation
977{
978 auto report = [&](int pct, const char* msg) {
979 if (progress_callback)
980 progress_callback({pct, msg});
981 };
982 auto cancelled = [&]() -> bool {
983 if (cancel_callback && cancel_callback()) {
984 BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << " cancelled";
985 return true;
986 }
987 return false;
988 };
989
990 out_mesh = mesh;
991 out_face_colors.clear();
992
993 if (mesh.indices.empty() || input_face_colors.empty()) {
994 BOOST_LOG_TRIVIAL(debug) << "ClusterAndSmooth: empty mesh or face colors.";
995 return false;
996 }
997 if (input_face_colors.size() != mesh.indices.size()) {
998 BOOST_LOG_TRIVIAL(warning) << "ClusterAndSmooth: face_colors size ("
999 << input_face_colors.size() << ") != indices size ("
1000 << mesh.indices.size() << "), clamping.";
1001 }
1002
1003 report(0, "Initializing");
1004 if (cancelled()) return false;
1005
1006 // Prepare face colors aligned to mesh size
1007 std::vector<RGB> face_colors(out_mesh.indices.size());
1008 for (size_t i = 0; i < out_mesh.indices.size(); ++i) {
1009 if (i < input_face_colors.size())
1010 face_colors[i] = input_face_colors[i];
1011 else
1012 face_colors[i] = {128, 128, 128};
1013 }
1014
1015 // Low-poly vertex-color meshes take the legacy OBJ import route: quantize the
1016 // vertex colors, then split only across cluster boundaries. Colors are exact
1017 // cluster centers afterwards, so repair / re-clustering / smoothing are skipped
1018 // to match the legacy behaviour, which never touched the mesh either.
1019 // A vertex color count that disagrees with the mesh falls through to the generic
1020 // pipeline below rather than failing the import outright.
1021 if (!vertex_colors.empty() &&
1022 vertex_colors.size() == out_mesh.vertices.size() &&
1023 out_mesh.facets_count() < settings.oversampling_min_face_count) {
1024 report(10, "Quantizing vertex colors");
1025 std::vector<RGB> cluster_centers;
1026 std::vector<std::size_t> vertex_cluster_ids;
1027 if (!quantize_vertex_colors(vertex_colors, settings, cancel_callback, cluster_centers, vertex_cluster_ids)) {
1028 BOOST_LOG_TRIVIAL(debug) << "ClusterAndSmooth: vertex color quantization failed.";
1029 return false;
1030 }
1031 if (cancelled()) return false;
1032
1033 report(50, "Splitting color boundaries");
1034 if (!adaptive_split_by_vertex_clusters(out_mesh, vertex_cluster_ids, cluster_centers, face_colors)) {
1035 BOOST_LOG_TRIVIAL(debug) << "ClusterAndSmooth: adaptive vertex-color split failed.";
1036 return false;
1037 }
1038 if (cancelled()) return false;
1039
1040 out_face_colors = std::move(face_colors);
1041 report(100, "Completed");
1042 return true;
1043 }
1044
1045 std::vector<RGB> clustered_face_colors;
1046 if (!repair_cluster_smooth(out_mesh, face_colors, clustered_face_colors,
1047 settings, progress_callback, cancel_callback,
1048 "ClusterAndSmooth"))
1049 return false;
1050
1051 out_face_colors = std::move(clustered_face_colors);
1052 return true;
1053}
static bool quantize_vertex_colors(const std::vector< VertexColor > &vertex_colors, const TextureToColorSettings &settings, AlgoCancelCallback cancel_callback, std::vector< RGB > &out_centers, std::vector< std::size_t > &out_vertex_cluster_ids)
Definition TextureToColor.cpp:400
static bool repair_cluster_smooth(TriMesh &mesh, std::vector< RGB > &face_colors, std::vector< RGB > &out_clustered_face_colors, const TextureToColorSettings &settings, AlgoProgressCallback progress_callback, AlgoCancelCallback cancel_callback, const char *log_prefix)
Definition TextureToColor.cpp:589
static bool adaptive_split_by_vertex_clusters(TriMesh &mesh, const std::vector< std::size_t > &vertex_cluster_ids, const std::vector< RGB > &cluster_centers, std::vector< RGB > &out_face_colors)
Definition TextureToColor.cpp:455
std::size_t oversampling_min_face_count
Definition TextureToColor.hpp:37
std::size_t facets_count() const
Definition TriMesh.hpp:27

References adaptive_split_by_vertex_clusters(), ClusterAndSmooth(), Slic3r::tex2color::TriMesh::facets_count(), Slic3r::tex2color::TextureToColorSettings::oversampling_min_face_count, quantize_vertex_colors(), and repair_cluster_smooth().

Referenced by ClusterAndSmooth(), and Slic3r::face_colors_to_painting().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ ComputeBoundaryEdgeStats()

BoundaryEdgeStats Slic3r::tex2color::ComputeBoundaryEdgeStats ( const cgalutils::CGALMesh & cgal_mesh)
inline
61{
62 namespace PMP = CGAL::Polygon_mesh_processing;
64 using HalfedgeDescriptor = boost::graph_traits<CGALMesh>::halfedge_descriptor;
65
66 std::vector<HalfedgeDescriptor> border_cycles;
67 CGAL::extract_boundary_cycles(cgal_mesh, std::back_inserter(border_cycles));
68
70 stats.cycle_count = border_cycles.size();
71 for (const HalfedgeDescriptor h0 : border_cycles) {
72 std::size_t len = 0;
73 HalfedgeDescriptor h = h0;
74 do {
75 ++len;
76 h = next(h, cgal_mesh);
77 } while (h != h0);
78 stats.max_cycle_edges = std::max(stats.max_cycle_edges, len);
79 stats.total_boundary_edges += len;
80 }
81 return stats;
82}
std::size_t max_cycle_edges
Definition Repair.hpp:54
std::size_t total_boundary_edges
Definition Repair.hpp:53
std::size_t cycle_count
Definition Repair.hpp:55
Definition Repair.hpp:52

References Slic3r::tex2color::BoundaryEdgeStats::cycle_count, Slic3r::tex2color::BoundaryEdgeStats::max_cycle_edges, and Slic3r::tex2color::BoundaryEdgeStats::total_boundary_edges.

◆ count_cluster_label_usage()

std::vector< std::size_t > Slic3r::tex2color::count_cluster_label_usage ( const std::vector< std::size_t > & face_labels,
std::size_t cluster_count )
static
73{
74 std::vector<std::size_t> usage(cluster_count, 0);
75 for (std::size_t label : face_labels) {
76 if (label < cluster_count) {
77 ++usage[label];
78 }
79 }
80 return usage;
81}

Referenced by discard_unused_cluster_centers(), and ensure_all_cluster_centers_used().

Here is the caller graph for this function:

◆ discard_unused_cluster_centers()

bool Slic3r::tex2color::discard_unused_cluster_centers ( std::vector< RGB > & cluster_centers,
std::vector< std::size_t > & face_labels,
const char * stage_name )
static
84{
85 if (cluster_centers.empty()) {
86 BOOST_LOG_TRIVIAL(warning) << "TextureToColor: cannot discard unused cluster centers at " << stage_name
87 << ", no cluster center is available.";
88 return false;
89 }
90
91 const std::vector<std::size_t> usage = count_cluster_label_usage(face_labels, cluster_centers.size());
92 std::vector<std::size_t> label_remap(cluster_centers.size(), std::numeric_limits<std::size_t>::max());
93 std::vector<RGB> used_cluster_centers;
94 used_cluster_centers.reserve(cluster_centers.size());
95
96 for (std::size_t cluster_id = 0; cluster_id < cluster_centers.size(); ++cluster_id) {
97 if (usage[cluster_id] == 0) {
98 continue;
99 }
100 label_remap[cluster_id] = used_cluster_centers.size();
101 used_cluster_centers.push_back(cluster_centers[cluster_id]);
102 }
103
104 if (used_cluster_centers.size() == cluster_centers.size()) {
105 return true;
106 }
107 if (used_cluster_centers.empty()) {
108 BOOST_LOG_TRIVIAL(warning) << "TextureToColor: cannot discard unused cluster centers at " << stage_name
109 << ", no face uses any valid cluster center.";
110 return false;
111 }
112
113 for (std::size_t& label : face_labels) {
114 if (label >= label_remap.size() || label_remap[label] == std::numeric_limits<std::size_t>::max()) {
115 BOOST_LOG_TRIVIAL(warning) << "TextureToColor: cannot remap cluster label " << label
116 << " at " << stage_name << ".";
117 return false;
118 }
119 label = label_remap[label];
120 }
121
122 BOOST_LOG_TRIVIAL(debug) << "TextureToColor: discarded " << (cluster_centers.size() - used_cluster_centers.size())
123 << " unused adaptive cluster centers at " << stage_name << ".";
124 cluster_centers = std::move(used_cluster_centers);
125 return true;
126}
static std::vector< std::size_t > count_cluster_label_usage(const std::vector< std::size_t > &face_labels, std::size_t cluster_count)
Definition TextureToColor.cpp:72

References count_cluster_label_usage().

Here is the call graph for this function:

◆ ensure_all_cluster_centers_used()

bool Slic3r::tex2color::ensure_all_cluster_centers_used ( const std::vector< RGB > & source_face_colors,
const std::vector< RGB > & cluster_centers,
std::vector< std::size_t > & face_labels,
const char * stage_name )
static
130{
131 if (source_face_colors.size() != face_labels.size()) {
132 BOOST_LOG_TRIVIAL(warning) << "TextureToColor: cannot preserve cluster colors at " << stage_name
133 << ", face color count does not match label count.";
134 return false;
135 }
136 if (cluster_centers.empty()) {
137 BOOST_LOG_TRIVIAL(warning) << "TextureToColor: cannot preserve cluster colors at " << stage_name
138 << ", no cluster center is available.";
139 return false;
140 }
141 if (cluster_centers.size() > face_labels.size()) {
142 BOOST_LOG_TRIVIAL(warning) << "TextureToColor: cannot use all cluster centers at " << stage_name
143 << ", centers=" << cluster_centers.size() << " faces=" << face_labels.size() << ".";
144 return false;
145 }
146
147 std::vector<std::size_t> usage = count_cluster_label_usage(face_labels, cluster_centers.size());
148 std::size_t missing_count = 0;
149 for (std::size_t cluster_id = 0; cluster_id < usage.size(); ++cluster_id) {
150 if (usage[cluster_id] != 0) {
151 continue;
152 }
153 ++missing_count;
154
155 double best_cost = std::numeric_limits<double>::max();
156 std::size_t best_face_id = std::numeric_limits<std::size_t>::max();
157 std::size_t best_old_cluster_id = std::numeric_limits<std::size_t>::max();
158
159 for (std::size_t fid = 0; fid < face_labels.size(); ++fid) {
160 const std::size_t old_cluster_id = face_labels[fid];
161 if (old_cluster_id >= cluster_centers.size() || usage[old_cluster_id] <= 1) {
162 continue;
163 }
164
165 const double old_dist = calc_rgb_color_difference_by_ciede2000(source_face_colors[fid], cluster_centers[old_cluster_id]);
166 const double new_dist = calc_rgb_color_difference_by_ciede2000(source_face_colors[fid], cluster_centers[cluster_id]);
167 const double cost = new_dist - old_dist;
168 if (cost < best_cost) {
169 best_cost = cost;
170 best_face_id = fid;
171 best_old_cluster_id = old_cluster_id;
172 }
173 }
174
175 if (best_face_id == std::numeric_limits<std::size_t>::max()) {
176 BOOST_LOG_TRIVIAL(warning) << "TextureToColor: failed to assign a seed face for unused cluster " << cluster_id
177 << " at " << stage_name << ".";
178 continue;
179 }
180
181 face_labels[best_face_id] = cluster_id;
182 --usage[best_old_cluster_id];
183 ++usage[cluster_id];
184 }
185
186 if (missing_count > 0) {
187 BOOST_LOG_TRIVIAL(debug) << "TextureToColor: reassigned seed faces for " << missing_count
188 << " unused cluster centers at " << stage_name << ".";
189 }
190
191 for (std::size_t count : usage) {
192 if (count == 0) {
193 return false;
194 }
195 }
196 return true;
197}
static unsigned int count
Definition AuxiliaryDataViewModel.cpp:246
double calc_rgb_color_difference_by_ciede2000(const RGB &rgb1, const RGB &rgb2)
Compute the CIEDE2000 color difference between two RGB colors.
Definition ColorUtils.cpp:643

References Slic3r::tex2color::color_utils::calc_rgb_color_difference_by_ciede2000(), count, and count_cluster_label_usage().

Here is the call graph for this function:

◆ get_pixel_color()

RGB Slic3r::tex2color::get_pixel_color ( float u,
float v,
const cv::Mat & texture )
static
200 {
201 u = u - std::floor(u);
202 v = v - std::floor(v);
203
204 // glTF UV convention: (0,0) = top-left, v increases downward
205 float fx = u * (texture.cols - 1);
206 float fy = v * (texture.rows - 1);
207
208 int x0 = std::clamp(static_cast<int>(fx), 0, texture.cols - 1);
209 int y0 = std::clamp(static_cast<int>(fy), 0, texture.rows - 1);
210 int x1 = std::min(x0 + 1, texture.cols - 1);
211 int y1 = std::min(y0 + 1, texture.rows - 1);
212
213 float wx = fx - x0;
214 float wy = fy - y0;
215
216 const int ch = texture.channels();
217 auto sample = [&](int row, int col) -> std::array<float, 3> {
218 const uchar* ptr = texture.data + row * texture.step[0] + col * ch;
219 return {static_cast<float>(ptr[2]), static_cast<float>(ptr[1]), static_cast<float>(ptr[0])};
220 };
221
222 auto c00 = sample(y0, x0);
223 auto c10 = sample(y0, x1);
224 auto c01 = sample(y1, x0);
225 auto c11 = sample(y1, x1);
226
227 // Bilinear blend: lerp(lerp(c00,c10,wx), lerp(c01,c11,wx), wy)
228 RGB color;
229 for (int i = 0; i < 3; ++i) {
230 float top = c00[i] * (1.0f - wx) + c10[i] * wx;
231 float bot = c01[i] * (1.0f - wx) + c11[i] * wx;
232 color[i] = static_cast<std::size_t>(std::clamp(top * (1.0f - wy) + bot * wy, 0.0f, 255.0f));
233 }
234 return color;
235}
std::array< std::size_t, 3 > RGB
Definition ColorUtils.hpp:18

Referenced by sample_face_color().

Here is the caller graph for this function:

◆ linear_subdivision()

bool Slic3r::tex2color::linear_subdivision ( TriMesh & mesh,
std::vector< FaceUVArray > & uv_coords,
const std::function< void(int)> & sub_progress = nullptr )
static
278 {
279 const auto& original_vertices = mesh.vertices;
280 const auto& original_faces = mesh.indices;
281 TriVertices sub_vertices = mesh.vertices;
282 sub_vertices.reserve(original_vertices.size() + original_faces.size() * 3);
283 TriFaces sub_faces;
284 std::vector<FaceUVArray> sub_uv_coords;
285
286 // Single-level flat map with edge key encoding replaces nested unordered_map;
287 // merges two vertex indices into a single uint64_t to reduce hash lookups and indirection.
288 if (original_vertices.size() >= (1ULL << 32)) [[unlikely]] {
289 BOOST_LOG_TRIVIAL(warning) << "[boundary] " << __FUNCTION__ << " vertex_count=" << original_vertices.size() << " exceeds 32-bit edge_key encoding range, skipping subdivision";
290 return false;
291 }
292 auto edge_key = [](std::size_t a, std::size_t b) -> uint64_t {
293 return a < b ? ((static_cast<uint64_t>(a) << 32) | b) : ((static_cast<uint64_t>(b) << 32) | a);
294 };
295 std::unordered_map<uint64_t, std::size_t> map_edge_to_sub_vtx;
296 map_edge_to_sub_vtx.reserve(original_faces.size() * 3 / 2);
297
298 for (const auto& face : original_faces) {
299 for (std::size_t i = 0; i < 3; ++i) {
300 std::size_t vtx_1 = face[i];
301 std::size_t vtx_2 = face[(i + 1) % 3];
302 uint64_t key = edge_key(vtx_1, vtx_2);
303 if (map_edge_to_sub_vtx.count(key) > 0) {
304 continue;
305 }
306 TriVertex edge_vtx = (original_vertices[vtx_1] + original_vertices[vtx_2]) * 0.5;
307 map_edge_to_sub_vtx[key] = sub_vertices.size();
308 sub_vertices.push_back(edge_vtx);
309 }
310 }
311 if (sub_progress) {
312 sub_progress(50);
313 }
314
315 // Subdivide faces and their UVs: each original face splits into 4 sub-faces (parallel writes, no contention)
316 const std::size_t N = original_faces.size();
317 sub_faces.resize(N * 4);
318 sub_uv_coords.resize(N * 4);
319 std::atomic<bool> has_missing_edge{false};
320
321 tbb::parallel_for(tbb::blocked_range<std::size_t>(0, N), [&](const tbb::blocked_range<size_t>& range) {
322 for (std::size_t fid = range.begin(); fid < range.end(); ++fid) {
323 const std::size_t base = fid * 4;
324 const auto& face = original_faces[fid];
325 std::size_t vtx_0 = face[0];
326 std::size_t vtx_1 = face[1];
327 std::size_t vtx_2 = face[2];
328
329 auto it01 = map_edge_to_sub_vtx.find(edge_key(vtx_0, vtx_1));
330 auto it12 = map_edge_to_sub_vtx.find(edge_key(vtx_1, vtx_2));
331 auto it20 = map_edge_to_sub_vtx.find(edge_key(vtx_2, vtx_0));
332 if (it01 == map_edge_to_sub_vtx.end() || it12 == map_edge_to_sub_vtx.end() || it20 == map_edge_to_sub_vtx.end()) [[unlikely]] {
333 has_missing_edge.store(true, std::memory_order_relaxed);
334 Vec3i32 degen(vtx_0, vtx_0, vtx_0);
335 FaceUVArray degen_uv = {uv_coords[fid][0], uv_coords[fid][0], uv_coords[fid][0]};
336 for (int k = 0; k < 4; ++k) {
337 sub_faces[base + k] = degen;
338 sub_uv_coords[base + k] = degen_uv;
339 }
340 continue;
341 }
342 std::size_t e01 = it01->second;
343 std::size_t e12 = it12->second;
344 std::size_t e20 = it20->second;
345
346 const Vec2f& uv0 = uv_coords[fid][0];
347 const Vec2f& uv1 = uv_coords[fid][1];
348 const Vec2f& uv2 = uv_coords[fid][2];
349 Vec2f uv_e01 = (uv0 + uv1) * 0.5f;
350 Vec2f uv_e12 = (uv1 + uv2) * 0.5f;
351 Vec2f uv_e20 = (uv2 + uv0) * 0.5f;
352
353 sub_faces[base + 0] = Vec3i32(vtx_0, e01, e20);
354 sub_uv_coords[base + 0] = {uv0, uv_e01, uv_e20};
355
356 sub_faces[base + 1] = Vec3i32(e01, vtx_1, e12);
357 sub_uv_coords[base + 1] = {uv_e01, uv1, uv_e12};
358
359 sub_faces[base + 2] = Vec3i32(e01, e12, e20);
360 sub_uv_coords[base + 2] = {uv_e01, uv_e12, uv_e20};
361
362 sub_faces[base + 3] = Vec3i32(e20, e12, vtx_2);
363 sub_uv_coords[base + 3] = {uv_e20, uv_e12, uv2};
364 }
365 });
366 // Remove degenerate triangles (three identical vertices) to avoid impacting downstream SDF / Remesh steps
367 if (has_missing_edge.load(std::memory_order_relaxed)) {
368 std::size_t write_idx = 0;
369 for (std::size_t i = 0; i < sub_faces.size(); ++i) {
370 if (sub_faces[i][0] == sub_faces[i][1] && sub_faces[i][1] == sub_faces[i][2]) {
371 continue;
372 }
373 if (write_idx != i) {
374 sub_faces[write_idx] = sub_faces[i];
375 sub_uv_coords[write_idx] = sub_uv_coords[i];
376 }
377 ++write_idx;
378 }
379 BOOST_LOG_TRIVIAL(warning) << "[warning] linear_subdivision has missing edge vertex, removed " << (sub_faces.size() - write_idx) << " degenerate triangles";
380 sub_faces.resize(write_idx);
381 sub_uv_coords.resize(write_idx);
382 }
383
384 if (sub_progress) {
385 sub_progress(100);
386 }
387 BOOST_LOG_TRIVIAL(debug) << "TextureToColor: input faces count = " << mesh.indices.size() << ".";
388 mesh = TriMesh(sub_faces, sub_vertices);
389 uv_coords = std::move(sub_uv_coords);
390 BOOST_LOG_TRIVIAL(debug) << "TextureToColor: output faces count = " << mesh.indices.size() << ".";
391 return true;
392}
@ N
Definition CalibrationWizardSavePage.cpp:124
std::array< Vec2f, 3 > FaceUVArray
Definition TextureToColor.cpp:276
auto range(Cont &&cont)
Definition libslic3r.h:405
Eigen::Matrix< float, 2, 1, Eigen::DontAlign > Vec2f
Definition Point.hpp:64

References Slic3r::range().

Referenced by TextureToColor().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ quantize_vertex_colors()

bool Slic3r::tex2color::quantize_vertex_colors ( const std::vector< VertexColor > & vertex_colors,
const TextureToColorSettings & settings,
AlgoCancelCallback cancel_callback,
std::vector< RGB > & out_centers,
std::vector< std::size_t > & out_vertex_cluster_ids )
static
406{
407 out_centers.clear();
408 out_vertex_cluster_ids.clear();
409 if (vertex_colors.empty())
410 return false;
411
412 std::vector<RGB> vertex_rgb(vertex_colors.size());
413 for (std::size_t i = 0; i < vertex_colors.size(); ++i) {
414 for (int c = 0; c < 3; ++c) {
415 float v = std::clamp(vertex_colors[i][c] * 255.0f, 0.0f, 255.0f);
416 vertex_rgb[i][c] = static_cast<std::size_t>(v);
417 }
418 }
419
420 ClusterParameters para;
421 para.cancel_callback = cancel_callback ? [&]() { return cancel_callback(); } : std::function<bool()>{};
422 if (settings.target_colors_num == 0) {
423 para.max_color_distance = settings.max_color_distance;
424 para.max_cluster_k = settings.max_cluster_k;
425 out_centers = cluster_adaptive(vertex_rgb, para);
426 } else {
427 para.cluster_k = settings.target_colors_num;
428 out_centers = cluster_k_means(vertex_rgb, para);
429 }
430 if (out_centers.empty()) {
431 BOOST_LOG_TRIVIAL(debug) << "quantize_vertex_colors: no cluster center generated.";
432 return false;
433 }
434
435 out_vertex_cluster_ids.resize(vertex_rgb.size());
436 for (std::size_t i = 0; i < vertex_rgb.size(); ++i) {
437 std::size_t nearest_id = 0;
438 if (!calc_nearest_color_id(out_centers, vertex_rgb[i], nearest_id))
439 nearest_id = 0;
440 out_vertex_cluster_ids[i] = nearest_id;
441 }
442 BOOST_LOG_TRIVIAL(debug) << "quantize_vertex_colors: quantized " << vertex_rgb.size()
443 << " vertex colors into " << out_centers.size() << " clusters.";
444 return true;
445}
std::vector< Color > cluster_adaptive(const std::vector< Color > &colors, const ClusterParameters &cluster_parameters)
Adaptive K-Means clustering that determines an appropriate number of clusters under a max color-dista...
Definition ColorUtils.cpp:1246
bool calc_nearest_color_id(const std::vector< RGB > &colors, const RGB &color, std::size_t &nearest_color_id)
Find the ID of the nearest color in a color list to a given color.
Definition ColorUtils.cpp:1435
std::vector< Color > cluster_k_means(const std::vector< Color > &colors, const ClusterParameters &cluster_parameters)
K-Means clustering algorithm that minimizes the sum of squared errors.
Definition ColorUtils.cpp:1065
std::size_t target_colors_num
Definition TextureToColor.hpp:31
std::size_t max_cluster_k
Definition TextureToColor.hpp:41
double max_color_distance
Definition TextureToColor.hpp:40

References Slic3r::tex2color::color_utils::calc_nearest_color_id(), Slic3r::tex2color::color_utils::ClusterParameters::cancel_callback, Slic3r::tex2color::color_utils::cluster_adaptive(), Slic3r::tex2color::color_utils::ClusterParameters::cluster_k, Slic3r::tex2color::color_utils::cluster_k_means(), Slic3r::tex2color::color_utils::ClusterParameters::max_cluster_k, Slic3r::tex2color::TextureToColorSettings::max_cluster_k, Slic3r::tex2color::color_utils::ClusterParameters::max_color_distance, Slic3r::tex2color::TextureToColorSettings::max_color_distance, and Slic3r::tex2color::TextureToColorSettings::target_colors_num.

Referenced by ClusterAndSmooth().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ repair_cluster_smooth()

bool Slic3r::tex2color::repair_cluster_smooth ( TriMesh & mesh,
std::vector< RGB > & face_colors,
std::vector< RGB > & out_clustered_face_colors,
const TextureToColorSettings & settings,
AlgoProgressCallback progress_callback,
AlgoCancelCallback cancel_callback,
const char * log_prefix )
static
597{
598 auto report = [&](int pct, const char* msg) {
599 if (progress_callback)
600 progress_callback({pct, msg});
601 };
602 auto cancelled = [&]() -> bool {
603 if (cancel_callback && cancel_callback()) {
604 BOOST_LOG_TRIVIAL(debug) << log_prefix << " cancelled";
605 return true;
606 }
607 return false;
608 };
609
610 report(0, "Repairing mesh");
611 if (cancelled()) return false;
612
613 // Resample face colors onto a repaired mesh via centroid nearest-neighbor.
614 auto resample_face_colors = [&](TriMesh&& repaired_mesh) -> bool {
615 TriVertices old_vertices = std::move(mesh.vertices);
616 TriFaces old_indices = std::move(mesh.indices);
617 auto aabb_tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(old_vertices, old_indices);
618 mesh = std::move(repaired_mesh);
619
620 if (is_closed(mesh)) {
621 BOOST_LOG_TRIVIAL(debug) << log_prefix << ": repaired mesh is closed.";
622 } else {
623 BOOST_LOG_TRIVIAL(debug) << log_prefix << ": repaired mesh is open.";
624 }
625
626 std::vector<RGB> new_face_colors(mesh.facets_count());
627 tbb::parallel_for(tbb::blocked_range<std::size_t>(0, mesh.facets_count()), [&](const tbb::blocked_range<size_t>& range) {
628 for (std::size_t fid = range.begin(); fid < range.end(); ++fid) {
629 const auto& face = mesh.indices[fid];
630 Vec3f center = (mesh.vertices[face[0]] + mesh.vertices[face[1]] + mesh.vertices[face[2]]) / 3.0f;
631 size_t hit_idx = 0;
632 Vec3f closest;
633 AABBTreeIndirect::squared_distance_to_indexed_triangle_set(
634 old_vertices, old_indices, aabb_tree, center, hit_idx, closest);
635 new_face_colors[fid] = face_colors[hit_idx];
636 }
637 });
638 face_colors = std::move(new_face_colors);
639 return true;
640 };
641
642 auto repair_and_resample = [&]() -> bool {
643 std::shared_ptr<TriMesh> repaired_mesh;
644 if (!RepairMesh(mesh, repaired_mesh)) {
645 BOOST_LOG_TRIVIAL(debug) << log_prefix << ": RepairMesh failed.";
646 return false;
647 }
648 if (cancelled()) return false;
649 return resample_face_colors(std::move(*repaired_mesh));
650 };
651
652 {
653 TriangleMesh stats_mesh(static_cast<const indexed_triangle_set&>(mesh));
654 const auto& stats = stats_mesh.stats();
655 // Orca's TriangleMeshStats only counts open edges: manifold() is open_edges == 0, and
656 // there are no separate non-manifold edge/vertex counters to test or log here.
657 if (!stats.manifold()) {
658 BOOST_LOG_TRIVIAL(info) << log_prefix << ": mesh has non-manifold geometry or open boundaries, open_edges="
659 << stats.open_edges;
662 *settings.mesh_repair_decision_required = true;
663 return false;
664 }
666 indexed_triangle_set repaired_its;
667 std::string repair_error;
668 bool repaired = settings.mesh_repair_callback && settings.mesh_repair_callback(
669 static_cast<const indexed_triangle_set&>(mesh), repaired_its,
670 [&](const char* message, unsigned /*percent*/) {
671 report(5, message ? message : "Repairing mesh");
672 },
673 [&]() { return cancelled(); }, &repair_error);
674 if (repaired) {
675 if (cancelled()) return false;
676 BOOST_LOG_TRIVIAL(info) << log_prefix << ": mesh repair finished.";
677 if (!resample_face_colors(TriMesh(std::move(repaired_its))))
678 return false;
679 } else {
680 BOOST_LOG_TRIVIAL(warning) << log_prefix << ": mesh repair failed: " << repair_error;
681 }
682 } else {
683 BOOST_LOG_TRIVIAL(info) << log_prefix << ": importing mesh without repair.";
684 }
685 }
686 }
687
689 BOOST_LOG_TRIVIAL(info) << log_prefix << ": mesh not halfedge-compatible, attempting RepairMesh.";
690 if (!repair_and_resample())
691 return false;
692 }
693
694#ifdef OUTPUT_TEST_RESULT
695 SaveToOFF(std::string(log_prefix) + "_1_repair.off", mesh, face_colors);
696#endif
697
698 report(20, "Color clustering");
699 if (cancelled()) return false;
700
701 // Clustering
702 std::vector<RGB> cluster_centers;
703 out_clustered_face_colors = face_colors;
704 std::vector<std::size_t> clustered_face_labels(face_colors.size());
705 const bool adaptive_cluster = settings.target_colors_num == 0;
706
707 if (adaptive_cluster) {
708 BOOST_LOG_TRIVIAL(debug) << log_prefix << ": use cluster adaptive method.";
711 para.max_cluster_k = settings.max_cluster_k;
712 para.cancel_callback = cancel_callback ? [&]() { return cancel_callback(); } : std::function<bool()>{};
713 cluster_centers = cluster_adaptive(face_colors, para);
714 if (cancelled()) return false;
715 } else {
716 BOOST_LOG_TRIVIAL(debug) << log_prefix << ": use cluster k-means method.";
718 para.cluster_k = settings.target_colors_num;
719 para.cancel_callback = cancel_callback ? [&]() { return cancel_callback(); } : std::function<bool()>{};
720 cluster_centers = cluster_k_means(face_colors, para);
721 if (cancelled()) return false;
722 }
723
724 BOOST_LOG_TRIVIAL(debug) << log_prefix << ": k = " << cluster_centers.size() << ".";
725 if (cluster_centers.empty()) {
726 BOOST_LOG_TRIVIAL(debug) << log_prefix << ": no cluster center generated.";
727 return false;
728 }
729
730 report(40, "Assigning cluster labels");
731 if (cancelled()) return false;
732
733 // Assign each face to nearest cluster center
734 {
735 std::atomic<size_t> done{0};
736 std::atomic<bool> cancel_requested{false};
737 const size_t total = mesh.indices.size();
738 const size_t interval = std::max<size_t>(total / 20, 1);
739 tbb::parallel_for(tbb::blocked_range<std::size_t>(0, total), [&](const tbb::blocked_range<size_t>& range) {
740 for (std::size_t fid = range.begin(); fid < range.end(); ++fid) {
741 if (cancel_requested.load(std::memory_order_relaxed)) return;
742 std::size_t nearest_id = 0;
743 calc_nearest_color_id(cluster_centers, face_colors[fid], nearest_id);
744 clustered_face_labels[fid] = nearest_id;
745 out_clustered_face_colors[fid] = cluster_centers[nearest_id];
746 size_t cnt = done.fetch_add(1, std::memory_order_relaxed) + 1;
747 if (cnt % interval == 0) {
748 if (cancelled()) { cancel_requested.store(true, std::memory_order_relaxed); return; }
749 }
750 }
751 });
752 if (cancel_requested.load() || cancelled()) return false;
753 }
754 if (adaptive_cluster) {
755 if (!discard_unused_cluster_centers(cluster_centers, clustered_face_labels, "cluster assignment"))
756 return false;
757 } else {
758 ensure_all_cluster_centers_used(face_colors, cluster_centers, clustered_face_labels, "cluster assignment");
759 }
760
761#ifdef OUTPUT_TEST_RESULT
762 {
763 std::vector<RGB> tmp = out_clustered_face_colors;
764 for (std::size_t i = 0; i < tmp.size(); ++i)
765 tmp[i] = cluster_centers[clustered_face_labels[i]];
766 SaveToOFF(std::string(log_prefix) + "_3_cluster.off", mesh, tmp);
767 }
768#endif
769
770 report(65, "Smoothing colors");
771 if (cancelled()) return false;
772
773 SmoothParameters smooth_parameters;
774 smooth_parameters.smooth_weight = settings.smooth_weight;
775 if (!smooth_region(mesh, clustered_face_labels, smooth_parameters)) {
776 BOOST_LOG_TRIVIAL(debug) << log_prefix << ": smooth region failed.";
777 return false;
778 }
779 if (adaptive_cluster) {
780 if (!discard_unused_cluster_centers(cluster_centers, clustered_face_labels, "color smoothing"))
781 return false;
782 } else {
783 ensure_all_cluster_centers_used(face_colors, cluster_centers, clustered_face_labels, "color smoothing");
784 }
785
786 report(90, "Updating face colors");
787 if (cancelled()) return false;
788
789 for (std::size_t i = 0; i < out_clustered_face_colors.size(); ++i)
790 out_clustered_face_colors[i] = cluster_centers[clustered_face_labels[i]];
791
792#ifdef OUTPUT_TEST_RESULT
793 SaveToOFF(std::string(log_prefix) + "_4_smooth.off", mesh, out_clustered_face_colors);
794#endif
795
796 report(100, "Completed");
797 return true;
798}
Tree< 3, typename VertexType::Scalar > build_aabb_tree_over_indexed_triangle_set(const std::vector< VertexType > &vertices, const std::vector< IndexedFaceType > &faces, const typename VertexType::Scalar eps=0)
Definition AABBTreeIndirect.hpp:677
bool is_mesh_halfedge_compatible(const TriMesh &mesh)
Definition CgalUtils.hpp:54
bool is_closed(const TriMesh &bbs_mesh)
Check whether the mesh is closed (watertight).
Definition ColorUtils.cpp:855
bool smooth_region(TriMesh &tri_mesh, std::vector< std::array< std::size_t, 3 > > &face_colors, const SmoothParameters &smooth_parameters)
Smooth region boundaries (RGB color labels).
Definition ColorUtils.cpp:941
double smooth_weight
Definition ColorUtils.hpp:41
double max_color_distance
Definition ColorUtils.hpp:29
std::size_t cluster_k
Definition ColorUtils.hpp:31
double smooth_weight
Definition TextureToColor.hpp:33
static bool ensure_all_cluster_centers_used(const std::vector< RGB > &source_face_colors, const std::vector< RGB > &cluster_centers, std::vector< std::size_t > &face_labels, const char *stage_name)
Definition TextureToColor.cpp:128
bool * mesh_repair_decision_required
Definition TextureToColor.hpp:46
bool RepairMesh(const TriMesh &mesh, std::shared_ptr< TriMesh > &out_mesh, AlgoProgressCallback progress_callback=nullptr, AlgoCancelCallback cancel_callback=nullptr, const RepairSetting &setting=RepairSetting{})
Definition Repair.hpp:109
static bool discard_unused_cluster_centers(std::vector< RGB > &cluster_centers, std::vector< std::size_t > &face_labels, const char *stage_name)
Definition TextureToColor.cpp:83
MeshRepairCallback mesh_repair_callback
Definition TextureToColor.hpp:48
MeshRepairDecision mesh_repair_decision
Definition TextureToColor.hpp:43
static void SaveToOFF(const std::string &path, const TriMesh &mesh, const std::vector< RGB > &face_colors)
Definition TextureToColor.cpp:40

References Slic3r::AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(), Slic3r::tex2color::TriMesh::facets_count(), Slic3r::tex2color::color_utils::is_closed(), and Slic3r::range().

Referenced by ClusterAndSmooth(), and TextureToColor().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ RepairMesh()

bool Slic3r::tex2color::RepairMesh ( const TriMesh & mesh,
std::shared_ptr< TriMesh > & out_mesh,
AlgoProgressCallback progress_callback = nullptr,
AlgoCancelCallback cancel_callback = nullptr,
const RepairSetting & setting = RepairSetting{} )
inline
113 {})
114{
115 namespace PMP = CGAL::Polygon_mesh_processing;
116 using Clock = std::chrono::steady_clock;
117 auto elapsed_ms = [](Clock::time_point t0) {
118 return std::chrono::duration_cast<std::chrono::milliseconds>(Clock::now() - t0).count();
119 };
120
121 const Clock::time_point t_total = Clock::now();
122
123 // Convert TriMesh to polygon soup (point container + triangle index container)
124 std::vector<cgalutils::Kernel::Point_3> soup_points;
125 std::vector<std::vector<std::size_t>> soup_triangles;
126
127 soup_points.reserve(mesh.vertices.size());
128 for (const TriVertex& v : mesh.vertices) {
129 soup_points.emplace_back(v.x(), v.y(), v.z());
130 }
131
132 soup_triangles.reserve(mesh.indices.size());
133 for (const TriFace& f : mesh.indices) {
134 soup_triangles.push_back({static_cast<std::size_t>(f[0]),
135 static_cast<std::size_t>(f[1]),
136 static_cast<std::size_t>(f[2])});
137 }
138
139 if (progress_callback) {
140 progress_callback({30, "Repairing polygon soup"});
141 }
142 if (cancel_callback && cancel_callback()) {
143 return false;
144 }
145
146 {
147 const auto t0 = Clock::now();
148 PMP::repair_polygon_soup(soup_points, soup_triangles);
149 BOOST_LOG_TRIVIAL(info) << "TextureToColor: RepairMesh stage=repair_polygon_soup took="
150 << elapsed_ms(t0) << " ms";
151 }
152
153 if (progress_callback) {
154 progress_callback({50, "Orienting polygon soup"});
155 }
156 if (cancel_callback && cancel_callback()) {
157 return false;
158 }
159
160 {
161 const auto t0 = Clock::now();
162 PMP::orient_polygon_soup(soup_points, soup_triangles);
163 BOOST_LOG_TRIVIAL(info) << "TextureToColor: RepairMesh stage=orient_polygon_soup took="
164 << elapsed_ms(t0) << " ms";
165 }
166
167 if (progress_callback) {
168 progress_callback({70, "Converting to CGAL mesh"});
169 }
170 if (cancel_callback && cancel_callback()) {
171 return false;
172 }
173
174 cgalutils::CGALMesh cgal_mesh;
175 {
176 const auto t0 = Clock::now();
177 PMP::polygon_soup_to_polygon_mesh(soup_points, soup_triangles, cgal_mesh);
178 BOOST_LOG_TRIVIAL(info) << "TextureToColor: RepairMesh stage=polygon_soup_to_polygon_mesh took="
179 << elapsed_ms(t0) << " ms";
180 }
181
182 {
183 const auto t0 = Clock::now();
184 PMP::remove_degenerate_faces(cgal_mesh);
185 BOOST_LOG_TRIVIAL(info) << "TextureToColor: RepairMesh stage=remove_degenerate_faces took="
186 << elapsed_ms(t0) << " ms";
187 }
188
189 if (progress_callback) {
190 progress_callback({80, "Closing mesh boundaries"});
191 }
192 if (cancel_callback && cancel_callback()) {
193 return false;
194 }
195
196 // Stitch borders and duplicate non-manifold vertices first so that the
197 // boundary statistics below reflect the post-stitch topology; otherwise
198 // boundaries that would close on stitching inflate the counts and may
199 // cause the gate to skip hole filling unnecessarily.
200 BoundaryEdgeStats stats;
201 {
202 const auto t0 = Clock::now();
203 PMP::stitch_borders(cgal_mesh);
204 PMP::duplicate_non_manifold_vertices(cgal_mesh);
205 stats = ComputeBoundaryEdgeStats(cgal_mesh);
206 BOOST_LOG_TRIVIAL(info) << "TextureToColor: RepairMesh stage=boundary_stats took="
207 << elapsed_ms(t0) << " ms"
208 << " total_boundary_edges=" << stats.total_boundary_edges
209 << " max_cycle_edges=" << stats.max_cycle_edges
210 << " cycle_count=" << stats.cycle_count;
211 }
212
213 const bool can_repair_holes =
214 stats.total_boundary_edges <= setting.max_boundary_edges &&
215 stats.max_cycle_edges <= setting.max_hole_edges;
216
217 if (can_repair_holes) {
218 const auto t0 = Clock::now();
220 BOOST_LOG_TRIVIAL(info) << "TextureToColor: RepairMesh stage=close_boundaries took="
221 << elapsed_ms(t0) << " ms";
222 } else {
223 BOOST_LOG_TRIVIAL(info)
224 << "TextureToColor: RepairMesh skip hole closing"
225 << ", total_boundary_edges=" << stats.total_boundary_edges
226 << " (limit=" << setting.max_boundary_edges << ")"
227 << ", max_cycle_edges=" << stats.max_cycle_edges
228 << " (limit=" << setting.max_hole_edges << ")"
229 << ", cycle_count=" << stats.cycle_count;
230 }
231
232 if (progress_callback) {
233 progress_callback({85, "Converting from CGAL mesh"});
234 }
235 if (cancel_callback && cancel_callback()) {
236 return false;
237 }
238
239 std::shared_ptr<TriMesh> out;
240 {
241 const auto t0 = Clock::now();
242 out = std::make_shared<TriMesh>(cgalutils::cgal_to_trimesh(cgal_mesh));
243 BOOST_LOG_TRIVIAL(info) << "TextureToColor: RepairMesh stage=cgal_to_trimesh took="
244 << elapsed_ms(t0) << " ms";
245 }
246
247 out_mesh = std::move(out);
248 if (progress_callback) {
249 progress_callback({100, "Done"});
250 }
251
252 BOOST_LOG_TRIVIAL(info) << "TextureToColor: RepairMesh total=" << elapsed_ms(t_total) << " ms";
253
254 return true;
255}
TriMesh cgal_to_trimesh(const CGALMesh &cm)
Definition CgalUtils.hpp:33
stl_triangle_vertex_indices TriFace
Definition TriMesh.hpp:12
std::size_t max_boundary_edges
Definition Repair.hpp:48
BoundaryEdgeStats ComputeBoundaryEdgeStats(const cgalutils::CGALMesh &cgal_mesh)
Definition Repair.hpp:60
void CloseBoundariesAndRepairManifoldness(cgalutils::CGALMesh &cgal_mesh)
Definition Repair.hpp:87
std::size_t max_hole_edges
Definition Repair.hpp:45

◆ sample_face_color()

RGB Slic3r::tex2color::sample_face_color ( const std::array< Vec2f, 3 > & uvs,
const cv::Mat & texture )
static
260 {
261 float r = 0.0f, g = 0.0f, b = 0.0f;
262 for (int k = 0; k < 7; ++k) {
263 float u = GAUSS_TRI_BARY[k][0] * uvs[0].x() + GAUSS_TRI_BARY[k][1] * uvs[1].x() + GAUSS_TRI_BARY[k][2] * uvs[2].x();
264 float v = GAUSS_TRI_BARY[k][0] * uvs[0].y() + GAUSS_TRI_BARY[k][1] * uvs[1].y() + GAUSS_TRI_BARY[k][2] * uvs[2].y();
265 RGB c = get_pixel_color(u, v, texture);
266 float w = GAUSS_TRI_WEIGHT[k];
267 r += w * c[0];
268 g += w * c[1];
269 b += w * c[2];
270 }
271 return RGB{static_cast<std::size_t>(std::clamp(r, 0.0f, 255.0f)), static_cast<std::size_t>(std::clamp(g, 0.0f, 255.0f)),
272 static_cast<std::size_t>(std::clamp(b, 0.0f, 255.0f))};
273}
static constexpr std::array< std::array< float, 3 >, 7 > GAUSS_TRI_BARY
Definition TextureToColor.cpp:238
static RGB get_pixel_color(float u, float v, const cv::Mat &texture)
Definition TextureToColor.cpp:200
static constexpr std::array< float, 7 > GAUSS_TRI_WEIGHT
Definition TextureToColor.cpp:247
std::array< float, 3 > RGB
Definition Color.hpp:12

References GAUSS_TRI_BARY, GAUSS_TRI_WEIGHT, and get_pixel_color().

Referenced by TextureToColor().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ SaveToOFF()

void Slic3r::tex2color::SaveToOFF ( const std::string & path,
const TriMesh & mesh,
const std::vector< RGB > & face_colors )
static
41{
42 std::filesystem::create_directories(std::filesystem::path(path).parent_path());
43 std::ofstream ofs(path);
44 if (!ofs.is_open()) {
45 BOOST_LOG_TRIVIAL(warning) << "SaveToOFF: failed to open " << path;
46 return;
47 }
48
49 const auto& vertices = mesh.vertices;
50 const auto& faces = mesh.indices;
51
52 ofs << "OFF\n";
53 ofs << vertices.size() << " " << faces.size() << " 0\n";
54
55 for (const auto& v : vertices) {
56 ofs << v.x() << " " << v.y() << " " << v.z() << "\n";
57 }
58
59 for (std::size_t i = 0; i < faces.size(); ++i) {
60 const auto& f = faces[i];
61 ofs << "3 " << f[0] << " " << f[1] << " " << f[2];
62 if (i < face_colors.size()) {
63 ofs << " " << face_colors[i][0] / 255.0
64 << " " << face_colors[i][1] / 255.0
65 << " " << face_colors[i][2] / 255.0
66 << " 1.0";
67 }
68 ofs << "\n";
69 }
70}

References Slic3r::f().

Referenced by TextureToColor().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ TextureToColor()

bool Slic3r::tex2color::TextureToColor ( const TriMesh & texture_mesh,
const std::vector< std::vector< Vec2f > > & uv_coords,
const cv::Mat & texture,
TriMesh & color_mesh,
std::vector< std::array< std::size_t, 3 > > & face_colors,
const TextureToColorSettings & settings = TextureToColorSettings(),
AlgoProgressCallback progress_callback = nullptr,
AlgoCancelCallback cancel_callback = nullptr )

将纹理贴图转换为网格面片颜色, 并通过聚类和光顺生成可用于多色打印的着色网格

基于纹理网格的UV坐标对纹理图像进行采样, 计算每个面片的颜色, 然后对颜色进行聚类(K-Means或自适应)和区域光顺, 最终输出带颜色信息的网格

Parameters
[in]texture_mesh带有UV坐标的输入三角网格
[in]uv_coords每个面片的UV坐标, 大小等于面片数, 每个面片有三个UV坐标
[in]texture纹理图像
[out]color_mesh输出的着色网格
[out]face_colors输出的着色网格的面片颜色, 大小等于面片数, 颜色值为[R, G, B], 范围0~255
[in]settings算法参数, 包括目标颜色数量、光顺权重等
[in]progress_callback进度回调函数
[in]cancel_callback取消回调函数
Returns
成功返回true, 输入数据无效(空网格、无UV、空纹理等)返回false
802 {
803 auto report = [&](int pct, const char* msg) {
804 if (progress_callback) {
805 progress_callback({pct, msg});
806 }
807 };
808 auto sub_report = [&](int sub_pct, int range_start, int range_end, const char* msg) {
809 int pct = range_start + sub_pct * (range_end - range_start) / 100;
810 report(pct, msg);
811 };
812 auto cancelled = [&]() -> bool {
813 if (cancel_callback && cancel_callback()) {
814 BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << " cancelled";
815 return true;
816 }
817 return false;
818 };
819
820 color_mesh.clear();
821 face_colors.clear();
822
823 report(0, "Initializing");
824 if (cancelled()) {
825 return false;
826 }
827
828 if (texture_mesh.indices.size() == 0) {
829 BOOST_LOG_TRIVIAL(debug) << "TextureToColor: texture mesh has no faces.";
830 return false;
831 }
832 if (texture.empty()) {
833 BOOST_LOG_TRIVIAL(debug) << "TextureToColor: texture is empty.";
834 return false;
835 }
836 if (texture.channels() < 3) {
837 BOOST_LOG_TRIVIAL(debug) << "TextureToColor: texture must have at least 3 channels, got " << texture.channels();
838 return false;
839 }
840 if (texture_mesh_uv_coords.size() != texture_mesh.indices.size()) {
841 BOOST_LOG_TRIVIAL(debug) << "TextureToColor: uv_coords size is not equal to texture mesh faces size.";
842 return false;
843 }
844 for (std::size_t fid = 0; fid < texture_mesh.indices.size(); ++fid) {
845 if (texture_mesh_uv_coords[fid].size() != 3) {
846 BOOST_LOG_TRIVIAL(debug) << "TextureToColor: uv_coords of single face size is not equal to 3.";
847 return false;
848 }
849 }
850 color_mesh = texture_mesh;
851
852 using Clock = std::chrono::high_resolution_clock;
853 const auto t_total_start = Clock::now();
854 auto t_step = t_total_start;
855 auto lap = [&](const char* step_name) {
856 auto now = Clock::now();
857 double ms = std::chrono::duration<double, std::milli>(now - t_step).count();
858 BOOST_LOG_TRIVIAL(debug) << "[timing] " << step_name << ": " << ms << "ms"
859 << " faces=" << color_mesh.facets_count();
860 t_step = now;
861 };
862
863 report(5, "Oversampling");
864 if (cancelled()) {
865 return false;
866 }
867
868 // Step 1: Oversampling (subdivision while propagating UVs)
869 // Convert external vector<vector<Vec2f>> to internal vector<array<Vec2f,3>> to eliminate inner-level heap allocations
870 std::vector<FaceUVArray> color_mesh_uv_coords(texture_mesh_uv_coords.size());
871 for (std::size_t i = 0; i < texture_mesh_uv_coords.size(); ++i) {
872 color_mesh_uv_coords[i] = {texture_mesh_uv_coords[i][0], texture_mesh_uv_coords[i][1], texture_mesh_uv_coords[i][2]};
873 }
874 {
875 // Estimate total iterations and map each iteration's sub-progress to the [5, 25] range
876 size_t estimated_iters = 0;
877 if (settings.oversampling_iters > 0) {
878 estimated_iters = settings.oversampling_iters;
879 } else {
880 size_t fc = color_mesh.facets_count();
881 while (fc < settings.oversampling_min_face_count) {
882 fc *= 4;
883 ++estimated_iters;
884 }
885 if (estimated_iters == 0) {
886 estimated_iters = 1;
887 }
888 }
889
890 auto make_iter_progress = [&](size_t iter) {
891 return [&, iter, estimated_iters](int pct) {
892 int iter_start = static_cast<int>(iter * 100 / estimated_iters);
893 int iter_end = static_cast<int>((iter + 1) * 100 / estimated_iters);
894 int sub_pct = iter_start + pct * (iter_end - iter_start) / 100;
895 sub_report(sub_pct, 5, 25, "Oversampling");
896 };
897 };
898
899 if (settings.oversampling_iters > 0) {
900 for (size_t i = 0; i < settings.oversampling_iters && color_mesh.facets_count() * 4.0 < settings.oversampling_max_face_count; ++i) {
901 if (cancelled()) return false;
902 linear_subdivision(color_mesh, color_mesh_uv_coords, make_iter_progress(i));
903 }
904 } else {
905 size_t iter = 0;
906 while (color_mesh.facets_count() < settings.oversampling_min_face_count) {
907 if (cancelled()) return false;
908 linear_subdivision(color_mesh, color_mesh_uv_coords, make_iter_progress(iter++));
909 }
910 }
911 }
912
913 lap("Oversampling");
914
915 face_colors.resize(color_mesh.indices.size());
916
917 report(25, "Computing face colors");
918 if (cancelled()) {
919 return false;
920 }
921
922 // Step 2: Compute each face's color (7-point Gaussian quadrature + bilinear interpolation sampling)
923 {
924 std::atomic<size_t> done_faces{0};
925 std::atomic<bool> cancel_requested{false};
926 const size_t total_faces = color_mesh.indices.size();
927 const size_t report_interval = std::max<size_t>(total_faces / 20, 1);
928 tbb::parallel_for(tbb::blocked_range<std::size_t>(0, total_faces), [&](const tbb::blocked_range<size_t>& range) {
929 for (std::size_t fid = range.begin(); fid < range.end(); ++fid) {
930 if (cancel_requested.load(std::memory_order_relaxed)) return;
931 face_colors[fid] = sample_face_color(color_mesh_uv_coords[fid], texture);
932 size_t cnt = done_faces.fetch_add(1, std::memory_order_relaxed) + 1;
933 if (cnt % report_interval == 0) {
934 if (cancelled()) { cancel_requested.store(true, std::memory_order_relaxed); return; }
935 sub_report(static_cast<int>(cnt * 100 / total_faces), 25, 40, "Computing face colors");
936 }
937 }
938 });
939 if (cancel_requested.load() || cancelled()) return false;
940 }
941 lap("Computing face colors");
942#ifdef OUTPUT_TEST_RESULT
943 SaveToOFF("texture_to_color_0_initialize.off", color_mesh, face_colors);
944#endif
945
946 // Map progress from repair_cluster_smooth's [0,100] to TextureToColor's [40,100]
947 AlgoProgressCallback rcs_progress = nullptr;
948 if (progress_callback) {
949 rcs_progress = [&](AlgoProgress p) {
950 int mapped_pct = 40 + p.percent * 60 / 100;
951 progress_callback({mapped_pct, p.message});
952 };
953 }
954
955 std::vector<RGB> clustered_face_colors;
956 if (!repair_cluster_smooth(color_mesh, face_colors, clustered_face_colors,
957 settings, rcs_progress, cancel_callback, "TextureToColor"))
958 return false;
959
960 face_colors = std::move(clustered_face_colors);
961 lap("Repair + Clustering + Smoothing");
962 double total_ms = std::chrono::duration<double, std::milli>(Clock::now() - t_total_start).count();
963 BOOST_LOG_TRIVIAL(debug) << "[timing] TextureToColor total: " << total_ms << "ms"
964 << " faces=" << color_mesh.facets_count();
965 report(100, "Completed");
966 return true;
967}
std::function< void(AlgoProgress)> AlgoProgressCallback
Definition Callbacks.hpp:11
std::size_t oversampling_max_face_count
Definition TextureToColor.hpp:38
static RGB sample_face_color(const std::array< Vec2f, 3 > &uvs, const cv::Mat &texture)
Definition TextureToColor.cpp:260
static bool linear_subdivision(TriMesh &mesh, std::vector< FaceUVArray > &uv_coords, const std::function< void(int)> &sub_progress=nullptr)
Definition TextureToColor.cpp:278
std::size_t oversampling_iters
Definition TextureToColor.hpp:36
Definition Callbacks.hpp:6

References Slic3r::tex2color::TriMesh::facets_count(), linear_subdivision(), Slic3r::tex2color::TextureToColorSettings::oversampling_iters, Slic3r::tex2color::TextureToColorSettings::oversampling_max_face_count, Slic3r::tex2color::TextureToColorSettings::oversampling_min_face_count, Slic3r::range(), repair_cluster_smooth(), sample_face_color(), SaveToOFF(), and TextureToColor().

Referenced by Slic3r::texture_to_painting(), and TextureToColor().

Here is the call graph for this function:
Here is the caller graph for this function:

Variable Documentation

◆ GAUSS_TRI_BARY

std::array<std::array<float, 3>, 7> Slic3r::tex2color::GAUSS_TRI_BARY
staticconstexpr
Initial value:
= {{
{1.0f / 3.0f, 1.0f / 3.0f, 1.0f / 3.0f},
{0.059715871f, 0.470142064f, 0.470142064f},
{0.470142064f, 0.059715871f, 0.470142064f},
{0.470142064f, 0.470142064f, 0.059715871f},
{0.797426985f, 0.101286507f, 0.101286507f},
{0.101286507f, 0.797426985f, 0.101286507f},
{0.101286507f, 0.101286507f, 0.797426985f},
}}
238 {{
239 {1.0f / 3.0f, 1.0f / 3.0f, 1.0f / 3.0f},
240 {0.059715871f, 0.470142064f, 0.470142064f},
241 {0.470142064f, 0.059715871f, 0.470142064f},
242 {0.470142064f, 0.470142064f, 0.059715871f},
243 {0.797426985f, 0.101286507f, 0.101286507f},
244 {0.101286507f, 0.797426985f, 0.101286507f},
245 {0.101286507f, 0.101286507f, 0.797426985f},
246}};

Referenced by sample_face_color().

◆ GAUSS_TRI_WEIGHT

std::array<float, 7> Slic3r::tex2color::GAUSS_TRI_WEIGHT = {0.225f, 0.132394152f, 0.132394152f, 0.132394152f, 0.125939181f, 0.125939181f, 0.125939181f}
staticconstexpr
247{0.225f, 0.132394152f, 0.132394152f, 0.132394152f, 0.125939181f, 0.125939181f, 0.125939181f};

Referenced by sample_face_color().

◆ MAX_REPAIRABLE_MESH_BOUNDARY_EDGES

std::size_t Slic3r::tex2color::MAX_REPAIRABLE_MESH_BOUNDARY_EDGES = 5000
inlineconstexpr

◆ MAX_REPAIRABLE_MESH_HOLE_EDGES

std::size_t Slic3r::tex2color::MAX_REPAIRABLE_MESH_HOLE_EDGES = 500
inlineconstexpr