mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 09:21:46 +00:00
Add model coordinates settings
Add the federation/model settings dialog and related interface wiring for model coordinate configuration. Generated with the assistance of an AI coding tool.
This commit is contained in:
@@ -49,6 +49,8 @@ set(INTERFACE_FILES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/components/Panel.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/modules/models/AddModelDialog.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/modules/models/AddModelDialog.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/modules/models/SettingsDialog.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/modules/models/SettingsDialog.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/modules/models/Types.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/modules/models/Controller.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/modules/models/Controller.h
|
||||
|
||||
@@ -38,7 +38,10 @@ namespace {
|
||||
|
||||
class DockTitleBar : public QWidget {
|
||||
public:
|
||||
explicit DockTitleBar(const QString& title, bool has_settings = false, QWidget* parent = nullptr)
|
||||
explicit DockTitleBar(const QString& title,
|
||||
bool has_settings = false,
|
||||
std::function<void()> on_settings = {},
|
||||
QWidget* parent = nullptr)
|
||||
: QWidget(parent)
|
||||
{
|
||||
auto* layout = new QHBoxLayout(this);
|
||||
@@ -58,11 +61,8 @@ public:
|
||||
settings->setFixedSize(18, 18);
|
||||
settings->setObjectName("panelTitleButton");
|
||||
settings->setToolTip(QString("%1 settings").arg(title));
|
||||
connect(settings, &QToolButton::clicked, this, [this, title]() {
|
||||
auto* anchor = parentWidget();
|
||||
QMenu menu(anchor);
|
||||
menu.addAction(QString("%1 settings coming soon").arg(title));
|
||||
menu.exec(QCursor::pos());
|
||||
connect(settings, &QToolButton::clicked, this, [on_settings = std::move(on_settings)]() {
|
||||
if (on_settings) on_settings();
|
||||
});
|
||||
layout->addWidget(settings);
|
||||
}
|
||||
@@ -118,7 +118,9 @@ Panel::Panel(const QString& title, QWidget* content, QWidget* parent, bool has_s
|
||||
setFeatures(QDockWidget::DockWidgetMovable |
|
||||
QDockWidget::DockWidgetFloatable |
|
||||
QDockWidget::DockWidgetClosable);
|
||||
setTitleBarWidget(new DockTitleBar(title, has_settings, this));
|
||||
setTitleBarWidget(new DockTitleBar(title, has_settings, [this]() {
|
||||
emit settingsRequested();
|
||||
}, this));
|
||||
setWidget(outer);
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,9 @@ public:
|
||||
void addBodyWidget(QWidget* widget);
|
||||
void clearBodyWidgets();
|
||||
|
||||
signals:
|
||||
void settingsRequested();
|
||||
|
||||
private:
|
||||
QVBoxLayout* body_layout_ = nullptr;
|
||||
};
|
||||
|
||||
@@ -128,6 +128,7 @@ QString buildAppStyleSheet() {
|
||||
QSpinBox,
|
||||
QDoubleSpinBox,
|
||||
QCheckBox,
|
||||
QRadioButton,
|
||||
QPushButton,
|
||||
QToolButton {
|
||||
color: ${primary_text};
|
||||
@@ -203,11 +204,26 @@ QString buildAppStyleSheet() {
|
||||
background: ${control_background};
|
||||
border: 1px solid ${border};
|
||||
border-radius: ${panel_radius}px;
|
||||
padding: ${padding}px ${padding}px;
|
||||
padding: ${padding}px 24px ${padding}px ${padding}px;
|
||||
}
|
||||
QComboBox:focus {
|
||||
border: 1px solid ${control_border_focus};
|
||||
}
|
||||
QComboBox::drop-down {
|
||||
subcontrol-origin: padding;
|
||||
subcontrol-position: top right;
|
||||
width: 18px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
}
|
||||
QComboBox::down-arrow {
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
border-left: 5px solid transparent;
|
||||
border-right: 5px solid transparent;
|
||||
border-top: 6px solid ${secondary_text};
|
||||
margin-right: 4px;
|
||||
}
|
||||
QComboBox QAbstractItemView {
|
||||
background: ${panel_background};
|
||||
border: 1px solid ${border};
|
||||
@@ -218,11 +234,38 @@ QString buildAppStyleSheet() {
|
||||
background: ${control_background};
|
||||
border: 1px solid ${border};
|
||||
border-radius: ${panel_radius}px;
|
||||
padding: ${padding}px ${padding}px;
|
||||
padding: ${padding}px 24px ${padding}px ${padding}px;
|
||||
}
|
||||
QSpinBox:focus, QDoubleSpinBox:focus {
|
||||
border: 1px solid ${control_border_focus};
|
||||
}
|
||||
QSpinBox::up-button, QDoubleSpinBox::up-button,
|
||||
QSpinBox::down-button, QDoubleSpinBox::down-button {
|
||||
subcontrol-origin: padding;
|
||||
width: 18px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
}
|
||||
QSpinBox::up-button, QDoubleSpinBox::up-button {
|
||||
subcontrol-position: top right;
|
||||
}
|
||||
QSpinBox::down-button, QDoubleSpinBox::down-button {
|
||||
subcontrol-position: bottom right;
|
||||
}
|
||||
QSpinBox::up-arrow, QDoubleSpinBox::up-arrow {
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
border-left: 4px solid transparent;
|
||||
border-right: 4px solid transparent;
|
||||
border-bottom: 5px solid ${secondary_text};
|
||||
}
|
||||
QSpinBox::down-arrow, QDoubleSpinBox::down-arrow {
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
border-left: 4px solid transparent;
|
||||
border-right: 4px solid transparent;
|
||||
border-top: 5px solid ${secondary_text};
|
||||
}
|
||||
QCheckBox {
|
||||
background: transparent;
|
||||
spacing: 8px;
|
||||
@@ -241,6 +284,29 @@ QString buildAppStyleSheet() {
|
||||
border: 1px solid ${selection_background};
|
||||
background: ${selection_background};
|
||||
}
|
||||
QRadioButton {
|
||||
background: transparent;
|
||||
color: ${primary_text};
|
||||
spacing: 8px;
|
||||
padding: 2px 0;
|
||||
}
|
||||
QRadioButton::indicator {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: 1px solid ${border};
|
||||
border-radius: 8px;
|
||||
background: ${control_background};
|
||||
}
|
||||
QRadioButton::indicator:hover {
|
||||
background: ${ribbon_button_hover};
|
||||
}
|
||||
QRadioButton::indicator:checked {
|
||||
border: 1px solid ${selection_background};
|
||||
background: ${selection_background};
|
||||
}
|
||||
QRadioButton::indicator:unchecked {
|
||||
background: ${control_background};
|
||||
}
|
||||
QPushButton {
|
||||
background: ${control_background};
|
||||
border: 1px solid ${border};
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
// 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);
|
||||
setStyleSheet(components::style::buildAppStyleSheet());
|
||||
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,229 @@
|
||||
// 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/SceneLoader.h"
|
||||
#include "../../../ifcviewer/ViewportWindow.h"
|
||||
|
||||
#include <QFileDialog>
|
||||
#include <QListView>
|
||||
#include <QMessageBox>
|
||||
#include <QTreeView>
|
||||
|
||||
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::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) {
|
||||
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, "IfcInterfaceMockup", 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_->federation(), host_);
|
||||
dialog.exec();
|
||||
}
|
||||
|
||||
QString ModelsPanelController::formatElapsed(qint64 ms) const {
|
||||
return (ms >= 1000)
|
||||
? QString::number(ms / 1000.0, 'f', 2) + " s"
|
||||
: QString::number(ms) + " ms";
|
||||
}
|
||||
|
||||
} // namespace ifcinterface::modules::models
|
||||
@@ -0,0 +1,66 @@
|
||||
// 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;
|
||||
|
||||
QWidget* host_ = nullptr;
|
||||
ModelsPanel* widget_ = nullptr;
|
||||
ifcinterface::SessionState* session_state_ = nullptr;
|
||||
ViewportWindow* viewport_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace ifcinterface::modules::models
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,133 @@
|
||||
// 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 "../../components/Section.h"
|
||||
#include "../../components/SvgIcon.h"
|
||||
|
||||
#include <QHeaderView>
|
||||
#include <QInputDialog>
|
||||
#include <QLineEdit>
|
||||
#include <QMenu>
|
||||
#include <QTreeWidget>
|
||||
#include <QTreeWidgetItem>
|
||||
|
||||
namespace ifcinterface::modules::models {
|
||||
|
||||
ModelsPanel::ModelsPanel(QWidget* parent)
|
||||
: components::Panel("Models", nullptr, parent, true)
|
||||
{
|
||||
auto* section = new components::Section("", components::SectionHeaderMode::Hidden, this);
|
||||
|
||||
tree_ = new QTreeWidget(section);
|
||||
tree_->setColumnCount(2);
|
||||
tree_->setHeaderLabels({"Model", ""});
|
||||
tree_->setIconSize(QSize(16, 16));
|
||||
tree_->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||
tree_->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
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();
|
||||
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) {
|
||||
menu.addAction(components::icons::makeSvgIcon(":/icons/folder-plus.svg"), "Add Group", [this]() {
|
||||
bool ok = false;
|
||||
const QString name = QInputDialog::getText(
|
||||
this, "New Group", "Group name:", QLineEdit::Normal, "Group", &ok);
|
||||
if (ok && !name.isEmpty()) {
|
||||
emit addGroupRequested(QString(), name);
|
||||
}
|
||||
});
|
||||
menu.exec(tree_->viewport()->mapToGlobal(pos));
|
||||
return;
|
||||
}
|
||||
|
||||
const auto kind = static_cast<ItemKind>(item->data(0, Qt::UserRole).toInt());
|
||||
const QString id = item->data(0, Qt::UserRole + 1).toString();
|
||||
|
||||
menu.addAction(components::icons::makeSvgIcon(":/icons/eye.svg"), "Toggle Visibility", [this, kind, id]() {
|
||||
emit visibilityToggleRequested(kind, id);
|
||||
});
|
||||
|
||||
if (kind == ItemKind::Group) {
|
||||
menu.addAction(components::icons::makeSvgIcon(":/icons/folder-plus.svg"), "Add Group", [this, id]() {
|
||||
bool ok = false;
|
||||
const QString name = QInputDialog::getText(
|
||||
this, "New Group", "Group name:", QLineEdit::Normal, "Group", &ok);
|
||||
if (ok && !name.isEmpty()) {
|
||||
emit addGroupRequested(id, name);
|
||||
}
|
||||
});
|
||||
menu.addAction(components::icons::makeSvgIcon(":/icons/folder-minus.svg"), "Remove Group", [this, id]() {
|
||||
emit removeGroupRequested(id);
|
||||
});
|
||||
} else {
|
||||
menu.addAction(components::icons::makeSvgIcon(":/icons/minus-square.svg"), "Remove Model", [this, id]() {
|
||||
emit removeModelRequested(id);
|
||||
});
|
||||
}
|
||||
menu.exec(tree_->viewport()->mapToGlobal(pos));
|
||||
});
|
||||
}
|
||||
|
||||
void ModelsPanel::setNodes(const QList<TreeNode>& nodes) {
|
||||
tree_->clear();
|
||||
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"));
|
||||
}
|
||||
|
||||
for (const auto& child : node.children) {
|
||||
addNode(item, child);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ifcinterface::modules::models
|
||||
@@ -0,0 +1,54 @@
|
||||
// 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 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,426 @@
|
||||
// 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 "../../../ifcviewer/Federation.h"
|
||||
#include "../../components/Section.h"
|
||||
#include "../../components/Style.h"
|
||||
#include "../../components/SvgIcon.h"
|
||||
#include "../../components/Tabs.h"
|
||||
|
||||
#include <QButtonGroup>
|
||||
#include <QComboBox>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QFormLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QPushButton>
|
||||
#include <QRadioButton>
|
||||
#include <QShowEvent>
|
||||
#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"},
|
||||
};
|
||||
|
||||
QString unitDisplay(const FederationConfig& cfg) {
|
||||
QString s;
|
||||
if (!cfg.unit_prefix.empty()) s += QString::fromStdString(cfg.unit_prefix) + " ";
|
||||
s += QString::fromStdString(cfg.unit_name);
|
||||
return s;
|
||||
}
|
||||
|
||||
QLineEdit* makeNumericField(QWidget* parent, const QString& placeholder = {}) {
|
||||
auto* field = new QLineEdit(parent);
|
||||
field->setPlaceholderText(placeholder);
|
||||
field->setMaximumWidth(96);
|
||||
return field;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
QWidget* makeVector3Row(QLineEdit* x,
|
||||
QLineEdit* y,
|
||||
QLineEdit* z,
|
||||
QWidget* parent) {
|
||||
auto* row = new QWidget(parent);
|
||||
auto* layout = new QHBoxLayout(row);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->setSpacing(components::style::metrics::padding);
|
||||
layout->addWidget(x);
|
||||
layout->addWidget(y);
|
||||
layout->addWidget(z);
|
||||
layout->addStretch(1);
|
||||
return row;
|
||||
}
|
||||
|
||||
QWidget* makeRotationRow(QLineEdit* rx,
|
||||
QLineEdit* ry,
|
||||
QLineEdit* rz,
|
||||
QWidget* parent) {
|
||||
auto* row = new QWidget(parent);
|
||||
auto* layout = new QHBoxLayout(row);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->setSpacing(components::style::metrics::padding);
|
||||
layout->addWidget(rx);
|
||||
layout->addWidget(ry);
|
||||
layout->addWidget(rz);
|
||||
layout->addStretch(1);
|
||||
return row;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
SettingsDialog::SettingsDialog(Federation* federation, QWidget* parent)
|
||||
: components::Dialog(parent)
|
||||
, federation_(federation)
|
||||
{
|
||||
setObjectName("appDialog");
|
||||
setWindowTitle("Model Settings");
|
||||
setModal(true);
|
||||
resize(560, 560);
|
||||
setStyleSheet(components::style::buildAppStyleSheet());
|
||||
setupUi();
|
||||
}
|
||||
|
||||
void SettingsDialog::showEvent(QShowEvent* event) {
|
||||
syncFromFederation();
|
||||
populateModelCombo();
|
||||
refreshUnitLabels();
|
||||
if (model_combo_->count() > 0) {
|
||||
syncFromModel(model_combo_->currentData().toString());
|
||||
}
|
||||
QDialog::showEvent(event);
|
||||
}
|
||||
|
||||
void SettingsDialog::setupUi() {
|
||||
auto* tabs = new components::TabWidget(this);
|
||||
|
||||
auto* federation_tab = new QWidget(tabs);
|
||||
auto* federation_layout = new QVBoxLayout(federation_tab);
|
||||
federation_layout->setContentsMargins(0, 0, 0, 0);
|
||||
federation_layout->setSpacing(components::style::metrics::padding);
|
||||
|
||||
auto* federation_unit_section = new components::Section("Federation Unit", components::SectionHeaderMode::Visible, federation_tab);
|
||||
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_);
|
||||
auto* unit_hint = new QLabel(
|
||||
"All federated false origin and model transformation values are interpreted in this unit.",
|
||||
federation_unit_body);
|
||||
unit_hint->setProperty("textRole", "secondary");
|
||||
unit_hint->setWordWrap(true);
|
||||
federation_unit_form->addRow(QString(), 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_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", makeVector3Row(xyz_x_, xyz_y_, xyz_z_, origin_body));
|
||||
origin_form->addRow("Z rotation (°)", rz_deg_);
|
||||
auto* origin_hint = new QLabel(
|
||||
"Nominate a federation point that becomes the new origin, with optional north rotation.",
|
||||
origin_body);
|
||||
origin_hint->setProperty("textRole", "secondary");
|
||||
origin_hint->setWordWrap(true);
|
||||
origin_form->addRow(QString(), 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(tabs);
|
||||
auto* model_layout = new QVBoxLayout(model_tab);
|
||||
model_layout->setContentsMargins(0, 0, 0, 0);
|
||||
model_layout->setSpacing(components::style::metrics::padding);
|
||||
|
||||
auto* picker_section = new components::Section("", components::SectionHeaderMode::Hidden, model_tab);
|
||||
auto* picker_body = new QWidget(picker_section);
|
||||
auto* picker_form = new QFormLayout(picker_body);
|
||||
picker_form->setContentsMargins(0, 0, 0, 0);
|
||||
picker_form->setHorizontalSpacing(16);
|
||||
picker_form->setVerticalSpacing(10);
|
||||
model_combo_ = new QComboBox(picker_body);
|
||||
picker_form->addRow("Current Model", model_combo_);
|
||||
picker_section->addBodyWidget(picker_body);
|
||||
|
||||
auto* a_section = new components::Section("Point A", components::SectionHeaderMode::Visible, model_tab);
|
||||
auto* a_body = new QWidget(a_section);
|
||||
auto* a_form = new QFormLayout(a_body);
|
||||
a_form->setContentsMargins(0, 0, 0, 0);
|
||||
a_form->setHorizontalSpacing(16);
|
||||
a_form->setVerticalSpacing(10);
|
||||
radio_local_ = new QRadioButton("ModelLocal", a_body);
|
||||
radio_global_ = new QRadioButton("ModelGlobal", a_body);
|
||||
radio_global_->setChecked(true);
|
||||
auto* a_frame_group = new QButtonGroup(this);
|
||||
a_frame_group->addButton(radio_local_);
|
||||
a_frame_group->addButton(radio_global_);
|
||||
auto* a_frame_row = new QHBoxLayout();
|
||||
a_frame_row->setContentsMargins(0, 0, 0, 0);
|
||||
a_frame_row->setSpacing(components::style::metrics::padding);
|
||||
a_frame_row->addWidget(radio_local_);
|
||||
a_frame_row->addWidget(radio_global_);
|
||||
a_frame_row->addStretch(1);
|
||||
a_form->addRow("Frame", a_frame_row);
|
||||
a_x_ = makeNumericField(a_body, "X");
|
||||
a_y_ = makeNumericField(a_body, "Y");
|
||||
a_z_ = makeNumericField(a_body, "Z");
|
||||
a_form->addRow("XYZ", makeVector3Row(a_x_, a_y_, a_z_, a_body));
|
||||
a_unit_label_ = new QLabel(a_body);
|
||||
a_unit_label_->setProperty("textRole", "secondary");
|
||||
a_form->addRow("Unit", a_unit_label_);
|
||||
a_section->addBodyWidget(a_body);
|
||||
|
||||
auto* b_section = new components::Section("Point B", components::SectionHeaderMode::Visible, model_tab);
|
||||
auto* b_body = new QWidget(b_section);
|
||||
auto* b_form = new QFormLayout(b_body);
|
||||
b_form->setContentsMargins(0, 0, 0, 0);
|
||||
b_form->setHorizontalSpacing(16);
|
||||
b_form->setVerticalSpacing(10);
|
||||
b_x_ = makeNumericField(b_body, "X");
|
||||
b_y_ = makeNumericField(b_body, "Y");
|
||||
b_z_ = makeNumericField(b_body, "Z");
|
||||
b_form->addRow("XYZ", makeVector3Row(b_x_, b_y_, b_z_, b_body));
|
||||
b_unit_label_ = new QLabel(b_body);
|
||||
b_unit_label_->setProperty("textRole", "secondary");
|
||||
b_form->addRow("Unit", b_unit_label_);
|
||||
b_section->addBodyWidget(b_body);
|
||||
|
||||
auto* rotation_section = new components::Section("Rotation", components::SectionHeaderMode::Visible, model_tab);
|
||||
auto* rotation_body = new QWidget(rotation_section);
|
||||
auto* rotation_form = new QFormLayout(rotation_body);
|
||||
rotation_form->setContentsMargins(0, 0, 0, 0);
|
||||
rotation_form->setHorizontalSpacing(16);
|
||||
rotation_form->setVerticalSpacing(10);
|
||||
rx_ = makeNumericField(rotation_body, "rx");
|
||||
ry_ = makeNumericField(rotation_body, "ry");
|
||||
rz_ = makeNumericField(rotation_body, "rz");
|
||||
rotation_form->addRow("RXYZ", makeRotationRow(rx_, ry_, rz_, rotation_body));
|
||||
rotation_section->addBodyWidget(rotation_body);
|
||||
|
||||
auto* pivot_section = new components::Section("Rotation Pivot", components::SectionHeaderMode::Visible, model_tab);
|
||||
auto* pivot_body = new QWidget(pivot_section);
|
||||
auto* pivot_form = new QFormLayout(pivot_body);
|
||||
pivot_form->setContentsMargins(0, 0, 0, 0);
|
||||
pivot_form->setHorizontalSpacing(16);
|
||||
pivot_form->setVerticalSpacing(10);
|
||||
pivot_x_ = makeNumericField(pivot_body, "X");
|
||||
pivot_y_ = makeNumericField(pivot_body, "Y");
|
||||
pivot_z_ = makeNumericField(pivot_body, "Z");
|
||||
pivot_form->addRow("XYZ", makeVector3Row(pivot_x_, pivot_y_, pivot_z_, pivot_body));
|
||||
pivot_unit_label_ = new QLabel(pivot_body);
|
||||
pivot_unit_label_->setProperty("textRole", "secondary");
|
||||
pivot_form->addRow("Unit", pivot_unit_label_);
|
||||
pivot_section->addBodyWidget(pivot_body);
|
||||
|
||||
model_layout->addWidget(picker_section);
|
||||
model_layout->addWidget(a_section);
|
||||
model_layout->addWidget(b_section);
|
||||
model_layout->addWidget(rotation_section);
|
||||
model_layout->addWidget(pivot_section);
|
||||
model_layout->addStretch(1);
|
||||
|
||||
tabs->addTab(federation_tab, "Federation");
|
||||
tabs->addTab(model_tab, "Model");
|
||||
|
||||
connect(model_combo_, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
|
||||
[this](int) {
|
||||
if (model_combo_->count() > 0) {
|
||||
syncFromModel(model_combo_->currentData().toString());
|
||||
}
|
||||
});
|
||||
connect(radio_local_, &QRadioButton::toggled, this, [this]() { onAFrameToggled(); });
|
||||
connect(radio_global_, &QRadioButton::toggled, this, [this]() { onAFrameToggled(); });
|
||||
|
||||
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);
|
||||
|
||||
auto* actions_section = new components::Section("", components::SectionHeaderMode::Hidden, this);
|
||||
actions_section->addBodyWidget(buttons);
|
||||
|
||||
addBodyWidget(tabs);
|
||||
addBodyWidget(actions_section);
|
||||
}
|
||||
|
||||
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::populateModelCombo() {
|
||||
model_combo_->blockSignals(true);
|
||||
QString previous = model_combo_->currentData().toString();
|
||||
model_combo_->clear();
|
||||
if (federation_) {
|
||||
for (const auto& model : federation_->models()) {
|
||||
model_combo_->addItem(model.display_name.isEmpty() ? model.id : model.display_name, model.id);
|
||||
}
|
||||
}
|
||||
int restore = -1;
|
||||
for (int i = 0; i < model_combo_->count(); ++i) {
|
||||
if (model_combo_->itemData(i).toString() == previous) {
|
||||
restore = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (restore >= 0) model_combo_->setCurrentIndex(restore);
|
||||
model_combo_->blockSignals(false);
|
||||
}
|
||||
|
||||
void SettingsDialog::syncFromModel(const QString& fed_id) {
|
||||
if (!federation_) return;
|
||||
const Federation::Model* model = federation_->findById(fed_id);
|
||||
if (!model) return;
|
||||
|
||||
const auto& xf = model->model_transformation;
|
||||
radio_local_->setChecked(xf.a_frame == AFrame::ModelLocal);
|
||||
radio_global_->setChecked(xf.a_frame == AFrame::ModelGlobal);
|
||||
a_x_->setText(formatNumber(xf.a.x()));
|
||||
a_y_->setText(formatNumber(xf.a.y()));
|
||||
a_z_->setText(formatNumber(xf.a.z()));
|
||||
b_x_->setText(formatNumber(xf.b.x()));
|
||||
b_y_->setText(formatNumber(xf.b.y()));
|
||||
b_z_->setText(formatNumber(xf.b.z()));
|
||||
rx_->setText(formatNumber(xf.rxyz_deg.x()));
|
||||
ry_->setText(formatNumber(xf.rxyz_deg.y()));
|
||||
rz_->setText(formatNumber(xf.rxyz_deg.z()));
|
||||
pivot_x_->setText(formatNumber(xf.pivot.x()));
|
||||
pivot_y_->setText(formatNumber(xf.pivot.y()));
|
||||
pivot_z_->setText(formatNumber(xf.pivot.z()));
|
||||
}
|
||||
|
||||
void SettingsDialog::refreshUnitLabels() {
|
||||
if (!federation_) return;
|
||||
const QString fed_unit = unitDisplay(federation_->config());
|
||||
b_unit_label_->setText("(federation: " + fed_unit + ")");
|
||||
pivot_unit_label_->setText("(federation: " + fed_unit + ")");
|
||||
onAFrameToggled();
|
||||
}
|
||||
|
||||
void SettingsDialog::onAFrameToggled() {
|
||||
if (!a_unit_label_) return;
|
||||
if (radio_local_->isChecked()) {
|
||||
a_unit_label_->setText("(model project length unit)");
|
||||
} else {
|
||||
a_unit_label_->setText("(model map unit)");
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (model_combo_->count() > 0) {
|
||||
ModelTransformation xf;
|
||||
xf.a_frame = radio_local_->isChecked() ? AFrame::ModelLocal : AFrame::ModelGlobal;
|
||||
xf.a = Eigen::Vector3d(parseNumber(a_x_), parseNumber(a_y_), parseNumber(a_z_));
|
||||
xf.b = Eigen::Vector3d(parseNumber(b_x_), parseNumber(b_y_), parseNumber(b_z_));
|
||||
xf.rxyz_deg = Eigen::Vector3d(parseNumber(rx_), parseNumber(ry_), parseNumber(rz_));
|
||||
xf.pivot = Eigen::Vector3d(parseNumber(pivot_x_), parseNumber(pivot_y_), parseNumber(pivot_z_));
|
||||
federation_->setModelTransformation(model_combo_->currentData().toString(), xf);
|
||||
}
|
||||
}
|
||||
accept();
|
||||
}
|
||||
|
||||
} // namespace ifcinterface::modules::models
|
||||
@@ -0,0 +1,84 @@
|
||||
// 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 <QString>
|
||||
|
||||
class Federation;
|
||||
class QComboBox;
|
||||
class QLabel;
|
||||
class QLineEdit;
|
||||
class QRadioButton;
|
||||
class QShowEvent;
|
||||
|
||||
namespace ifcinterface::modules::models {
|
||||
|
||||
class SettingsDialog : public components::Dialog {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit SettingsDialog(Federation* federation, QWidget* parent = nullptr);
|
||||
|
||||
protected:
|
||||
void showEvent(QShowEvent* event) override;
|
||||
|
||||
private:
|
||||
void setupUi();
|
||||
void syncFromFederation();
|
||||
void populateModelCombo();
|
||||
void syncFromModel(const QString& fed_id);
|
||||
void refreshUnitLabels();
|
||||
void onAFrameToggled();
|
||||
void onAccepted();
|
||||
|
||||
Federation* federation_ = nullptr;
|
||||
|
||||
QComboBox* unit_combo_ = nullptr;
|
||||
QLineEdit* xyz_x_ = nullptr;
|
||||
QLineEdit* xyz_y_ = nullptr;
|
||||
QLineEdit* xyz_z_ = nullptr;
|
||||
QLineEdit* rz_deg_ = nullptr;
|
||||
|
||||
QComboBox* model_combo_ = nullptr;
|
||||
QRadioButton* radio_local_ = nullptr;
|
||||
QRadioButton* radio_global_ = nullptr;
|
||||
QLineEdit* a_x_ = nullptr;
|
||||
QLineEdit* a_y_ = nullptr;
|
||||
QLineEdit* a_z_ = nullptr;
|
||||
QLabel* a_unit_label_ = nullptr;
|
||||
QLineEdit* b_x_ = nullptr;
|
||||
QLineEdit* b_y_ = nullptr;
|
||||
QLineEdit* b_z_ = nullptr;
|
||||
QLabel* b_unit_label_ = nullptr;
|
||||
QLineEdit* rx_ = nullptr;
|
||||
QLineEdit* ry_ = nullptr;
|
||||
QLineEdit* rz_ = nullptr;
|
||||
QLineEdit* pivot_x_ = nullptr;
|
||||
QLineEdit* pivot_y_ = nullptr;
|
||||
QLineEdit* pivot_z_ = nullptr;
|
||||
QLabel* pivot_unit_label_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace ifcinterface::modules::models
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,44 @@
|
||||
// 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;
|
||||
};
|
||||
|
||||
} // namespace ifcinterface::modules::models
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,96 @@
|
||||
// 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 "../../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(); });
|
||||
|
||||
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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user