mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-12 18:31:18 +00:00
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.
This commit is contained in:
@@ -13,6 +13,7 @@
|
||||
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/format.hpp"
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "libslic3r/TriangleSelector.hpp"
|
||||
|
||||
#include "slic3r/GUI/GLCanvas3D.hpp"
|
||||
@@ -76,6 +77,23 @@ void TextureDisplacementBakeJob::process(Ctl &ctl)
|
||||
},
|
||||
color, m_input.volume_to_world, nullptr, &m_stats));
|
||||
|
||||
// Bring the colour that was already painted onto the mesh this bake just built, while still on
|
||||
// the worker thread. Triangle ids only mean the same thing on both meshes when the bake preserved
|
||||
// the topology - the classic path moves vertices and nothing else - and the one-run pipeline
|
||||
// rebuilds the mesh from scratch. Deserializing the old mask against the new one only checks that
|
||||
// the ids are in range, and a bake almost always *grows* the triangle count, so every stale id
|
||||
// passes that check and lands on an unrelated face: colour debris where nothing was painted, and
|
||||
// the earlier pass's colour destroyed rather than kept. remap_painting() transfers it by position
|
||||
// instead, which is what lets successive bakes accumulate. It is spatial work over both meshes,
|
||||
// which is why it belongs here and not in finalize().
|
||||
if (!m_input.mmu_segmentation.bitstream.empty() && !m_result.empty() && !ctl.was_canceled()) {
|
||||
if (m_result.its.indices.size() == m_input.base_mesh.indices.size())
|
||||
m_carried_mmu = m_input.mmu_segmentation;
|
||||
else
|
||||
m_carried_mmu = TriangleSelector::remap_painting(m_input.base_mesh, m_input.mmu_segmentation,
|
||||
m_result.its, Transform3d::Identity(), {});
|
||||
}
|
||||
|
||||
// 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);
|
||||
@@ -126,6 +144,18 @@ void TextureDisplacementBakeJob::finalize(bool canceled, std::exception_ptr &ept
|
||||
{ mix.a_percent(), 100 - mix.a_percent() });
|
||||
});
|
||||
|
||||
// Read while the volume still holds the mesh the paint was recorded against, and after the
|
||||
// filament slots above - adding one can clamp the paint, and that clamp has to survive.
|
||||
// Normally process() has already carried the colour onto the baked topology; it only has to
|
||||
// be redone here when the paint changed after the job was queued, because the snapshot
|
||||
// process() worked from is then not what the user is looking at.
|
||||
const bool mmu_changed = volume->mmu_segmentation_facets.timestamp() != m_input.mmu_segmentation_timestamp;
|
||||
const indexed_triangle_set pre_bake_mesh = mmu_changed ? volume->mesh().its
|
||||
: indexed_triangle_set{};
|
||||
const TriangleSelector::TriangleSplittingData live_mmu = mmu_changed
|
||||
? volume->mmu_segmentation_facets.get_data()
|
||||
: TriangleSelector::TriangleSplittingData{};
|
||||
|
||||
volume->set_mesh(std::move(m_result));
|
||||
volume->set_new_unique_id();
|
||||
volume->calculate_convex_hull();
|
||||
@@ -134,13 +164,19 @@ void TextureDisplacementBakeJob::finalize(bool canceled, std::exception_ptr &ept
|
||||
// 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 comes from m_carried_mmu, which
|
||||
// process() brought onto this mesh's topology; this bake's own colour goes on top of it, so it
|
||||
// wins wherever it painted and leaves the rest alone.
|
||||
if (!m_triangle_color.empty() && m_triangle_color.size() == volume->mesh().its.indices.size()) {
|
||||
if (mmu_changed && !live_mmu.bitstream.empty())
|
||||
m_carried_mmu = pre_bake_mesh.indices.size() == volume->mesh().its.indices.size()
|
||||
? live_mmu
|
||||
: TriangleSelector::remap_painting(pre_bake_mesh, live_mmu,
|
||||
volume->mesh().its,
|
||||
Transform3d::Identity(), {});
|
||||
TriangleSelector selector(volume->mesh());
|
||||
const TriangleSelector::TriangleSplittingData &existing = volume->mmu_segmentation_facets.get_data();
|
||||
if (!existing.bitstream.empty())
|
||||
selector.deserialize(existing, false);
|
||||
if (!m_carried_mmu.bitstream.empty())
|
||||
selector.deserialize(m_carried_mmu, false);
|
||||
for (size_t i = 0; i < m_triangle_color.size(); ++i) {
|
||||
const size_t entry = size_t(m_triangle_color[i]);
|
||||
if (entry > 0 && entry <= filament_of.size() && filament_of[entry - 1] >= 0)
|
||||
@@ -222,6 +258,8 @@ void queue_texture_displacement_bake(const ModelVolume &volume, const TextureCol
|
||||
input.layers = volume.texture_displacement_layers;
|
||||
input.options = volume.texture_displacement_options;
|
||||
input.volume_to_world = texture_displacement_volume_to_world(volume);
|
||||
input.mmu_segmentation = volume.mmu_segmentation_facets.get_data();
|
||||
input.mmu_segmentation_timestamp = volume.mmu_segmentation_facets.timestamp();
|
||||
for (int i = 0; i < int(TEXTURE_DISPLACEMENT_MAX_LAYERS); ++i)
|
||||
input.facets_data[size_t(i)] = volume.texture_displacement_facet(i).get_data();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user