Swap interface into IfcViewerFull

Replace the old IfcViewerFull application tree with the interface-based viewer while preserving the IfcViewerFull target and build workflow.

Generated with the assistance of an AI coding tool.
This commit is contained in:
Dion Moult
2026-05-15 07:18:26 +10:00
parent 03b8b9c1bd
commit 4a91e6a87d
117 changed files with 672 additions and 3201 deletions
@@ -0,0 +1,139 @@
// This file was generated with the assistance of an AI coding tool.
/********************************************************************************
* *
* This file is part of IfcOpenShell. *
* *
* IfcOpenShell is free software: you can redistribute it and/or modify *
* it under the terms of the Lesser GNU General Public License as published by *
* the Free Software Foundation, either version 3.0 of the License, or *
* (at your option) any later version. *
* *
* IfcOpenShell is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* Lesser GNU General Public License for more details. *
* *
* You should have received a copy of the Lesser GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
********************************************************************************/
#include "AddModelDialog.h"
#include "../../components/Dialog.h"
#include "../../components/Buttons.h"
#include "../../components/Section.h"
#include "../../components/Style.h"
#include <QEvent>
#include <QHBoxLayout>
#include <QLabel>
#include <QToolButton>
#include <QVBoxLayout>
namespace ifcinterface::modules::models {
namespace {
class HoverDescriptionFilter : public QObject {
public:
HoverDescriptionFilter(QLabel* label, QString hover_text, QString default_text)
: label_(label), hover_text_(std::move(hover_text)), default_text_(std::move(default_text)) {}
protected:
bool eventFilter(QObject* watched, QEvent* event) override {
Q_UNUSED(watched);
if (event->type() == QEvent::Enter) {
label_->setText(hover_text_);
} else if (event->type() == QEvent::Leave) {
label_->setText(default_text_);
}
return false;
}
private:
QLabel* label_ = nullptr;
QString hover_text_;
QString default_text_;
};
} // namespace
AddModelDialog::AddModelDialog(QWidget* parent)
: components::Dialog(parent)
{
setObjectName("appDialog");
setWindowTitle("Add Model");
setModal(true);
setupUi();
}
void AddModelDialog::setupUi() {
if (auto* root = qobject_cast<QVBoxLayout*>(layout())) {
root->setSizeConstraint(QLayout::SetFixedSize);
}
const QString default_description = "Choose what to add to the project";
auto* description_section = new components::Section("", components::SectionHeaderMode::Hidden, this);
auto* description = new QLabel(default_description, description_section);
description->setProperty("textRole", "secondary");
description->setWordWrap(true);
description->setAlignment(Qt::AlignCenter);
description->setMinimumWidth((90 * 4) + (components::style::metrics::padding * 3));
description->setMinimumHeight(description->fontMetrics().lineSpacing() * 2 + 4);
description_section->addBodyWidget(description);
auto* choices_section = new components::Section("", components::SectionHeaderMode::Hidden, this);
auto* choices = new QWidget(choices_section);
auto* row = new QHBoxLayout(choices);
row->setContentsMargins(0, 0, 0, 0);
row->setSpacing(components::style::metrics::padding);
auto* add_ifc = components::buttons::makeButton("Add IFC File", ":/icons/cube.svg", choices);
connect(add_ifc, &QToolButton::clicked, this, [this]() {
selected_mode_ = SourceMode::IfcFile;
accept();
});
add_ifc->installEventFilter(new HoverDescriptionFilter(
description,
"Add IFC files and load both geometry and data.",
default_description));
auto* add_database = components::buttons::makeButton("Add IFC\nDatabase", ":/icons/database.svg", choices);
connect(add_database, &QToolButton::clicked, this, [this]() {
selected_mode_ = SourceMode::IfcDatabase;
accept();
});
add_database->installEventFilter(new HoverDescriptionFilter(
description,
"Add IFC RDB databases for optimised performance",
default_description));
auto* add_geometry = components::buttons::makeButton("Add Geometry", ":/icons/cube-bandage.svg", choices);
connect(add_geometry, &QToolButton::clicked, this, [this]() {
selected_mode_ = SourceMode::GeometryOnly;
accept();
});
add_geometry->installEventFilter(new HoverDescriptionFilter(
description,
"Add pure geometry for fast visualisation",
default_description));
auto* convert_database = components::buttons::makeButton("Convert IFC File\nto Database", ":/icons/database-restore.svg", choices);
connect(convert_database, &QToolButton::clicked, this, [description]() {
description->setText("IFC-to-database conversion is coming soon.");
});
convert_database->installEventFilter(new HoverDescriptionFilter(
description,
"Convert IFC files to databases for smaller filesizes, reduced memory, and faster access. No data is lost.",
default_description));
row->addWidget(components::buttons::makeButtonGroup("ADD", {add_ifc, add_database, add_geometry}, choices, true, 8));
row->addWidget(components::buttons::makeButtonGroup("TOOLS", {convert_database}, choices, false, 8));
choices_section->addBodyWidget(choices);
addBodyWidget(description_section);
addBodyWidget(choices_section);
}
} // namespace ifcinterface::modules::models
@@ -0,0 +1,50 @@
// This file was generated with the assistance of an AI coding tool.
/********************************************************************************
* *
* This file is part of IfcOpenShell. *
* *
* IfcOpenShell is free software: you can redistribute it and/or modify *
* it under the terms of the Lesser GNU General Public License as published by *
* the Free Software Foundation, either version 3.0 of the License, or *
* (at your option) any later version. *
* *
* IfcOpenShell is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* Lesser GNU General Public License for more details. *
* *
* You should have received a copy of the Lesser GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
********************************************************************************/
#ifndef IFCINTERFACE_PANELS_ADDMODELDIALOG_H
#define IFCINTERFACE_PANELS_ADDMODELDIALOG_H
#include "../../components/Dialog.h"
namespace ifcinterface::modules::models {
enum class SourceMode {
None,
IfcFile,
IfcDatabase,
GeometryOnly,
};
class AddModelDialog : public components::Dialog {
Q_OBJECT
public:
explicit AddModelDialog(QWidget* parent = nullptr);
SourceMode selectedMode() const { return selected_mode_; }
private:
void setupUi();
SourceMode selected_mode_ = SourceMode::None;
};
} // namespace ifcinterface::modules::models
#endif
@@ -0,0 +1,318 @@
// This file was generated with the assistance of an AI coding tool.
/********************************************************************************
* *
* This file is part of IfcOpenShell. *
* *
* IfcOpenShell is free software: you can redistribute it and/or modify *
* it under the terms of the Lesser GNU General Public License as published by *
* the Free Software Foundation, either version 3.0 of the License, or *
* (at your option) any later version. *
* *
* IfcOpenShell is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* Lesser GNU General Public License for more details. *
* *
* You should have received a copy of the Lesser GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
********************************************************************************/
#include "Controller.h"
#include "SettingsDialog.h"
#include "Panel.h"
#include "../../ElementRegistry.h"
#include "../../SessionState.h"
#include "AddModelDialog.h"
#include "../../../ifcviewer/Federation.h"
#include "../../../ifcviewer/LodBuilder.h"
#include "../../../ifcviewer/SceneLoader.h"
#include "../../../ifcviewer/SidecarCache.h"
#include "../../../ifcviewer/ViewportWindow.h"
#include <QElapsedTimer>
#include <QFileDialog>
#include <QDebug>
#include <QListView>
#include <QMessageBox>
#include <QTreeView>
#include <Eigen/Dense>
namespace ifcinterface::modules::models {
ModelsPanelController::ModelsPanelController(QWidget* host,
ModelsPanel* widget,
ifcinterface::SessionState* session_state,
ViewportWindow* viewport,
QObject* parent)
: QObject(parent)
, host_(host)
, widget_(widget)
, session_state_(session_state)
, viewport_(viewport)
{
connect(widget_, &ModelsPanel::visibilityToggleRequested, this,
[this](ItemKind kind, const QString& id) {
Federation* federation = session_state_->federation();
if (kind == ItemKind::Group) {
if (const Federation::Group* group = federation->findGroupById(id)) {
federation->setGroupVisible(id, !group->visible);
session_state_->notifyVisibilityChanged();
session_state_->setStatusMessage("Models", group->visible ? "Group hidden" : "Group shown");
}
} else {
if (const Federation::Model* model = federation->findById(id)) {
federation->setModelVisible(id, !model->visible);
session_state_->notifyVisibilityChanged();
session_state_->setStatusMessage("Models", model->visible ? "Model hidden" : "Model shown");
}
}
});
connect(widget_, &ModelsPanel::addGroupRequested, this,
[this](const QString& parent_group_id, const QString& name) {
session_state_->federation()->addGroup(name, parent_group_id);
session_state_->notifyFederationStructureChanged();
session_state_->setStatusMessage("Models", "Group added");
});
connect(widget_, &ModelsPanel::renameGroupRequested, this,
[this](const QString& id, const QString& name) {
session_state_->federation()->setGroupName(id, name);
session_state_->notifyFederationStructureChanged();
session_state_->setStatusMessage("Models", "Group renamed");
});
connect(widget_, &ModelsPanel::moveGroupRequested, this,
[this](const QString& id, const QString& parent_group_id) {
session_state_->federation()->setGroupParent(id, parent_group_id);
session_state_->notifyFederationStructureChanged();
session_state_->setStatusMessage("Models", parent_group_id.isEmpty() ? "Group moved to root"
: "Group moved");
});
connect(widget_, &ModelsPanel::moveModelsRequested, this,
[this](const QStringList& ids, const QString& parent_group_id) {
for (const auto& id : ids) {
session_state_->federation()->setModelGroup(id, parent_group_id);
}
session_state_->notifyFederationStructureChanged();
session_state_->setStatusMessage("Models", parent_group_id.isEmpty() ? "Model(s) moved to root"
: "Model(s) moved");
});
connect(widget_, &ModelsPanel::removeGroupRequested, this,
[this](const QString& id) {
session_state_->federation()->removeGroup(id);
session_state_->notifyFederationStructureChanged();
session_state_->setStatusMessage("Models", "Group removed");
});
connect(widget_, &ModelsPanel::removeModelRequested, this,
[this](const QString& id) {
removeLoadedModel(id);
session_state_->setStatusMessage("Models", "Model removed");
});
connect(widget_, &components::Panel::settingsRequested, this, [this]() {
openSettings();
});
}
void ModelsPanelController::bindLoader(SceneLoader* loader) {
connect(loader, &SceneLoader::loadStarted, this,
[this](uint32_t /*mid*/, const QString& display_name) {
session_state_->setStatusMessage("Loading", display_name);
});
connect(loader, &SceneLoader::loadedFromSidecar, this,
[this, loader](uint32_t mid, qint64 elapsed_ms) {
session_state_->setStatusMessage(
"Loaded",
QString("%1 from cache in %2")
.arg(loader->displayName(mid))
.arg(formatElapsed(elapsed_ms)));
});
connect(loader, &SceneLoader::loadedFromStream, this,
[this, loader](uint32_t mid, qint64 elapsed_ms) {
writeSidecarForModel(loader, mid);
session_state_->setStatusMessage(
"Loaded",
QString("%1 streamed in %2")
.arg(loader->displayName(mid))
.arg(formatElapsed(elapsed_ms)));
});
connect(loader, &SceneLoader::loadCancelled, this,
[this, loader](uint32_t mid) {
session_state_->setStatusMessage("Cancelled", loader->displayName(mid));
});
connect(loader, &SceneLoader::loadError, this,
[this, host = host_](uint32_t /*mid*/, const QString& message) {
session_state_->setStatusMessage("Error", message);
QMessageBox::warning(host, "IfcViewer", message);
});
connect(loader, &SceneLoader::allLoadsFinished, this,
[this, loader]() {
session_state_->setStatusMessage("Loaded", QString("%1 model(s)").arg(loader->modelCount()));
});
}
void ModelsPanelController::addFiles() {
modules::models::AddModelDialog dialog(host_);
if (dialog.exec() != QDialog::Accepted) return;
QStringList paths;
switch (dialog.selectedMode()) {
case modules::models::SourceMode::IfcFile: {
QFileDialog file_dialog(host_, "Add IFC Files");
file_dialog.setFileMode(QFileDialog::ExistingFiles);
file_dialog.setNameFilter("IFC Files (*.ifc);;All Files (*)");
file_dialog.setOption(QFileDialog::DontUseNativeDialog, true);
if (file_dialog.exec() == QDialog::Accepted) {
paths = file_dialog.selectedFiles();
}
break;
}
case modules::models::SourceMode::IfcDatabase: {
QFileDialog database_dialog(host_, "Add IFC Databases");
database_dialog.setFileMode(QFileDialog::Directory);
database_dialog.setOption(QFileDialog::ShowDirsOnly, true);
database_dialog.setOption(QFileDialog::DontResolveSymlinks, true);
database_dialog.setOption(QFileDialog::DontUseNativeDialog, true);
if (auto* list = database_dialog.findChild<QListView*>("listView")) {
list->setSelectionMode(QAbstractItemView::ExtendedSelection);
}
if (auto* tree = database_dialog.findChild<QTreeView*>()) {
tree->setSelectionMode(QAbstractItemView::ExtendedSelection);
}
if (database_dialog.exec() == QDialog::Accepted) {
paths = database_dialog.selectedFiles();
}
break;
}
case modules::models::SourceMode::GeometryOnly: {
QFileDialog file_dialog(host_, "Add Geometry Only");
file_dialog.setFileMode(QFileDialog::ExistingFiles);
file_dialog.setNameFilter("IFC Viewer Cache (*.ifcview);;All Files (*)");
file_dialog.setOption(QFileDialog::DontUseNativeDialog, true);
if (file_dialog.exec() == QDialog::Accepted) {
paths = file_dialog.selectedFiles();
}
break;
}
case modules::models::SourceMode::None:
return;
}
addFiles(paths);
}
void ModelsPanelController::addFiles(const QStringList& paths) {
QStringList accepted_paths;
QStringList accepted_fed_ids;
for (const auto& path : paths) {
const QString fed_id = session_state_->federation()->addModel(path);
if (fed_id.isEmpty()) continue;
accepted_paths << path;
accepted_fed_ids << fed_id;
}
loadModels(accepted_paths, accepted_fed_ids);
}
void ModelsPanelController::loadModels(const QStringList& paths, const QStringList& fed_ids) {
if (paths.isEmpty()) return;
const auto ids = session_state_->loader()->addFiles(paths);
for (int i = 0; i < paths.size() && i < static_cast<int>(ids.size()) && i < fed_ids.size(); ++i) {
session_state_->setModelMapping(fed_ids[i], ids[i]);
}
session_state_->notifyModelsChanged();
}
void ModelsPanelController::removeLoadedModel(const QString& fed_id) {
const uint32_t mid = session_state_->modelIdForFedId(fed_id);
if (mid == 0) {
session_state_->federation()->removeModel(fed_id);
return;
}
if (session_state_->loader()->isLoadingModel(mid)) return;
viewport_->setSelectedObjectId(0);
session_state_->setSelectedObjectId(0);
session_state_->federation()->removeModel(fed_id);
viewport_->removeModel(mid);
session_state_->loader()->removeModel(mid);
session_state_->elementRegistry()->removeModel(mid);
session_state_->removeModelMappingByFedId(fed_id);
session_state_->notifySelectionChanged();
session_state_->notifyModelsChanged();
}
void ModelsPanelController::openSettings() {
SettingsDialog dialog(session_state_, host_);
dialog.exec();
}
QString ModelsPanelController::formatElapsed(qint64 ms) const {
return (ms >= 1000)
? QString::number(ms / 1000.0, 'f', 2) + " s"
: QString::number(ms) + " ms";
}
void ModelsPanelController::writeSidecarForModel(SceneLoader* loader, uint32_t mid) const {
if (!loader || !viewport_ || !session_state_) return;
SidecarData sidecar_data;
if (!viewport_->snapshotModel(mid, sidecar_data)) return;
if (const ModelGeoref* georef = loader->modelGeoref(mid)) {
sidecar_data.has_coordinate_operation = georef->has_coordinate_operation ? 1 : 0;
Eigen::Map<Eigen::Matrix<double, 4, 4, Eigen::ColMajor>>(
sidecar_data.coordinate_operation_meters) = georef->coordinate_operation_meters;
sidecar_data.project_length_to_meters = georef->units.project_length_to_meters;
sidecar_data.map_unit_to_meters = georef->units.map_unit_to_meters;
}
auto* element_registry = session_state_->elementRegistry();
if (element_registry) {
for (const auto& info : element_registry->basicElementInfoForModel(mid)) {
PackedElementInfo packed;
packed.object_id = info.object_id;
packed.model_id = info.model_id;
packed.ifc_id = info.ifc_id;
packed.parent_id = info.parent_id;
const std::string guid = info.guid.toStdString();
packed.guid_offset = static_cast<uint32_t>(sidecar_data.string_table.size());
packed.guid_length = static_cast<uint32_t>(guid.size());
sidecar_data.string_table += guid;
const std::string name = info.name.toStdString();
packed.name_offset = static_cast<uint32_t>(sidecar_data.string_table.size());
packed.name_length = static_cast<uint32_t>(name.size());
sidecar_data.string_table += name;
const std::string type = info.type.toStdString();
packed.type_offset = static_cast<uint32_t>(sidecar_data.string_table.size());
packed.type_length = static_cast<uint32_t>(type.size());
sidecar_data.string_table += type;
sidecar_data.elements.push_back(packed);
}
}
QElapsedTimer lod_timer;
lod_timer.start();
buildLods(sidecar_data);
const LodStats lod_stats = summariseLods(sidecar_data);
qDebug(" LOD build: %lld ms — %u/%u meshes got LOD1 "
"(%u tris -> %u tris for those meshes)",
lod_timer.elapsed(),
lod_stats.meshes_with_lod1, lod_stats.meshes_total,
lod_stats.tris_lod0_for_lod1, lod_stats.tris_lod1);
viewport_->applyLodExtension(mid, sidecar_data);
QElapsedTimer sidecar_timer;
sidecar_timer.start();
const bool ok = writeSidecar(loader->filePath(mid).toStdString(), sidecar_data);
qDebug(" Sidecar write: %lld ms (%s)", sidecar_timer.elapsed(), ok ? "ok" : "FAILED");
}
} // namespace ifcinterface::modules::models
@@ -0,0 +1,67 @@
// This file was generated with the assistance of an AI coding tool.
/********************************************************************************
* *
* This file is part of IfcOpenShell. *
* *
* IfcOpenShell is free software: you can redistribute it and/or modify *
* it under the terms of the Lesser GNU General Public License as published by *
* the Free Software Foundation, either version 3.0 of the License, or *
* (at your option) any later version. *
* *
* IfcOpenShell is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* Lesser GNU General Public License for more details. *
* *
* You should have received a copy of the Lesser GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
********************************************************************************/
#ifndef IFCINTERFACE_PANELS_MODELSPANELCONTROLLER_H
#define IFCINTERFACE_PANELS_MODELSPANELCONTROLLER_H
#include "Types.h"
#include <QObject>
#include <QStringList>
class QWidget;
namespace ifcinterface { class SessionState; }
class ViewportWindow;
class SceneLoader;
namespace ifcinterface::modules::models {
class ModelsPanel;
class ModelsPanelController : public QObject {
Q_OBJECT
public:
explicit ModelsPanelController(QWidget* host,
ModelsPanel* widget,
ifcinterface::SessionState* session_state,
ViewportWindow* viewport,
QObject* parent = nullptr);
void bindLoader(SceneLoader* loader);
void addFiles();
void addFiles(const QStringList& paths);
void loadModels(const QStringList& paths, const QStringList& fed_ids);
void removeLoadedModel(const QString& fed_id);
void openSettings();
private:
QString formatElapsed(qint64 ms) const;
void writeSidecarForModel(SceneLoader* loader, uint32_t mid) const;
QWidget* host_ = nullptr;
ModelsPanel* widget_ = nullptr;
ifcinterface::SessionState* session_state_ = nullptr;
ViewportWindow* viewport_ = nullptr;
};
} // namespace ifcinterface::modules::models
#endif
+447
View File
@@ -0,0 +1,447 @@
// This file was generated with the assistance of an AI coding tool.
/********************************************************************************
* *
* This file is part of IfcOpenShell. *
* *
* IfcOpenShell is free software: you can redistribute it and/or modify *
* it under the terms of the Lesser GNU General Public License as published by *
* the Free Software Foundation, either version 3.0 of the License, or *
* (at your option) any later version. *
* *
* IfcOpenShell is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* Lesser GNU General Public License for more details. *
* *
* You should have received a copy of the Lesser GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
********************************************************************************/
#include "Panel.h"
#include "../../InterfaceSettings.h"
#include "../../components/Section.h"
#include "../../components/SvgIcon.h"
#include <QBrush>
#include <QColor>
#include <QDataStream>
#include <QDragEnterEvent>
#include <QDragMoveEvent>
#include <QHeaderView>
#include <QInputDialog>
#include <QLineEdit>
#include <QMenu>
#include <QMimeData>
#include <QDropEvent>
#include <QSizePolicy>
#include <QTreeWidget>
#include <QTreeWidgetItem>
#include <functional>
namespace ifcinterface::modules::models {
namespace {
constexpr auto kDragMimeType = "application/x-ifcinterface-model-items";
class ModelsTreeWidget : public QTreeWidget {
public:
explicit ModelsTreeWidget(QWidget* parent = nullptr) : QTreeWidget(parent) {}
std::function<void(const QStringList&, const QString&)> on_model_drop;
std::function<void(const QString&, const QString&)> on_group_drop;
protected:
QStringList mimeTypes() const override {
return {QString::fromUtf8(kDragMimeType)};
}
QMimeData* mimeData(const QList<QTreeWidgetItem*>& items) const override {
Q_UNUSED(items);
const QList<QTreeWidgetItem*> selected = selectedItems();
if (selected.isEmpty()) return nullptr;
const int first_kind = selected.first()->data(0, Qt::UserRole).toInt();
if (first_kind == static_cast<int>(ItemKind::Group) && selected.size() != 1) {
return nullptr;
}
QByteArray payload;
QDataStream stream(&payload, QIODevice::WriteOnly);
stream << first_kind;
if (first_kind == static_cast<int>(ItemKind::Group)) {
stream << selected.first()->data(0, Qt::UserRole + 1).toString();
} else {
QStringList ids;
for (QTreeWidgetItem* item : selected) {
if (item->data(0, Qt::UserRole).toInt() != first_kind) return nullptr;
ids.push_back(item->data(0, Qt::UserRole + 1).toString());
}
ids.removeDuplicates();
stream << ids;
}
auto* mime = new QMimeData();
mime->setData(QString::fromUtf8(kDragMimeType), payload);
return mime;
}
Qt::DropActions supportedDropActions() const override {
return Qt::MoveAction;
}
void dragEnterEvent(QDragEnterEvent* event) override {
if (event->mimeData()->hasFormat(QString::fromUtf8(kDragMimeType))) {
event->acceptProposedAction();
return;
}
QTreeWidget::dragEnterEvent(event);
}
void dragMoveEvent(QDragMoveEvent* event) override {
if (!event->mimeData()->hasFormat(QString::fromUtf8(kDragMimeType))) {
QTreeWidget::dragMoveEvent(event);
return;
}
QString group_id;
if (!decodeDropTarget(event->position().toPoint(), group_id)) {
event->ignore();
return;
}
if (canAcceptDrop(event->mimeData(), itemAt(event->position().toPoint()), group_id)) {
event->acceptProposedAction();
} else {
event->ignore();
}
}
void dropEvent(QDropEvent* event) override {
if (!event->mimeData()->hasFormat(QString::fromUtf8(kDragMimeType))) {
QTreeWidget::dropEvent(event);
return;
}
QString target_group_id;
if (!decodeDropTarget(event->position().toPoint(), target_group_id)) {
event->ignore();
return;
}
QTreeWidgetItem* target_item = itemAt(event->position().toPoint());
if (!canAcceptDrop(event->mimeData(), target_item, target_group_id)) {
event->ignore();
return;
}
QByteArray payload = event->mimeData()->data(QString::fromUtf8(kDragMimeType));
QDataStream stream(&payload, QIODevice::ReadOnly);
int kind = 0;
stream >> kind;
if (kind == static_cast<int>(ItemKind::Group)) {
QString group_id;
stream >> group_id;
if (on_group_drop) on_group_drop(group_id, target_group_id);
} else if (kind == static_cast<int>(ItemKind::Model)) {
QStringList ids;
stream >> ids;
ids.removeDuplicates();
if (!ids.isEmpty() && on_model_drop) on_model_drop(ids, target_group_id);
} else {
event->ignore();
return;
}
event->acceptProposedAction();
}
private:
bool decodeDropTarget(const QPoint& pos, QString& target_group_id) const {
target_group_id.clear();
QTreeWidgetItem* target_item = itemAt(pos);
if (!target_item) return true;
const int kind = target_item->data(0, Qt::UserRole).toInt();
if (kind != static_cast<int>(ItemKind::Group)) return false;
target_group_id = target_item->data(0, Qt::UserRole + 1).toString();
return true;
}
bool canAcceptDrop(const QMimeData* mime,
QTreeWidgetItem* target_item,
const QString& target_group_id) const {
QByteArray payload = mime->data(QString::fromUtf8(kDragMimeType));
QDataStream stream(&payload, QIODevice::ReadOnly);
int kind = 0;
stream >> kind;
if (kind == static_cast<int>(ItemKind::Group)) {
QString group_id;
stream >> group_id;
if (group_id.isEmpty()) return false;
if (!target_item) return true;
if (target_item->data(0, Qt::UserRole).toInt() != static_cast<int>(ItemKind::Group)) return false;
if (group_id == target_group_id) return false;
for (QTreeWidgetItem* cur = target_item; cur != nullptr; cur = cur->parent()) {
if (cur->data(0, Qt::UserRole + 1).toString() == group_id) return false;
}
return true;
}
if (kind == static_cast<int>(ItemKind::Model)) {
QStringList ids;
stream >> ids;
ids.removeDuplicates();
if (ids.isEmpty()) return false;
return target_item == nullptr || !target_group_id.isNull();
}
return false;
}
};
QString promptGroupName(QWidget* parent, const QString& title, const QString& label, const QString& value) {
bool ok = false;
const QString name = QInputDialog::getText(parent, title, label, QLineEdit::Normal, value, &ok);
return ok ? name.trimmed() : QString();
}
QString itemId(QTreeWidgetItem* item) {
return item ? item->data(0, Qt::UserRole + 1).toString() : QString();
}
ItemKind itemKind(QTreeWidgetItem* item) {
return static_cast<ItemKind>(item->data(0, Qt::UserRole).toInt());
}
QList<QTreeWidgetItem*> selectedItemsOfKind(QTreeWidget* tree, ItemKind kind) {
QList<QTreeWidgetItem*> matches;
for (QTreeWidgetItem* item : tree->selectedItems()) {
if (itemKind(item) == kind) matches.push_back(item);
}
return matches;
}
} // namespace
ModelsPanel::ModelsPanel(QWidget* parent)
: components::Panel("Models", nullptr, parent, true)
{
auto* section = new components::Section("", components::SectionHeaderMode::Hidden, this);
section->setBodyExpanding(true);
auto* tree = new ModelsTreeWidget(section);
tree_ = tree;
tree_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
tree_->setColumnCount(2);
tree_->setHeaderLabels({"Model", ""});
tree_->setIconSize(QSize(16, 16));
tree_->setSelectionMode(QAbstractItemView::ExtendedSelection);
tree_->setContextMenuPolicy(Qt::CustomContextMenu);
tree_->setDragEnabled(true);
tree_->viewport()->setAcceptDrops(true);
tree_->setDropIndicatorShown(true);
tree_->setDragDropMode(QAbstractItemView::DragDrop);
tree_->setDefaultDropAction(Qt::MoveAction);
tree_->setUniformRowHeights(true);
tree_->header()->setStretchLastSection(false);
tree_->header()->setSectionResizeMode(0, QHeaderView::Stretch);
tree_->header()->setSectionResizeMode(1, QHeaderView::Fixed);
tree_->header()->resizeSection(1, 28);
tree_->header()->hide();
tree->on_model_drop = [this](const QStringList& ids, const QString& target_group_id) {
emit moveModelsRequested(ids, target_group_id);
};
tree->on_group_drop = [this](const QString& id, const QString& target_group_id) {
emit moveGroupRequested(id, target_group_id);
};
section->addBodyWidget(tree_);
addBodyWidget(section);
connect(tree_, &QTreeWidget::itemClicked, this, [this](QTreeWidgetItem* item, int column) {
if (!item || column != 1) return;
emit visibilityToggleRequested(
static_cast<ItemKind>(item->data(0, Qt::UserRole).toInt()),
item->data(0, Qt::UserRole + 1).toString());
});
connect(tree_, &QTreeWidget::customContextMenuRequested, this, [this](const QPoint& pos) {
auto* item = tree_->itemAt(pos);
QMenu menu(tree_);
if (!item) {
QAction* add_group_action = menu.addAction(
components::icons::makeSvgIcon(":/icons/folder-plus.svg"), "Add Group");
connect(add_group_action, &QAction::triggered, this, [this]() {
const QString name = promptGroupName(this, "New Group", "Group name:", "Group");
if (!name.isEmpty()) {
emit addGroupRequested(QString(), name);
}
});
menu.exec(tree_->viewport()->mapToGlobal(pos));
return;
}
const auto kind = itemKind(item);
const QString id = itemId(item);
QAction* toggle_visibility_action = menu.addAction(
components::icons::makeSvgIcon(":/icons/eye.svg"), "Toggle Visibility");
connect(toggle_visibility_action, &QAction::triggered, this, [this, kind, id]() {
emit visibilityToggleRequested(kind, id);
});
if (kind == ItemKind::Group) {
QAction* add_group_action = menu.addAction(
components::icons::makeSvgIcon(":/icons/folder-plus.svg"), "New Subgroup");
connect(add_group_action, &QAction::triggered, this, [this, id]() {
const QString name = promptGroupName(this, "New Group", "Group name:", "Group");
if (!name.isEmpty()) {
emit addGroupRequested(id, name);
}
});
QAction* rename_group_action = menu.addAction(
components::icons::makeSvgIcon(":/icons/folder.svg"), "Rename Group");
connect(rename_group_action, &QAction::triggered, this, [this, item, id]() {
const QString name = promptGroupName(
this, "Rename Group", "Group name:", item->text(0));
if (!name.isEmpty()) emit renameGroupRequested(id, name);
});
QMenu* move_menu = menu.addMenu("Move to Parent");
QAction* move_root_action = move_menu->addAction("(Root)");
connect(move_root_action, &QAction::triggered, this, [this, id]() {
emit moveGroupRequested(id, QString());
});
move_menu->addSeparator();
QList<QTreeWidgetItem*> stack;
for (int i = 0; i < tree_->topLevelItemCount(); ++i) {
stack.push_back(tree_->topLevelItem(i));
}
while (!stack.isEmpty()) {
QTreeWidgetItem* candidate = stack.takeFirst();
if (candidate != item && itemKind(candidate) == ItemKind::Group) {
bool would_cycle = false;
for (QTreeWidgetItem* cur = candidate; cur != nullptr; cur = cur->parent()) {
if (cur == item) {
would_cycle = true;
break;
}
}
auto* action = move_menu->addAction(candidate->text(0));
action->setEnabled(!would_cycle && candidate != item->parent());
connect(action, &QAction::triggered, this, [this, id, candidate]() {
emit moveGroupRequested(id, itemId(candidate));
});
}
for (int i = 0; i < candidate->childCount(); ++i) {
stack.push_back(candidate->child(i));
}
}
QAction* remove_group_action = menu.addAction(
components::icons::makeSvgIcon(":/icons/folder-minus.svg"), "Remove Group");
connect(remove_group_action, &QAction::triggered, this, [this, id]() {
emit removeGroupRequested(id);
});
} else {
QString parent_group_id;
if (auto* parent_item = item->parent()) {
if (itemKind(parent_item) == ItemKind::Group) {
parent_group_id = itemId(parent_item);
}
}
QAction* add_group_action = menu.addAction(
components::icons::makeSvgIcon(":/icons/folder-plus.svg"), "New Group");
connect(add_group_action, &QAction::triggered, this, [this, parent_group_id]() {
const QString name = promptGroupName(this, "New Group", "Group name:", "Group");
if (!name.isEmpty()) {
emit addGroupRequested(parent_group_id, name);
}
});
QStringList selected_model_ids;
const QList<QTreeWidgetItem*> selected_models = selectedItemsOfKind(tree_, ItemKind::Model);
if (selected_models.contains(item)) {
for (QTreeWidgetItem* selected : selected_models) {
selected_model_ids.push_back(itemId(selected));
}
selected_model_ids.removeDuplicates();
} else {
selected_model_ids = {id};
}
QMenu* move_menu = menu.addMenu("Move to Group");
QAction* move_root_action = move_menu->addAction("(Root)");
connect(move_root_action, &QAction::triggered, this, [this, selected_model_ids]() {
emit moveModelsRequested(selected_model_ids, QString());
});
move_menu->addSeparator();
QList<QTreeWidgetItem*> stack;
for (int i = 0; i < tree_->topLevelItemCount(); ++i) {
stack.push_back(tree_->topLevelItem(i));
}
while (!stack.isEmpty()) {
QTreeWidgetItem* candidate = stack.takeFirst();
if (itemKind(candidate) == ItemKind::Group) {
const QString group_id = itemId(candidate);
QAction* action = move_menu->addAction(candidate->text(0));
connect(action, &QAction::triggered, this, [this, selected_model_ids, group_id]() {
emit moveModelsRequested(selected_model_ids, group_id);
});
}
for (int i = 0; i < candidate->childCount(); ++i) {
stack.push_back(candidate->child(i));
}
}
QAction* remove_model_action = menu.addAction(
components::icons::makeSvgIcon(":/icons/minus-square.svg"), "Remove Model");
connect(remove_model_action, &QAction::triggered, this, [this, id]() {
emit removeModelRequested(id);
});
}
menu.exec(tree_->viewport()->mapToGlobal(pos));
});
}
void ModelsPanel::setNodes(const QList<TreeNode>& nodes) {
tree_->clear();
const bool has_hierarchy = std::any_of(nodes.begin(), nodes.end(), [](const TreeNode& node) {
return !node.children.isEmpty() || node.kind == ItemKind::Group;
});
tree_->setRootIsDecorated(has_hierarchy);
for (const auto& node : nodes) {
addNode(tree_->invisibleRootItem(), node);
}
tree_->expandAll();
}
void ModelsPanel::addNode(QTreeWidgetItem* parent, const TreeNode& node) {
auto* item = new QTreeWidgetItem(parent, {node.name, ""});
item->setData(0, Qt::UserRole, static_cast<int>(node.kind));
item->setData(0, Qt::UserRole + 1, node.id);
item->setSizeHint(0, QSize(0, 24));
if (node.kind == ItemKind::Group) {
item->setIcon(0, components::icons::makeSvgIcon(":/icons/folder.svg"));
item->setIcon(1, components::icons::makeSvgIcon(node.visible ? ":/icons/eye.svg" : ":/icons/eye-closed.svg"));
} else {
item->setIcon(0, components::icons::makeSvgIcon(":/icons/cube.svg"));
item->setIcon(1, components::icons::makeSvgIcon(node.visible ? ":/icons/eye-solid.svg" : ":/icons/eye-closed.svg"));
}
if (!node.visible) {
const QBrush disabled_brush(
QColor(ifcinterface::InterfaceSettings::instance().color("disabled_text")));
item->setForeground(0, disabled_brush);
item->setForeground(1, disabled_brush);
}
for (const auto& child : node.children) {
addNode(item, child);
}
}
} // namespace ifcinterface::modules::models
+57
View File
@@ -0,0 +1,57 @@
// This file was generated with the assistance of an AI coding tool.
/********************************************************************************
* *
* This file is part of IfcOpenShell. *
* *
* IfcOpenShell is free software: you can redistribute it and/or modify *
* it under the terms of the Lesser GNU General Public License as published by *
* the Free Software Foundation, either version 3.0 of the License, or *
* (at your option) any later version. *
* *
* IfcOpenShell is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* Lesser GNU General Public License for more details. *
* *
* You should have received a copy of the Lesser GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
********************************************************************************/
#ifndef IFCINTERFACE_MODULES_MODELS_PANEL_H
#define IFCINTERFACE_MODULES_MODELS_PANEL_H
#include "Types.h"
#include "../../components/Panel.h"
class QTreeWidget;
class QTreeWidgetItem;
namespace ifcinterface::modules::models {
class ModelsPanel : public components::Panel {
Q_OBJECT
public:
explicit ModelsPanel(QWidget* parent = nullptr);
void setNodes(const QList<TreeNode>& nodes);
signals:
void visibilityToggleRequested(ItemKind kind, const QString& id);
void addGroupRequested(const QString& parent_group_id, const QString& name);
void renameGroupRequested(const QString& id, const QString& name);
void moveGroupRequested(const QString& id, const QString& parent_group_id);
void moveModelsRequested(const QStringList& ids, const QString& parent_group_id);
void removeGroupRequested(const QString& id);
void removeModelRequested(const QString& id);
private:
void addNode(QTreeWidgetItem* parent, const TreeNode& node);
QTreeWidget* tree_ = nullptr;
};
} // namespace ifcinterface::modules::models
#endif
@@ -0,0 +1,482 @@
// This file was generated with the assistance of an AI coding tool.
/********************************************************************************
* *
* This file is part of IfcOpenShell. *
* *
* IfcOpenShell is free software: you can redistribute it and/or modify *
* it under the terms of the Lesser GNU General Public License as published by *
* the Free Software Foundation, either version 3.0 of the License, or *
* (at your option) any later version. *
* *
* IfcOpenShell is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* Lesser GNU General Public License for more details. *
* *
* You should have received a copy of the Lesser GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
********************************************************************************/
#include "SettingsDialog.h"
#include "SettingsView.h"
#include "../../SessionState.h"
#include "../../../ifcviewer/Federation.h"
#include "../../../ifcviewer/SceneLoader.h"
#include "../../components/Section.h"
#include "../../components/Style.h"
#include "../../components/SvgIcon.h"
#include "../../components/Tabs.h"
#include <QComboBox>
#include <QDialogButtonBox>
#include <QFormLayout>
#include <QHeaderView>
#include <QHBoxLayout>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QRegularExpression>
#include <QSizePolicy>
#include <QShowEvent>
#include <QSignalBlocker>
#include <QScrollBar>
#include <QTableWidget>
#include <QTableWidgetItem>
#include <QVBoxLayout>
namespace ifcinterface::modules::models {
namespace {
struct UnitChoice {
const char* label;
const char* prefix;
const char* name;
};
const UnitChoice kUnitChoices[] = {
{"Metres (m)", "", "METRE"},
{"Millimetres (mm)", "MILLI", "METRE"},
{"Centimetres (cm)", "CENTI", "METRE"},
{"Kilometres (km)", "KILO", "METRE"},
{"Feet (ft)", "", "foot"},
{"Inches (in)", "", "inch"},
{"Yards (yd)", "", "yard"},
{"Miles (mi)", "", "mile"},
};
QLineEdit* makeNumericField(QWidget* parent, const QString& placeholder = {}) {
auto* field = new QLineEdit(parent);
field->setPlaceholderText(placeholder);
field->setMaximumWidth(96);
return field;
}
QWidget* makeEqualThirdsRow(QWidget* parent, QLineEdit* a, QLineEdit* b, QLineEdit* c) {
auto* row = new QWidget(parent);
auto* layout = new QHBoxLayout(row);
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(components::style::metrics::padding);
for (QLineEdit* field : {a, b, c}) {
field->setMaximumWidth(QWIDGETSIZE_MAX);
field->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
layout->addWidget(field, 1);
}
return row;
}
QString formatNumber(double value) {
return QString::number(value, 'f', 6);
}
double parseNumber(QLineEdit* field) {
bool ok = false;
const double value = field->text().toDouble(&ok);
return ok ? value : 0.0;
}
QString formatVector3(const Eigen::Vector3d& value) {
return QString("%1, %2, %3").arg(formatNumber(value.x()), formatNumber(value.y()), formatNumber(value.z()));
}
Eigen::Vector3d parseVector3(const QString& text) {
const QStringList parts = text.split(QRegularExpression("[,\\s]+"), Qt::SkipEmptyParts);
if (parts.size() != 3) return Eigen::Vector3d::Zero();
bool ok_x = false;
bool ok_y = false;
bool ok_z = false;
const double x = parts[0].toDouble(&ok_x);
const double y = parts[1].toDouble(&ok_y);
const double z = parts[2].toDouble(&ok_z);
if (!ok_x || !ok_y || !ok_z) return Eigen::Vector3d::Zero();
return Eigen::Vector3d(x, y, z);
}
QLabel* makeReadOnlyValue(QWidget* parent) {
auto* label = new QLabel(parent);
label->setTextInteractionFlags(Qt::TextSelectableByMouse);
label->setWordWrap(true);
return label;
}
} // namespace
SettingsDialog::SettingsDialog(ifcinterface::SessionState* session_state, QWidget* parent)
: components::TabbedDialog(parent)
, session_state_(session_state)
, federation_(session_state ? session_state->federation() : nullptr)
, loader_(session_state ? session_state->loader() : nullptr)
, settings_view_(new SettingsView(this, session_state))
{
setObjectName("appDialog");
setWindowTitle("Model Settings");
setModal(true);
resize(980, 560);
setupUi();
}
void SettingsDialog::showEvent(QShowEvent* event) {
federation_ = session_state_ ? session_state_->federation() : federation_;
loader_ = session_state_ ? session_state_->loader() : loader_;
syncFromFederation();
populateModelTable();
QDialog::showEvent(event);
}
void SettingsDialog::setupUi() {
auto* federation_tab = new QWidget(this);
auto* federation_layout = new QVBoxLayout(federation_tab);
federation_layout->setContentsMargins(0, 0, 0, 0);
federation_layout->setSpacing(components::style::metrics::padding);
federation_layout->setAlignment(Qt::AlignTop);
auto* federation_unit_section =
new components::Section("Federation Unit", components::SectionHeaderMode::Visible, federation_tab);
auto* unit_hint = new QLabel(
"All measurements, federated false origins, and destination model transforms will be interpreted in this unit.",
federation_unit_section);
unit_hint->setProperty("textRole", "secondary");
unit_hint->setWordWrap(true);
auto* federation_unit_body = new QWidget(federation_unit_section);
auto* federation_unit_form = new QFormLayout(federation_unit_body);
federation_unit_form->setContentsMargins(0, 0, 0, 0);
federation_unit_form->setHorizontalSpacing(16);
federation_unit_form->setVerticalSpacing(10);
unit_combo_ = new QComboBox(federation_unit_body);
for (const auto& uc : kUnitChoices) {
QStringList data;
data << QString::fromUtf8(uc.prefix) << QString::fromUtf8(uc.name);
unit_combo_->addItem(uc.label, data);
}
federation_unit_form->addRow("Unit", unit_combo_);
federation_unit_section->addBodyWidget(unit_hint);
federation_unit_section->addBodyWidget(federation_unit_body);
auto* origin_section =
new components::Section("Federated False Origin", components::SectionHeaderMode::Visible, federation_tab);
auto* origin_hint = new QLabel(
"Nominate a false origin and project north to use when viewing the federation of models.",
origin_section);
origin_hint->setProperty("textRole", "secondary");
origin_hint->setWordWrap(true);
auto* origin_body = new QWidget(origin_section);
auto* origin_form = new QFormLayout(origin_body);
origin_form->setContentsMargins(0, 0, 0, 0);
origin_form->setHorizontalSpacing(16);
origin_form->setVerticalSpacing(10);
xyz_x_ = makeNumericField(origin_body, "X");
xyz_y_ = makeNumericField(origin_body, "Y");
xyz_z_ = makeNumericField(origin_body, "Z");
rz_deg_ = makeNumericField(origin_body, "deg");
origin_form->addRow("XYZ", makeEqualThirdsRow(origin_body, xyz_x_, xyz_y_, xyz_z_));
origin_form->addRow("Z rotation (°)", rz_deg_);
origin_section->addBodyWidget(origin_hint);
origin_section->addBodyWidget(origin_body);
federation_layout->addWidget(federation_unit_section);
federation_layout->addWidget(origin_section);
federation_layout->addStretch(1);
auto* model_tab = new QWidget(this);
auto* model_layout = new QVBoxLayout(model_tab);
model_layout->setContentsMargins(0, 0, 0, 0);
model_layout->setSpacing(components::style::metrics::padding);
model_layout->setAlignment(Qt::AlignTop);
auto* georef_section =
new components::Section("Selected Model Georeferencing", components::SectionHeaderMode::Visible, model_tab);
auto* georef_body = new QWidget(georef_section);
auto* georef_layout = new QHBoxLayout(georef_body);
georef_layout->setContentsMargins(0, 0, 0, 0);
georef_layout->setSpacing(16);
georef_layout->setAlignment(Qt::AlignTop);
georef_present_value_ = makeReadOnlyValue(georef_body);
georef_type_value_ = makeReadOnlyValue(georef_body);
georef_project_unit_value_ = makeReadOnlyValue(georef_body);
georef_map_unit_value_ = makeReadOnlyValue(georef_body);
georef_easting_value_ = makeReadOnlyValue(georef_body);
georef_northing_value_ = makeReadOnlyValue(georef_body);
georef_height_value_ = makeReadOnlyValue(georef_body);
georef_x_axis_abscissa_value_ = makeReadOnlyValue(georef_body);
georef_x_axis_ordinate_value_ = makeReadOnlyValue(georef_body);
georef_rotation_dd_value_ = makeReadOnlyValue(georef_body);
georef_rotation_dms_value_ = makeReadOnlyValue(georef_body);
georef_scale_value_ = makeReadOnlyValue(georef_body);
georef_factor_x_value_ = makeReadOnlyValue(georef_body);
georef_factor_y_value_ = makeReadOnlyValue(georef_body);
georef_factor_z_value_ = makeReadOnlyValue(georef_body);
auto* georef_col_1 = new QFormLayout();
georef_col_1->setContentsMargins(0, 0, 0, 0);
georef_col_1->setHorizontalSpacing(12);
georef_col_1->setVerticalSpacing(8);
georef_col_1->addRow("Georeferenced", georef_present_value_);
georef_col_1->addRow("Coordinate Operation", georef_type_value_);
georef_col_1->addRow("Project Unit", georef_project_unit_value_);
georef_col_1->addRow("Map Unit", georef_map_unit_value_);
georef_col_1->addRow("Easting", georef_easting_value_);
georef_col_1->addRow("Northing", georef_northing_value_);
georef_col_1->addRow("OrthogonalHeight", georef_height_value_);
auto* georef_col_2 = new QFormLayout();
georef_col_2->setContentsMargins(0, 0, 0, 0);
georef_col_2->setHorizontalSpacing(12);
georef_col_2->setVerticalSpacing(8);
georef_col_2->addRow("XAxisAbscissa", georef_x_axis_abscissa_value_);
georef_col_2->addRow("XAxisOrdinate", georef_x_axis_ordinate_value_);
georef_col_2->addRow("Rotation (DD)", georef_rotation_dd_value_);
georef_col_2->addRow("Rotation (DMS)", georef_rotation_dms_value_);
auto* georef_col_3 = new QFormLayout();
georef_col_3->setContentsMargins(0, 0, 0, 0);
georef_col_3->setHorizontalSpacing(12);
georef_col_3->setVerticalSpacing(8);
georef_col_3->addRow("Scale", georef_scale_value_);
georef_col_3->addRow("FactorX", georef_factor_x_value_);
georef_col_3->addRow("FactorY", georef_factor_y_value_);
georef_col_3->addRow("FactorZ", georef_factor_z_value_);
auto* georef_col_1_widget = new QWidget(georef_body);
georef_col_1_widget->setLayout(georef_col_1);
auto* georef_col_2_widget = new QWidget(georef_body);
georef_col_2_widget->setLayout(georef_col_2);
auto* georef_col_3_widget = new QWidget(georef_body);
georef_col_3_widget->setLayout(georef_col_3);
georef_layout->addWidget(georef_col_1_widget, 1);
georef_layout->addWidget(georef_col_2_widget, 1);
georef_layout->addWidget(georef_col_3_widget, 1);
georef_section->addBodyWidget(georef_body);
auto* table_section =
new components::Section("Model Transformations", components::SectionHeaderMode::Visible, model_tab);
auto* table_body = new QWidget(table_section);
auto* table_layout = new QVBoxLayout(table_body);
table_layout->setContentsMargins(0, 0, 0, 0);
table_layout->setSpacing(components::style::metrics::padding);
model_table_ = new QTableWidget(table_body);
model_table_->setObjectName("modelCoordinatesTable");
model_table_->setColumnCount(6);
model_table_->setHorizontalHeaderLabels(
{"Model", "From", "From Point", "To Point", "Rotate", "Pivot Point"});
model_table_->verticalHeader()->setVisible(false);
model_table_->horizontalHeader()->setStretchLastSection(false);
model_table_->horizontalHeader()->setSectionsMovable(false);
model_table_->horizontalHeader()->setSectionsClickable(true);
model_table_->horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive);
model_table_->horizontalHeader()->resizeSection(0, 180);
model_table_->horizontalHeader()->resizeSection(1, 150);
model_table_->horizontalHeader()->resizeSection(2, 180);
model_table_->horizontalHeader()->resizeSection(3, 180);
model_table_->horizontalHeader()->resizeSection(4, 180);
model_table_->horizontalHeader()->resizeSection(5, 180);
model_table_->setSelectionBehavior(QAbstractItemView::SelectRows);
model_table_->setSelectionMode(QAbstractItemView::SingleSelection);
model_table_->setShowGrid(true);
model_table_->setWordWrap(false);
model_table_->setEditTriggers(QAbstractItemView::DoubleClicked | QAbstractItemView::EditKeyPressed);
model_table_->setAlternatingRowColors(false);
model_table_->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
auto* table_hint = new QLabel(
"This section lets you override model coordinates by specifying an optional translation from a point to "
"another desired point, and an optional rotation.",
table_section);
table_hint->setProperty("textRole", "secondary");
table_hint->setWordWrap(true);
table_layout->addWidget(model_table_, 1);
table_section->addBodyWidget(table_hint);
table_section->addBodyWidget(table_body);
model_layout->addWidget(georef_section);
model_layout->addWidget(table_section);
addTab("Federation", federation_tab);
addTab("Model", model_tab);
connect(model_table_, &QTableWidget::currentCellChanged, this,
[this](int /*current_row*/, int /*current_column*/, int /*previous_row*/, int /*previous_column*/) {
updateSelectedModelGeoref();
});
auto* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
if (auto* ok = buttons->button(QDialogButtonBox::Ok)) {
ok->setText("OK");
ok->setIcon(components::icons::makeSvgIcon(":/icons/check.svg"));
}
if (auto* cancel = buttons->button(QDialogButtonBox::Cancel)) {
cancel->setText("Cancel");
cancel->setIcon(components::icons::makeSvgIcon(":/icons/xmark-circle.svg"));
}
connect(buttons, &QDialogButtonBox::accepted, this, [this]() { onAccepted(); });
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
addFooterWidget(buttons);
}
void SettingsDialog::syncFromFederation() {
if (!federation_) return;
const auto& cfg = federation_->config();
int idx = -1;
for (int i = 0; i < unit_combo_->count(); ++i) {
const QStringList data = unit_combo_->itemData(i).toStringList();
if (data.size() == 2 &&
data[0].toStdString() == cfg.unit_prefix &&
data[1].toStdString() == cfg.unit_name) {
idx = i;
break;
}
}
unit_combo_->setCurrentIndex(idx >= 0 ? idx : 0);
const auto& origin = federation_->federatedFalseOrigin();
xyz_x_->setText(formatNumber(origin.xyz.x()));
xyz_y_->setText(formatNumber(origin.xyz.y()));
xyz_z_->setText(formatNumber(origin.xyz.z()));
rz_deg_->setText(formatNumber(origin.rz_deg));
}
void SettingsDialog::populateModelTable() {
model_rows_.clear();
const QSignalBlocker blocker(model_table_);
model_table_->clearContents();
model_table_->setRowCount(0);
if (!federation_) return;
int row = 0;
for (const auto& model : federation_->models()) {
const auto& xf = model.model_transformation;
model_table_->insertRow(row);
auto* model_item = new QTableWidgetItem(model.display_name.isEmpty() ? model.id : model.display_name);
model_item->setData(Qt::UserRole, model.id);
model_table_->setItem(row, 0, model_item);
ModelRowWidgets widgets;
widgets.fed_id = model.id;
widgets.frame = new QComboBox(model_table_);
widgets.frame->addItem("Local", static_cast<int>(AFrame::ModelLocal));
widgets.frame->addItem("Global", static_cast<int>(AFrame::ModelGlobal));
widgets.frame->setCurrentIndex(xf.a_frame == AFrame::ModelGlobal ? 1 : 0);
model_table_->setCellWidget(row, 1, widgets.frame);
widgets.from_point = new QTableWidgetItem(formatVector3(xf.a));
widgets.to_point = new QTableWidgetItem(formatVector3(xf.b));
widgets.rotate = new QTableWidgetItem(formatVector3(xf.rxyz_deg));
widgets.pivot = new QTableWidgetItem(formatVector3(xf.pivot));
model_table_->setItem(row, 2, widgets.from_point);
model_table_->setItem(row, 3, widgets.to_point);
model_table_->setItem(row, 4, widgets.rotate);
model_table_->setItem(row, 5, widgets.pivot);
model_rows_.push_back(widgets);
model_table_->setRowHeight(row, 42);
++row;
}
int table_height = model_table_->frameWidth() * 2 + model_table_->horizontalHeader()->height();
for (int i = 0; i < model_table_->rowCount(); ++i) {
table_height += model_table_->rowHeight(i);
}
if (model_table_->horizontalScrollBar()->isVisible()) {
table_height += model_table_->horizontalScrollBar()->sizeHint().height();
}
model_table_->setMinimumHeight(table_height);
model_table_->setMaximumHeight(table_height);
if (model_table_->rowCount() > 0) {
model_table_->setCurrentCell(0, 0);
}
updateSelectedModelGeoref();
}
void SettingsDialog::renderSelectedModelGeoref(const SelectedModelGeorefState& state) {
georef_present_value_->setText(state.georef_present);
georef_type_value_->setText(state.coordinate_operation_type);
georef_project_unit_value_->setText(state.project_unit);
georef_map_unit_value_->setText(state.map_unit);
georef_easting_value_->setText(state.easting);
georef_northing_value_->setText(state.northing);
georef_height_value_->setText(state.height);
georef_x_axis_abscissa_value_->setText(state.x_axis_abscissa);
georef_x_axis_ordinate_value_->setText(state.x_axis_ordinate);
georef_rotation_dd_value_->setText(state.rotation_dd);
georef_rotation_dms_value_->setText(state.rotation_dms);
georef_scale_value_->setText(state.scale);
georef_factor_x_value_->setText(state.factor_x);
georef_factor_y_value_->setText(state.factor_y);
georef_factor_z_value_->setText(state.factor_z);
}
void SettingsDialog::updateSelectedModelGeoref() {
const int row = model_table_->currentRow();
if (row < 0 || row >= static_cast<int>(model_rows_.size())) {
renderSelectedModelGeoref(
{"No model selected", "", "", "", "", "", "", "", "", "", "", "", "", "", ""});
return;
}
if (!settings_view_) {
renderSelectedModelGeoref(
{"Unavailable", "No settings view", "", "", "", "", "", "", "", "", "", "", "", "", ""});
return;
}
settings_view_->refresh(model_rows_[row].fed_id);
}
void SettingsDialog::onAccepted() {
if (federation_) {
const QStringList data = unit_combo_->currentData().toStringList();
FederationConfig cfg;
if (data.size() == 2) {
cfg.unit_prefix = data[0].toStdString();
cfg.unit_name = data[1].toStdString();
}
federation_->setConfig(cfg);
FederatedFalseOrigin origin;
origin.xyz = Eigen::Vector3d(parseNumber(xyz_x_), parseNumber(xyz_y_), parseNumber(xyz_z_));
origin.rz_deg = parseNumber(rz_deg_);
federation_->setFederatedFalseOrigin(origin);
for (const auto& row : model_rows_) {
ModelTransformation xf;
xf.a_frame = static_cast<AFrame>(row.frame->currentData().toInt());
xf.a = parseVector3(row.from_point->text());
xf.b = parseVector3(row.to_point->text());
xf.rxyz_deg = parseVector3(row.rotate->text());
xf.pivot = parseVector3(row.pivot->text());
federation_->setModelTransformation(row.fed_id, xf);
}
}
accept();
}
} // namespace ifcinterface::modules::models
@@ -0,0 +1,105 @@
// This file was generated with the assistance of an AI coding tool.
/********************************************************************************
* *
* This file is part of IfcOpenShell. *
* *
* IfcOpenShell is free software: you can redistribute it and/or modify *
* it under the terms of the Lesser GNU General Public License as published by *
* the Free Software Foundation, either version 3.0 of the License, or *
* (at your option) any later version. *
* *
* IfcOpenShell is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* Lesser GNU General Public License for more details. *
* *
* You should have received a copy of the Lesser GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
********************************************************************************/
#ifndef IFCINTERFACE_MODULES_MODELS_SETTINGSDIALOG_H
#define IFCINTERFACE_MODULES_MODELS_SETTINGSDIALOG_H
#include "../../components/Dialog.h"
#include "Types.h"
#include <QString>
#include <vector>
class Federation;
class SceneLoader;
class QComboBox;
class QLabel;
class QLineEdit;
class QShowEvent;
class QTableWidget;
class QTableWidgetItem;
namespace ifcinterface {
class SessionState;
}
namespace ifcinterface::modules::models {
class SettingsView;
class SettingsDialog : public components::TabbedDialog {
Q_OBJECT
public:
explicit SettingsDialog(ifcinterface::SessionState* session_state, QWidget* parent = nullptr);
void renderSelectedModelGeoref(const SelectedModelGeorefState& state);
protected:
void showEvent(QShowEvent* event) override;
private:
struct ModelRowWidgets {
QString fed_id;
QComboBox* frame = nullptr;
QTableWidgetItem* from_point = nullptr;
QTableWidgetItem* to_point = nullptr;
QTableWidgetItem* rotate = nullptr;
QTableWidgetItem* pivot = nullptr;
};
void setupUi();
void syncFromFederation();
void populateModelTable();
void updateSelectedModelGeoref();
void onAccepted();
ifcinterface::SessionState* session_state_ = nullptr;
Federation* federation_ = nullptr;
SceneLoader* loader_ = nullptr;
QComboBox* unit_combo_ = nullptr;
QLineEdit* xyz_x_ = nullptr;
QLineEdit* xyz_y_ = nullptr;
QLineEdit* xyz_z_ = nullptr;
QLineEdit* rz_deg_ = nullptr;
QLabel* georef_present_value_ = nullptr;
QLabel* georef_type_value_ = nullptr;
QLabel* georef_project_unit_value_ = nullptr;
QLabel* georef_map_unit_value_ = nullptr;
QLabel* georef_easting_value_ = nullptr;
QLabel* georef_northing_value_ = nullptr;
QLabel* georef_height_value_ = nullptr;
QLabel* georef_x_axis_abscissa_value_ = nullptr;
QLabel* georef_x_axis_ordinate_value_ = nullptr;
QLabel* georef_rotation_dd_value_ = nullptr;
QLabel* georef_rotation_dms_value_ = nullptr;
QLabel* georef_scale_value_ = nullptr;
QLabel* georef_factor_x_value_ = nullptr;
QLabel* georef_factor_y_value_ = nullptr;
QLabel* georef_factor_z_value_ = nullptr;
QTableWidget* model_table_ = nullptr;
std::vector<ModelRowWidgets> model_rows_;
SettingsView* settings_view_ = nullptr;
};
} // namespace ifcinterface::modules::models
#endif
@@ -0,0 +1,246 @@
// This file was generated with the assistance of an AI coding tool.
/********************************************************************************
* *
* This file is part of IfcOpenShell. *
* *
* IfcOpenShell is free software: you can redistribute it and/or modify *
* it under the terms of the Lesser GNU General Public License as published by *
* the Free Software Foundation, either version 3.0 of the License, or *
* (at your option) any later version. *
* *
* IfcOpenShell is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* Lesser GNU General Public License for more details. *
* *
* You should have received a copy of the Lesser GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
********************************************************************************/
#include "SettingsView.h"
#include "SettingsDialog.h"
#include "../../SessionState.h"
#include "../../../ifcviewer/Federation.h"
#include "../../../ifcviewer/Geolocation.h"
#include "../../../ifcviewer/SceneLoader.h"
#include "../../../ifcviewer/Unit.h"
#include <cmath>
namespace ifcinterface::modules::models {
namespace {
QString formatNumber(double value) {
return QString::number(value, 'f', 6);
}
QString formatAngleDms(double degrees) {
const double absolute = std::fabs(degrees);
const int d = static_cast<int>(absolute);
const double minutes_total = (absolute - static_cast<double>(d)) * 60.0;
const int m = static_cast<int>(minutes_total);
const double s = (minutes_total - static_cast<double>(m)) * 60.0;
const QString sign = degrees < 0.0 ? "-" : "";
return QString("%1%2° %3' %4\"").arg(sign).arg(d).arg(m, 2, 10, QChar('0')).arg(formatNumber(s));
}
SelectedModelGeorefState unknownState(const QString& georef, const QString& type) {
return {
georef,
type,
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
};
}
QString formatCachedUnitScale(double meters_per_unit) {
return QString("Cached scale: 1 unit = %1 m").arg(formatNumber(meters_per_unit));
}
std::string enumString(const attribute_value& av) {
if (av.isNull()) return {};
if (av.type() != ifcopenshell::Argument_ENUMERATION) return {};
enumeration_reference er = av;
return std::string(er.value() ? er.value() : "");
}
QString formatNamedUnit(const express::Base& unit) {
if (!unit) return "";
auto entity = unit.as<express::Entity>();
if (unit.declaration().is("IfcSIUnit")) {
const std::string prefix = enumString(entity.get("Prefix"));
const std::string name = enumString(entity.get("Name"));
QString text;
if (!prefix.empty()) {
text += QString::fromStdString(prefix) + " ";
}
text += QString::fromStdString(name);
auto symbol_it = kUnitSymbols.find(name);
if (symbol_it != kUnitSymbols.end()) {
text += " (" + QString::fromStdString(symbol_it->second) + ")";
}
return text;
}
auto name_attr = entity.get("Name");
if (name_attr.isNull()) return "";
const std::string name = static_cast<std::string>(name_attr);
QString text = QString::fromStdString(name);
auto symbol_it = kUnitSymbols.find(name);
if (symbol_it != kUnitSymbols.end()) {
text += " (" + QString::fromStdString(symbol_it->second) + ")";
}
return text;
}
std::optional<QString> coordinateOperationType(ifcopenshell::file* ifc_file) {
if (!ifc_file) return std::nullopt;
try {
const auto coordops = ifc_file->instances_by_type("IfcCoordinateOperation");
if (!coordops.empty()) {
return QString::fromStdString(coordops[0].declaration().name());
}
} catch (...) {
return std::nullopt;
}
if (ifc_file->schema()->name() == "IFC2X3") {
if (getHelmertTransformationParameters(ifc_file)) {
return QStringLiteral("ePSet_MapConversion");
}
}
return QStringLiteral("None");
}
SelectedModelGeorefState stateFromLiveFile(ifcopenshell::file* ifc_file) {
if (!ifc_file) return unknownState("Not available yet", "No data source");
const auto params = getHelmertTransformationParameters(ifc_file);
const auto coordop_type = coordinateOperationType(ifc_file);
const auto project_unit_value = getProjectUnit(ifc_file, "LENGTHUNIT");
const auto map_unit_value = getMapUnit(ifc_file);
const QString project_unit = project_unit_value ? formatNamedUnit(*project_unit_value) : QString("");
const QString map_unit = map_unit_value ? formatNamedUnit(*map_unit_value) : project_unit;
if (!params) {
auto state = unknownState("No", coordop_type.value_or("Unknown"));
state.project_unit = project_unit;
state.map_unit = map_unit;
return state;
}
const double rotation_dd = xaxis2angleDeg(params->xaa, params->xao);
return {
"Yes",
coordop_type.value_or("Unknown"),
project_unit,
map_unit,
formatNumber(params->e),
formatNumber(params->n),
formatNumber(params->h),
formatNumber(params->xaa),
formatNumber(params->xao),
formatNumber(rotation_dd),
formatAngleDms(rotation_dd),
formatNumber(params->scale),
formatNumber(params->factor_x),
formatNumber(params->factor_y),
formatNumber(params->factor_z),
};
}
SelectedModelGeorefState stateFromCachedGeoref(const ModelGeoref& georef) {
if (!georef.has_coordinate_operation) {
return unknownState("No", "None");
}
const Eigen::Matrix4d& m = georef.coordinate_operation_meters;
const Eigen::Vector3d translation = m.block<3, 1>(0, 3);
const Eigen::Vector3d x_axis = m.block<3, 1>(0, 0);
const Eigen::Vector3d y_axis = m.block<3, 1>(0, 1);
const double factor_x = x_axis.norm();
const double factor_y = y_axis.norm();
const double factor_z = m.block<3, 1>(0, 2).norm();
const double scale = (factor_x + factor_y) * 0.5;
const double x_axis_abscissa = factor_x > 0.0 ? x_axis.x() / factor_x : 1.0;
const double x_axis_ordinate = factor_x > 0.0 ? x_axis.y() / factor_x : 0.0;
constexpr double kRadiansToDegrees = 57.29577951308232;
const double rotation_dd = std::atan2(x_axis_ordinate, x_axis_abscissa) * kRadiansToDegrees;
return {
"Yes",
"Cached coordinate operation",
formatCachedUnitScale(georef.units.project_length_to_meters),
formatCachedUnitScale(georef.units.map_unit_to_meters),
formatNumber(translation.x()),
formatNumber(translation.y()),
formatNumber(translation.z()),
formatNumber(x_axis_abscissa),
formatNumber(x_axis_ordinate),
formatNumber(rotation_dd),
formatAngleDms(rotation_dd),
formatNumber(scale),
formatNumber(factor_x),
formatNumber(factor_y),
formatNumber(factor_z),
};
}
} // namespace
SettingsView::SettingsView(SettingsDialog* widget,
ifcinterface::SessionState* session_state)
: widget_(widget), session_state_(session_state)
{
}
void SettingsView::refresh(const QString& fed_id) const {
if (!widget_) {
return;
}
if (!session_state_) {
widget_->renderSelectedModelGeoref(unknownState("Unavailable", "No session state"));
return;
}
SceneLoader* loader = session_state_->loader();
if (!loader) {
widget_->renderSelectedModelGeoref(unknownState("Unavailable", "No loader"));
return;
}
const uint32_t mid = session_state_->modelIdForFedId(fed_id);
if (mid == 0) {
widget_->renderSelectedModelGeoref(unknownState("Not loaded", "No live model"));
return;
}
if (auto* ifc_file = loader->ifcFile(mid)) {
widget_->renderSelectedModelGeoref(stateFromLiveFile(ifc_file));
return;
}
const ModelGeoref* georef = loader->modelGeoref(mid);
if (!georef) {
widget_->renderSelectedModelGeoref(unknownState("Not available yet", "No data source"));
return;
}
widget_->renderSelectedModelGeoref(stateFromCachedGeoref(*georef));
}
} // namespace ifcinterface::modules::models
@@ -0,0 +1,48 @@
// This file was generated with the assistance of an AI coding tool.
/********************************************************************************
* *
* This file is part of IfcOpenShell. *
* *
* IfcOpenShell is free software: you can redistribute it and/or modify *
* it under the terms of the Lesser GNU General Public License as published by *
* the Free Software Foundation, either version 3.0 of the License, or *
* (at your option) any later version. *
* *
* IfcOpenShell is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* Lesser GNU General Public License for more details. *
* *
* You should have received a copy of the Lesser GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
********************************************************************************/
#ifndef IFCINTERFACE_MODULES_MODELS_SETTINGSVIEW_H
#define IFCINTERFACE_MODULES_MODELS_SETTINGSVIEW_H
#include "Types.h"
namespace ifcinterface {
class SessionState;
}
namespace ifcinterface::modules::models {
class SettingsDialog;
class SettingsView {
public:
explicit SettingsView(SettingsDialog* widget,
ifcinterface::SessionState* session_state);
void refresh(const QString& fed_id) const;
private:
SettingsDialog* widget_ = nullptr;
ifcinterface::SessionState* session_state_ = nullptr;
};
} // namespace ifcinterface::modules::models
#endif
+62
View File
@@ -0,0 +1,62 @@
// This file was generated with the assistance of an AI coding tool.
/********************************************************************************
* *
* This file is part of IfcOpenShell. *
* *
* IfcOpenShell is free software: you can redistribute it and/or modify *
* it under the terms of the Lesser GNU General Public License as published by *
* the Free Software Foundation, either version 3.0 of the License, or *
* (at your option) any later version. *
* *
* IfcOpenShell is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* Lesser GNU General Public License for more details. *
* *
* You should have received a copy of the Lesser GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
********************************************************************************/
#ifndef IFCINTERFACE_PANELS_MODELSPANELTYPES_H
#define IFCINTERFACE_PANELS_MODELSPANELTYPES_H
#include <QList>
#include <QString>
namespace ifcinterface::modules::models {
enum class ItemKind {
Group,
Model,
};
struct TreeNode {
QString id;
QString name;
ItemKind kind = ItemKind::Group;
bool visible = true;
QList<TreeNode> children;
};
struct SelectedModelGeorefState {
QString georef_present;
QString coordinate_operation_type;
QString project_unit;
QString map_unit;
QString easting;
QString northing;
QString height;
QString x_axis_abscissa;
QString x_axis_ordinate;
QString rotation_dd;
QString rotation_dms;
QString scale;
QString factor_x;
QString factor_y;
QString factor_z;
};
} // namespace ifcinterface::modules::models
#endif
+100
View File
@@ -0,0 +1,100 @@
// This file was generated with the assistance of an AI coding tool.
/********************************************************************************
* *
* This file is part of IfcOpenShell. *
* *
* IfcOpenShell is free software: you can redistribute it and/or modify *
* it under the terms of the Lesser GNU General Public License as published by *
* the Free Software Foundation, either version 3.0 of the License, or *
* (at your option) any later version. *
* *
* IfcOpenShell is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* Lesser GNU General Public License for more details. *
* *
* You should have received a copy of the Lesser GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
********************************************************************************/
#include "View.h"
#include "Panel.h"
#include "../../InterfaceSettings.h"
#include "../../SessionState.h"
#include "../../../ifcviewer/Federation.h"
namespace ifcinterface::modules::models {
namespace {
TreeNode makeGroupNode(const Federation* federation, const Federation::Group* group) {
TreeNode node;
node.id = group->id;
node.name = group->display_name;
node.kind = ItemKind::Group;
node.visible = group->visible;
for (const auto& child_group : group->children) {
node.children.append(makeGroupNode(federation, child_group.get()));
}
for (const auto& model : federation->models()) {
if (model.group_id != group->id) continue;
node.children.append({
model.id,
model.display_name,
ItemKind::Model,
federation->isModelEffectivelyVisible(model.id),
{}
});
}
return node;
}
} // namespace
ModelsPanelView::ModelsPanelView(ModelsPanel* widget,
ifcinterface::SessionState* session_state,
QObject* parent)
: QObject(parent), widget_(widget), session_state_(session_state)
{
connect(session_state_, &ifcinterface::SessionState::modelsChanged,
this, [this]() { reload(); });
connect(session_state_, &ifcinterface::SessionState::federationStructureChanged,
this, [this]() { reload(); });
connect(session_state_, &ifcinterface::SessionState::visibilityChanged,
this, [this]() { reload(); });
connect(session_state_, &ifcinterface::SessionState::projectReset,
this, [this]() { reload(); });
connect(session_state_, &ifcinterface::SessionState::projectOpened,
this, [this](const QString&) { reload(); });
connect(&ifcinterface::InterfaceSettings::instance(),
&ifcinterface::InterfaceSettings::themeChanged,
this, [this]() { reload(); });
reload();
}
void ModelsPanelView::reload() {
Federation* federation = session_state_->federation();
QList<TreeNode> nodes;
for (const auto& root_group : federation->rootGroups()) {
nodes.append(makeGroupNode(federation, root_group.get()));
}
for (const auto& model : federation->models()) {
if (!model.group_id.isEmpty()) continue;
nodes.append({
model.id,
model.display_name,
ItemKind::Model,
federation->isModelEffectivelyVisible(model.id),
{}
});
}
widget_->setNodes(nodes);
}
} // namespace ifcinterface::modules::models
+50
View File
@@ -0,0 +1,50 @@
// This file was generated with the assistance of an AI coding tool.
/********************************************************************************
* *
* This file is part of IfcOpenShell. *
* *
* IfcOpenShell is free software: you can redistribute it and/or modify *
* it under the terms of the Lesser GNU General Public License as published by *
* the Free Software Foundation, either version 3.0 of the License, or *
* (at your option) any later version. *
* *
* IfcOpenShell is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* Lesser GNU General Public License for more details. *
* *
* You should have received a copy of the Lesser GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
********************************************************************************/
#ifndef IFCINTERFACE_PANELS_MODELSPANELVIEW_H
#define IFCINTERFACE_PANELS_MODELSPANELVIEW_H
#include "Types.h"
#include <QObject>
namespace ifcinterface { class SessionState; }
namespace ifcinterface::modules::models {
class ModelsPanel;
class ModelsPanelView : public QObject {
Q_OBJECT
public:
explicit ModelsPanelView(ModelsPanel* widget,
ifcinterface::SessionState* session_state,
QObject* parent = nullptr);
private:
void reload();
ModelsPanel* widget_ = nullptr;
ifcinterface::SessionState* session_state_ = nullptr;
};
} // namespace ifcinterface::modules::models
#endif