OrcaSlicer latest
OrcaSlicer
Loading...
Searching...
No Matches
texture_unwrap_dump.cpp File Reference
#include "nanosvg/nanosvg.h"
#include "nanosvg/nanosvgrast.h"
#include <chrono>
#include <cstdio>
#include <string>
#include <functional>
#include <unordered_map>
#include <vector>
#include "libslic3r/Model.hpp"
#include "libslic3r/TextureDisplacement.hpp"
#include "libslic3r/Format/bbs_3mf.hpp"
#include "libslic3r/Utils.hpp"
#include <boost/filesystem.hpp>
Include dependency graph for texture_unwrap_dump.cpp:

Go to the source code of this file.

Namespaces

namespace  anonymous_namespace{texture_unwrap_dump.cpp}

Macros

#define NANOSVG_IMPLEMENTATION
#define NANOSVGRAST_IMPLEMENTATION

Functions

uint64_t anonymous_namespace{texture_unwrap_dump.cpp}::edge_key (int a, int b)
void anonymous_namespace{texture_unwrap_dump.cpp}::chart_topology (const indexed_triangle_set &mesh, const std::vector< int > &faces, int &loops, int &euler)
float anonymous_namespace{texture_unwrap_dump.cpp}::signed_area_2d (const Vec2f &a, const Vec2f &b, const Vec2f &c)
int main (int argc, char **argv)

Macro Definition Documentation

◆ NANOSVG_IMPLEMENTATION

#define NANOSVG_IMPLEMENTATION

◆ NANOSVGRAST_IMPLEMENTATION

#define NANOSVGRAST_IMPLEMENTATION

Function Documentation

◆ main()

