mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-10 17:21:10 +00:00
refactor(imex): make render the sole author of ghost RGB; scope cache key to mesh inputs
Follow-up to the live ghost-color fix, addressing review findings: with the render loop restamping ghost colors every frame, the bake-time resolution in calc_imex_ghosts was dead code (its RGB was displayed for zero frames), and the pem + head-filament-map entries in the ghost cache key had become color-only inputs that forced a full mesh re-bake — including a visible hitch on every ghost-picker selection — for what is now a pure recolor. - calc_imex_ghosts bakes an alpha-only placeholder; IMEX_GHOST_ALPHA is hoisted to file scope as the single opacity authority (no more reading alpha back out of the field the restamp overwrites). - build_imex_ghost_cache_key drops pem and the head-filament map; the forced invalidations in set/reset_imex_head_filament_map go with them. Picker selections now recolor live with no rebuild. - The restamp moves into PartPlate::update_imex_ghost_colors(), beside the transform refresh, so PartPlate owns its volumes' colors and the canvas calls one hook. Plate-level inputs (pem, override map) are hoisted once per frame via a new get_imex_head_filament_color overload that the single-head form delegates to, keeping tooltip parity by construction. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
f41752546c
commit
28c0c681fe
@@ -7556,24 +7556,14 @@ void GLCanvas3D::_render_imex_ghosts()
|
||||
PartPlateList& ppl = wxGetApp().plater()->get_partplate_list();
|
||||
PartPlate* active_plate = ppl.get_curr_plate();
|
||||
if (active_plate) {
|
||||
// Refresh per-frame so ghost positions reflect the primary's live drag state.
|
||||
// Refresh per-frame so ghost positions reflect the primary's live drag state,
|
||||
// and restamp ghost RGB from the live filament palette so color-only changes
|
||||
// (palette edits, late-loading project colors) never leave stale ghosts.
|
||||
active_plate->update_imex_ghost_transforms(primary_live_xf);
|
||||
active_plate->update_imex_ghost_colors();
|
||||
const auto& ghosts = active_plate->get_imex_ghost_volumes();
|
||||
// Re-resolve ghost colors live instead of trusting the bake: the ghost cache key
|
||||
// covers mesh-shaping inputs (mode, pem, object set) but not filament_colour, so
|
||||
// a palette edit or a late-loading project color would leave stale — or
|
||||
// UNPRINTABLE-black — ghosts until an unrelated rebuild. This is the same
|
||||
// resolution the hover tooltip runs, so the two can never disagree.
|
||||
std::map<int, ColorRGBA> head_colors;
|
||||
for (const auto& g : ghosts) {
|
||||
if (!g || !g->is_active) continue;
|
||||
const int head = PartPlate::imex_ghost_head_from_composite_id(g->composite_id.object_id);
|
||||
auto hc = head_colors.find(head);
|
||||
if (hc == head_colors.end())
|
||||
hc = head_colors.emplace(head, active_plate->get_imex_head_filament_color(head)).first;
|
||||
ColorRGBA color = hc->second;
|
||||
color.a(g->color.a()); // keep the ghost alpha baked by calc_imex_ghosts
|
||||
g->color = color;
|
||||
const Transform3d model_matrix = g->world_matrix();
|
||||
shader->set_uniform("volume_world_matrix", model_matrix);
|
||||
shader->set_uniform("slope.volume_world_normal_matrix",
|
||||
|
||||
@@ -438,14 +438,14 @@ void PartPlate::set_imex_head_filament_map(const std::map<int,int>& m)
|
||||
m_config.set_key_value("imex_head_filament_map", new ConfigOptionString(os.str()));
|
||||
}
|
||||
update_slice_result_valid_state(false);
|
||||
m_imex_ghost_cache_key = "\x01"; // force ghost rebuild
|
||||
// No ghost rebuild: routing only affects color, which update_imex_ghost_colors
|
||||
// restamps live every frame.
|
||||
}
|
||||
|
||||
void PartPlate::reset_imex_head_filament_map()
|
||||
{
|
||||
m_config.erase("imex_head_filament_map");
|
||||
update_slice_result_valid_state(false);
|
||||
m_imex_ghost_cache_key = "\x01";
|
||||
}
|
||||
|
||||
ColorRGBA PartPlate::get_imex_head_filament_color(int physical_head) const
|
||||
@@ -454,8 +454,19 @@ ColorRGBA PartPlate::get_imex_head_filament_color(int physical_head) const
|
||||
if (!pb)
|
||||
return GLVolume::UNPRINTABLE_COLOR;
|
||||
|
||||
const ConfigOptionInts pem = effective_physical_extruder_map(*pb);
|
||||
const auto plate_map = get_imex_head_filament_map();
|
||||
return get_imex_head_filament_color(physical_head,
|
||||
effective_physical_extruder_map(*pb),
|
||||
get_imex_head_filament_map());
|
||||
}
|
||||
|
||||
ColorRGBA PartPlate::get_imex_head_filament_color(int physical_head,
|
||||
const ConfigOptionInts& pem,
|
||||
const std::map<int, int>& plate_map) const
|
||||
{
|
||||
auto* pb = wxGetApp().preset_bundle;
|
||||
if (!pb)
|
||||
return GLVolume::UNPRINTABLE_COLOR;
|
||||
|
||||
const int logical = resolve_filament_for_head(plate_map, pem, physical_head);
|
||||
if (logical < 0)
|
||||
return GLVolume::UNPRINTABLE_COLOR;
|
||||
@@ -1035,23 +1046,16 @@ void PartPlate::ensure_imex_zones()
|
||||
|
||||
std::string PartPlate::build_imex_ghost_cache_key() const
|
||||
{
|
||||
// Ghost shape depends on: mode topology (same inputs as zone key) + pem +
|
||||
// set of objects on plate + each primary instance's transform.
|
||||
// Ghost shape depends on: mode topology (same inputs as zone key) + set of
|
||||
// objects on plate + each primary instance's transform. Filament routing and
|
||||
// colors are deliberately NOT keyed: ghost RGB is restamped live every frame
|
||||
// by update_imex_ghost_colors, so color-only changes never need a mesh rebuild.
|
||||
std::string k = build_imex_cache_key();
|
||||
if (k.empty()) return ""; // ghost-off when zones-off
|
||||
|
||||
if (auto* pb = wxGetApp().preset_bundle) {
|
||||
// Keyed on the effective pem (project → printer → pei-derived) so a printer
|
||||
// swap that only changes printer_extruder_id still invalidates the ghost cache.
|
||||
const ConfigOptionInts pem = effective_physical_extruder_map(*pb);
|
||||
k += "|pem";
|
||||
for (int v : pem.values) { k += ':'; k += std::to_string(v); }
|
||||
}
|
||||
k += "|obj";
|
||||
for (const auto& oi : obj_to_instance_set)
|
||||
k += std::to_string(oi.first) + "." + std::to_string(oi.second) + ",";
|
||||
for (const auto& kv : get_imex_head_filament_map())
|
||||
k += "|m" + std::to_string(kv.first) + "=" + std::to_string(kv.second);
|
||||
return k;
|
||||
}
|
||||
|
||||
@@ -1095,6 +1099,10 @@ bool PartPlate::resolve_active_mode_tools(std::string& out_tools_str, int& out_p
|
||||
return true;
|
||||
}
|
||||
|
||||
// Ghost opacity. RGB is owned by update_imex_ghost_colors (restamped per frame);
|
||||
// calc_imex_ghosts only bakes this alpha into the volume.
|
||||
static constexpr float IMEX_GHOST_ALPHA = 0.55f;
|
||||
|
||||
void PartPlate::calc_imex_ghosts()
|
||||
{
|
||||
m_imex_ghost_volumes.clear();
|
||||
@@ -1184,7 +1192,6 @@ void PartPlate::calc_imex_ghosts()
|
||||
return {aggregated_primary, aggregated_target - aggregated_primary};
|
||||
};
|
||||
|
||||
constexpr float GHOST_ALPHA = 0.55f;
|
||||
|
||||
// Mesh is object-local and identical across all instances and heads of a given object.
|
||||
// Build the merged TriangleMesh once per obj_idx and reuse across the inner head loop.
|
||||
@@ -1231,8 +1238,9 @@ void PartPlate::calc_imex_ghosts()
|
||||
primary_phys, phys, role, gantry, pri_center, mirror_axis_for(phys));
|
||||
const Transform3d ghost_xf = head_xf * inst_world;
|
||||
|
||||
ColorRGBA color = get_imex_head_filament_color(phys);
|
||||
color.a(GHOST_ALPHA);
|
||||
// RGB here is a placeholder — update_imex_ghost_colors restamps every
|
||||
// ghost from the live filament palette each frame; only alpha matters.
|
||||
const ColorRGBA color = {0.5f, 0.5f, 0.5f, IMEX_GHOST_ALPHA};
|
||||
|
||||
auto ghost = std::make_unique<GLVolume>(color);
|
||||
ghost->set_instance_transformation(ghost_xf);
|
||||
@@ -1346,6 +1354,34 @@ void PartPlate::update_imex_ghost_transforms(
|
||||
}
|
||||
}
|
||||
|
||||
// Restamp every ghost's RGB from the current filament palette. Called per frame
|
||||
// from the render loop, so ghosts can never go stale against filament preset or
|
||||
// color changes — the ghost cache key deliberately ignores color-only inputs.
|
||||
// This is the same resolution the hover tooltip runs (resolve_filament_for_head
|
||||
// over the effective pem + per-plate override map), so the two cannot disagree.
|
||||
void PartPlate::update_imex_ghost_colors()
|
||||
{
|
||||
if (m_imex_ghost_volumes.empty() || !m_plater) return;
|
||||
auto* pb = wxGetApp().preset_bundle;
|
||||
if (!pb) return;
|
||||
|
||||
// Plate-level inputs hoisted once; per-head results memoized across instances.
|
||||
const ConfigOptionInts pem = effective_physical_extruder_map(*pb);
|
||||
const auto plate_map = get_imex_head_filament_map();
|
||||
|
||||
std::map<int, ColorRGBA> head_colors;
|
||||
for (auto& ghost : m_imex_ghost_volumes) {
|
||||
if (!ghost) continue;
|
||||
const int head = imex_ghost_head_from_composite_id(ghost->composite_id.object_id);
|
||||
auto hc = head_colors.find(head);
|
||||
if (hc == head_colors.end())
|
||||
hc = head_colors.emplace(head, get_imex_head_filament_color(head, pem, plate_map)).first;
|
||||
ColorRGBA color = hc->second;
|
||||
color.a(IMEX_GHOST_ALPHA);
|
||||
ghost->color = color;
|
||||
}
|
||||
}
|
||||
|
||||
// True if `hull` (scaled, plate-list coordinates) overlaps a secondary zone or a
|
||||
// carriage danger strip. Shared by the object and prime-tower checks so both use one
|
||||
// definition of "overlap" — see imex_hull_violates_zones() for those semantics.
|
||||
|
||||
@@ -343,6 +343,11 @@ public:
|
||||
// respecting any imex_head_filament_map override. Falls back to GLVolume::UNPRINTABLE_COLOR
|
||||
// when no filament routes to the head.
|
||||
ColorRGBA get_imex_head_filament_color(int physical_head) const;
|
||||
// Same resolution against caller-hoisted plate-level inputs (hot paths that
|
||||
// resolve several heads should hoist pem/plate_map once and use this form).
|
||||
ColorRGBA get_imex_head_filament_color(int physical_head,
|
||||
const ConfigOptionInts& pem,
|
||||
const std::map<int, int>& plate_map) const;
|
||||
|
||||
// Visual ghosts of every object on this plate, one per active secondary head.
|
||||
// Non-selectable, non-draggable. See IMEX ghost renderer spec.
|
||||
@@ -357,6 +362,10 @@ public:
|
||||
void update_imex_ghost_transforms(
|
||||
const std::function<std::optional<Transform3d>(int, int)>& primary_live_xf = {});
|
||||
|
||||
// Restamp ghost RGB from the live filament palette (per frame, from the render
|
||||
// loop). Color is deliberately not part of the ghost cache key — see the .cpp.
|
||||
void update_imex_ghost_colors();
|
||||
|
||||
// Invalidate the ghost cache so the next render_imex_zones() call rebuilds fully.
|
||||
void invalidate_imex_ghosts() { m_imex_ghost_cache_key = "\x01"; }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user