mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-18 21:20:57 +00:00
Carry existing colour across a bake instead of reindexing it (#16392)
* Carry existing colour across a bake instead of reindexing it Baking, painting colour, then baking again left colour debris on surfaces nothing had painted and destroyed what the earlier bake had laid down. TextureDisplacementBakeJob::finalize() deserialized the volume's pre-bake mmu_segmentation_facets into a TriangleSelector built on the post-bake mesh, on the assumption that the bake is topology-preserving. That holds for the classic path, which moves vertices and leaves the topology alone, but not for the one-run pipeline, which rebuilds the mesh. TriangleSelector::deserialize() only checks that the stored triangle ids are in range, and a bake almost always grows the triangle count, so every stale id passed that check and landed on an unrelated face. Measured on a 26 mm slab baked twice at 0.19 mm with a 750 k budget: 7580 triangles ended up carrying paint neither bake produced, as far as 31 mm from any colour that was actually laid down, while the first pass's colour was lost where the second bake left the surface alone. TriangleSelector::remap_painting() already transfers paint between meshes by position, and ModelVolume::restore_painting() uses it for the same reason, so the bake job now uses it too. It runs in process(), on the worker thread: it builds an AABB tree over both meshes and took around a second on the case above, which would have frozen the UI in finalize(). The bake's own colour is still applied last, so it wins where it painted and the rest is kept, which is what makes successive bakes accumulate rather than overwrite. If the volume is painted while the bake is running, a timestamp check falls back to the live mask so that stroke is not dropped. The out_triangle comment in TextureColorRequest claimed the bake is topology-preserving, which is what justified the original indexing; it now says which mesh the colours belong to. The regression test bakes twice, checks both results are closed, and checks the first bake's colour survives the second at the positions where it was laid down and nowhere else. * Carry the paint across with transfer_facet_paint The branch used TriangleSelector::remap_painting(), which pairs a source paint triangle with a target triangle overlapping it to within TriangleCursor::tolerance. That is right after a remesh, which leaves the surface where it was, and wrong after a bake, which has moved it by the relief depth - most of the paint finds no partner. transfer_facet_paint() asks the other question: every triangle of the new mesh takes the state painted at its foot point on the old one, which is exactly where it came from however deep the relief. It also does all four paint channels rather than colour alone, and it runs where the volume is read, so the snapshot and the timestamp that guarded against the user painting while the bake ran are no longer needed: the annotation is read at commit time and is by definition current. The function and its call site are byte for byte what the model-UV branch carries, so the two merge in any order and in either order produce one mechanism rather than two fighting over mmu_segmentation_facets. The regression test is unchanged except for the call it makes. --------- Co-authored-by: SoftFever <103989404+SoftFever@users.noreply.github.com>
This commit is contained in:
1 parent
af29e4d553
commit
39c77325ce
4 files changed
+292
-3
No files matched your search
@@ -8,6 +8,7 @@
|
||||
#include "libslic3r/TextureDisplacement.hpp"
|
||||
#include <exception>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include "slic3r/GUI/Jobs/Worker.hpp"
|
||||
#include <memory>
|
||||
|
||||
@@ -26,6 +27,7 @@
|
||||
#include "slic3r/Utils/UndoRedo.hpp"
|
||||
#include "libslic3r/ObjectID.hpp"
|
||||
#include "libslic3r/TriangleMesh.hpp"
|
||||
#include <array>
|
||||
|
||||
namespace Slic3r::GUI {
|
||||
|
||||
@@ -126,16 +128,71 @@ void TextureDisplacementBakeJob::finalize(bool canceled, std::exception_ptr &ept
|
||||
{ mix.a_percent(), 100 - mix.a_percent() });
|
||||
});
|
||||
|
||||
// Whether the bake replaced the triangles or only moved the vertices it was given. The classic
|
||||
// path is topology-preserving by contract, so every per-triangle annotation on the volume still
|
||||
// means what it did; the default pipeline refines, cuts and simplifies, so none of them do.
|
||||
// Both are checked: the flag is what the bake promised, the count is what it delivered. Only
|
||||
// the triangles matter - the annotations are per facet and reference no vertex index - so a
|
||||
// vertex count that changed on its own (the bake compacts unreferenced vertices away) is not a
|
||||
// reason to remap anything.
|
||||
const bool topology_changed = m_input.options.pipeline_v2 ||
|
||||
m_result.its.indices.size() != m_input.base_mesh.indices.size();
|
||||
|
||||
// Carry the volume's existing paint - the model's own colours above all - across that rebuild.
|
||||
// Without it a bake left the mmu_segmentation_facets bitstream pointing at a triangle set that
|
||||
// no longer existed, which is what scrambled the colours of an imported coloured model even
|
||||
// with colour switched off on every layer and nothing in the bake that was meant to touch them.
|
||||
//
|
||||
// transfer_facet_paint() rather than ModelVolume::restore_painting(): the latter's remap wants
|
||||
// a target triangle overlapping the source paint to within 0.01 mm, which the baked surface is
|
||||
// nowhere near - it has moved by the relief depth. Nothing to do when the topology is intact:
|
||||
// triangle i is still triangle i, so the paint is already exactly right and carrying it at all
|
||||
// could only lose detail.
|
||||
std::array<TriangleSelector::TriangleSplittingData, 4> before;
|
||||
if (topology_changed) {
|
||||
before[0] = volume->supported_facets.get_data();
|
||||
before[1] = volume->seam_facets.get_data();
|
||||
before[2] = volume->mmu_segmentation_facets.get_data();
|
||||
before[3] = volume->fuzzy_skin_facets.get_data();
|
||||
}
|
||||
|
||||
volume->set_mesh(std::move(m_result));
|
||||
volume->set_new_unique_id();
|
||||
volume->calculate_convex_hull();
|
||||
|
||||
if (topology_changed) {
|
||||
const std::array<FacetsAnnotation *, 4> target{ &volume->supported_facets, &volume->seam_facets,
|
||||
&volume->mmu_segmentation_facets,
|
||||
&volume->fuzzy_skin_facets };
|
||||
for (size_t ch = 0; ch < target.size(); ++ch) {
|
||||
const std::vector<uint8_t> state =
|
||||
transfer_facet_paint(m_input.base_mesh, before[ch], volume->mesh().its);
|
||||
// Nothing was painted in this channel, or there is nothing to paint it onto: either
|
||||
// way the annotation must not keep describing the mesh that is gone.
|
||||
if (state.empty()) {
|
||||
target[ch]->reset();
|
||||
continue;
|
||||
}
|
||||
TriangleSelector selector(volume->mesh());
|
||||
bool any = false;
|
||||
for (size_t i = 0; i < state.size(); ++i)
|
||||
if (state[i] != 0) {
|
||||
selector.set_facet(int(i), EnforcerBlockerType(state[i]));
|
||||
any = true;
|
||||
}
|
||||
if (any)
|
||||
target[ch]->set(selector);
|
||||
else
|
||||
target[ch]->reset();
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
// colour for everything outside it. What was already painted is whatever the transfer above
|
||||
// left in the annotation, so this bake's colour lands on top of it rather than instead of it.
|
||||
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();
|
||||
@@ -159,7 +216,6 @@ void TextureDisplacementBakeJob::finalize(bool canceled, std::exception_ptr &ept
|
||||
// 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; });
|
||||
|
||||
Reference in new issue
Block a user