int main ( int argc,
char ** argv )
89{
90 if (argc < 2) {
91 std::printf("usage: texture_unwrap_dump <project.3mf>\n");
92 return 2;
93 }
94
95 Model model;
96 DynamicPrintConfig config;
97 ConfigSubstitutionContext ctx(ForwardCompatibilitySubstitutionRule::Enable);
98 PlateDataPtrs plate_data;
99 std::vector<Preset *> project_presets;
100 bool is_bbl_3mf = false, is_orca_3mf = false;
101 Semver file_version;
102 // The importer writes a backup copy under the data dir and silently loses objects without one.
103 const boost::filesystem::path tmp = boost::filesystem::temp_directory_path() / "texture_unwrap_dump";
104 boost::filesystem::create_directories(tmp);
105 set_data_dir(tmp.string());
106
107 // LoadModel so the meshes come through; AddDefaultInstances because an object with no instance is
108 // dropped by the plate mapping, which is what "skip this object" in the log means.
109 if (!load_bbs_3mf(argv[1], &config, &ctx, &model, &plate_data, &project_presets, &is_bbl_3mf, &is_orca_3mf,
110 &file_version, nullptr,
111 LoadStrategy::LoadModel | LoadStrategy::LoadConfig | LoadStrategy::AddDefaultInstances |
112 LoadStrategy::Silence)) {
113 std::printf("failed to load %s\n", argv[1]);
114 return 1;
115 }
116
117 std::printf("loaded: %zu object(s)\n", model.objects.size());
118
119 for (const ModelObject *object : model.objects)
120 for (const ModelVolume *volume : object->volumes) {
121 if (volume->texture_displacement_layers.empty()) {
122 std::printf("volume \"%s\": no texture displacement layers; paint masks per slot:",
123 volume->name.c_str());
124 for (int i = 0; i < int(TEXTURE_DISPLACEMENT_MAX_LAYERS); ++i)
125 std::printf(" %zu", volume->texture_displacement_facet(i).get_data().triangles_to_split.size());
126 std::printf("\n");
127 continue;
128 }
129 std::printf("volume \"%s\": %zu base triangles, %zu layer(s)\n", volume->name.c_str(),
130 volume->mesh().its.indices.size(), volume->texture_displacement_layers.size());
131
132 for (const TextureDisplacementLayer &layer : volume->texture_displacement_layers) {
133 std::printf("\n layer %d \"%s\" mapping=%d seam_angle=%.1f connect=%d islands_stored=%zu\n",
134 layer.slot, layer.name.c_str(), int(layer.projection_method),
135 layer.lscm_seam_angle_deg, int(layer.auto_connect_islands), layer.islands.size());
136 if (layer.projection_method != TextureProjectionMethod::LSCM)
137 continue;
138
139 const indexed_triangle_set patch =
141 std::printf(" patch: %zu vertices, %zu triangles\n", patch.vertices.size(), patch.indices.size());
142 if (patch.indices.empty())
143 continue;
144
145 const auto t0 = std::chrono::steady_clock::now();
146 const PatchUnwrap unwrap = compute_patch_unwrap(patch, layer.lscm_seam_angle_deg, 0.f,
147 layer.lscm_seam_edges);
148 const auto t1 = std::chrono::steady_clock::now();
149 std::printf(" TIMING compute_patch_unwrap: %.0f ms\n",
150 std::chrono::duration<double, std::milli>(t1 - t0).count());
151 std::printf(" unwrap: %d charts, %zu unwrapped triangles\n", unwrap.chart_count,
152 unwrap.indices.size());
153
154 // Group the patch's faces by chart so each can be examined on its own.
155 std::vector<std::vector<int>> chart_faces(size_t(std::max(unwrap.chart_count, 0)));
156 for (size_t i = 0; i < unwrap.indices.size(); ++i) {
157 const int chart = unwrap.vertex_chart[size_t(unwrap.indices[i][0])];
158 if (chart >= 0 && size_t(chart) < chart_faces.size())
159 chart_faces[size_t(chart)].push_back(unwrap.source_face[i]);
160 }
161
162 int bad_charts = 0;
163 for (size_t c = 0; c < chart_faces.size(); ++c) {
164 int loops = 0, euler = 0;
165 chart_topology(patch, chart_faces[c], loops, euler);
166
167 // Flipped triangles: the unwrap folded over itself, which is what a planar fallback
168 // does to a chart that is not flat. Measured on the unwrap's own triangles.
169 int pos = 0, neg = 0;
170 for (size_t i = 0; i < unwrap.indices.size(); ++i) {
171 const stl_triangle_vertex_indices &t = unwrap.indices[i];
172 if (unwrap.vertex_chart[size_t(t[0])] != int(c))
173 continue;
174 const float a = signed_area_2d(unwrap.uvs[size_t(t[0])], unwrap.uvs[size_t(t[1])],
175 unwrap.uvs[size_t(t[2])]);
176 if (a > 0.f) ++pos; else if (a < 0.f) ++neg;
177 }
178 const int flipped = std::min(pos, neg);
179 const bool disk = loops == 1 && euler == 1;
180 if (!disk || flipped > 0) {
181 ++bad_charts;
182 std::printf(" chart %2zu: %4zu faces loops=%d euler=%d%s flipped=%d/%d%s\n", c,
183 chart_faces[c].size(), loops, euler, disk ? "" : " NOT A DISK", flipped,
184 pos + neg, flipped ? " FOLDED" : "");
185 }
186 }
187 std::printf(" charts with a defect: %d / %d\n", bad_charts, unwrap.chart_count);
188
189 // What the eye actually sees. Every patch edge shared by two charts should carry the same
190 // UV on both sides once the islands are laid out as a connected net; where it does not,
191 // the texture jumps across that seam. Measured through compute_lscm_uvs(), i.e. the exact
192 // coordinates the bake and the checker overlay sample.
193 {
194 const auto n0 = std::chrono::steady_clock::now();
195 const std::vector<TextureIsland> net = compute_connected_net(unwrap);
196 const auto n1 = std::chrono::steady_clock::now();
197 std::printf(" TIMING compute_connected_net: %.0f ms (%zu islands)\n",
198 std::chrono::duration<double, std::milli>(n1 - n0).count(), net.size());
199 }
200 const auto t2 = std::chrono::steady_clock::now();
201 const std::vector<Vec2f> uv = compute_lscm_uvs(patch, layer);
202 const auto t3 = std::chrono::steady_clock::now();
203 std::printf(" TIMING compute_lscm_uvs: %.0f ms (called on every preview, overlay and bake)\n",
204 std::chrono::duration<double, std::milli>(t3 - t2).count());
205 if (uv.size() != patch.vertices.size()) {
206 std::printf(" compute_lscm_uvs returned %zu uvs for %zu vertices\n", uv.size(),
207 patch.vertices.size());
208 continue;
209 }
210 // Per-corner UVs carry each chart's own placement, so an edge shared by two charts shows
211 // the jump directly: the same mesh vertex lands at two different UVs. That is exactly what
212 // the eye reads as the texture breaking.
213 const auto t4 = std::chrono::steady_clock::now();
214 const std::vector<Vec2f> corner = compute_lscm_corner_uvs(patch, layer);
215 const auto t5 = std::chrono::steady_clock::now();
216 std::printf(" TIMING compute_lscm_corner_uvs: %.0f ms\n",
217 std::chrono::duration<double, std::milli>(t5 - t4).count());
218 // Keyed by edge, holding the UV each incident face gives to the edge's *lower-numbered*
219 // endpoint. Comparing that same vertex on both sides is the point: indexing by corner
220 // position instead compares opposite ends of the edge, because the two faces wind it in
221 // opposite directions.
222 std::unordered_map<uint64_t, std::vector<Vec2f>> edge_seen;
223 if (corner.size() == patch.indices.size() * 3)
224 for (size_t f = 0; f < patch.indices.size(); ++f) {
225 const stl_triangle_vertex_indices &t = patch.indices[f];
226 for (int k = 0; k < 3; ++k) {
227 const int a = t[k], b = t[(k + 1) % 3];
228 const int probe = std::min(a, b);
229 const int local = (a == probe) ? k : (k + 1) % 3;
230 edge_seen[edge_key(a, b)].push_back(corner[f * 3 + size_t(local)]);
231 }
232 }
233 // Which chart each patch face belongs to, so a broken edge can be attributed to a pair.
234 std::vector<int> chart_of_face(patch.indices.size(), -1);
235 for (size_t i = 0; i < unwrap.indices.size(); ++i)
236 chart_of_face[size_t(unwrap.source_face[i])] = unwrap.vertex_chart[size_t(unwrap.indices[i][0])];
237
238 std::unordered_map<uint64_t, std::vector<int>> edge_faces;
239 for (size_t f = 0; f < patch.indices.size(); ++f) {
240 const stl_triangle_vertex_indices &t = patch.indices[f];
241 for (int k = 0; k < 3; ++k)
242 edge_faces[edge_key(t[k], t[(k + 1) % 3])].push_back(int(f));
243 }
244
245 int adjacent = 0, broken = 0, broken_same_chart = 0;
246 float worst = 0.f;
247 std::map<std::pair<int, int>, std::pair<int, float>> by_pair;
248 for (const auto &[key, seen] : edge_seen) {
249 if (seen.size() != 2)
250 continue;
251 ++adjacent;
252 const float d = (seen[0] - seen[1]).norm();
253 if (d <= 1e-4f)
254 continue;
255 ++broken;
256 worst = std::max(worst, d);
257 const auto &faces_here = edge_faces[key];
258 int c1 = -1, c2 = -1;
259 if (faces_here.size() == 2) {
260 c1 = chart_of_face[size_t(faces_here[0])];
261 c2 = chart_of_face[size_t(faces_here[1])];
262 }
263 if (c1 == c2)
264 ++broken_same_chart;
265 auto &slot = by_pair[{ std::min(c1, c2), std::max(c1, c2) }];
266 ++slot.first;
267 slot.second = std::max(slot.second, d);
268 }
269 std::printf(" broken edges inside a single chart: %d\n", broken_same_chart);
270 std::printf(" broken by chart pair:");
271 for (const auto &[pk, v] : by_pair)
272 std::printf(" (%d,%d)x%d/%.1f", pk.first, pk.second, v.first, v.second);
273 std::printf("\n");
274 // Total length of the seams left broken, in mm: how much visibly torn edge the layout has,
275 // which is what the eye adds up. A count alone hides whether the breaks are hairlines or
276 // whole sides of an island.
277 float seam_mm = 0.f;
278 for (const auto &[key, seen] : edge_seen) {
279 if (seen.size() != 2 || (seen[0] - seen[1]).norm() <= 1e-4f)
280 continue;
281 seam_mm += (patch.vertices[size_t(key >> 32)] - patch.vertices[size_t(uint32_t(key))]).norm();
282 }
283 std::printf(" interior edges: %d, discontinuous: %d, total torn seam: %.2f mm (worst jump %.3f)\n",
284 adjacent, broken, seam_mm, worst);
285 std::printf(" stored islands %zu vs charts %d -> %s\n", layer.islands.size(),
286 unwrap.chart_count,
287 layer.islands.size() == size_t(unwrap.chart_count) ? "stored placements used"
288 : "net rebuilt");
289 }
290 }
291 return 0;
292}
static unsigned int count
Definition AuxiliaryDataViewModel.cpp:246
Definition PrintConfig.hpp:915
const TriangleSelector::TriangleSplittingData & get_data() const noexcept
Definition Model.hpp:763
Definition Model.hpp:382
ModelVolumePtrs volumes
Definition Model.hpp:393
Definition Model.hpp:832
const TriangleMesh & mesh() const
Definition Model.hpp:892
FacetsAnnotation & texture_displacement_facet(int slot)
Definition Model.hpp:937
std::vector< TextureDisplacementLayer > texture_displacement_layers
Definition Model.hpp:974
std::string name
Definition Model.hpp:834
Definition Model.hpp:1696
ModelObjectPtrs objects
Definition Model.hpp:1702
Definition Semver.hpp:24
indexed_triangle_set its
Definition TriangleMesh.hpp:164
@ local
Definition Time.hpp:13
Vec3d pos(const Pt &p)
Definition ReprojectPointsOnMesh.hpp:16
std::vector< PlateData * > PlateDataPtrs
Definition bbs_3mf.hpp:278
void set_data_dir(const std::string &dir)
Definition utils.cpp:353
PatchUnwrap compute_patch_unwrap(const indexed_triangle_set &patch, float seam_angle_deg, float padding_mm, const std::vector< std::pair< int, int > > &seam_edges)
Definition TextureDisplacement.cpp:1230
std::vector< Vec2f > compute_lscm_uvs(const indexed_triangle_set &patch, const TextureDisplacementLayer &layer)
Definition TextureDisplacement.cpp:1993
std::vector< TextureIsland > compute_connected_net(const PatchUnwrap &unwrap)
Definition TextureDisplacement.cpp:1673
static constexpr size_t TEXTURE_DISPLACEMENT_MAX_LAYERS
Definition TextureDisplacement.hpp:44
static double f(double x, double z_sin, double z_cos, bool vertical, bool flip)
Definition FillGyroid.cpp:124
indexed_triangle_set extract_painted_patch(const indexed_triangle_set &base_mesh, const TriangleSelector::TriangleSplittingData &facet_data)
Definition TextureDisplacement.cpp:2457
std::vector< Vec2f > compute_lscm_corner_uvs(const indexed_triangle_set &patch, const TextureDisplacementLayer &layer)
Definition TextureDisplacement.cpp:2024
bool load_bbs_3mf(const char *path, DynamicPrintConfig *config, ConfigSubstitutionContext *config_substitutions, Model *model, PlateDataPtrs *plate_data_list, std::vector< Preset * > *project_presets, bool *is_bbl_3mf, bool *is_orca_3mf, Semver *file_version, Import3mfProgressFn proFn, LoadStrategy strategy, BBLProject *project, int plate_id)
Definition bbs_3mf.cpp:9569
float signed_area_2d(const Vec2f &a, const Vec2f &b, const Vec2f &c)
Definition texture_unwrap_dump.cpp:81
void chart_topology(const indexed_triangle_set &mesh, const std::vector< int > &faces, int &loops, int &euler)
Definition texture_unwrap_dump.cpp:46
uint64_t edge_key(int a, int b)
Definition texture_unwrap_dump.cpp:37
Definition BonjourDialog.hpp:16
Definition Config.hpp:274
Definition TextureDisplacement.hpp:771
std::vector< stl_triangle_vertex_indices > indices
Definition TextureDisplacement.hpp:779
std::vector< Vec2f > uvs
Definition TextureDisplacement.hpp:772
std::vector< int > vertex_chart
Definition TextureDisplacement.hpp:774
int chart_count
Definition TextureDisplacement.hpp:786
std::vector< int > source_face
Definition TextureDisplacement.hpp:780
Definition TextureDisplacement.hpp:210
int slot
Definition TextureDisplacement.hpp:214
std::vector< TextureIsland > islands
Definition TextureDisplacement.hpp:308
std::vector< std::pair< int, int > > lscm_seam_edges
Definition TextureDisplacement.hpp:280
float lscm_seam_angle_deg
Definition TextureDisplacement.hpp:271
TextureProjectionMethod projection_method
Definition TextureDisplacement.hpp:269
bool auto_connect_islands
Definition TextureDisplacement.hpp:262
std::string name
Definition TextureDisplacement.hpp:216
std::vector< TriangleBitStreamMapping > triangles_to_split
Definition TriangleSelector.hpp:289

