mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-29 16:22:53 +00:00
bonsaiviewer: View Selected Model from the models panel context menu
Right-clicking a model in the models panel now offers "View Selected Model",
which frames the camera on just that model's geometry — View All, scoped to
one model. With several models selected the action reads "View Selected
Models" and frames their union, matching how the panel's existing Move to
Group already treats a multi-selection.
The AABB fold behind viewAll moves into InstanceCompose, which exists so this
kind of logic is unit-testable without a Qt window or a wgpu device (populating
ViewportCore's model map needs a real GPU, so the fold was previously
untestable in place). It splits in two:
- sceneWorldAabb — every VISIBLE model, what viewAll frames.
- modelsWorldAabb — only the named models, hidden or not. A model the caller
named explicitly is framed even if hidden; second-guessing
that is worse than honouring it. Models with no loaded
geometry contribute nothing, and if none of them do the
camera is left alone rather than flying to the origin.
Both are covered by six new cases in test_instance_compose (131 total).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -262,6 +262,30 @@ void removeModel(SessionState& session, ViewportWindow& viewport, QWidget& host,
|
||||
session.setStatusMessage("Models", "Model removed");
|
||||
}
|
||||
|
||||
void viewModels(SessionState& session, ViewportWindow& viewport, const QStringList& model_ids) {
|
||||
// Federation ids are the panel's currency; the viewport speaks session
|
||||
// model ids. sessionModelIdForModelId returns 0 for a model the viewport
|
||||
// has never been given geometry for — skip those rather than framing id 0.
|
||||
std::vector<uint32_t> session_model_ids;
|
||||
session_model_ids.reserve(std::size_t(model_ids.size()));
|
||||
for (const QString& model_id : model_ids) {
|
||||
const uint32_t session_model_id = session.sessionModelIdForModelId(model_id);
|
||||
if (session_model_id != 0) session_model_ids.push_back(session_model_id);
|
||||
}
|
||||
|
||||
if (!viewport.viewModels(session_model_ids)) {
|
||||
session.setStatusMessage("Models", "Nothing to view — no loaded geometry");
|
||||
return;
|
||||
}
|
||||
|
||||
QString label = QString("%1 models").arg(model_ids.size());
|
||||
if (model_ids.size() == 1) {
|
||||
const Federation::Model* model = session.federation()->findById(model_ids.front());
|
||||
label = model ? model->display_name : model_ids.front();
|
||||
}
|
||||
session.setStatusMessage("Models", QString("Viewing %1").arg(label));
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
|
||||
void loadModels(SessionState& session, const QStringList& paths, const QStringList& model_ids) {
|
||||
|
||||
@@ -60,6 +60,11 @@ void moveGroup(SessionState& session, const QString& id, const QString& parent_g
|
||||
void moveModels(SessionState& session, const QStringList& ids, const QString& parent_group_id);
|
||||
void removeGroup(SessionState& session, QWidget& host, const QString& group_id);
|
||||
void removeModel(SessionState& session, ViewportWindow& viewport, QWidget& host, const QString& model_id);
|
||||
// "View Selected Model" — frame the camera on just these models' geometry, the
|
||||
// way View All frames the whole federation. Models that carry no loaded
|
||||
// geometry (never loaded, or still streaming their metadata) contribute
|
||||
// nothing; if none of them do, the camera is left where it is.
|
||||
void viewModels(SessionState& session, ViewportWindow& viewport, const QStringList& model_ids);
|
||||
void addModel(SessionState& session, QWidget& host);
|
||||
// Connector picker → pull_models_interactive → addCloudModel + load.
|
||||
// Reachable from AddModelDialog's CloudModel button; the underlying call
|
||||
|
||||
@@ -321,6 +321,19 @@ ModelsPanel::ModelsPanel(bonsaiviewer::SessionState* session_state,
|
||||
parent_group_id = idOf(parent_index);
|
||||
}
|
||||
|
||||
const QStringList selected_model_ids = selectedModelIdsAt(tree_, index);
|
||||
|
||||
// Frame the camera on just these models — View All, scoped. Right
|
||||
// above Rename so the two "do something with this model" actions
|
||||
// that need no dialog sit together at the top.
|
||||
QAction* view_models = menu.addAction(
|
||||
components::icons::makeSvgIcon(":/icons/cube-scan.svg"),
|
||||
selected_model_ids.size() > 1 ? "View Selected Models"
|
||||
: "View Selected Model");
|
||||
connect(view_models, &QAction::triggered, this, [this, selected_model_ids]() {
|
||||
commands::viewModels(*session_state_, *viewport_, selected_model_ids);
|
||||
});
|
||||
|
||||
QAction* rename = menu.addAction(
|
||||
components::icons::makeSvgIcon(":/icons/cube.svg"), "Rename");
|
||||
connect(rename, &QAction::triggered, this, [this, id]() {
|
||||
@@ -333,8 +346,6 @@ ModelsPanel::ModelsPanel(bonsaiviewer::SessionState* session_state,
|
||||
commands::addGroup(*session_state_, *this, parent_group_id);
|
||||
});
|
||||
|
||||
const QStringList selected_model_ids = selectedModelIdsAt(tree_, index);
|
||||
|
||||
QMenu* move_menu = menu.addMenu("Move to Group");
|
||||
QAction* move_root = move_menu->addAction("(Root)");
|
||||
connect(move_root, &QAction::triggered, this, [this, selected_model_ids]() {
|
||||
|
||||
Reference in New Issue
Block a user