ifcviewer: visibility (hide/isolate/show-all) + X-ray — desktop parity on web

Lift the visibility + X-ray ops into ViewportCore so desktop and web share them.
The cull already reads visibility_ (hidden objects skipped) and the frame uniform
reads xray_alpha_cap_, both per frame, so each op just mutates state + schedules
a frame — no GPU buffers to rebuild, no rendering work:
- hideSelected (hide the selection, then deselect), isolateSelected (hide every
  non-selected object in a visible model), showAll (clear the hidden set),
  toggleXray (flip the alpha cap 1.0<->0.3; cull routes all to the transparent
  pass), xrayActive().
- Desktop ViewportWindow: the H / Shift+H / Alt+H / Alt+X keys and the menu-action
  wrappers now call the core; the four inline/duplicated implementations are gone.
- Web main_web: same keys (H hide · Shift+H isolate · Alt+H show all · Alt+X
  x-ray) + toolbar buttons (Hide / Isolate / Show all / X-ray, the last reflecting
  active state).

Verified on web: x-ray toggles and visibly translucent-izes the scene, hide after
a pick removes geometry, 0 GPU errors. 113/113 desktop + 6/6 web smoke; desktop
app builds.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Dion Moult
2026-07-02 15:47:03 +10:00
parent bdfa70468e
commit 68d6280c93
6 changed files with 97 additions and 76 deletions
+43
View File
@@ -4996,6 +4996,49 @@ void ViewportCore::applyPickToSelection(std::uint32_t object_id, bool add, bool
else selection_.replace(object_id);
}
void ViewportCore::hideSelected() {
if (selection_.count() == 0) return;
for (uint32_t id : selection_.selectionIds()) visibility_.hide(id);
const size_t n = selection_.count();
selection_.clear(); // hiding deselects, matching the desktop/GL behaviour
Log::info().noquote().nospace() << "[wgpu] hid " << n << " selected";
host_->requestFrame();
}
void ViewportCore::isolateSelected() {
if (selection_.count() == 0) return;
// Hide every object in a VISIBLE model that isn't selected. Model-hidden
// objects stay model-hidden (element-level hiding on top is redundant), and
// object_id 0 (unpickable) is skipped.
const auto& sel_ids = selection_.selectionIds();
for (const auto& [mid, m] : models_gpu_) {
if (m.hidden) continue;
for (const InstanceCpu& inst : m.instances) {
if (inst.object_id == 0) continue;
if (sel_ids.find(inst.object_id) == sel_ids.end())
visibility_.hide(inst.object_id);
}
}
Log::info().noquote().nospace() << "[wgpu] isolated " << selection_.count();
host_->requestFrame();
}
void ViewportCore::showAll() {
if (visibility_.hiddenCount() == 0) return;
visibility_.clear();
Log::info() << "[wgpu] show all";
host_->requestFrame();
}
void ViewportCore::toggleXray() {
constexpr float kXrayOnCap = 0.3f;
xray_alpha_cap_ = (xray_alpha_cap_ < 1.0f) ? 1.0f : kXrayOnCap;
Log::info().noquote().nospace()
<< "[wgpu] x-ray " << (xray_alpha_cap_ < 1.0f ? "ON" : "OFF")
<< " (cap=" << xray_alpha_cap_ << ")";
host_->requestFrame();
}
#if defined(__EMSCRIPTEN__)
void ViewportCore::pickObjectAtAsync(int x_pixels, int y_pixels,
std::function<void(std::uint32_t)> cb) {