OrcaSlicer latest
OrcaSlicer
Loading...
Searching...
No Matches
Slic3r::GUI::TextureDisplacementBakeJob Class Reference

#include </home/runner/work/OrcaSlicer/OrcaSlicer/src/slic3r/GUI/Jobs/TextureDisplacementBakeJob.hpp>

Inheritance diagram for Slic3r::GUI::TextureDisplacementBakeJob:
Collaboration diagram for Slic3r::GUI::TextureDisplacementBakeJob:

Classes

struct  VolumeResult

Public Types

enum  JobPrepareState { PREPARE_STATE_DEFAULT = 0 , PREPARE_STATE_MENU = 1 }

Public Member Functions

 TextureDisplacementBakeJob (TextureDisplacementBakeInput &&input, std::function< void()> on_finished)
void process (Ctl &ctl) override
void finalize (bool canceled, std::exception_ptr &eptr) override

Private Attributes

TextureDisplacementBakeInput m_input
std::vector< VolumeResult > m_results
std::function< void()> m_on_finished

Detailed Description


Class Documentation

◆ Slic3r::GUI::TextureDisplacementBakeJob::VolumeResult

struct Slic3r::GUI::TextureDisplacementBakeJob::VolumeResult
Collaboration diagram for Slic3r::GUI::TextureDisplacementBakeJob::VolumeResult:
Class Members
TriangleMesh mesh
TextureBakeStats stats
vector< uint8_t > triangle_color

Member Enumeration Documentation

◆ JobPrepareState

Enumerator
PREPARE_STATE_DEFAULT 
PREPARE_STATE_MENU 
22 {
25 };
@ PREPARE_STATE_MENU
Definition Job.hpp:24
@ PREPARE_STATE_DEFAULT
Definition Job.hpp:23

Constructor & Destructor Documentation

◆ TextureDisplacementBakeJob()

Slic3r::GUI::TextureDisplacementBakeJob::TextureDisplacementBakeJob ( TextureDisplacementBakeInput && input,
std::function< void()> on_finished )
36 : m_input(std::move(input)), m_on_finished(std::move(on_finished))
37{
38}
std::function< void()> m_on_finished
Definition TextureDisplacementBakeJob.hpp:89
TextureDisplacementBakeInput m_input
Definition TextureDisplacementBakeJob.hpp:87

References m_input, and m_on_finished.

Member Function Documentation

◆ finalize()

void Slic3r::GUI::TextureDisplacementBakeJob::finalize ( bool canceled,
std::exception_ptr & eptr )
overridevirtual

Reimplemented from Slic3r::GUI::Job.

103{
104 struct OnExit
105 {
106 std::function<void()> fn;
107 ~OnExit() { if (fn) fn(); }
108 } on_exit{m_on_finished};
109
110 if (canceled || eptr)
111 return;
112
113 // A bake that moved nothing - no layer could be sampled, or every sample was zero - must not be
114 // committed: committing is what clears the baked layers' paint, so the user would see the painted
115 // region simply vanish with no relief in its place and no idea why. Keep the paint and say so.
116 // Judged per part, because on a multibody object only some parts may carry a layer that displaces;
117 // the ones that did move are committed and the rest are left exactly as they were.
118 const auto moved = [this](size_t i) {
119 const TriangleMesh &result = m_results[i].mesh;
120 if (result.empty())
121 return false;
122 const indexed_triangle_set &out = result.its;
123 const indexed_triangle_set &base = m_input.volumes[i].base_mesh;
124 bool unchanged = out.indices.size() == base.indices.size() && out.vertices.size() == base.vertices.size();
125 for (size_t v = 0; unchanged && v < out.vertices.size(); ++v)
126 unchanged = (out.vertices[v] - base.vertices[v]).cwiseAbs().maxCoeff() < 1e-5f;
127 return !unchanged;
128 };
129
130 std::vector<size_t> to_commit;
131 for (size_t i = 0; i < m_results.size(); ++i)
132 if (moved(i))
133 to_commit.push_back(i);
134 if (to_commit.empty()) {
135 show_error(nullptr, _u8L("The bake produced no displacement, so nothing was changed and the paint was kept. "
136 "Check that the painted layer has a texture and a non-zero depth."));
137 return;
138 }
139
140 Plater *plater = wxGetApp().plater();
141
142 // The filament each palette entry prints in. This is where a mix becomes a mixed filament slot,
143 // and only a mix the bake actually painted with: the preview never creates one, so the project
144 // gains only the slots this result needs. Decided once for the whole bake, over the entries every
145 // part used between them, so the same mix cannot end up in two slots. Done before anything below
146 // names them - adding a slot runs ModelVolume::update_extruder_count(), which clamps paint above
147 // the old filament count.
148 const auto filaments_for_palette = [this, plater, &to_commit]() {
149 // palette_filaments() only cares *which* entries appear, so the union of the parts' entries
150 // stands in for their concatenated per-triangle colours.
151 std::vector<char> seen(m_input.color.palette.size() + 1, 0);
152 std::vector<uint8_t> used;
153 for (const size_t i : to_commit)
154 for (const uint8_t entry : m_results[i].triangle_color)
155 if (entry > 0 && size_t(entry) < seen.size() && !seen[size_t(entry)]) {
156 seen[size_t(entry)] = 1;
157 used.push_back(entry);
158 }
159 Sidebar &sidebar = plater->sidebar();
161 m_input.color.palette, used, [&sidebar](const PrintableColor &mix) {
162 return sidebar.ensure_mixed_filament({ unsigned(mix.a + 1), unsigned(mix.b + 1) },
163 { mix.a_percent(), 100 - mix.a_percent() });
164 });
165 };
166
167 const auto commit = [this, plater, &to_commit, &filaments_for_palette]() {
168 const std::vector<int> filament_of = filaments_for_palette();
169 ModelObject *object = nullptr;
170
171 for (const size_t i : to_commit) {
172 ModelVolume *volume = get_model_volume(m_input.volumes[i].volume_id, plater->model().objects);
173 // The lookup doubles as a staleness check: anything that replaces a volume's mesh also gives it
174 // a new id, so a part replaced since this bake started - by an undo, another bake, or a
175 // boolean - simply fails to find its volume and is skipped.
176 if (volume == nullptr)
177 continue;
178 const TextureDisplacementBakeVolume &input = m_input.volumes[i];
179 const std::vector<uint8_t> &triangle_color = m_results[i].triangle_color;
180
181 // Whether the bake replaced the triangles or only moved the vertices it was given. The classic
182 // path is topology-preserving by contract, so every per-triangle annotation on the volume still
183 // means what it did; the default pipeline refines, cuts and simplifies, so none of them do.
184 // Both are checked: the flag is what the bake promised, the count is what it delivered. Only
185 // the triangles matter - the annotations are per facet and reference no vertex index - so a
186 // vertex count that changed on its own (the bake compacts unreferenced vertices away) is not a
187 // reason to remap anything.
188 const bool topology_changed = input.options.pipeline_v2 ||
189 m_results[i].mesh.its.indices.size() != input.base_mesh.indices.size();
190
191 // Carry the volume's existing paint - the model's own colours above all - across that rebuild.
192 // Without it a bake left the mmu_segmentation_facets bitstream pointing at a triangle set that
193 // no longer existed, which is what scrambled the colours of an imported coloured model even
194 // with colour switched off on every layer and nothing in the bake that was meant to touch them.
195 //
196 // transfer_facet_paint() rather than ModelVolume::restore_painting(): the latter's remap wants
197 // a target triangle overlapping the source paint to within 0.01 mm, which the baked surface is
198 // nowhere near - it has moved by the relief depth. Nothing to do when the topology is intact:
199 // triangle i is still triangle i, so the paint is already exactly right and carrying it at all
200 // could only lose detail.
201 std::array<TriangleSelector::TriangleSplittingData, 4> before;
202 if (topology_changed) {
203 before[0] = volume->supported_facets.get_data();
204 before[1] = volume->seam_facets.get_data();
205 before[2] = volume->mmu_segmentation_facets.get_data();
206 before[3] = volume->fuzzy_skin_facets.get_data();
207 }
208
209 volume->set_mesh(std::move(m_results[i].mesh));
210 volume->set_new_unique_id();
211 volume->calculate_convex_hull();
212
213 if (topology_changed) {
214 const std::array<FacetsAnnotation *, 4> target{ &volume->supported_facets, &volume->seam_facets,
215 &volume->mmu_segmentation_facets,
216 &volume->fuzzy_skin_facets };
217 for (size_t ch = 0; ch < target.size(); ++ch) {
218 const std::vector<uint8_t> state =
219 transfer_facet_paint(input.base_mesh, before[ch], volume->mesh().its);
220 // Nothing was painted in this channel, or there is nothing to paint it onto: either
221 // way the annotation must not keep describing the mesh that is gone.
222 if (state.empty()) {
223 target[ch]->reset();
224 continue;
225 }
226 TriangleSelector selector(volume->mesh());
227 bool any = false;
228 for (size_t t = 0; t < state.size(); ++t)
229 if (state[t] != 0) {
230 selector.set_facet(int(t), EnforcerBlockerType(state[t]));
231 any = true;
232 }
233 if (any)
234 target[ch]->set(selector);
235 else
236 target[ch]->reset();
237 }
238 }
239
240 // Colour lands in mmu_segmentation_facets, merged *over* whatever is already painted there
241 // rather than replacing it: a triangle the texture does not colour keeps its existing filament,
242 // and one the user never painted at all stays at NONE, which already means "the volume's own
243 // filament". That is what confines the effect to the painted area without having to invent a
244 // colour for everything outside it. What was already painted is whatever the transfer above
245 // left in the annotation, so this bake's colour lands on top of it rather than instead of it.
246 if (!triangle_color.empty() && triangle_color.size() == volume->mesh().its.indices.size()) {
247 TriangleSelector selector(volume->mesh());
248 const TriangleSelector::TriangleSplittingData &existing = volume->mmu_segmentation_facets.get_data();
249 if (!existing.bitstream.empty())
250 selector.deserialize(existing, false);
251 for (size_t t = 0; t < triangle_color.size(); ++t) {
252 const size_t entry = size_t(triangle_color[t]);
253 if (entry > 0 && entry <= filament_of.size() && filament_of[entry - 1] >= 0)
254 selector.set_facet(int(t), EnforcerBlockerType(filament_of[entry - 1] + 1));
255 }
256 volume->mmu_segmentation_facets.set(selector);
257 }
258
259 // Clear the paint mask of every layer that was actually baked so a repeat bake (or the paint
260 // overlay) doesn't act on triangles that no longer represent the same unbaked surface. The
261 // texture layer definitions themselves are left untouched so the user can keep sculpting with
262 // the same textures.
263 //
264 // A mask the bake did *not* consume only still means what it did if the topology is unchanged,
265 // which is true of the classic path (it moves existing vertices) but not of the one-run
266 // pipeline, which rebuilds and then simplifies the mesh. A mask left behind against the old
267 // topology is exactly what makes the gizmo reload an empty selector over a non-empty mask and
268 // then erase it on the next flush - see GLGizmoTextureDisplacement::update_from_model_object().
269 for (int slot = 0; slot < int(TEXTURE_DISPLACEMENT_MAX_LAYERS); ++slot) {
270 const auto it = std::find_if(input.layers.begin(), input.layers.end(),
271 [slot](const TextureDisplacementLayer &l) { return l.slot == slot; });
272 if (topology_changed || (it != input.layers.end() && !it->empty()))
273 volume->texture_displacement_facet(slot).reset();
274 }
275
276 // The model's own UV channel. Resampled onto a rebuilt mesh from the surface the bake started
277 // from: a displaced vertex came from its foot point on that surface, which is the position its
278 // uv belongs to - so no distance limit, the relief depth is exactly how far it has moved.
279 if (!input.model_uv.empty())
280 volume->texture_uv = topology_changed ? transfer_uv_channel(input.base_mesh, input.model_uv,
281 volume->mesh().its) :
282 input.model_uv;
283
284 if (object == nullptr)
285 object = volume->get_object();
286 }
287
288 if (object == nullptr)
289 return;
290
291 if (ObjectList *obj_list = wxGetApp().obj_list()) {
292 const ModelObjectPtrs &objs = plater->model().objects;
293 auto it = std::find(objs.begin(), objs.end(), object);
294 if (it != objs.end())
295 obj_list->update_info_items(size_t(it - objs.begin()));
296 }
297
298 plater->changed_object(*object);
299 };
300
301 // Standard mode's Bake is remesh -> subdivide -> displace under a single snapshot, and this job runs
302 // long after that snapshot's scope has closed. Adding one here would put an undo step *between* the
303 // subdivision and the displacement: the first Undo would land on a mesh carrying every added
304 // triangle and no relief at all, and pressing Bake again from there would subdivide that mesh a
305 // second time. So the caller says who owns the undo step.
306 if (m_input.take_snapshot) {
307 Plater::TakeSnapshot snapshot(plater, _u8L("Bake texture displacement"), UndoRedo::SnapshotType::GizmoAction);
308 commit();
309 } else {
310 commit();
311 }
312
313 // The refinement went finer than the budget could keep: the simplification had to take detail back
314 // out to fit, so what was baked carries less of the texture than the resolution asked for. Said
315 // here, with the numbers, because it is the only place that knows them - and not as an error
316 // dialog: the result is a usable mesh, just not the one the settings described. Summed over the
317 // parts that hit it, since the budget is spent per part and one message is enough.
318 size_t refined = 0, budget = 0;
319 for (const size_t i : to_commit)
320 if (m_results[i].stats.budget_limited) {
321 refined += m_results[i].stats.triangles_refined;
322 budget += m_results[i].stats.triangles_budget;
323 }
324 if (refined > 0) {
325 // Thousands under a million, so a small budget is not reported as "0.1 M".
326 const auto count = [](size_t n) {
327 return n >= 1000000 ? Slic3r::format("%1$.1f M", double(n) / 1000000.) :
328 Slic3r::format("%1% k", (n + 500) / 1000);
329 };
330 wxGetApp().notification_manager()->push_notification(
332 Slic3r::format(_u8L("The triangle budget limited the detail: this resolution needs %1% triangles, "
333 "the budget kept %2%. Raise Budget or use a coarser Resolution for the full detail."),
334 count(refined), count(budget)));
335 }
336}
static unsigned int count
Definition AuxiliaryDataViewModel.cpp:246
static std::vector< int > palette_filaments(const std::vector< PaletteEntry > &palette, const std::vector< uint8_t > &triangle_color, const std::function< int(const PaletteEntry &)> &slot_for_mix)
Definition GLGizmoTextureDisplacement.cpp:5995
@ WarningNotificationLevel
Definition NotificationManager.hpp:224
void update_info_items(size_t obj_idx, wxDataViewItemArray *selections=nullptr, bool added_object=false, bool color_mode_changed=false)
Definition GUI_ObjectList.cpp:4039
void changed_object(ModelObject &object)
Definition Plater.cpp:22227
const Model & model() const
Definition Plater.cpp:16047
Sidebar & sidebar()
Definition Plater.cpp:16046
std::vector< VolumeResult > m_results
Definition TextureDisplacementBakeJob.hpp:88
ModelObjectPtrs objects
Definition Model.hpp:1702
static ObjectList * obj_list()
Definition GUI_Factories.cpp:405
static Plater * plater()
Definition GUI_Factories.cpp:400
void show_error(wxWindow *parent, const wxString &message, bool has_code_excerpts)
Definition GUI.cpp:296
@ CustomNotification
Definition NotificationManager.hpp:66
std::string format(const char *fmt, TArgs &&... args)
Definition format.hpp:36
static ModelVolume * get_model_volume(const Selection &selection, Model &model)
Definition GLGizmoBrimEars.cpp:66
@ ObjectList
Definition Shortcuts.hpp:21
@ Plater
Definition Shortcuts.hpp:21
@ GizmoAction
Definition UndoRedo.hpp:36
EnforcerBlockerType
Definition TriangleSelector.hpp:24
static constexpr size_t TEXTURE_DISPLACEMENT_MAX_LAYERS
Definition TextureDisplacement.hpp:44
std::vector< uint8_t > transfer_facet_paint(const indexed_triangle_set &src_mesh, const TriangleSelector::TriangleSplittingData &src_paint, const indexed_triangle_set &dst_mesh)
Definition TextureDisplacement.cpp:1902
std::string format(const char *fmt, TArgs &&... args)
Definition format.hpp:45
std::vector< Vec2f > transfer_uv_channel(const indexed_triangle_set &src_mesh, const std::vector< Vec2f > &src_uv, const indexed_triangle_set &dst_mesh, float max_distance)
Definition TextureDisplacement.cpp:2126
std::vector< ModelObject * > ModelObjectPtrs
Definition CutUtils.hpp:13
#define _u8L(s)
Definition I18N.hpp:11