References Slic3r::AddDefaultInstances, Slic3r::TextureDisplacementLayer::auto_connect_islands, Slic3r::PatchUnwrap::chart_count, anonymous_namespace{texture_unwrap_dump.cpp}::chart_topology(), Slic3r::compute_connected_net(), Slic3r::compute_lscm_corner_uvs(), Slic3r::compute_lscm_uvs(), Slic3r::compute_patch_unwrap(), count, anonymous_namespace{texture_unwrap_dump.cpp}::edge_key(), Slic3r::Enable, Slic3r::extract_painted_patch(), Slic3r::f(), Slic3r::FacetsAnnotation::get_data(), Slic3r::PatchUnwrap::indices, Slic3r::TextureDisplacementLayer::islands, Slic3r::TriangleMesh::its, Slic3r::load_bbs_3mf(), Slic3r::LoadConfig, Slic3r::LoadModel, Slic3r::LSCM, Slic3r::TextureDisplacementLayer::lscm_seam_angle_deg, Slic3r::TextureDisplacementLayer::lscm_seam_edges, Slic3r::ModelVolume::mesh(), Slic3r::ModelVolume::name, Slic3r::TextureDisplacementLayer::name, Slic3r::Model::objects, Slic3r::TextureDisplacementLayer::projection_method, Slic3r::set_data_dir(), anonymous_namespace{texture_unwrap_dump.cpp}::signed_area_2d(), Slic3r::Silence, Slic3r::TextureDisplacementLayer::slot, Slic3r::PatchUnwrap::source_face, Slic3r::ModelVolume::texture_displacement_facet(), Slic3r::ModelVolume::texture_displacement_layers, Slic3r::TEXTURE_DISPLACEMENT_MAX_LAYERS, Slic3r::TriangleSelector::TriangleSplittingData::triangles_to_split, Slic3r::PatchUnwrap::uvs, Slic3r::PatchUnwrap::vertex_chart, and Slic3r::ModelObject::volumes.

Here is the call graph for this function: