mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-09 08:41:14 +00:00
* Remove Unused Project Includes and Forward-Declare Where a Type Is Only Referenced Generated with include-what-you-use and applied conservatively. Only OrcaSlicer's own headers, the ones under src/ and tests/, are removed or forward-declared; standard-library and third-party includes are left alone. An include is removed only when both the Release and the Debug configuration leave it unused, never from inside a conditional block, and never from a file with platform-specific blocks, which only gain includes. Files whose only use of a header sits behind a feature or debug macro (libvgcode's OpenGL ES and marker code, the ARACHNE/TESTS_EXPORT_SVGS debug output) keep their includes. clonable_ptr.hpp gains #pragma once; it had no include guard and was only safe while Config.hpp was its sole includer. * Remove Unused Project Includes From Files With Platform-Specific Code A Linux include-what-you-use run cannot see the code inside _WIN32, __APPLE__ or __linux__ blocks, so its verdict is only taken where nothing the removed header declares, directly or through what it includes, is named inside those blocks. Removals also have to hold in both the Release and Debug configuration and never touch a line inside a conditional block. * Restore the libslic3r Precompiled Header and Direct Includes Lost in the Platform Pass The platform-file pass treated pchheader.hpp as an ordinary header and emptied it, and left GUI_Preview.hpp and 14 other files relying on headers they no longer reached directly. * Restore MainFrame.hpp in ParamsDialog.cpp for the Windows-Only Reparent Call * Include Headers That Files Reached Through Ones the Cleanup Removed * Drop Includes Duplicated by the Cleanup or by Main's Own Additions * Leave PreciseSeam.cpp as Main Has It After the Precise Seam Rework
223 lines
11 KiB
C++
223 lines
11 KiB
C++
#include "TextureDisplacementBakeJob.hpp"
|
|
|
|
#include <algorithm>
|
|
#include <functional>
|
|
#include <utility>
|
|
#include <string>
|
|
#include "libslic3r/TextureDisplacement.hpp"
|
|
#include <exception>
|
|
#include <cstddef>
|
|
#include "slic3r/GUI/Jobs/Worker.hpp"
|
|
#include <memory>
|
|
|
|
#include "libslic3r/Model.hpp"
|
|
#include "libslic3r/format.hpp"
|
|
#include "libslic3r/TriangleSelector.hpp"
|
|
|
|
#include "slic3r/GUI/GLCanvas3D.hpp"
|
|
#include "slic3r/GUI/GUI.hpp"
|
|
#include "slic3r/GUI/GUI_App.hpp"
|
|
#include "slic3r/GUI/GUI_ObjectList.hpp"
|
|
#include "slic3r/GUI/Gizmos/GLGizmoTextureDisplacement.hpp"
|
|
#include "slic3r/GUI/I18N.hpp"
|
|
#include "slic3r/GUI/NotificationManager.hpp"
|
|
#include "slic3r/GUI/Plater.hpp"
|
|
#include "slic3r/Utils/UndoRedo.hpp"
|
|
#include "libslic3r/ObjectID.hpp"
|
|
#include "libslic3r/TriangleMesh.hpp"
|
|
|
|
namespace Slic3r::GUI {
|
|
|
|
TextureDisplacementBakeJob::TextureDisplacementBakeJob(TextureDisplacementBakeInput &&input, std::function<void()> on_finished)
|
|
: m_input(std::move(input)), m_on_finished(std::move(on_finished))
|
|
{
|
|
}
|
|
|
|
void TextureDisplacementBakeJob::process(Ctl &ctl)
|
|
{
|
|
const std::string status = _u8L("Baking texture displacement");
|
|
ctl.update_status(1, status);
|
|
|
|
// Only ever touches m_input (captured by value before this job was queued) and local state -
|
|
// never the live Model - so this is safe to run concurrently with the UI thread.
|
|
//
|
|
// The progress hook matters for more than cosmetics: the framework's progress notification only
|
|
// grows a close button once it reaches 100%, so a job that reports 0 and nothing else leaves an
|
|
// uncloseable notification pinned on screen. It also carries the Cancel button's effect into the
|
|
// bake, which on a subdivided mesh can run for several seconds.
|
|
// Colour, when any layer asks for it, is computed in the same pass as the displacement: both need
|
|
// the same per-layer projection and UV work, and doing it twice would double the expensive part.
|
|
TextureColorRequest color_request;
|
|
TextureColorRequest *color = nullptr;
|
|
if (!m_input.color.empty()) {
|
|
color_request.quantize = GLGizmoTextureDisplacement::make_palette_quantizer(m_input.color.palette);
|
|
if (!m_input.color.palette_pure.empty())
|
|
color_request.quantize_pure = GLGizmoTextureDisplacement::make_palette_quantizer(m_input.color.palette_pure);
|
|
color_request.resolve = GLGizmoTextureDisplacement::make_mix_resolver(
|
|
m_input.color.palette, m_input.color.mix_mode, m_input.color.layer_height,
|
|
m_input.color.dither_cell_mm);
|
|
color_request.despeckle_passes = m_input.color.despeckle_passes;
|
|
color_request.out_triangle = &m_triangle_color;
|
|
if (color_request.quantize)
|
|
color = &color_request;
|
|
}
|
|
|
|
int last_reported = 1;
|
|
m_result = TriangleMesh(build_texture_displacement(
|
|
m_input.base_mesh, m_input.layers, m_input.facets_data, m_input.options,
|
|
[&ctl, &status, &last_reported](int percent) {
|
|
if (ctl.was_canceled())
|
|
return false;
|
|
// The notification repaints (and wakes the idle loop) on every call, so only push a
|
|
// message when the displayed integer percentage actually moves.
|
|
if (percent > last_reported) {
|
|
last_reported = percent;
|
|
ctl.update_status(percent, status);
|
|
}
|
|
return true;
|
|
},
|
|
color, m_input.volume_to_world, nullptr, &m_stats));
|
|
|
|
// Always finish at 100: this is what closes the notification. Reported even on cancel, where
|
|
// build_texture_displacement() returns an empty mesh and finalize() commits nothing.
|
|
ctl.update_status(100, status);
|
|
}
|
|
|
|
void TextureDisplacementBakeJob::finalize(bool canceled, std::exception_ptr &eptr)
|
|
{
|
|
struct OnExit
|
|
{
|
|
std::function<void()> fn;
|
|
~OnExit() { if (fn) fn(); }
|
|
} on_exit{m_on_finished};
|
|
|
|
if (canceled || eptr || m_result.empty())
|
|
return;
|
|
|
|
// A bake that moved nothing - no layer could be sampled, or every sample was zero - must not be
|
|
// committed: committing is what clears the baked layers' paint, so the user would see the painted
|
|
// region simply vanish with no relief in its place and no idea why. Keep the paint and say so.
|
|
{
|
|
const indexed_triangle_set &out = m_result.its;
|
|
bool unchanged = out.indices.size() == m_input.base_mesh.indices.size() &&
|
|
out.vertices.size() == m_input.base_mesh.vertices.size();
|
|
for (size_t i = 0; unchanged && i < out.vertices.size(); ++i)
|
|
unchanged = (out.vertices[i] - m_input.base_mesh.vertices[i]).cwiseAbs().maxCoeff() < 1e-5f;
|
|
if (unchanged) {
|
|
show_error(nullptr, _u8L("The bake produced no displacement, so nothing was changed and the paint was kept. "
|
|
"Check that the painted layer has a texture and a non-zero depth."));
|
|
return;
|
|
}
|
|
}
|
|
|
|
Plater *plater = wxGetApp().plater();
|
|
|
|
const auto commit = [this, plater]() {
|
|
ModelVolume *volume = get_model_volume(m_input.volume_id, plater->model().objects);
|
|
if (volume == nullptr)
|
|
return;
|
|
|
|
volume->set_mesh(std::move(m_result));
|
|
volume->set_new_unique_id();
|
|
volume->calculate_convex_hull();
|
|
|
|
// Colour lands in mmu_segmentation_facets, merged *over* whatever is already painted there
|
|
// rather than replacing it: a triangle the texture does not colour keeps its existing filament,
|
|
// and one the user never painted at all stays at NONE, which already means "the volume's own
|
|
// filament". That is what confines the effect to the painted area without having to invent a
|
|
// colour for everything outside it. Safe to index straight onto the new mesh - the bake is
|
|
// topology-preserving, so triangle i is still triangle i.
|
|
if (!m_triangle_color.empty() && m_triangle_color.size() == volume->mesh().its.indices.size()) {
|
|
TriangleSelector selector(volume->mesh());
|
|
const TriangleSelector::TriangleSplittingData &existing = volume->mmu_segmentation_facets.get_data();
|
|
if (!existing.bitstream.empty())
|
|
selector.deserialize(existing, false);
|
|
for (size_t i = 0; i < m_triangle_color.size(); ++i)
|
|
if (m_triangle_color[i] > 0)
|
|
selector.set_facet(int(i), EnforcerBlockerType(m_triangle_color[i]));
|
|
volume->mmu_segmentation_facets.set(selector);
|
|
}
|
|
|
|
// Clear the paint mask of every layer that was actually baked so a repeat bake (or the paint
|
|
// overlay) doesn't act on triangles that no longer represent the same unbaked surface. The
|
|
// texture layer definitions themselves are left untouched so the user can keep sculpting with
|
|
// the same textures.
|
|
//
|
|
// A mask the bake did *not* consume only still means what it did if the topology is unchanged,
|
|
// which is true of the classic path (it moves existing vertices) but not of the one-run
|
|
// pipeline, which rebuilds and then simplifies the mesh. A mask left behind against the old
|
|
// topology is exactly what makes the gizmo reload an empty selector over a non-empty mask and
|
|
// then erase it on the next flush - see GLGizmoTextureDisplacement::update_from_model_object().
|
|
const bool topology_changed = m_input.base_mesh.indices.size() != volume->mesh().its.indices.size();
|
|
for (int slot = 0; slot < int(TEXTURE_DISPLACEMENT_MAX_LAYERS); ++slot) {
|
|
const auto it = std::find_if(m_input.layers.begin(), m_input.layers.end(),
|
|
[slot](const TextureDisplacementLayer &l) { return l.slot == slot; });
|
|
if (topology_changed || (it != m_input.layers.end() && !it->empty()))
|
|
volume->texture_displacement_facet(slot).reset();
|
|
}
|
|
|
|
ModelObject *object = volume->get_object();
|
|
if (object == nullptr)
|
|
return;
|
|
|
|
if (ObjectList *obj_list = wxGetApp().obj_list()) {
|
|
const ModelObjectPtrs &objs = plater->model().objects;
|
|
auto it = std::find(objs.begin(), objs.end(), object);
|
|
if (it != objs.end())
|
|
obj_list->update_info_items(size_t(it - objs.begin()));
|
|
}
|
|
|
|
plater->changed_object(*object);
|
|
};
|
|
|
|
// Standard mode's Bake is remesh -> subdivide -> displace under a single snapshot, and this job runs
|
|
// long after that snapshot's scope has closed. Adding one here would put an undo step *between* the
|
|
// subdivision and the displacement: the first Undo would land on a mesh carrying every added
|
|
// triangle and no relief at all, and pressing Bake again from there would subdivide that mesh a
|
|
// second time. So the caller says who owns the undo step.
|
|
if (m_input.take_snapshot) {
|
|
Plater::TakeSnapshot snapshot(plater, _u8L("Bake texture displacement"), UndoRedo::SnapshotType::GizmoAction);
|
|
commit();
|
|
} else {
|
|
commit();
|
|
}
|
|
|
|
// The refinement went finer than the budget could keep: the simplification had to take detail back
|
|
// out to fit, so what was baked carries less of the texture than the resolution asked for. Said
|
|
// here, with the numbers, because it is the only place that knows them - and not as an error
|
|
// dialog: the result is a usable mesh, just not the one the settings described.
|
|
if (m_stats.budget_limited) {
|
|
// Thousands under a million, so a small budget is not reported as "0.1 M".
|
|
const auto count = [](size_t n) {
|
|
return n >= 1000000 ? Slic3r::format("%1$.1f M", double(n) / 1000000.) :
|
|
Slic3r::format("%1% k", (n + 500) / 1000);
|
|
};
|
|
wxGetApp().notification_manager()->push_notification(
|
|
NotificationType::CustomNotification, NotificationManager::NotificationLevel::WarningNotificationLevel,
|
|
Slic3r::format(_u8L("The triangle budget limited the detail: this resolution needs %1% triangles, "
|
|
"the budget kept %2%. Raise Budget or use a coarser Resolution for the full detail."),
|
|
count(m_stats.triangles_refined), count(m_stats.triangles_budget)));
|
|
}
|
|
}
|
|
|
|
void queue_texture_displacement_bake(const ModelVolume &volume, const TextureColorSettings &color,
|
|
std::function<void()> on_finished, bool take_snapshot)
|
|
{
|
|
TextureDisplacementBakeInput input;
|
|
input.color = color;
|
|
input.take_snapshot = take_snapshot;
|
|
input.volume_id = volume.id();
|
|
input.base_mesh = volume.mesh().its;
|
|
input.layers = volume.texture_displacement_layers;
|
|
input.options = volume.texture_displacement_options;
|
|
input.volume_to_world = texture_displacement_volume_to_world(volume);
|
|
for (int i = 0; i < int(TEXTURE_DISPLACEMENT_MAX_LAYERS); ++i)
|
|
input.facets_data[size_t(i)] = volume.texture_displacement_facet(i).get_data();
|
|
|
|
|
|
auto &worker = wxGetApp().plater()->get_ui_job_worker();
|
|
queue_job(worker, std::make_unique<TextureDisplacementBakeJob>(std::move(input), std::move(on_finished)));
|
|
}
|
|
|
|
} // namespace Slic3r::GUI
|