References _u8L, Slic3r::TriangleMesh::empty(), finalize(), Slic3r::TriangleMesh::its, m_input, m_on_finished, m_results, Slic3r::GUI::GLGizmoTextureDisplacement::palette_filaments(), Slic3r::GUI::plater(), and Slic3r::GUI::show_error().

Referenced by finalize().

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

◆ process()

void Slic3r::GUI::TextureDisplacementBakeJob::process ( Ctl & ctl)
overridevirtual

Implements Slic3r::GUI::Job.

41{
42 const std::string status = _u8L("Baking texture displacement");
43 ctl.update_status(1, status);
44
45 // Only ever touches m_input (captured by value before this job was queued) and local state -
46 // never the live Model - so this is safe to run concurrently with the UI thread.
47 //
48 // The progress hook matters for more than cosmetics: the framework's progress notification only
49 // grows a close button once it reaches 100%, so a job that reports 0 and nothing else leaves an
50 // uncloseable notification pinned on screen. It also carries the Cancel button's effect into the
51 // bake, which on a subdivided mesh can run for several seconds.
52 // Colour, when any layer asks for it, is computed in the same pass as the displacement: both need
53 // the same per-layer projection and UV work, and doing it twice would double the expensive part.
54 //
55 // The quantizers are built once for the whole job rather than per part: each fills a colour lookup
56 // cube that costs tens of milliseconds, and the palette is the object's, not the part's.
57 TextureColorRequest color_request;
58 TextureColorRequest *color = nullptr;
59 if (!m_input.color.empty()) {
60 color_request.quantize = GLGizmoTextureDisplacement::make_palette_quantizer(m_input.color.palette);
61 if (!m_input.color.palette_pure.empty())
62 color_request.quantize_pure = GLGizmoTextureDisplacement::make_palette_quantizer(m_input.color.palette_pure);
63 color_request.despeckle_passes = m_input.color.despeckle_passes;
64 if (color_request.quantize)
65 color = &color_request;
66 }
67
68 // Each part gets its own slice of the 1..100 range, in order, so one progress bar covers the whole
69 // bake however many parts it displaces.
70 const size_t count = m_input.volumes.size();
71 m_results.resize(count);
72 int last_reported = 1;
73 for (size_t i = 0; i < count; ++i) {
74 const TextureDisplacementBakeVolume &volume = m_input.volumes[i];
75 if (color != nullptr)
76 color_request.out_triangle = &m_results[i].triangle_color;
77 m_results[i].mesh = TriangleMesh(build_texture_displacement(
78 volume.base_mesh, volume.layers, volume.facets_data, volume.options,
79 [&ctl, &status, &last_reported, i, count](int percent) {
80 if (ctl.was_canceled())
81 return false;
82 // The notification repaints (and wakes the idle loop) on every call, so only push a
83 // message when the displayed integer percentage actually moves.
84 const int overall = int((i * 100 + size_t(std::max(0, percent))) / count);
85 if (overall > last_reported) {
86 last_reported = overall;
87 ctl.update_status(overall, status);
88 }
89 return true;
90 },
91 color, volume.volume_to_world, nullptr, &m_results[i].stats,
92 volume.model_uv.empty() ? nullptr : &volume.model_uv, &volume.texture_anchor, volume.resting_drop));
93 if (ctl.was_canceled())
94 break;
95 }
96
97 // Always finish at 100: this is what closes the notification. Reported even on cancel, where
98 // build_texture_displacement() returns an empty mesh and finalize() commits nothing.
99 ctl.update_status(100, status);
100}
static ColorQuantizeFn make_palette_quantizer(const std::vector< PaletteEntry > &palette)
Definition GLGizmoTextureDisplacement.cpp:6019
indexed_triangle_set build_texture_displacement(const indexed_triangle_set &base_mesh, const std::vector< TextureDisplacementLayer > &layers, const TextureDisplacementFacetsData &facets_data, const TextureDisplacementOptions &options, const DisplacementProgressFn &progress, const TextureColorRequest *color, const Transform3d &volume_to_world, BakeStageRecorder *debug, TextureBakeStats *stats, const std::vector< Vec2f > *model_uv, const Vec3d *texture_anchor, float resting_drop)
Definition TextureDisplacement.cpp:3495

References _u8L, Slic3r::GUI::TextureDisplacementBakeVolume::base_mesh, Slic3r::build_texture_displacement(), count, Slic3r::TextureColorRequest::despeckle_passes, Slic3r::GUI::TextureDisplacementBakeVolume::facets_data, Slic3r::GUI::TextureDisplacementBakeVolume::layers, m_input, m_results, Slic3r::GUI::GLGizmoTextureDisplacement::make_palette_quantizer(), Slic3r::GUI::TextureDisplacementBakeVolume::options, Slic3r::TextureColorRequest::out_triangle, Slic3r::TextureColorRequest::quantize, Slic3r::TextureColorRequest::quantize_pure, Slic3r::GUI::Job::Ctl::update_status(), and Slic3r::GUI::Job::Ctl::was_canceled().

Here is the call graph for this function:

Member Data Documentation

◆ m_input

TextureDisplacementBakeInput Slic3r::GUI::TextureDisplacementBakeJob::m_input
private

◆ m_on_finished

std::function<void()> Slic3r::GUI::TextureDisplacementBakeJob::m_on_finished
private

◆ m_results

std::vector<VolumeResult> Slic3r::GUI::TextureDisplacementBakeJob::m_results
private

Referenced by finalize(), and process().


The documentation for this class was generated from the following files: