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
+75 -4
View File
@@ -1,3 +1,4 @@
# This file was generated with the assistance of an AI coding tool.
################################################################################
# #
# This file is part of IfcOpenShell. #
@@ -19,19 +20,89 @@
message("Running CMakeLists.txt in /src/ifcviewer-full")
file(GLOB IFCVIEWER_FULL_CPP_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
file(GLOB IFCVIEWER_FULL_H_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.h)
set(IFCVIEWER_FULL_FILES ${IFCVIEWER_FULL_CPP_FILES} ${IFCVIEWER_FULL_H_FILES})
set(QT_VERSION 6 CACHE STRING "Qt version")
find_package(Qt${QT_VERSION} COMPONENTS Core Gui Widgets Svg REQUIRED PATHS ${QT_DIR})
set(IFCVIEWER_FULL_FILES
${CMAKE_CURRENT_SOURCE_DIR}/main.cpp
${CMAKE_CURRENT_SOURCE_DIR}/ElementRegistry.cpp
${CMAKE_CURRENT_SOURCE_DIR}/ElementRegistry.h
${CMAKE_CURRENT_SOURCE_DIR}/InterfaceSettings.cpp
${CMAKE_CURRENT_SOURCE_DIR}/InterfaceSettings.h
${CMAKE_CURRENT_SOURCE_DIR}/SessionState.cpp
${CMAKE_CURRENT_SOURCE_DIR}/SessionState.h
${CMAKE_CURRENT_SOURCE_DIR}/MainWindow.cpp
${CMAKE_CURRENT_SOURCE_DIR}/MainWindow.h
${CMAKE_CURRENT_SOURCE_DIR}/Measurement.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Measurement.h
${CMAKE_CURRENT_SOURCE_DIR}/components/SvgIcon.cpp
${CMAKE_CURRENT_SOURCE_DIR}/components/SvgIcon.h
${CMAKE_CURRENT_SOURCE_DIR}/components/Style.cpp
${CMAKE_CURRENT_SOURCE_DIR}/components/Style.h
${CMAKE_CURRENT_SOURCE_DIR}/components/Dialog.cpp
${CMAKE_CURRENT_SOURCE_DIR}/components/Dialog.h
${CMAKE_CURRENT_SOURCE_DIR}/components/Tabs.cpp
${CMAKE_CURRENT_SOURCE_DIR}/components/Tabs.h
${CMAKE_CURRENT_SOURCE_DIR}/components/KeyValueTable.cpp
${CMAKE_CURRENT_SOURCE_DIR}/components/KeyValueTable.h
${CMAKE_CURRENT_SOURCE_DIR}/components/Buttons.cpp
${CMAKE_CURRENT_SOURCE_DIR}/components/Buttons.h
${CMAKE_CURRENT_SOURCE_DIR}/components/Section.cpp
${CMAKE_CURRENT_SOURCE_DIR}/components/Section.h
${CMAKE_CURRENT_SOURCE_DIR}/components/Panel.cpp
${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/SettingsView.cpp
${CMAKE_CURRENT_SOURCE_DIR}/modules/models/SettingsView.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
${CMAKE_CURRENT_SOURCE_DIR}/modules/models/Panel.cpp
${CMAKE_CURRENT_SOURCE_DIR}/modules/models/Panel.h
${CMAKE_CURRENT_SOURCE_DIR}/modules/models/View.cpp
${CMAKE_CURRENT_SOURCE_DIR}/modules/models/View.h
${CMAKE_CURRENT_SOURCE_DIR}/modules/todo/Panel.cpp
${CMAKE_CURRENT_SOURCE_DIR}/modules/todo/Panel.h
${CMAKE_CURRENT_SOURCE_DIR}/modules/properties/Types.h
${CMAKE_CURRENT_SOURCE_DIR}/modules/properties/Panel.cpp
${CMAKE_CURRENT_SOURCE_DIR}/modules/properties/Panel.h
${CMAKE_CURRENT_SOURCE_DIR}/modules/properties/View.cpp
${CMAKE_CURRENT_SOURCE_DIR}/modules/properties/View.h
${CMAKE_CURRENT_SOURCE_DIR}/modules/project/Controller.cpp
${CMAKE_CURRENT_SOURCE_DIR}/modules/project/Controller.h
${CMAKE_CURRENT_SOURCE_DIR}/modules/settings/Dialog.cpp
${CMAKE_CURRENT_SOURCE_DIR}/modules/settings/Dialog.h
${CMAKE_CURRENT_SOURCE_DIR}/modules/spatial_hierarchy/Types.h
${CMAKE_CURRENT_SOURCE_DIR}/modules/spatial_hierarchy/Panel.cpp
${CMAKE_CURRENT_SOURCE_DIR}/modules/spatial_hierarchy/Panel.h
${CMAKE_CURRENT_SOURCE_DIR}/modules/spatial_hierarchy/View.cpp
${CMAKE_CURRENT_SOURCE_DIR}/modules/spatial_hierarchy/View.h
${CMAKE_CURRENT_SOURCE_DIR}/modules/viewport/Panel.cpp
${CMAKE_CURRENT_SOURCE_DIR}/modules/viewport/Panel.h
${CMAKE_CURRENT_SOURCE_DIR}/modules/viewport/Controller.cpp
${CMAKE_CURRENT_SOURCE_DIR}/modules/viewport/Controller.h
${CMAKE_CURRENT_SOURCE_DIR}/interface_resources.qrc
)
add_executable(IfcViewerFull ${IFCVIEWER_FULL_FILES})
set_target_properties(IfcViewerFull PROPERTIES
AUTOMOC ON
AUTORCC ON
WIN32_EXECUTABLE ON
MACOSX_BUNDLE ON
)
target_link_libraries(IfcViewerFull PRIVATE IfcViewer)
target_link_libraries(IfcViewerFull PRIVATE
IfcViewer
Qt${QT_VERSION}::Core
Qt${QT_VERSION}::Gui
Qt${QT_VERSION}::Svg
Qt${QT_VERSION}::Widgets
)
install(TARGETS IfcViewerFull EXPORT ${IFCOPENSHELL_EXPORT_TARGETS})
ifcopenshell_deploy_qt_runtime(IfcViewerFull)
+126
View File
@@ -0,0 +1,126 @@
// 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 "ElementRegistry.h"
#include "../ifcviewer/GeometryStreamer.h"
#include "../ifcviewer/SceneLoader.h"
#include "../ifcviewer/SidecarCache.h"
namespace ifcinterface {
ElementRegistry::ElementRegistry(QObject* parent)
: QObject(parent)
{
}
void ElementRegistry::bindLoader(SceneLoader* loader) {
loader_ = loader;
connect(loader, &SceneLoader::sidecarElementsReady,
this, &ElementRegistry::onSidecarElementsReady);
connect(loader, &SceneLoader::streamedElementsReady,
this, &ElementRegistry::onStreamedElementsReady);
}
void ElementRegistry::clear() {
elements_.clear();
}
void ElementRegistry::removeModel(uint32_t model_id) {
for (auto it = elements_.begin(); it != elements_.end();) {
if (it->second.model_id == model_id) {
it = elements_.erase(it);
} else {
++it;
}
}
}
std::vector<BasicElementInfo> ElementRegistry::basicElementInfoForModel(uint32_t model_id) const {
std::vector<BasicElementInfo> result;
result.reserve(elements_.size());
for (const auto& [object_id, info] : elements_) {
(void)object_id;
if (info.model_id != model_id) continue;
result.push_back(info);
}
return result;
}
std::optional<BasicElementInfo> ElementRegistry::findBasicElementInfo(uint32_t object_id) const {
auto it = elements_.find(object_id);
if (it == elements_.end()) return std::nullopt;
return it->second;
}
std::optional<express::Base> ElementRegistry::findEntity(uint32_t object_id) const {
if (!loader_) return std::nullopt;
auto info = findBasicElementInfo(object_id);
if (!info) return std::nullopt;
auto* file = loader_->ifcFile(info->model_id);
if (!file) return std::nullopt;
try {
auto instance = file->instance_by_id(info->ifc_id);
if (!instance) return std::nullopt;
return instance;
} catch (...) {
return std::nullopt;
}
}
void ElementRegistry::onSidecarElementsReady(uint32_t /*mid*/,
std::vector<PackedElementInfo> elements,
std::string string_table) {
auto str = [&](uint32_t offset, uint32_t length) -> QString {
if (length == 0 || offset + length > string_table.size()) return {};
return QString::fromStdString(string_table.substr(offset, length));
};
for (const auto& pe : elements) {
BasicElementInfo info;
info.object_id = pe.object_id;
info.model_id = pe.model_id;
info.ifc_id = pe.ifc_id;
info.parent_id = pe.parent_id;
info.guid = str(pe.guid_offset, pe.guid_length);
info.name = str(pe.name_offset, pe.name_length);
info.type = str(pe.type_offset, pe.type_length);
elements_[info.object_id] = info;
}
}
void ElementRegistry::onStreamedElementsReady(uint32_t /*mid*/, std::vector<ElementInfo> elements) {
for (const auto& e : elements) {
BasicElementInfo info;
info.object_id = e.object_id;
info.model_id = e.model_id;
info.ifc_id = e.ifc_id;
info.parent_id = e.parent_id;
info.guid = QString::fromStdString(e.guid);
info.name = QString::fromStdString(e.name);
info.type = QString::fromStdString(e.type);
elements_[info.object_id] = info;
}
}
} // namespace ifcinterface
+72
View File
@@ -0,0 +1,72 @@
// 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_ELEMENTREGISTRY_H
#define IFCINTERFACE_ELEMENTREGISTRY_H
#include <QObject>
#include <QString>
#include "../ifcparse/express.h"
#include <optional>
#include <string>
#include <unordered_map>
#include <vector>
class SceneLoader;
struct PackedElementInfo;
struct ElementInfo;
namespace ifcinterface {
struct BasicElementInfo {
uint32_t object_id = 0;
uint32_t model_id = 0;
int ifc_id = 0;
int parent_id = 0;
QString guid;
QString name;
QString type;
};
class ElementRegistry : public QObject {
Q_OBJECT
public:
explicit ElementRegistry(QObject* parent = nullptr);
void bindLoader(SceneLoader* loader);
void clear();
void removeModel(uint32_t model_id);
std::vector<BasicElementInfo> basicElementInfoForModel(uint32_t model_id) const;
std::optional<BasicElementInfo> findBasicElementInfo(uint32_t object_id) const;
std::optional<express::Base> findEntity(uint32_t object_id) const;
private:
void onSidecarElementsReady(uint32_t mid,
std::vector<PackedElementInfo> elements,
std::string string_table);
void onStreamedElementsReady(uint32_t mid, std::vector<ElementInfo> elements);
SceneLoader* loader_ = nullptr;
std::unordered_map<uint32_t, BasicElementInfo> elements_;
};
} // namespace ifcinterface
#endif
@@ -1,174 +0,0 @@
/********************************************************************************
* *
* 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 "FederationSettingsDialog.h"
#include "Federation.h"
#include <QComboBox>
#include <QDialogButtonBox>
#include <QDoubleSpinBox>
#include <QFormLayout>
#include <QGroupBox>
#include <QLabel>
#include <QShowEvent>
#include <QStringList>
#include <QVBoxLayout>
namespace {
// Common length units users will pick. itemData() carries
// (prefix, name) — empty prefix for non-prefixed or conversion-based units.
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" },
};
} // namespace
FederationSettingsDialog::FederationSettingsDialog(Federation* federation,
QWidget* parent)
: QDialog(parent), federation_(federation)
{
setWindowTitle("Federation Settings");
setupUi();
}
void FederationSettingsDialog::setupUi() {
auto* root = new QVBoxLayout(this);
// Unit
{
auto* group = new QGroupBox("Federation Unit", this);
auto* form = new QFormLayout(group);
unit_combo_ = new QComboBox(group);
for (const auto& uc : kUnitChoices) {
QStringList data;
data << QString::fromUtf8(uc.prefix) << QString::fromUtf8(uc.name);
unit_combo_->addItem(uc.label, data);
}
unit_combo_->setToolTip(
"All Federated False Origin and Model Transformation numbers "
"are interpreted in this unit. Changing it after data has been "
"entered re-interprets the numbers — same physical location "
"expressed in the new unit.");
form->addRow("Unit", unit_combo_);
root->addWidget(group);
}
// Federated False Origin
{
auto* group = new QGroupBox("Federated False Origin", this);
group->setToolTip(
"Nominate a point in the federation that becomes the new (0,0,0). "
"Optional Z-rotation aligns north for the federation.");
auto* form = new QFormLayout(group);
auto build_xyz = [this](QDoubleSpinBox*& sb) {
sb = new QDoubleSpinBox();
sb->setRange(-1e12, 1e12);
sb->setDecimals(6);
sb->setSingleStep(1.0);
};
build_xyz(xyz_x_);
build_xyz(xyz_y_);
build_xyz(xyz_z_);
form->addRow("X", xyz_x_);
form->addRow("Y", xyz_y_);
form->addRow("Z", xyz_z_);
rz_deg_ = new QDoubleSpinBox();
rz_deg_->setRange(-360.0, 360.0);
rz_deg_->setDecimals(6);
rz_deg_->setSingleStep(1.0);
form->addRow("Z rotation (°)", rz_deg_);
root->addWidget(group);
}
auto* button_box = new QDialogButtonBox(
QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
root->addWidget(button_box);
connect(button_box, &QDialogButtonBox::accepted, this,
&FederationSettingsDialog::onAccepted);
connect(button_box, &QDialogButtonBox::rejected, this, &QDialog::reject);
}
void FederationSettingsDialog::showEvent(QShowEvent* event) {
syncFromFederation();
QDialog::showEvent(event);
}
void FederationSettingsDialog::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;
}
}
if (idx >= 0) {
unit_combo_->setCurrentIndex(idx);
} else {
// Federation has a unit that isn't in the common list — fall back to
// the first entry. Saving keeps whatever the user picks; the
// original federation unit is preserved unless they hit Ok.
unit_combo_->setCurrentIndex(0);
}
const auto& origin = federation_->federatedFalseOrigin();
xyz_x_->setValue(origin.xyz.x());
xyz_y_->setValue(origin.xyz.y());
xyz_z_->setValue(origin.xyz.z());
rz_deg_->setValue(origin.rz_deg);
}
void FederationSettingsDialog::onAccepted() {
if (!federation_) { accept(); return; }
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(xyz_x_->value(), xyz_y_->value(), xyz_z_->value());
origin.rz_deg = rz_deg_->value();
federation_->setFederatedFalseOrigin(origin);
accept();
}
+189
View File
@@ -0,0 +1,189 @@
// 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 "InterfaceSettings.h"
#include "components/Style.h"
#include <QColor>
#include <QSettings>
namespace {
constexpr const char* kThemeModeKey = "interface/theme/mode";
constexpr const char* kThemeColorPrefix = "interface/theme/colors/";
using Spec = ifcinterface::InterfaceSettings::ThemeColorSpec;
const std::vector<Spec> kThemeColorSpecs = {
{"app_background", "App Background", ifcinterface::components::style::palette::app_background, "#eef1f5"},
{"border", "Border", ifcinterface::components::style::palette::border, "#c9d1dc"},
{"selection_background", "Selection Background", ifcinterface::components::style::palette::selection_background,
"#2f9e44"},
{"tab_bar_background", "Tab Bar Background", ifcinterface::components::style::palette::tab_bar_background,
"#eef1f5"},
{"tab_background", "Tab Background", ifcinterface::components::style::palette::tab_background, "#e3e8ef"},
{"ribbon_background", "Ribbon Background", ifcinterface::components::style::palette::ribbon_background,
"#e3e8ef"},
{"ribbon_button_hover", "Ribbon Button Hover", ifcinterface::components::style::palette::ribbon_button_hover,
"#d6dde7"},
{"ribbon_button_pressed", "Ribbon Button Pressed", ifcinterface::components::style::palette::ribbon_button_pressed,
"#cad3df"},
{"viewport_shell_background", "Viewport Shell Background",
ifcinterface::components::style::palette::viewport_shell_background, "#dbe1e8"},
{"viewport_background", "Viewport Background", ifcinterface::components::style::palette::viewport_background,
"#eef2f6"},
{"panel_background", "Panel Background", ifcinterface::components::style::palette::panel_background, "#ffffff"},
{"control_background", "Control Background", ifcinterface::components::style::palette::control_background,
"#f5f7fa"},
{"control_border_focus", "Control Border Focus", ifcinterface::components::style::palette::control_border_focus,
"#7c8ca3"},
{"box_background", "Box Background", ifcinterface::components::style::palette::box_background, "#f5f7fa"},
{"scroll_handle", "Scroll Handle", ifcinterface::components::style::palette::scroll_handle, "#b1bac8"},
{"scroll_handle_hover", "Scroll Handle Hover", ifcinterface::components::style::palette::scroll_handle_hover,
"#929daf"},
{"status_background", "Status Background", ifcinterface::components::style::palette::status_background,
"#edf1f5"},
{"section_header_background", "Section Header Background",
ifcinterface::components::style::palette::section_header_background, "#eef2f6"},
{"primary_text", "Primary Text", ifcinterface::components::style::palette::primary_text, "#1f2937"},
{"secondary_text", "Secondary Text", ifcinterface::components::style::palette::secondary_text, "#5b6676"},
{"disabled_text", "Disabled Text", ifcinterface::components::style::palette::disabled_text, "#8b95a3"},
{"warning_text", "Warning Text", ifcinterface::components::style::palette::warning_text, "#b26b00"},
{"selection_text", "Selection Text", ifcinterface::components::style::palette::selection_text, "#ffffff"},
{"hover_text", "Hover Text", ifcinterface::components::style::palette::hover_text, "#0f172a"},
{"icon_color", "Icon Color", "#e7ebf2", "#445066"},
{"icon_active_color", "Icon Active Color", "#ffffff", "#101828"},
{"icon_disabled_color", "Icon Disabled Color", "#6f7988", "#98a2b3"},
{"icon_accent_color", "Accent Icon Color", "#39b54a", "#2f9e44"},
{"icon_accent_active_color", "Accent Icon Active Color", "#53c763", "#267e37"},
};
int colorIndexForKey(const QString& key) {
for (size_t i = 0; i < kThemeColorSpecs.size(); ++i) {
if (QString::fromUtf8(kThemeColorSpecs[i].key) == key) {
return static_cast<int>(i);
}
}
return -1;
}
QString normalizedColor(const QString& value, const QString& fallback) {
const QString trimmed = value.trimmed();
if (!QColor::isValidColorName(trimmed)) return fallback;
return QColor(trimmed).name(QColor::HexRgb);
}
} // namespace
namespace ifcinterface {
InterfaceSettings& InterfaceSettings::instance() {
static InterfaceSettings inst;
return inst;
}
const std::vector<InterfaceSettings::ThemeColorSpec>& InterfaceSettings::themeColorSpecs() {
return kThemeColorSpecs;
}
InterfaceSettings::InterfaceSettings() {
custom_colors_.resize(kThemeColorSpecs.size());
load();
}
InterfaceSettings::ThemeMode InterfaceSettings::themeMode() const {
return theme_mode_;
}
void InterfaceSettings::setThemeMode(ThemeMode mode) {
if (theme_mode_ == mode) return;
theme_mode_ = mode;
persist();
emit themeModeChanged(mode);
emit themeChanged();
}
QString InterfaceSettings::color(const QString& key) const {
const int index = colorIndexForKey(key);
if (index < 0) return {};
const auto& spec = kThemeColorSpecs[static_cast<size_t>(index)];
switch (theme_mode_) {
case ThemeMode::Dark:
return QString::fromUtf8(spec.dark_default);
case ThemeMode::Light:
return QString::fromUtf8(spec.light_default);
case ThemeMode::Custom:
return customColor(key);
}
return QString::fromUtf8(spec.dark_default);
}
QString InterfaceSettings::customColor(const QString& key) const {
const int index = colorIndexForKey(key);
if (index < 0) return {};
const auto& spec = kThemeColorSpecs[static_cast<size_t>(index)];
const QString& stored = custom_colors_[static_cast<size_t>(index)];
return stored.isEmpty() ? QString::fromUtf8(spec.dark_default) : stored;
}
void InterfaceSettings::setCustomColor(const QString& key, const QString& value) {
const int index = colorIndexForKey(key);
if (index < 0) return;
const auto& spec = kThemeColorSpecs[static_cast<size_t>(index)];
const QString normalized = normalizedColor(value, QString::fromUtf8(spec.dark_default));
QString& stored = custom_colors_[static_cast<size_t>(index)];
if (stored == normalized) return;
stored = normalized;
persist();
if (theme_mode_ == ThemeMode::Custom) {
emit themeChanged();
}
}
void InterfaceSettings::load() {
QSettings settings;
int raw_mode = settings.value(kThemeModeKey, static_cast<int>(ThemeMode::Dark)).toInt();
if (raw_mode < static_cast<int>(ThemeMode::Dark) || raw_mode > static_cast<int>(ThemeMode::Custom)) {
raw_mode = static_cast<int>(ThemeMode::Dark);
}
theme_mode_ = static_cast<ThemeMode>(raw_mode);
for (size_t i = 0; i < kThemeColorSpecs.size(); ++i) {
const auto& spec = kThemeColorSpecs[i];
const QString stored = settings.value(QString::fromUtf8(kThemeColorPrefix) + QString::fromUtf8(spec.key),
QString::fromUtf8(spec.dark_default))
.toString();
custom_colors_[i] = normalizedColor(stored, QString::fromUtf8(spec.dark_default));
}
}
void InterfaceSettings::persist() const {
QSettings settings;
settings.setValue(kThemeModeKey, static_cast<int>(theme_mode_));
for (size_t i = 0; i < kThemeColorSpecs.size(); ++i) {
settings.setValue(QString::fromUtf8(kThemeColorPrefix) + QString::fromUtf8(kThemeColorSpecs[i].key),
custom_colors_[i]);
}
}
} // namespace ifcinterface
+72
View File
@@ -0,0 +1,72 @@
// 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_INTERFACESETTINGS_H
#define IFCINTERFACE_INTERFACESETTINGS_H
#include <QObject>
#include <QString>
#include <vector>
namespace ifcinterface {
class InterfaceSettings : public QObject {
Q_OBJECT
public:
enum class ThemeMode {
Dark = 0,
Light = 1,
Custom = 2,
};
Q_ENUM(ThemeMode)
struct ThemeColorSpec {
const char* key;
const char* label;
const char* dark_default;
const char* light_default;
};
static InterfaceSettings& instance();
static const std::vector<ThemeColorSpec>& themeColorSpecs();
ThemeMode themeMode() const;
void setThemeMode(ThemeMode mode);
QString color(const QString& key) const;
QString customColor(const QString& key) const;
void setCustomColor(const QString& key, const QString& value);
signals:
void themeModeChanged(ThemeMode mode);
void themeChanged();
private:
InterfaceSettings();
void load();
void persist() const;
ThemeMode theme_mode_ = ThemeMode::Dark;
std::vector<QString> custom_colors_;
};
} // namespace ifcinterface
#endif
File diff suppressed because it is too large Load Diff
+68 -177
View File
@@ -1,3 +1,4 @@
// This file was generated with the assistance of an AI coding tool.
/********************************************************************************
* *
* This file is part of IfcOpenShell. *
@@ -17,195 +18,85 @@
* *
********************************************************************************/
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#ifndef IFCINTERFACE_SHELL_MAINWINDOW_H
#define IFCINTERFACE_SHELL_MAINWINDOW_H
#include <QHash>
#include <QMainWindow>
#include <QTreeWidget>
#include <QTableWidget>
#include <QProgressBar>
#include <QLabel>
#include <QSplitter>
#include <QElapsedTimer>
#include <map>
#include <unordered_map>
#include <vector>
#include "Measurement.h"
#include "ViewportWindow.h"
#include "SceneLoader.h"
#include <QStringList>
class QLabel;
class QDockWidget;
class QProgressBar;
class QStackedWidget;
class QToolButton;
class Federation;
class SettingsWindow;
class FederationSettingsDialog;
class ModelTransformationDialog;
class SceneLoader;
namespace ifcinterface { class ElementRegistry; }
namespace ifcinterface { class SessionState; }
namespace ifcinterface::components { class TabBar; }
namespace ifcinterface::modules::models { class ModelsPanelController; }
namespace ifcinterface::modules::models { class ModelsPanel; }
namespace ifcinterface::modules::models { class ModelsPanelView; }
namespace ifcinterface::modules::project { class ProjectController; }
namespace ifcinterface::modules::spatial_hierarchy { class SpatialHierarchyPanel; }
namespace ifcinterface::modules::spatial_hierarchy { class SpatialHierarchyPanelView; }
namespace ifcinterface::modules::properties { class PropertiesPanel; }
namespace ifcinterface::modules::properties { class PropertiesPanelView; }
namespace ifcinterface::modules::viewport { class ViewportController; }
namespace ifcinterface::modules::viewport { class ViewportPanel; }
namespace ifcinterface::shell {
class MainWindow : public QMainWindow {
Q_OBJECT
public:
explicit MainWindow(QWidget* parent = nullptr);
~MainWindow();
void addFiles(const QStringList& paths);
bool openFederation(const QString& path);
void setPendingCamera(const QString& params);
void setPendingBenchmark(int frames);
protected:
void closeEvent(QCloseEvent* event) override;
private slots:
void onFileOpen();
void onDatabaseOpen();
void onFederationNew();
void onFederationOpen();
bool onFederationSave();
bool onFederationSaveAs();
void onSetHomeView();
void onGoHomeView();
void onFileSettings();
void onFederationSettings();
void onModelTransformations();
void onObjectPicked(uint32_t object_id);
void onTreeSelectionChanged();
void onTreeContextMenu(const QPoint& pos);
void onLoadStarted(uint32_t mid, QString display_name);
void onLoadProgressChanged(int percent);
void onSidecarElementsReady(uint32_t mid,
std::vector<PackedElementInfo> elements,
std::string string_table);
void onLoadedFromSidecar(uint32_t mid, qint64 elapsed_ms);
void onDataSourceReady(uint32_t mid);
void onStreamedElementsReady(uint32_t mid, std::vector<ElementInfo> elements);
void onLoadedFromStream(uint32_t mid, qint64 elapsed_ms);
void onLoadCancelled(uint32_t mid);
void onLoadError(uint32_t mid, QString message);
void onAllLoadsFinished();
private:
void setupUi();
void setupMenus();
void loadModelsFromPaths(const QStringList& paths,
const QStringList& fed_ids);
void clearScene();
bool confirmDiscardIfDirty();
void setupChrome();
void setupRibbon();
void setupViewport();
void setupPanels();
void setupStatus();
void setupLoader();
QWidget* buildHomeRibbonPage();
QWidget* buildNavigateRibbonPage();
QWidget* buildInspectRibbonPage();
QWidget* buildPanelsRibbonPage();
void updateWindowTitle();
void populateProperties(uint32_t object_id);
// Push the volume HUD + per-object volume labels for the current
// selection. No-op when a measurement tool is active — that tool
// owns the overlay state until it's exited.
void updateVolumeReadout();
void appendElementToTree(uint32_t model_id,
uint32_t object_id,
int ifc_id,
int parent_ifc_id,
const std::string& guid,
const std::string& name,
const std::string& type);
void writeSidecarForModel(uint32_t mid);
void removeModelUi(uint32_t mid);
void removeModel(uint32_t mid);
// Returns the model_id whose tree root is `item`, or 0 if `item` is not
// a model root (i.e. an element row, group, or null).
uint32_t modelIdForRoot(QTreeWidgetItem* item) const;
// Returns the group_id whose tree item is `item`, or empty string when
// `item` is null or is not a group item.
QString groupIdForItem(QTreeWidgetItem* item) const;
// Place / reparent a model root under its group (or at top level when
// group_id is empty / unknown). Idempotent. No-op if there's no tree
// root yet for `mid`.
void reparentModelTreeRoot(uint32_t mid);
// Place / reparent a group item under its parent group (or at top
// level). Idempotent. No-op if there's no tree item for `group_id`.
void reparentGroupTreeItem(const QString& group_id);
// Lazily create the QTreeWidgetItem for `group_id` if not already in
// group_tree_items_. Returns the item. Sets text + flags but does
// not place it under a parent — call reparentGroupTreeItem afterwards.
QTreeWidgetItem* ensureGroupTreeItem(const QString& group_id);
// Recompute italic/grey on a group row from current effective visibility.
void refreshGroupRowAppearance(const QString& group_id);
// Walk descendants of `group_id` and re-push effective visibility to
// the viewport for every model under it. When `group_id` is empty,
// re-pushes every model in the federation.
void applyVisibilityCascadeFromGroup(const QString& group_id);
// Walk every group_id whose ancestor chain currently includes
// `group_id` (inclusive).
std::vector<QString> descendantGroupIds(const QString& group_id) const;
// Push the federation's `visible` flag for `mid` onto the viewport.
// No-op if `mid` is not in the federation map. Idempotent — safe to
// call before the model is finalised on the viewport (hideModel is a
// lookup-and-set on models_gpu_; missing entries are skipped).
void applyModelVisibilityToViewport(uint32_t mid);
void applyPendingBenchmark();
QToolButton* makePanelToggle(const QString& text, QDockWidget* dock);
// Push a model's CoordinateOperation matrix to the viewport (or
// identity, when the AppSettings toggle is off or the model has no
// map conversion). No-op if the model isn't yet known to the loader
// or its IFC file isn't available (sidecar-hit before data-source
// load); the call retries on onDataSourceReady.
void applyCoordinateOperationToViewport(uint32_t mid);
// Push a model's ModelTransformation (stage 4) matrix to the viewport,
// composed from the federation's authoring intent + the model's units
// + the active CoordinateOperation matrix. Identity when the model is
// not in the federation.
void applyModelTransformationToViewport(uint32_t mid);
// Push the federation-wide FederatedFalseOrigin (stage 3) matrix to
// the viewport. Affects every loaded model.
void applyFederatedFalseOriginToViewport();
// For an untitled federation whose FederatedFalseOrigin is still at
// its default, derive a sensible origin from `mid`'s first instance
// placement + georef and push it via Federation. Idempotent: a
// non-default origin (user-edited, already guessed by a sibling load
// in the same batch, or loaded from a saved .ifcfed) is left
// untouched, so multi-file batches naturally anchor on whichever
// model finishes first.
void maybeGuessFederatedFalseOrigin(uint32_t mid);
QString formatElapsed(qint64 ms) const;
ViewportWindow* viewport_ = nullptr;
SceneLoader* loader_ = nullptr;
Federation* federation_ = nullptr;
SettingsWindow* settings_ = nullptr;
FederationSettingsDialog* federation_settings_ = nullptr;
ModelTransformationDialog* model_transformations_ = nullptr;
QWidget* viewport_container_ = nullptr;
QTreeWidget* element_tree_ = nullptr;
QTableWidget* property_table_ = nullptr;
QProgressBar* progress_bar_ = nullptr;
QLabel* status_label_ = nullptr;
QLabel* stats_label_ = nullptr;
// Per-model tree roots, keyed by model_id. May live at the top level
// of element_tree_ or as a child of a group tree item.
std::map<uint32_t, QTreeWidgetItem*> tree_roots_;
// Per-group tree items, keyed by Federation group id.
std::unordered_map<QString, QTreeWidgetItem*> group_tree_items_;
// Bidirectional federation_id <-> model_id map. Federation owns the
// persistent ids; SceneLoader owns the runtime model_ids.
std::unordered_map<QString, uint32_t> fed_id_to_model_id_;
std::unordered_map<uint32_t, QString> model_id_to_fed_id_;
// Display-side element registry for tree + property lookup.
std::unordered_map<uint32_t, ElementInfo> element_map_;
std::unordered_map<uint32_t, QTreeWidgetItem*> tree_items_;
// Scoped (model_id, ifc_id) -> object_id
std::unordered_map<uint64_t, uint32_t> scoped_ifc_id_to_object_id_;
static uint64_t scopedKey(uint32_t model_id, int ifc_id) {
return (static_cast<uint64_t>(model_id) << 32) | static_cast<uint32_t>(ifc_id);
}
QString pending_camera_;
int pending_benchmark_ = 0;
AreaMeasurement area_measurement_;
LengthMeasurement length_measurement_;
private:
Federation* federation_ = nullptr;
QLabel* status_mode_label_ = nullptr;
QLabel* status_selection_label_ = nullptr;
QLabel* status_perf_label_ = nullptr;
QProgressBar* status_progress_bar_ = nullptr;
ifcinterface::components::TabBar* ribbon_tabs_ = nullptr;
QStackedWidget* ribbon_pages_ = nullptr;
ifcinterface::modules::viewport::ViewportPanel* viewport_widget_ = nullptr;
ifcinterface::modules::viewport::ViewportController* viewport_controller_ = nullptr;
SceneLoader* loader_ = nullptr;
ifcinterface::ElementRegistry* element_registry_ = nullptr;
ifcinterface::SessionState* session_state_ = nullptr;
ifcinterface::modules::models::ModelsPanel* models_panel_ = nullptr;
ifcinterface::modules::spatial_hierarchy::SpatialHierarchyPanel* spatial_panel_ = nullptr;
QDockWidget* layers_panel_ = nullptr;
ifcinterface::modules::properties::PropertiesPanel* properties_panel_ = nullptr;
QDockWidget* stored_views_panel_ = nullptr;
QDockWidget* search_panel_ = nullptr;
QDockWidget* spreadsheet_panel_ = nullptr;
QDockWidget* audit_panel_ = nullptr;
QDockWidget* clash_panel_ = nullptr;
QDockWidget* issues_panel_ = nullptr;
ifcinterface::modules::models::ModelsPanelController* models_controller_ = nullptr;
ifcinterface::modules::models::ModelsPanelView* models_view_ = nullptr;
ifcinterface::modules::project::ProjectController* project_controller_ = nullptr;
ifcinterface::modules::spatial_hierarchy::SpatialHierarchyPanelView* spatial_view_ = nullptr;
ifcinterface::modules::properties::PropertiesPanelView* properties_view_ = nullptr;
};
#endif // MAINWINDOW_H
} // namespace ifcinterface::shell
#endif
+1
View File
@@ -1,3 +1,4 @@
// This file was generated with the assistance of an AI coding tool.
/********************************************************************************
* *
* This file is part of IfcOpenShell. *
+4 -3
View File
@@ -1,3 +1,4 @@
// This file was generated with the assistance of an AI coding tool.
/********************************************************************************
* *
* This file is part of IfcOpenShell. *
@@ -17,8 +18,8 @@
* *
********************************************************************************/
#ifndef IFCVIEWER_FULL_MEASUREMENT_H
#define IFCVIEWER_FULL_MEASUREMENT_H
#ifndef IFCINTERFACE_MEASUREMENT_H
#define IFCINTERFACE_MEASUREMENT_H
#include <cstddef>
#include "ViewportWindow.h"
@@ -158,4 +159,4 @@ private:
ViewportWindow::MeshLocalPick first_pick_{};
};
#endif // IFCVIEWER_FULL_MEASUREMENT_H
#endif // IFCINTERFACE_MEASUREMENT_H
@@ -1,248 +0,0 @@
/********************************************************************************
* *
* 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 "ModelTransformationDialog.h"
#include "Federation.h"
#include <QButtonGroup>
#include <QComboBox>
#include <QDialogButtonBox>
#include <QDoubleSpinBox>
#include <QFormLayout>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QRadioButton>
#include <QShowEvent>
#include <QVBoxLayout>
namespace {
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;
}
} // namespace
ModelTransformationDialog::ModelTransformationDialog(Federation* federation,
QWidget* parent)
: QDialog(parent), federation_(federation)
{
setWindowTitle("Model Transformation");
setupUi();
}
void ModelTransformationDialog::setupUi() {
auto* root = new QVBoxLayout(this);
// Model picker.
{
auto* row = new QHBoxLayout();
row->addWidget(new QLabel("Model:", this));
model_combo_ = new QComboBox(this);
row->addWidget(model_combo_, 1);
root->addLayout(row);
connect(model_combo_, QOverload<int>::of(&QComboBox::currentIndexChanged),
this, &ModelTransformationDialog::onModelChanged);
}
auto build_xyz = [](QDoubleSpinBox*& sb) {
sb = new QDoubleSpinBox();
sb->setRange(-1e12, 1e12);
sb->setDecimals(6);
sb->setSingleStep(1.0);
};
// A-frame + A.
{
auto* group = new QGroupBox("Point A", this);
group->setToolTip(
"The model-side anchor point. ModelLocal expresses A in the "
"model's project length unit, before its CoordinateOperation. "
"ModelGlobal expresses A in the model's map unit, after its "
"CoordinateOperation.");
auto* form = new QFormLayout(group);
radio_local_ = new QRadioButton("ModelLocal", group);
radio_global_ = new QRadioButton("ModelGlobal", group);
radio_global_->setChecked(true);
auto* af_group = new QButtonGroup(this);
af_group->addButton(radio_local_);
af_group->addButton(radio_global_);
connect(radio_local_, &QRadioButton::toggled,
this, &ModelTransformationDialog::onAFrameToggled);
connect(radio_global_, &QRadioButton::toggled,
this, &ModelTransformationDialog::onAFrameToggled);
auto* af_row = new QHBoxLayout();
af_row->addWidget(radio_local_);
af_row->addWidget(radio_global_);
af_row->addStretch();
form->addRow("Frame", af_row);
build_xyz(a_x_); build_xyz(a_y_); build_xyz(a_z_);
form->addRow("X", a_x_);
form->addRow("Y", a_y_);
form->addRow("Z", a_z_);
a_unit_label_ = new QLabel("(model unit)", group);
form->addRow("Unit", a_unit_label_);
root->addWidget(group);
}
// B.
{
auto* group = new QGroupBox("Point B (federation target)", this);
group->setToolTip(
"Where in the federation point A should land. In federation units.");
auto* form = new QFormLayout(group);
build_xyz(b_x_); build_xyz(b_y_); build_xyz(b_z_);
form->addRow("X", b_x_);
form->addRow("Y", b_y_);
form->addRow("Z", b_z_);
b_unit_label_ = new QLabel("(federation unit)", group);
form->addRow("Unit", b_unit_label_);
root->addWidget(group);
}
// Rotation (intrinsic XYZ).
{
auto* group = new QGroupBox("Rotation (intrinsic XYZ, degrees)", this);
group->setToolTip(
"Composed as R = R_z(rz) · R_y(ry) · R_x(rx). Y-up models "
"rotated to Z-up: rx = 90, ry = 0, rz = 0.");
auto* form = new QFormLayout(group);
rx_ = new QDoubleSpinBox(); rx_->setRange(-360, 360); rx_->setDecimals(6);
ry_ = new QDoubleSpinBox(); ry_->setRange(-360, 360); ry_->setDecimals(6);
rz_ = new QDoubleSpinBox(); rz_->setRange(-360, 360); rz_->setDecimals(6);
form->addRow("rx (°)", rx_);
form->addRow("ry (°)", ry_);
form->addRow("rz (°)", rz_);
root->addWidget(group);
}
// Pivot.
{
auto* group = new QGroupBox("Rotation Pivot", this);
group->setToolTip(
"The point the rotation rotates around, in federation units. "
"Set pivot = B to keep A landing on B regardless of rotation.");
auto* form = new QFormLayout(group);
build_xyz(pivot_x_); build_xyz(pivot_y_); build_xyz(pivot_z_);
form->addRow("X", pivot_x_);
form->addRow("Y", pivot_y_);
form->addRow("Z", pivot_z_);
pivot_unit_label_ = new QLabel("(federation unit)", group);
form->addRow("Unit", pivot_unit_label_);
root->addWidget(group);
}
auto* button_box = new QDialogButtonBox(
QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
root->addWidget(button_box);
connect(button_box, &QDialogButtonBox::accepted, this,
&ModelTransformationDialog::onAccepted);
connect(button_box, &QDialogButtonBox::rejected, this, &QDialog::reject);
}
void ModelTransformationDialog::showEvent(QShowEvent* event) {
populateModelCombo();
refreshUnitLabels();
if (model_combo_->count() > 0) {
syncFromModel(model_combo_->currentData().toString());
}
QDialog::showEvent(event);
}
void ModelTransformationDialog::populateModelCombo() {
model_combo_->blockSignals(true);
QString prev_id = model_combo_->currentData().toString();
model_combo_->clear();
if (federation_) {
for (const auto& m : federation_->models()) {
QString label = m.display_name.isEmpty() ? m.id : m.display_name;
model_combo_->addItem(label, m.id);
}
}
int restore = -1;
for (int i = 0; i < model_combo_->count(); ++i) {
if (model_combo_->itemData(i).toString() == prev_id) { restore = i; break; }
}
if (restore >= 0) model_combo_->setCurrentIndex(restore);
model_combo_->blockSignals(false);
}
void ModelTransformationDialog::syncFromModel(const QString& fed_id) {
if (!federation_) return;
const Federation::Model* m = federation_->findById(fed_id);
if (!m) return;
const ModelTransformation& xf = m->model_transformation;
radio_local_->setChecked(xf.a_frame == AFrame::ModelLocal);
radio_global_->setChecked(xf.a_frame == AFrame::ModelGlobal);
a_x_->setValue(xf.a.x()); a_y_->setValue(xf.a.y()); a_z_->setValue(xf.a.z());
b_x_->setValue(xf.b.x()); b_y_->setValue(xf.b.y()); b_z_->setValue(xf.b.z());
rx_->setValue(xf.rxyz_deg.x());
ry_->setValue(xf.rxyz_deg.y());
rz_->setValue(xf.rxyz_deg.z());
pivot_x_->setValue(xf.pivot.x());
pivot_y_->setValue(xf.pivot.y());
pivot_z_->setValue(xf.pivot.z());
}
void ModelTransformationDialog::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 ModelTransformationDialog::onModelChanged(int /*idx*/) {
if (model_combo_->count() == 0) return;
syncFromModel(model_combo_->currentData().toString());
}
void ModelTransformationDialog::onAFrameToggled() {
if (!a_unit_label_) return;
if (radio_local_->isChecked()) {
a_unit_label_->setText("(model project length unit, e.g. millimetre)");
} else {
a_unit_label_->setText("(model map unit, e.g. metre)");
}
}
void ModelTransformationDialog::onAccepted() {
if (!federation_ || model_combo_->count() == 0) { accept(); return; }
const QString fed_id = model_combo_->currentData().toString();
ModelTransformation xf;
xf.a_frame = radio_local_->isChecked() ? AFrame::ModelLocal : AFrame::ModelGlobal;
xf.a = Eigen::Vector3d(a_x_->value(), a_y_->value(), a_z_->value());
xf.b = Eigen::Vector3d(b_x_->value(), b_y_->value(), b_z_->value());
xf.rxyz_deg = Eigen::Vector3d(rx_->value(), ry_->value(), rz_->value());
xf.pivot = Eigen::Vector3d(pivot_x_->value(), pivot_y_->value(), pivot_z_->value());
federation_->setModelTransformation(fed_id, xf);
accept();
}
@@ -1,88 +0,0 @@
/********************************************************************************
* *
* 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 MODELTRANSFORMATIONDIALOG_H
#define MODELTRANSFORMATIONDIALOG_H
#include <QDialog>
#include <QString>
QT_BEGIN_NAMESPACE
class QComboBox;
class QDoubleSpinBox;
class QLabel;
class QRadioButton;
QT_END_NAMESPACE
class Federation;
// Edits one model's ModelTransformation (the per-model placement within
// the federation: a_frame, a, b, rxyz Euler degrees, pivot). A combobox
// at the top picks which model to edit. Switching models discards any
// unsaved form edits — Ok saves the currently-visible model. On Ok,
// calls Federation::setModelTransformation, which fires
// modelTransformationChanged → MainWindow recomposes the viewport for
// that model.
class ModelTransformationDialog : public QDialog {
Q_OBJECT
public:
explicit ModelTransformationDialog(Federation* federation, QWidget* parent = nullptr);
protected:
void showEvent(QShowEvent* event) override;
private slots:
void onModelChanged(int idx);
void onAFrameToggled();
void onAccepted();
private:
void setupUi();
void populateModelCombo();
void syncFromModel(const QString& fed_id);
void refreshUnitLabels();
Federation* federation_ = nullptr;
QComboBox* model_combo_ = nullptr;
QRadioButton* radio_local_ = nullptr;
QRadioButton* radio_global_ = nullptr;
QDoubleSpinBox* a_x_ = nullptr;
QDoubleSpinBox* a_y_ = nullptr;
QDoubleSpinBox* a_z_ = nullptr;
QLabel* a_unit_label_ = nullptr;
QDoubleSpinBox* b_x_ = nullptr;
QDoubleSpinBox* b_y_ = nullptr;
QDoubleSpinBox* b_z_ = nullptr;
QLabel* b_unit_label_ = nullptr;
QDoubleSpinBox* rx_ = nullptr;
QDoubleSpinBox* ry_ = nullptr;
QDoubleSpinBox* rz_ = nullptr;
QDoubleSpinBox* pivot_x_ = nullptr;
QDoubleSpinBox* pivot_y_ = nullptr;
QDoubleSpinBox* pivot_z_ = nullptr;
QLabel* pivot_unit_label_ = nullptr;
};
#endif
+111
View File
@@ -0,0 +1,111 @@
// 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 "SessionState.h"
#include "../ifcviewer/Federation.h"
namespace ifcinterface {
SessionState::SessionState(QObject* parent)
: QObject(parent)
{
}
void SessionState::bindFederation(Federation* federation) {
federation_ = federation;
}
void SessionState::bindLoader(SceneLoader* loader) {
loader_ = loader;
}
void SessionState::bindElementRegistry(ElementRegistry* element_registry) {
element_registry_ = element_registry;
}
void SessionState::setSelectedObjectId(uint32_t object_id) {
selected_object_id_ = object_id;
}
void SessionState::setStatusMessage(const QString& mode, const QString& detail) {
status_mode_ = mode;
status_detail_ = detail;
emit statusMessageChanged(status_mode_, status_detail_);
}
void SessionState::setModelMapping(const QString& fed_id, uint32_t model_id) {
fed_id_to_model_id_[fed_id] = model_id;
model_id_to_fed_id_[model_id] = fed_id;
}
void SessionState::removeModelMappingByFedId(const QString& fed_id) {
auto it = fed_id_to_model_id_.find(fed_id);
if (it == fed_id_to_model_id_.end()) return;
model_id_to_fed_id_.remove(it.value());
fed_id_to_model_id_.erase(it);
}
void SessionState::clearModelMappings() {
fed_id_to_model_id_.clear();
model_id_to_fed_id_.clear();
}
uint32_t SessionState::modelIdForFedId(const QString& fed_id) const {
return fed_id_to_model_id_.value(fed_id, 0);
}
QString SessionState::fedIdForModelId(uint32_t model_id) const {
return model_id_to_fed_id_.value(model_id);
}
QList<uint32_t> SessionState::modelIds() const {
return model_id_to_fed_id_.keys();
}
void SessionState::notifySelectionChanged() {
emit selectionChanged(selected_object_id_);
}
void SessionState::notifyModelsChanged() {
emit modelsChanged();
}
void SessionState::notifyFederationStructureChanged() {
emit federationStructureChanged();
}
void SessionState::notifyVisibilityChanged() {
emit visibilityChanged();
}
void SessionState::notifyProjectOpened(const QString& path) {
emit projectOpened(path);
}
void SessionState::notifyProjectSaved(const QString& path) {
emit projectSaved(path);
}
void SessionState::notifyProjectReset() {
emit projectReset();
}
} // namespace ifcinterface
+93
View File
@@ -0,0 +1,93 @@
// 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_SESSIONSTATE_H
#define IFCINTERFACE_SESSIONSTATE_H
#include <QHash>
#include <QObject>
#include <QString>
class Federation;
class SceneLoader;
namespace ifcinterface {
class ElementRegistry;
class SessionState : public QObject {
Q_OBJECT
public:
explicit SessionState(QObject* parent = nullptr);
void bindFederation(Federation* federation);
void bindLoader(SceneLoader* loader);
void bindElementRegistry(ElementRegistry* element_registry);
Federation* federation() const { return federation_; }
SceneLoader* loader() const { return loader_; }
ElementRegistry* elementRegistry() const { return element_registry_; }
QString statusMode() const { return status_mode_; }
QString statusDetail() const { return status_detail_; }
void setSelectedObjectId(uint32_t object_id);
uint32_t selectedObjectId() const { return selected_object_id_; }
void setStatusMessage(const QString& mode, const QString& detail);
void setModelMapping(const QString& fed_id, uint32_t model_id);
void removeModelMappingByFedId(const QString& fed_id);
void clearModelMappings();
uint32_t modelIdForFedId(const QString& fed_id) const;
QString fedIdForModelId(uint32_t model_id) const;
QList<uint32_t> modelIds() const;
void notifySelectionChanged();
void notifyModelsChanged();
void notifyFederationStructureChanged();
void notifyVisibilityChanged();
void notifyProjectOpened(const QString& path);
void notifyProjectSaved(const QString& path);
void notifyProjectReset();
signals:
void projectOpened(const QString& path);
void projectSaved(const QString& path);
void projectReset();
void modelsChanged();
void federationStructureChanged();
void visibilityChanged();
void selectionChanged(uint32_t object_id);
void statusMessageChanged(const QString& mode, const QString& detail);
private:
Federation* federation_ = nullptr;
SceneLoader* loader_ = nullptr;
ElementRegistry* element_registry_ = nullptr;
uint32_t selected_object_id_ = 0;
QString status_mode_;
QString status_detail_;
QHash<QString, uint32_t> fed_id_to_model_id_;
QHash<uint32_t, QString> model_id_to_fed_id_;
};
} // namespace ifcinterface
#endif
-171
View File
@@ -1,171 +0,0 @@
/********************************************************************************
* *
* 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 "SettingsWindow.h"
#include "AppSettings.h"
#include <QCheckBox>
#include <QDialogButtonBox>
#include <QDoubleSpinBox>
#include <QFormLayout>
#include <QLineEdit>
#include <QShowEvent>
#include <QSpinBox>
#include <QVBoxLayout>
SettingsWindow::SettingsWindow(QWidget *parent)
: QDialog(parent)
{
setWindowTitle("Settings");
setupUi();
}
void SettingsWindow::setupUi() {
auto* form = new QFormLayout();
geometry_library_edit_ = new QLineEdit(this);
geometry_library_edit_->setMinimumWidth(280);
form->addRow("Geometry Library", geometry_library_edit_);
show_stats_check_ = new QCheckBox(this);
form->addRow("Show Performance Stats", show_stats_check_);
backface_culling_check_ = new QCheckBox(this);
backface_culling_check_->setToolTip(
"Skip triangles facing away from the camera. Big FPS win on "
"closed solids; disable if you see holes in open geometry.");
form->addRow("Backface Culling", backface_culling_check_);
void_limit_spin_ = new QSpinBox(this);
void_limit_spin_->setRange(0, 100000);
void_limit_spin_->setToolTip(
"Skip elements with more openings (HasOpenings) than this. "
"A handful of pathological elements can dominate boolean-subtraction "
"time; dropping them keeps load times sane.");
form->addRow("Void Limit", void_limit_spin_);
deflection_tolerance_spin_ = new QDoubleSpinBox(this);
deflection_tolerance_spin_->setRange(0.000001, 1000.0);
deflection_tolerance_spin_->setDecimals(6);
deflection_tolerance_spin_->setSingleStep(0.001);
deflection_tolerance_spin_->setToolTip(
"Linear chord error between curved geometry and its triangulation, "
"in model length units. Smaller = smoother curves but more "
"triangles and slower load.");
form->addRow("Deflection Tolerance", deflection_tolerance_spin_);
angular_tolerance_spin_ = new QDoubleSpinBox(this);
angular_tolerance_spin_->setRange(0.000001, 3.141592);
angular_tolerance_spin_->setDecimals(6);
angular_tolerance_spin_->setSingleStep(0.05);
angular_tolerance_spin_->setToolTip(
"Maximum angle (radians) between adjacent facet normals on a "
"curved surface. Smaller = smoother shading but more triangles.");
form->addRow("Angular Tolerance", angular_tolerance_spin_);
min_pixel_radius_spin_ = new QDoubleSpinBox(this);
min_pixel_radius_spin_->setRange(0.0, 100.0);
min_pixel_radius_spin_->setDecimals(2);
min_pixel_radius_spin_->setSingleStep(0.5);
min_pixel_radius_spin_->setToolTip(
"Minimum projected sphere radius (in pixels) for an instance to "
"be drawn. Bigger = faster but more pop-in on small detail.");
form->addRow("Min Pixel Radius", min_pixel_radius_spin_);
motion_min_pixel_radius_spin_ = new QDoubleSpinBox(this);
motion_min_pixel_radius_spin_->setRange(0.0, 100.0);
motion_min_pixel_radius_spin_->setDecimals(2);
motion_min_pixel_radius_spin_->setSingleStep(1.0);
motion_min_pixel_radius_spin_->setToolTip(
"Aggressive cull threshold while the camera is moving. 0 = no "
"motion boost (motion uses the same threshold as still frames). "
"Big perceived FPS win on heavy scenes.");
form->addRow("Motion Min Pixel Radius", motion_min_pixel_radius_spin_);
lod1_pixel_threshold_spin_ = new QDoubleSpinBox(this);
lod1_pixel_threshold_spin_->setRange(0.0, 1000.0);
lod1_pixel_threshold_spin_->setDecimals(1);
lod1_pixel_threshold_spin_->setSingleStep(1.0);
lod1_pixel_threshold_spin_->setToolTip(
"Pixel radius below which an instance switches to its LOD1 "
"representation. 0 disables LOD1 entirely (always draw LOD0).");
form->addRow("LOD1 Pixel Threshold", lod1_pixel_threshold_spin_);
hiz_enabled_check_ = new QCheckBox(this);
hiz_enabled_check_->setToolTip(
"Enable HiZ (hierarchical Z) occlusion culling. Hides geometry "
"behind opaque blockers based on a downsampled depth pyramid "
"from the previous frame. Big perf win on dense interiors.");
form->addRow("HiZ Occlusion", hiz_enabled_check_);
hiz_resolution_spin_ = new QSpinBox(this);
hiz_resolution_spin_->setRange(64, 4096);
hiz_resolution_spin_->setSingleStep(64);
hiz_resolution_spin_->setToolTip(
"Base HiZ pyramid width in texels (height tracks aspect). "
"Bigger = tighter occlusion but more readback bandwidth. "
"Changes take effect on next viewport reinitialization.");
form->addRow("HiZ Resolution", hiz_resolution_spin_);
auto* button_box = new QDialogButtonBox(
QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
auto* root = new QVBoxLayout(this);
root->addLayout(form);
root->addWidget(button_box);
connect(button_box, &QDialogButtonBox::accepted, this, &SettingsWindow::onAccepted);
connect(button_box, &QDialogButtonBox::rejected, this, &SettingsWindow::reject);
}
void SettingsWindow::showEvent(QShowEvent* event) {
// Re-sync widgets from the persisted settings every time the dialog is
// shown, so a previous Cancel doesn't leave stale text in the field.
syncFromSettings();
QDialog::showEvent(event);
}
void SettingsWindow::syncFromSettings() {
geometry_library_edit_->setText(AppSettings::instance().geometryLibrary());
show_stats_check_->setChecked(AppSettings::instance().showStats());
backface_culling_check_->setChecked(AppSettings::instance().backfaceCulling());
void_limit_spin_->setValue(AppSettings::instance().voidLimit());
deflection_tolerance_spin_->setValue(AppSettings::instance().deflectionTolerance());
angular_tolerance_spin_->setValue(AppSettings::instance().angularTolerance());
min_pixel_radius_spin_->setValue(AppSettings::instance().minPixelRadius());
motion_min_pixel_radius_spin_->setValue(AppSettings::instance().motionMinPixelRadius());
lod1_pixel_threshold_spin_->setValue(AppSettings::instance().lod1PixelThreshold());
hiz_enabled_check_->setChecked(AppSettings::instance().hizEnabled());
hiz_resolution_spin_->setValue(AppSettings::instance().hizResolution());
}
void SettingsWindow::onAccepted() {
AppSettings::instance().setGeometryLibrary(geometry_library_edit_->text());
AppSettings::instance().setShowStats(show_stats_check_->isChecked());
AppSettings::instance().setBackfaceCulling(backface_culling_check_->isChecked());
AppSettings::instance().setVoidLimit(void_limit_spin_->value());
AppSettings::instance().setDeflectionTolerance(deflection_tolerance_spin_->value());
AppSettings::instance().setAngularTolerance(angular_tolerance_spin_->value());
AppSettings::instance().setMinPixelRadius(min_pixel_radius_spin_->value());
AppSettings::instance().setMotionMinPixelRadius(motion_min_pixel_radius_spin_->value());
AppSettings::instance().setLod1PixelThreshold(lod1_pixel_threshold_spin_->value());
AppSettings::instance().setHizEnabled(hiz_enabled_check_->isChecked());
AppSettings::instance().setHizResolution(hiz_resolution_spin_->value());
accept();
}
+77
View File
@@ -0,0 +1,77 @@
// 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 "Buttons.h"
#include "SvgIcon.h"
#include <QFrame>
#include <QHBoxLayout>
#include <QLabel>
#include <QToolButton>
#include <QVBoxLayout>
namespace ifcinterface::components::buttons {
QToolButton* makeButton(const QString& text,
const QString& icon_path,
QWidget* parent) {
auto* button = new QToolButton(parent);
button->setObjectName("ribbonButton");
button->setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
button->setIcon(components::icons::makeAccentSvgIcon(icon_path));
button->setIconSize(QSize(20, 20));
button->setText(text);
button->setMinimumSize(QSize(90, 68));
button->setAutoRaise(false);
return button;
}
QWidget* makeButtonGroup(const QString& title,
const QList<QToolButton*>& buttons,
QWidget* parent,
bool trailing_separator,
int vertical_spacing) {
auto* group = new QFrame(parent);
group->setObjectName("ribbonGroup");
group->setProperty("separator", trailing_separator);
auto* group_layout = new QVBoxLayout(group);
group_layout->setContentsMargins(8, 6, 8, 4);
group_layout->setSpacing(vertical_spacing);
auto* button_row = new QHBoxLayout();
button_row->setContentsMargins(0, 0, 0, 0);
button_row->setSpacing(4);
for (auto* button : buttons) {
button_row->addWidget(button);
}
auto* label = new QLabel(title, group);
label->setObjectName("ribbonGroupLabel");
label->setProperty("textRole", "secondary");
label->setAlignment(Qt::AlignCenter);
group_layout->addLayout(button_row);
group_layout->addWidget(label);
return group;
}
} // namespace ifcinterface::components::buttons
+43
View File
@@ -0,0 +1,43 @@
// 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_COMPONENTS_BUTTONS_H
#define IFCINTERFACE_COMPONENTS_BUTTONS_H
#include <QList>
class QToolButton;
class QWidget;
namespace ifcinterface::components::buttons {
QToolButton* makeButton(const QString& text,
const QString& icon_path,
QWidget* parent);
QWidget* makeButtonGroup(const QString& title,
const QList<QToolButton*>& buttons,
QWidget* parent,
bool trailing_separator = true,
int vertical_spacing = 4);
} // namespace ifcinterface::components::buttons
#endif
+143
View File
@@ -0,0 +1,143 @@
// 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 "Dialog.h"
#include "Style.h"
#include "Tabs.h"
#include <QDialog>
#include <QFrame>
#include <QScrollArea>
#include <QVBoxLayout>
namespace ifcinterface::components {
Dialog::Dialog(QWidget* parent, bool scrollable)
: QDialog(parent)
{
auto* outer_layout = new QVBoxLayout(this);
outer_layout->setContentsMargins(style::metrics::padding,
style::metrics::padding,
style::metrics::padding,
style::metrics::padding);
outer_layout->setSpacing(0);
auto* frame = new QFrame(this);
frame->setObjectName("panel");
auto* frame_layout = new QVBoxLayout(frame);
frame_layout->setContentsMargins(0,
style::metrics::section_body_padding,
0,
style::metrics::section_body_padding);
frame_layout->setSpacing(0);
auto* scroll = new QScrollArea(frame);
scroll->setWidgetResizable(true);
scroll->setFrameShape(QFrame::NoFrame);
auto* scroll_body = new QWidget(scroll);
scroll_body->setObjectName("panelScrollBody");
body_layout_ = new QVBoxLayout(scroll_body);
body_layout_->setContentsMargins(0, 0, 0, 0);
body_layout_->setSpacing(style::metrics::section_body_padding);
body_layout_->setAlignment(Qt::AlignTop);
scroll->setWidget(scroll_body);
frame_layout->addWidget(scroll, scrollable ? 1 : 0);
auto* footer = new QWidget(frame);
footer_layout_ = new QVBoxLayout(footer);
footer_layout_->setContentsMargins(style::metrics::section_body_padding,
style::metrics::section_body_padding,
style::metrics::section_body_padding,
0);
footer_layout_->setSpacing(style::metrics::section_body_padding);
footer_layout_->setAlignment(Qt::AlignTop);
frame_layout->addWidget(footer);
outer_layout->addWidget(frame);
}
void Dialog::addBodyWidget(QWidget* widget) {
body_layout_->addWidget(widget);
}
void Dialog::addFooterWidget(QWidget* widget) {
footer_layout_->addWidget(widget);
}
TabbedDialog::TabbedDialog(QWidget* parent)
: QDialog(parent)
{
auto* outer_layout = new QVBoxLayout(this);
outer_layout->setContentsMargins(style::metrics::padding,
style::metrics::padding,
style::metrics::padding,
style::metrics::padding);
outer_layout->setSpacing(0);
auto* frame = new QFrame(this);
frame->setObjectName("panel");
auto* frame_layout = new QVBoxLayout(frame);
frame_layout->setContentsMargins(0,
style::metrics::section_body_padding,
0,
style::metrics::section_body_padding);
frame_layout->setSpacing(0);
tabs_ = new TabWidget(frame);
frame_layout->addWidget(tabs_, 1);
auto* footer = new QWidget(frame);
footer_layout_ = new QVBoxLayout(footer);
footer_layout_->setContentsMargins(style::metrics::section_body_padding,
style::metrics::section_body_padding,
style::metrics::section_body_padding,
0);
footer_layout_->setSpacing(style::metrics::section_body_padding);
footer_layout_->setAlignment(Qt::AlignTop);
frame_layout->addWidget(footer);
outer_layout->addWidget(frame);
}
void TabbedDialog::addTab(const QString& title, QWidget* widget) {
auto* scroll = new QScrollArea(tabs_);
scroll->setWidgetResizable(true);
scroll->setFrameShape(QFrame::NoFrame);
auto* scroll_body = new QWidget(scroll);
scroll_body->setObjectName("panelScrollBody");
auto* layout = new QVBoxLayout(scroll_body);
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(style::metrics::section_body_padding);
layout->setAlignment(Qt::AlignTop);
layout->addWidget(widget);
scroll->setWidget(scroll_body);
tabs_->addTab(scroll, title);
}
void TabbedDialog::addFooterWidget(QWidget* widget) {
footer_layout_->addWidget(widget);
}
} // namespace ifcinterface::components
+63
View File
@@ -0,0 +1,63 @@
// 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_COMPONENTS_DIALOG_H
#define IFCINTERFACE_COMPONENTS_DIALOG_H
#include <QDialog>
class QVBoxLayout;
class QWidget;
class QTabWidget;
namespace ifcinterface::components {
class Dialog : public QDialog {
Q_OBJECT
public:
explicit Dialog(QWidget* parent = nullptr,
bool scrollable = false);
void addBodyWidget(QWidget* widget);
void addFooterWidget(QWidget* widget);
private:
QVBoxLayout* body_layout_ = nullptr;
QVBoxLayout* footer_layout_ = nullptr;
};
class TabbedDialog : public QDialog {
Q_OBJECT
public:
explicit TabbedDialog(QWidget* parent = nullptr);
void addTab(const QString& title, QWidget* widget);
void addFooterWidget(QWidget* widget);
private:
QTabWidget* tabs_ = nullptr;
QVBoxLayout* footer_layout_ = nullptr;
};
} // namespace ifcinterface::components
#endif
@@ -0,0 +1,72 @@
// 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 "KeyValueTable.h"
#include "SvgIcon.h"
#include <QGridLayout>
#include <QLabel>
namespace ifcinterface::components {
KeyValueTable::KeyValueTable(const QList<KeyValueTableRow>& rows, QWidget* parent)
: QWidget(parent)
{
setObjectName("keyValueTable");
auto* layout = new QGridLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
layout->setHorizontalSpacing(12);
layout->setVerticalSpacing(6);
layout->setColumnStretch(1, 1);
int row_index = 0;
for (const auto& row_data : rows) {
auto* key = new QLabel(row_data.key, this);
key->setProperty("textRole", "secondary");
if (row_data.key_minimum_width > 0) {
key->setMinimumWidth(row_data.key_minimum_width);
}
auto* value = new QLabel(row_data.value, this);
value->setObjectName(row_data.value_object_name.isEmpty()
? "keyValueValueLabel"
: row_data.value_object_name);
value->setWordWrap(true);
value->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
layout->addWidget(key, row_index, 0, Qt::AlignLeft | Qt::AlignTop);
layout->addWidget(value, row_index, 1);
if (!row_data.trailing_icon_path.isEmpty()) {
auto* icon = new QLabel(this);
icon->setObjectName(row_data.trailing_icon_object_name.isEmpty()
? "keyValueTrailingIconLabel"
: row_data.trailing_icon_object_name);
icon->setPixmap(icons::makeSvgPixmap(row_data.trailing_icon_path, QSize(14, 14)));
icon->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
layout->addWidget(icon, row_index, 2, Qt::AlignRight | Qt::AlignTop);
}
++row_index;
}
}
} // namespace ifcinterface::components
@@ -0,0 +1,47 @@
// 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_COMPONENTS_KEYVALUETABLE_H
#define IFCINTERFACE_COMPONENTS_KEYVALUETABLE_H
#include <QList>
#include <QString>
#include <QWidget>
namespace ifcinterface::components {
struct KeyValueTableRow {
QString key;
QString value;
QString value_object_name = "keyValueValueLabel";
QString trailing_icon_path;
QString trailing_icon_object_name;
int key_minimum_width = 0;
};
class KeyValueTable : public QWidget {
Q_OBJECT
public:
explicit KeyValueTable(const QList<KeyValueTableRow>& rows, QWidget* parent = nullptr);
};
} // namespace ifcinterface::components
#endif
+140
View File
@@ -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 "Panel.h"
#include "Style.h"
#include "SvgIcon.h"
#include <QDockWidget>
#include <QFrame>
#include <QHBoxLayout>
#include <QLabel>
#include <QMenu>
#include <QScrollArea>
#include <QToolButton>
#include <QVBoxLayout>
namespace ifcinterface::components {
namespace {
class DockTitleBar : public QWidget {
public:
explicit DockTitleBar(const QString& title,
bool has_settings = false,
std::function<void()> on_settings = {},
QWidget* parent = nullptr)
: QWidget(parent)
{
auto* layout = new QHBoxLayout(this);
layout->setContentsMargins(10, 6, 6, 6);
layout->setSpacing(6);
auto* text = new QLabel(title.toUpper(), this);
text->setObjectName("panelTitleText");
layout->addWidget(text);
layout->addStretch(1);
if (has_settings) {
auto* settings = new QToolButton(this);
settings->setIcon(icons::makeSvgIcon(":/icons/settings.svg"));
settings->setAutoRaise(true);
settings->setCursor(Qt::ArrowCursor);
settings->setFixedSize(18, 18);
settings->setObjectName("panelTitleButton");
settings->setToolTip(QString("%1 settings").arg(title));
connect(settings, &QToolButton::clicked, this, [on_settings = std::move(on_settings)]() {
if (on_settings) on_settings();
});
layout->addWidget(settings);
}
}
};
} // namespace
Panel::Panel(const QString& title, QWidget* content, QWidget* parent, bool has_settings, bool scrollable)
: QDockWidget(title, parent)
{
auto* outer = new QFrame();
auto* outer_layout = new QVBoxLayout(outer);
outer_layout->setContentsMargins(style::metrics::padding,
style::metrics::padding,
style::metrics::padding,
style::metrics::padding);
outer_layout->setSpacing(0);
auto* frame = new QFrame(outer);
frame->setObjectName("panel");
auto* frame_layout = new QVBoxLayout(frame);
frame_layout->setContentsMargins(0, style::metrics::section_body_padding, 0, style::metrics::section_body_padding);
frame_layout->setSpacing(0);
if (scrollable) {
auto* scroll = new QScrollArea(frame);
scroll->setWidgetResizable(true);
scroll->setFrameShape(QFrame::NoFrame);
auto* scroll_body = new QWidget(scroll);
scroll_body->setObjectName("panelScrollBody");
body_layout_ = new QVBoxLayout(scroll_body);
body_layout_->setContentsMargins(0, 0, 0, 0);
body_layout_->setSpacing(style::metrics::section_body_padding);
body_layout_->setAlignment(Qt::AlignTop);
scroll->setWidget(scroll_body);
frame_layout->addWidget(scroll);
} else {
auto* body = new QWidget(frame);
body_layout_ = new QVBoxLayout(body);
body_layout_->setContentsMargins(0, 0, 0, 0);
body_layout_->setSpacing(style::metrics::section_body_padding);
body_layout_->setAlignment(Qt::AlignTop);
frame_layout->addWidget(body);
}
if (content) {
addBodyWidget(content);
}
outer_layout->addWidget(frame);
setObjectName(title);
setFeatures(QDockWidget::DockWidgetMovable |
QDockWidget::DockWidgetFloatable |
QDockWidget::DockWidgetClosable);
setTitleBarWidget(new DockTitleBar(title, has_settings, [this]() {
emit settingsRequested();
}, this));
setWidget(outer);
}
void Panel::addBodyWidget(QWidget* widget) {
body_layout_->addWidget(widget);
}
void Panel::clearBodyWidgets() {
while (auto* item = body_layout_->takeAt(0)) {
if (auto* widget = item->widget()) widget->deleteLater();
delete item;
}
}
} // namespace ifcinterface::components
+53
View File
@@ -0,0 +1,53 @@
// 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_COMPONENTS_PANEL_PANELCHROME_H
#define IFCINTERFACE_COMPONENTS_PANEL_PANELCHROME_H
#include <QDockWidget>
class QWidget;
class QVBoxLayout;
namespace ifcinterface::components {
class Panel : public QDockWidget {
Q_OBJECT
public:
explicit Panel(const QString& title,
QWidget* content = nullptr,
QWidget* parent = nullptr,
bool has_settings = false,
bool scrollable = false);
void addBodyWidget(QWidget* widget);
void clearBodyWidgets();
signals:
void settingsRequested();
private:
QVBoxLayout* body_layout_ = nullptr;
};
} // namespace ifcinterface::components
#endif
+118
View File
@@ -0,0 +1,118 @@
// 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 "Section.h"
#include "Style.h"
#include <QFrame>
#include <QHBoxLayout>
#include <QSizePolicy>
#include <QToolButton>
#include <QVBoxLayout>
namespace ifcinterface::components {
Section::Section(const QString& title, SectionHeaderMode header_mode, QWidget* parent)
: QWidget(parent)
{
setObjectName("panelSection");
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum);
auto* layout = new QVBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(style::metrics::padding);
if (header_mode == SectionHeaderMode::Visible) {
auto* header = new QFrame(this);
header->setObjectName("panelSectionHeader");
header_layout_ = new QHBoxLayout(header);
header_layout_->setContentsMargins(0, 0, 0, 0);
header_layout_->setSpacing(style::metrics::padding);
toggle_button_ = new QToolButton(header);
toggle_button_->setObjectName("panelSectionHeaderButton");
toggle_button_->setText(title);
toggle_button_->setCheckable(true);
toggle_button_->setChecked(true);
toggle_button_->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
toggle_button_->setArrowType(Qt::DownArrow);
header_layout_->addWidget(toggle_button_);
header_layout_->addStretch(1);
layout->addWidget(header);
connect(toggle_button_, &QToolButton::toggled, this, [this](bool expanded) {
toggle_button_->setArrowType(expanded ? Qt::DownArrow : Qt::RightArrow);
body_->setVisible(expanded);
body_->setMaximumHeight(expanded ? QWIDGETSIZE_MAX : 0);
body_->setMinimumHeight(0);
setSizePolicy(QSizePolicy::Preferred,
body_expanding_ && expanded ? QSizePolicy::Expanding : QSizePolicy::Maximum);
updateGeometry();
});
}
body_ = new QWidget(this);
body_->setObjectName("panelSectionBody");
body_layout_ = new QVBoxLayout(body_);
body_layout_->setContentsMargins(style::metrics::section_body_padding,
0,
style::metrics::section_body_padding,
0);
body_layout_->setSpacing(style::metrics::padding);
body_->setMinimumHeight(0);
layout->addWidget(body_);
}
void Section::addBodyWidget(QWidget* widget) {
body_layout_->addWidget(widget);
}
void Section::clearBody() {
while (auto* item = body_layout_->takeAt(0)) {
if (auto* widget = item->widget()) widget->deleteLater();
delete item;
}
}
void Section::addHeaderWidget(QWidget* widget) {
if (!header_layout_) return;
header_layout_->addWidget(widget);
}
bool Section::isExpanded() const {
return !toggle_button_ || toggle_button_->isChecked();
}
void Section::setExpanded(bool expanded) {
if (!toggle_button_) return;
toggle_button_->setChecked(expanded);
}
void Section::setBodyExpanding(bool expanding) {
body_expanding_ = expanding;
body_->setSizePolicy(QSizePolicy::Preferred, expanding ? QSizePolicy::Expanding : QSizePolicy::Preferred);
body_layout_->setStretch(0, expanding ? 1 : 0);
setSizePolicy(QSizePolicy::Preferred,
expanding && isExpanded() ? QSizePolicy::Expanding : QSizePolicy::Maximum);
updateGeometry();
}
} // namespace ifcinterface::components
+61
View File
@@ -0,0 +1,61 @@
// 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_COMPONENTS_SECTION_H
#define IFCINTERFACE_COMPONENTS_SECTION_H
#include <QWidget>
class QHBoxLayout;
class QToolButton;
class QVBoxLayout;
namespace ifcinterface::components {
enum class SectionHeaderMode {
Visible,
Hidden,
};
class Section : public QWidget {
Q_OBJECT
public:
explicit Section(const QString& title,
SectionHeaderMode header_mode = SectionHeaderMode::Visible,
QWidget* parent = nullptr);
void addBodyWidget(QWidget* widget);
void clearBody();
void addHeaderWidget(QWidget* widget);
bool isExpanded() const;
void setExpanded(bool expanded);
void setBodyExpanding(bool expanding);
private:
QWidget* body_ = nullptr;
QVBoxLayout* body_layout_ = nullptr;
QHBoxLayout* header_layout_ = nullptr;
QToolButton* toggle_button_ = nullptr;
bool body_expanding_ = false;
};
} // namespace ifcinterface::components
#endif
+497
View File
@@ -0,0 +1,497 @@
// 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 "Style.h"
#include "../InterfaceSettings.h"
namespace ifcinterface::components::style {
QString buildAppStyleSheet() {
const auto& theme = ifcinterface::InterfaceSettings::instance();
QString stylesheet = QStringLiteral(R"(
QMainWindow#appWindow {
background: ${app_background};
color: ${primary_text};
selection-background-color: ${selection_background};
selection-color: ${selection_text};
}
QDialog#appDialog {
background: ${app_background};
color: ${primary_text};
}
QMessageBox,
QMessageBox QWidget,
QMessageBox QLabel {
background: ${app_background};
color: ${primary_text};
}
QFileDialog,
QFileDialog QWidget,
QFileDialog QStackedWidget,
QFileDialog QSplitter {
background: ${app_background};
color: ${primary_text};
}
QTabBar#appTabBar {
background: ${tab_bar_background};
}
QTabBar#appTabBar::tab {
background: ${tab_background};
color: ${secondary_text};
padding: 8px 14px;
margin-right: 2px;
border-bottom: 2px solid transparent;
}
QTabBar#appTabBar::tab:selected {
color: ${primary_text};
border-bottom: 2px solid ${selection_background};
}
QTabBar#appTabBar::tab:hover {
color: ${hover_text};
}
QFrame#ribbonBand {
background: ${ribbon_background};
border-top: 1px solid ${border};
}
QTabWidget#appTabWidget::pane {
border: none;
background: transparent;
}
QFrame#ribbonPage {
background: transparent;
}
QFrame#ribbonGroup {
background: transparent;
border-right: 1px solid ${border};
}
QFrame#ribbonGroup[separator="false"] {
border-right: none;
}
QLabel#ribbonGroupLabel {
font-size: ${font_small}px;
font-weight: 600;
}
QToolButton#ribbonButton {
background: transparent;
border: none;
padding: 6px 4px 4px 4px;
color: ${primary_text};
}
QToolButton#ribbonButton:hover {
background: ${ribbon_button_hover};
}
QToolButton#ribbonButton:pressed {
background: ${ribbon_button_pressed};
}
QFrame#viewportShell {
background: ${viewport_shell_background};
border-top: none;
}
QFrame#viewportFrame {
border: 1px solid ${border};
}
QDockWidget {
color: ${primary_text};
}
QLabel {
color: ${primary_text};
background: transparent;
}
QAbstractItemView,
QTreeWidget,
QListWidget,
QTableWidget,
QLineEdit,
QComboBox,
QSpinBox,
QDoubleSpinBox,
QCheckBox,
QRadioButton,
QPushButton,
QToolButton {
color: ${primary_text};
}
QLabel[textRole="secondary"] {
color: ${secondary_text};
}
QLabel[textRole="disabled"] {
color: ${disabled_text};
}
QLabel[textRole="warning"] {
color: ${warning_text};
}
QLabel#panelTitleText {
color: ${primary_text};
font-size: ${font_small}px;
font-weight: 600;
}
QToolButton#panelTitleButton {
border: none;
background: transparent;
}
QToolButton#panelTitleButton:hover {
color: ${hover_text};
background: ${ribbon_button_hover};
}
QFrame#panel {
background: ${panel_background};
border: 1px solid ${border};
border-radius: ${panel_radius}px;
}
QTreeWidget, QListWidget, QTableWidget, QAbstractScrollArea {
background: ${panel_background};
border: none;
outline: none;
gridline-color: ${border};
}
QTableWidget#modelCoordinatesTable {
font-size: ${font_small}px;
}
QTableWidget#modelCoordinatesTable QHeaderView::section {
font-size: ${font_small}px;
}
QTreeWidget::viewport, QListWidget::viewport, QTableWidget::viewport {
background: ${panel_background};
}
QHeaderView::section {
background: ${control_background};
color: ${primary_text};
border: none;
border-bottom: 1px solid ${border};
padding: 7px 8px;
font-weight: 600;
}
QTableCornerButton::section {
background: ${control_background};
border: none;
}
QScrollArea {
background: ${panel_background};
border: none;
}
QScrollArea > QWidget > QWidget {
background: ${panel_background};
}
QLineEdit {
background: ${control_background};
border: 1px solid ${border};
border-radius: ${panel_radius}px;
padding: ${padding}px ${padding}px;
color: ${primary_text};
}
QLineEdit:focus {
border: 1px solid ${control_border_focus};
}
QComboBox {
background: ${control_background};
border: 1px solid ${border};
border-radius: ${panel_radius}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};
selection-background-color: ${selection_background};
selection-color: ${selection_text};
}
QSpinBox, QDoubleSpinBox {
background: ${control_background};
border: 1px solid ${border};
border-radius: ${panel_radius}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;
}
QCheckBox::indicator {
width: 16px;
height: 16px;
border: 1px solid ${border};
border-radius: ${panel_radius}px;
background: ${control_background};
}
QCheckBox::indicator:hover {
background: ${ribbon_button_hover};
}
QCheckBox::indicator:checked {
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};
border-radius: ${panel_radius}px;
padding: ${padding}px ${padding}px;
}
QPushButton:hover {
background: ${ribbon_button_hover};
}
QPushButton:pressed {
background: ${ribbon_button_pressed};
}
QMenu {
background: ${panel_background};
color: ${primary_text};
border: 1px solid ${border};
padding: 4px;
}
QMenu::item {
background: transparent;
color: ${primary_text};
padding: 6px 24px 6px 10px;
border-radius: ${panel_radius}px;
}
QMenu::item:selected {
background: ${selection_background};
color: ${selection_text};
}
QMenu::item:disabled {
color: ${disabled_text};
}
QMenu::separator {
height: 1px;
background: ${border};
margin: 4px 8px;
}
QMenu::icon {
padding-left: 2px;
}
QFrame#entityClassBox,
QGroupBox#propertySetBox {
background: ${box_background};
border: 1px solid ${border};
border-radius: ${panel_radius}px;
}
QLabel#entityClassLabel {
color: ${primary_text};
font-weight: 700;
background: transparent;
}
QWidget#keyValueTable {
background: transparent;
}
QTreeView::item, QListView::item, QTableView::item {
padding: 4px;
}
QScrollBar:vertical {
background: transparent;
width: 10px;
margin: 2px 2px 2px 0;
}
QScrollBar:horizontal {
background: transparent;
height: 10px;
margin: 0 2px 2px 2px;
}
QScrollBar::handle:vertical, QScrollBar::handle:horizontal {
background: ${scroll_handle};
border-radius: ${panel_radius}px;
min-height: 24px;
min-width: 24px;
}
QScrollBar::handle:vertical:hover, QScrollBar::handle:horizontal:hover {
background: ${scroll_handle_hover};
}
QScrollBar::add-line, QScrollBar::sub-line,
QScrollBar::add-page, QScrollBar::sub-page {
background: transparent;
border: none;
}
QStatusBar {
background: ${status_background};
}
QStatusBar QLabel {
color: ${secondary_text};
background: transparent;
border: none;
padding: 2px 8px;
}
QGroupBox {
background: transparent;
border: 1px solid ${border};
border-radius: ${panel_radius}px;
margin-top: 10px;
padding-top: 10px;
}
QGroupBox#propertySetBox::title {
subcontrol-origin: margin;
left: ${padding}px;
padding: 0 4px;
color: ${primary_text};
}
QGroupBox#propertySetBox > QWidget {
background: ${box_background};
}
QWidget#panelSection {
background: transparent;
}
QWidget#panelSectionFilterWrapper {
background: transparent;
}
QLabel#keyValueTrailingIconLabel {
background: transparent;
}
QWidget#panelScrollBody {
background: ${panel_background};
}
QFrame#panelSectionHeader {
background: ${section_header_background};
}
QToolButton#panelSectionHeaderButton {
background: transparent;
border: none;
color: ${primary_text};
font-weight: 700;
text-align: left;
padding: ${section_header_padding}px;
margin: 0;
}
QToolButton#panelSectionHeaderButton:hover {
color: ${hover_text};
}
QToolButton#panelSectionHeaderButton::menu-indicator {
image: none;
width: 0;
}
QToolButton#panelSectionFilterToggle {
background: transparent;
border: none;
padding: ${section_header_padding}px;
}
QToolButton#panelSectionFilterToggle:hover {
background: ${ribbon_button_hover};
}
QWidget#panelSectionBody {
background: transparent;
}
QLabel#keyValueValueLabel {
color: ${primary_text};
background: transparent;
}
)");
stylesheet.replace("${panel_radius}", QString::number(metrics::panel_radius));
stylesheet.replace("${padding}", QString::number(metrics::padding));
stylesheet.replace("${section_header_padding}", QString::number(metrics::section_header_padding));
stylesheet.replace("${font_small}", QString::number(typography::small));
stylesheet.replace("${app_background}", theme.color("app_background"));
stylesheet.replace("${border}", theme.color("border"));
stylesheet.replace("${selection_background}", theme.color("selection_background"));
stylesheet.replace("${selection_text}", theme.color("selection_text"));
stylesheet.replace("${tab_bar_background}", theme.color("tab_bar_background"));
stylesheet.replace("${tab_background}", theme.color("tab_background"));
stylesheet.replace("${hover_text}", theme.color("hover_text"));
stylesheet.replace("${ribbon_background}", theme.color("ribbon_background"));
stylesheet.replace("${ribbon_button_hover}", theme.color("ribbon_button_hover"));
stylesheet.replace("${ribbon_button_pressed}", theme.color("ribbon_button_pressed"));
stylesheet.replace("${viewport_shell_background}", theme.color("viewport_shell_background"));
stylesheet.replace("${viewport_background}", theme.color("viewport_background"));
stylesheet.replace("${panel_background}", theme.color("panel_background"));
stylesheet.replace("${control_background}", theme.color("control_background"));
stylesheet.replace("${control_border_focus}", theme.color("control_border_focus"));
stylesheet.replace("${box_background}", theme.color("box_background"));
stylesheet.replace("${scroll_handle}", theme.color("scroll_handle"));
stylesheet.replace("${scroll_handle_hover}", theme.color("scroll_handle_hover"));
stylesheet.replace("${status_background}", theme.color("status_background"));
stylesheet.replace("${section_header_background}", theme.color("section_header_background"));
stylesheet.replace("${primary_text}", theme.color("primary_text"));
stylesheet.replace("${secondary_text}", theme.color("secondary_text"));
stylesheet.replace("${disabled_text}", theme.color("disabled_text"));
stylesheet.replace("${warning_text}", theme.color("warning_text"));
return stylesheet;
}
} // namespace ifcinterface::components::style
+77
View File
@@ -0,0 +1,77 @@
// 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_COMPONENTS_STYLE_H
#define IFCINTERFACE_COMPONENTS_STYLE_H
#include <QString>
namespace ifcinterface::components::style::metrics {
inline constexpr int padding = 6;
inline constexpr int section_body_padding = 10;
inline constexpr int section_header_padding = 2;
inline constexpr int panel_radius = 3;
} // namespace ifcinterface::components::style::metrics
namespace ifcinterface::components::style::typography {
inline constexpr int small = 10;
} // namespace ifcinterface::components::style::typography
namespace ifcinterface::components::style::palette {
inline constexpr auto app_background = "#26292f";
inline constexpr auto border = "#3e444e";
inline constexpr auto selection_background = "#39b54a";
inline constexpr auto tab_bar_background = "#26292f";
inline constexpr auto tab_background = "#31353d";
inline constexpr auto ribbon_background = "#31353d";
inline constexpr auto ribbon_button_hover = "#3a3f48";
inline constexpr auto ribbon_button_pressed = "#24282f";
inline constexpr auto viewport_shell_background = "#202329";
inline constexpr auto viewport_background = "#2e3338";
inline constexpr auto panel_background = "#2b2f36";
inline constexpr auto control_background = "#31353d";
inline constexpr auto control_border_focus = "#5b6472";
inline constexpr auto box_background = "#26292f";
inline constexpr auto scroll_handle = "#525a67";
inline constexpr auto scroll_handle_hover = "#697385";
inline constexpr auto status_background = "#26292f";
inline constexpr auto section_header_background = "#26292f";
inline constexpr auto primary_text = "#d0d5dd";
inline constexpr auto secondary_text = "#9aa4b3";
inline constexpr auto disabled_text = "#8f98a6";
inline constexpr auto warning_text = "#e4b35a";
inline constexpr auto selection_text = "#14161a";
inline constexpr auto hover_text = "#ffffff";
} // namespace ifcinterface::components::style::palette
namespace ifcinterface::components::style {
QString buildAppStyleSheet();
} // namespace ifcinterface::components::style
#endif
+86
View File
@@ -0,0 +1,86 @@
// 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 "SvgIcon.h"
#include "../InterfaceSettings.h"
#include <QFile>
#include <QPainter>
#include <QRegularExpression>
#include <QSvgRenderer>
namespace ifcinterface::components::icons {
QPixmap renderTintedSvgPixmap(const QString& icon_path, const QString& color, const QSize& size) {
QFile file(icon_path);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
return QIcon(icon_path).pixmap(size);
}
QString svg = QString::fromUtf8(file.readAll());
svg.replace("currentColor", color, Qt::CaseSensitive);
svg.replace(QRegularExpression(R"(stroke="[^"]*")"), QString("stroke=\"%1\"").arg(color));
svg.replace(QRegularExpression(R"(fill="none")"), "fill=\"none\"");
QSvgRenderer renderer(svg.toUtf8());
QPixmap pixmap(size);
pixmap.fill(Qt::transparent);
QPainter painter(&pixmap);
renderer.render(&painter);
return pixmap;
}
QIcon makeTintedSvgIcon(const QString& icon_path, const QString& normal,
const QString& active, const QString& disabled) {
QFile file(icon_path);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
return QIcon(icon_path);
}
QIcon icon;
icon.addPixmap(renderTintedSvgPixmap(icon_path, normal, QSize(20, 20)), QIcon::Normal, QIcon::Off);
icon.addPixmap(renderTintedSvgPixmap(icon_path, active, QSize(20, 20)), QIcon::Active, QIcon::Off);
icon.addPixmap(renderTintedSvgPixmap(icon_path, active, QSize(20, 20)), QIcon::Selected, QIcon::Off);
icon.addPixmap(renderTintedSvgPixmap(icon_path, disabled, QSize(20, 20)), QIcon::Disabled, QIcon::Off);
return icon;
}
QIcon makeAccentSvgIcon(const QString& icon_path) {
const auto& theme = ifcinterface::InterfaceSettings::instance();
return makeTintedSvgIcon(icon_path,
theme.color("icon_accent_color"),
theme.color("icon_accent_active_color"),
theme.color("icon_disabled_color"));
}
QIcon makeSvgIcon(const QString& icon_path) {
const auto& theme = ifcinterface::InterfaceSettings::instance();
return makeTintedSvgIcon(icon_path,
theme.color("icon_color"),
theme.color("icon_active_color"),
theme.color("icon_disabled_color"));
}
QPixmap makeSvgPixmap(const QString& icon_path, const QSize& size) {
return renderTintedSvgPixmap(icon_path, ifcinterface::InterfaceSettings::instance().color("icon_color"), size);
}
} // namespace ifcinterface::components::icons
+42
View File
@@ -0,0 +1,42 @@
// 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_COMPONENTS_ICONS_SVGICON_H
#define IFCINTERFACE_COMPONENTS_ICONS_SVGICON_H
#include <QIcon>
#include <QPixmap>
#include <QString>
#include <QSize>
namespace ifcinterface::components::icons {
QPixmap renderTintedSvgPixmap(const QString& icon_path, const QString& color, const QSize& size);
QIcon makeTintedSvgIcon(const QString& icon_path,
const QString& normal = "#39b54a",
const QString& active = "#53c763",
const QString& disabled = "#6f7988");
QIcon makeAccentSvgIcon(const QString& icon_path);
QIcon makeSvgIcon(const QString& icon_path);
QPixmap makeSvgPixmap(const QString& icon_path, const QSize& size);
} // namespace ifcinterface::components::icons
#endif
+40
View File
@@ -0,0 +1,40 @@
// 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 "Tabs.h"
namespace ifcinterface::components {
TabBar::TabBar(QWidget* parent)
: QTabBar(parent)
{
setObjectName("appTabBar");
setExpanding(false);
setDrawBase(false);
}
TabWidget::TabWidget(QWidget* parent)
: QTabWidget(parent)
{
setObjectName("appTabWidget");
setTabBar(new TabBar(this));
}
} // namespace ifcinterface::components
+45
View File
@@ -0,0 +1,45 @@
// 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_COMPONENTS_TABS_H
#define IFCINTERFACE_COMPONENTS_TABS_H
#include <QTabBar>
#include <QTabWidget>
namespace ifcinterface::components {
class TabBar : public QTabBar {
Q_OBJECT
public:
explicit TabBar(QWidget* parent = nullptr);
};
class TabWidget : public QTabWidget {
Q_OBJECT
public:
explicit TabWidget(QWidget* parent = nullptr);
};
} // namespace ifcinterface::components
#endif
@@ -0,0 +1,8 @@
<svg width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 17C12.5523 17 13 16.5523 13 16C13 15.4477 12.5523 15 12 15C11.4477 15 11 15.4477 11 16C11 16.5523 11.4477 17 12 17Z" fill="currentColor" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M21 7.35304L21 16.647C21 16.8649 20.8819 17.0656 20.6914 17.1715L12.2914 21.8381C12.1102 21.9388 11.8898 21.9388 11.7086 21.8381L3.30861 17.1715C3.11814 17.0656 3 16.8649 3 16.647L2.99998 7.35304C2.99998 7.13514 3.11812 6.93437 3.3086 6.82855L11.7086 2.16188C11.8898 2.06121 12.1102 2.06121 12.2914 2.16188L20.6914 6.82855C20.8818 6.93437 21 7.13514 21 7.35304Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M20.5 16.7222L12.2914 12.1618C12.1102 12.0612 11.8898 12.0612 11.7086 12.1618L3.5 16.7222" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3.52844 7.29363L11.7086 11.8382C11.8898 11.9388 12.1102 11.9388 12.2914 11.8382L20.5 7.27783" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 3L12 12" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 19.5V22" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@@ -0,0 +1,8 @@
<svg width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 23C12.5523 23 13 22.5523 13 22C13 21.4477 12.5523 21 12 21C11.4477 21 11 21.4477 11 22C11 22.5523 11.4477 23 12 23Z" fill="currentColor" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3 8C3.55228 8 4 7.55228 4 7C4 6.44772 3.55228 6 3 6C2.44772 6 2 6.44772 2 7C2 7.55228 2.44772 8 3 8Z" fill="currentColor" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3 18C3.55228 18 4 17.5523 4 17C4 16.4477 3.55228 16 3 16C2.44772 16 2 16.4477 2 17C2 17.5523 2.44772 18 3 18Z" fill="currentColor" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M21 7.35304L21 16.647C21 16.8649 20.8819 17.0656 20.6914 17.1715L12.2914 21.8381C12.1102 21.9388 11.8898 21.9388 11.7086 21.8381L3.30861 17.1715C3.11814 17.0656 3 16.8649 3 16.647L2.99998 7.35304C2.99998 7.13514 3.11812 6.93437 3.3086 6.82855L11.7086 2.16188C11.8898 2.06121 12.1102 2.06121 12.2914 2.16188L20.6914 6.82855C20.8818 6.93437 21 7.13514 21 7.35304Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3.52844 7.29363L11.7086 11.8382C11.8898 11.9388 12.1102 11.9388 12.2914 11.8382L20.5 7.27783" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 21L12 12" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

+9
View File
@@ -0,0 +1,9 @@
<svg width="24" height="24" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10 9.01L10.01 8.99889" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M14 9.01L14.01 8.99889" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10 13.01L10.01 12.9989" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M14 13.01L14.01 12.9989" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10 17.01L10.01 16.9989" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M14 17.01L14.01 16.9989" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6 20.4V5.6C6 5.26863 6.26863 5 6.6 5H12V3.6C12 3.26863 12.2686 3 12.6 3H17.4C17.7314 3 18 3.26863 18 3.6V20.4C18 20.7314 17.7314 21 17.4 21H6.6C6.26863 21 6 20.7314 6 20.4Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1005 B

+6
View File
@@ -0,0 +1,6 @@
<svg width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 21H21V12C21 9.61305 20.0518 7.32387 18.364 5.63604C16.6761 3.94821 14.3869 3 12 3C9.61305 3 7.32387 3.94821 5.63604 5.63604C3.94821 7.32387 3 9.61305 3 12V21Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3 17L21 17" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9 17V13H21" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13 13V9H20" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 644 B

+3
View File
@@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 12.5L9.5 17L19 7.5" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 227 B

+9
View File
@@ -0,0 +1,9 @@
<svg width="24" height="24" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7 9.01L7.01 8.99889" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11 9.01L11.01 8.99889" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7 13.01L7.01 12.9989" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11 13.01L11.01 12.9989" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7 17.01L7.01 16.9989" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11 17.01L11.01 16.9989" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M15 21H3.6C3.26863 21 3 20.7314 3 20.4V5.6C3 5.26863 3.26863 5 3.6 5H9V3.6C9 3.26863 9.26863 3 9.6 3H14.4C14.7314 3 15 3.26863 15 3.6V9M15 21H20.4C20.7314 21 21 20.7314 21 20.4V9.6C21 9.26863 20.7314 9 20.4 9H15M15 21V17M15 9V13M15 13H17M15 13V17M15 17H17" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1,5 @@
<svg width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 6L12 12L18 12" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M21.8883 10.5C21.1645 5.68874 17.013 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22C16.1006 22 19.6248 19.5318 21.1679 16" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M17 16H21.4C21.7314 16 22 16.2686 22 16.6V21" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 562 B

@@ -0,0 +1,4 @@
<svg width="24" height="24" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 20.4V3.6C3 3.26863 3.26863 3 3.6 3H20.4C20.7314 3 21 3.26863 21 3.6V20.4C21 20.7314 20.7314 21 20.4 21H3.6C3.26863 21 3 20.7314 3 20.4Z" stroke="currentColor" stroke-width="1.5"/>
<path d="M12 8C8.72727 8 8.72727 10 8.72727 11C7.81818 11 6 11.5 6 13.5C6 15.5 7.81818 16 8.72727 16H15.2727C16.1818 16 18 15.5 18 13.5C18 11.5 16.1818 11 15.2727 11C15.2727 10 15.2727 8 12 8Z" stroke="currentColor" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 556 B

+10
View File
@@ -0,0 +1,10 @@
<!-- This file was generated with the assistance of an AI coding tool. -->
<svg width="24" height="24" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21 7.35304V16.647C21 16.8649 20.8819 17.0656 20.6914 17.1715L12.2914 21.8381C12.1102 21.9388 11.8898 21.9388 11.7086 21.8381L3.30861 17.1715C3.11814 17.0656 3 16.8649 3 16.647V7.35304C3 7.13514 3.11814 6.93437 3.30861 6.82855L11.7086 2.16188C11.8898 2.06121 12.1102 2.06121 12.2914 2.16188L20.6914 6.82855C20.8819 6.93437 21 7.13514 21 7.35304Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3.52844 7.29357L11.7086 11.8381C11.8898 11.9388 12.1102 11.9388 12.2914 11.8381L20.5 7.27777" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 21V12" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8.5 8.5L15.5 15.5" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8.5 15.5L15.5 8.5" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10.5 8.5L8.5 10.5" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M15.5 13.5L13.5 15.5" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@@ -0,0 +1,9 @@
<svg width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.52844 7.29346L9 10.3332" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M22 2L20 2M12 12L12 10C12 6.45478 14.3061 3.44817 17.5 2.39838" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M22 12L20 12M12 22L12 20C12 16.4548 14.3061 13.4482 17.5 12.3984" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 22L3.30861 17.1715C3.11813 17.0656 3 16.8649 3 16.647L2.99998 7.35304C2.99998 7.13514 3.11812 6.93437 3.3086 6.82855L11.7086 2.16188C11.8898 2.06121 12.1102 2.06121 12.2914 2.16188L15 3.66667" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 11.9999L3.52842 7.29346" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 21L12 12" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M15 13.5L15 4" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

+8
View File
@@ -0,0 +1,8 @@
<svg width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.4961 19.7165L18.4961 16.2879C18.8077 16.1099 19 15.7785 19 15.4197V9.58032C19 9.22147 18.8077 8.89012 18.4961 8.71208L12.4961 5.28351C12.1887 5.10783 11.8113 5.10783 11.5039 5.28351L5.50386 8.71208C5.19229 8.89012 5 9.22147 5 9.58032V15.4197C5 15.7785 5.19229 16.1099 5.50386 16.2879L11.5039 19.7165C11.8113 19.8922 12.1887 19.8922 12.4961 19.7165Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5.5 9.5L12 13M12 13L18.5 9.5M12 13V19.5" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3 3.01013L3.01 2.99902" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3 21.0101L3.01 20.999" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M21 3.01013L21.01 2.99902" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M21 21.0101L21.01 20.999" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1,7 @@
<svg width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.25 3C2.25 2.58579 2.58579 2.25 3 2.25H6C6.41421 2.25 6.75 2.58579 6.75 3C6.75 3.41421 6.41421 3.75 6 3.75H3.75V6C3.75 6.41421 3.41421 6.75 3 6.75C2.58579 6.75 2.25 6.41421 2.25 6V3Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M17.25 3C17.25 2.58579 17.5858 2.25 18 2.25H21C21.4142 2.25 21.75 2.58579 21.75 3V6C21.75 6.41421 21.4142 6.75 21 6.75C20.5858 6.75 20.25 6.41421 20.25 6V3.75H18C17.5858 3.75 17.25 3.41421 17.25 3Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M3 17.25C3.41421 17.25 3.75 17.5858 3.75 18V20.25H6C6.41421 20.25 6.75 20.5858 6.75 21C6.75 21.4142 6.41421 21.75 6 21.75H3C2.58579 21.75 2.25 21.4142 2.25 21V18C2.25 17.5858 2.58579 17.25 3 17.25Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M21 17.25C21.4142 17.25 21.75 17.5858 21.75 18V21C21.75 21.4142 21.4142 21.75 21 21.75H18C17.5858 21.75 17.25 21.4142 17.25 21C17.25 20.5858 17.5858 20.25 18 20.25H20.25V18C20.25 17.5858 20.5858 17.25 21 17.25Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.9004 6.6654C12.3462 6.33289 11.6538 6.33289 11.0996 6.6654L7.09963 9.0654C6.57252 9.38167 6.25 9.95131 6.25 10.566V14.4336C6.25 15.0483 6.57252 15.618 7.09963 15.9342L11.0996 18.3342C11.6538 18.6668 12.3462 18.6668 12.9004 18.3342L16.9004 15.9342C17.4275 15.618 17.75 15.0483 17.75 14.4336V10.566C17.75 9.95131 17.4275 9.38167 16.9004 9.0654L12.9004 6.6654ZM9.3642 10.6785C9.00209 10.4773 8.5455 10.6078 8.34437 10.9699C8.14324 11.332 8.27373 11.7886 8.63583 11.9898L11.25 13.4418V16.0001C11.25 16.4144 11.5858 16.7501 12 16.7501C12.4142 16.7501 12.75 16.4144 12.75 16.0001V13.4456C12.9152 13.3554 13.1243 13.241 13.3607 13.1115C13.9447 12.7916 14.6961 12.3787 15.3642 12.0077C15.7263 11.8066 15.8568 11.35 15.6557 10.9879C15.4546 10.6257 14.998 10.4952 14.6359 10.6964C13.9716 11.0653 13.223 11.4766 12.6401 11.796C12.3908 11.9325 12.172 12.0521 12.0032 12.1443L9.3642 10.6785Z" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

+8
View File
@@ -0,0 +1,8 @@
<svg width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6 3H3V6" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M18 3H21V6" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6 21H3V18" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M18 21H21V18" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12.5145 17.6913L16.5145 15.2913C16.8157 15.1106 17 14.7851 17 14.4338V10.5662C17 10.2149 16.8157 9.88942 16.5145 9.7087L12.5145 7.3087C12.1978 7.11869 11.8022 7.11869 11.4855 7.3087L7.4855 9.7087C7.1843 9.88942 7 10.2149 7 10.5662V14.4338C7 14.7851 7.1843 15.1106 7.4855 15.2913L11.4855 17.6913C11.8022 17.8813 12.1978 17.8813 12.5145 17.6913Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7.5 10.5L12 12.9995M12 12.9995C12 12.9995 15.7637 10.9492 16.5 10.5M12 12.9995V17.5" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

+5
View File
@@ -0,0 +1,5 @@
<svg width="24" height="24" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21 7.35304L21 16.647C21 16.8649 20.8819 17.0656 20.6914 17.1715L12.2914 21.8381C12.1102 21.9388 11.8898 21.9388 11.7086 21.8381L3.30861 17.1715C3.11814 17.0656 3 16.8649 3 16.647L2.99998 7.35304C2.99998 7.13514 3.11812 6.93437 3.3086 6.82855L11.7086 2.16188C11.8898 2.06121 12.1102 2.06121 12.2914 2.16188L20.6914 6.82855C20.8818 6.93437 21 7.13514 21 7.35304Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3.52844 7.29357L11.7086 11.8381C11.8898 11.9388 12.1102 11.9388 12.2914 11.8381L20.5 7.27777" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 21L12 12" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 834 B

@@ -0,0 +1,3 @@
<svg width="24" height="24" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.5027 9.96958C20.7073 10.4588 20.6154 12.1941 19.3658 12.5533L13.0605 14.3658L10.1807 20.2606C9.60996 21.4288 7.88499 21.218 7.6124 19.9468L4.67677 6.25646C4.44638 5.18204 5.5121 4.2878 6.53019 4.70126L19.5027 9.96958Z" stroke="currentColor" stroke-width="1.5"/>
</svg>

After

Width:  |  Height:  |  Size: 437 B

@@ -0,0 +1,6 @@
<svg width="24" height="24" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4 6V12C4 12 4 15 11 15C18 15 18 12 18 12V6" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11 3C18 3 18 6 18 6C18 6 18 9 11 9C4 9 4 6 4 6C4 6 4 3 11 3Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11 21C4 21 4 18 4 18V12" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M19 22V16M19 16L22 19M19 16L16 19" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 611 B

+6
View File
@@ -0,0 +1,6 @@
<!-- This file was generated with the assistance of an AI coding tool. -->
<svg width="24" height="24" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 6.5C5 4.567 8.13401 3 12 3C15.866 3 19 4.567 19 6.5C19 8.433 15.866 10 12 10C8.13401 10 5 8.433 5 6.5Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M19 6.5V12.5C19 14.433 15.866 16 12 16C8.13401 16 5 14.433 5 12.5V6.5" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M19 12.5V17.5C19 19.433 15.866 21 12 21C8.13401 21 5 19.433 5 17.5V12.5" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 688 B

@@ -0,0 +1,5 @@
<svg width="24" height="24" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6 18L18 18" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 6V14M12 14L15.5 10.5M12 14L8.5 10.5" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3 20.4V3.6C3 3.26863 3.26863 3 3.6 3H20.4C20.7314 3 21 3.26863 21 3.6V20.4C21 20.7314 20.7314 21 20.4 21H3.6C3.26863 21 3 20.7314 3 20.4Z" stroke="currentColor" stroke-width="1.5"/>
</svg>

After

Width:  |  Height:  |  Size: 528 B

+11
View File
@@ -0,0 +1,11 @@
<svg width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.463 17H10.537C10.2313 17 9.97446 16.7701 9.9407 16.4663L9.07403 8.66626C9.03454 8.31084 9.31275 8 9.67036 8L14.3296 8C14.6872 8 14.9655 8.31084 14.926 8.66626L14.0593 16.4663C14.0255 16.7701 13.7687 17 13.463 17Z" stroke="currentColor" stroke-linecap="round"/>
<path d="M4.5 7C5.88071 7 7 5.88071 7 4.5C7 3.11929 5.88071 2 4.5 2C3.11929 2 2 3.11929 2 4.5C2 5.88071 3.11929 7 4.5 7Z" stroke="currentColor" stroke-miterlimit="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4.5 4.5L9 8" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4.49988 19.5L9.5 15.5" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M19.5 4.5L15 8" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M19.5 19.5L14.5 15.5" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4.5 22C5.88071 22 7 20.8807 7 19.5C7 18.1193 5.88071 17 4.5 17C3.11929 17 2 18.1193 2 19.5C2 20.8807 3.11929 22 4.5 22Z" stroke="currentColor" stroke-miterlimit="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M19.5 7C20.8807 7 22 5.88071 22 4.5C22 3.11929 20.8807 2 19.5 2C18.1193 2 17 3.11929 17 4.5C17 5.88071 18.1193 7 19.5 7Z" stroke="currentColor" stroke-miterlimit="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M19.5 22C20.8807 22 22 20.8807 22 19.5C22 18.1193 20.8807 17 19.5 17C18.1193 17 17 18.1193 17 19.5C17 20.8807 18.1193 22 19.5 22Z" stroke="currentColor" stroke-miterlimit="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

+6
View File
@@ -0,0 +1,6 @@
<svg width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M19.5 16L17.0248 12.6038" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 17.5V14" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4.5 16L6.96895 12.6124" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3 8C6.6 16 17.4 16 21 8" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 532 B

+4
View File
@@ -0,0 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 13C6.6 5 17.4 5 21 13" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 17C10.3431 17 9 15.6569 9 14C9 12.3431 10.3431 11 12 11C13.6569 11 15 12.3431 15 14C15 15.6569 13.6569 17 12 17Z" fill="currentColor" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 465 B

+4
View File
@@ -0,0 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 13C6.6 5 17.4 5 21 13" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 17C10.3431 17 9 15.6569 9 14C9 12.3431 10.3431 11 12 11C13.6569 11 15 12.3431 15 14C15 15.6569 13.6569 17 12 17Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 426 B

@@ -0,0 +1,7 @@
<svg width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20 7.77961V15.5763C20 15.7783 19.8983 15.9668 19.7295 16.0778L13.1137 20.4253C13.0388 20.4745 12.954 20.5063 12.8652 20.5184L2.68109 21.9071C2.32083 21.9563 2.00002 21.6762 2.00002 21.3126L2 9.01164C2 8.79805 2.11354 8.60057 2.29813 8.49311L13.087 2.21229C13.3057 2.08493 13.5809 2.10857 13.7747 2.27137L19.7859 7.32017C19.9216 7.43417 20 7.60236 20 7.77961Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M2.5 9L12.727 11.922C12.8998 11.9714 13.0857 11.9407 13.2334 11.8384L19.5 7.5" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13 20.5L13 12" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M16.5 14.01L16.51 13.9989" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M22 17.01L22.01 16.9989" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

+3
View File
@@ -0,0 +1,3 @@
<svg width="24" height="24" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.99961 3H19.9997C20.552 3 20.9997 3.44764 20.9997 3.99987L20.9999 5.58569C21 5.85097 20.8946 6.10538 20.707 6.29295L14.2925 12.7071C14.105 12.8946 13.9996 13.149 13.9996 13.4142L13.9996 19.7192C13.9996 20.3698 13.3882 20.8472 12.7571 20.6894L10.7571 20.1894C10.3119 20.0781 9.99961 19.6781 9.99961 19.2192L9.99961 13.4142C9.99961 13.149 9.89425 12.8946 9.70672 12.7071L3.2925 6.29289C3.10496 6.10536 2.99961 5.851 2.99961 5.58579V4C2.99961 3.44772 3.44732 3 3.99961 3Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 674 B

@@ -0,0 +1,8 @@
<svg width="24" height="24" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 7.5V5C3 3.89543 3.89543 3 5 3H16.1716C16.702 3 17.2107 3.21071 17.5858 3.58579L20.4142 6.41421C20.7893 6.78929 21 7.29799 21 7.82843V19C21 20.1046 20.1046 21 19 21H5C3.89543 21 3 20.1046 3 19V16.5" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6 21V17" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M18 21V13.6C18 13.2686 17.7314 13 17.4 13H15" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M16 3V8.4C16 8.73137 15.7314 9 15.4 9H13.5" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8 3V6" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M1 12H12M12 12L9 9M12 12L9 15" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 942 B

+5
View File
@@ -0,0 +1,5 @@
<svg width="24" height="24" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 19V5C3 3.89543 3.89543 3 5 3H16.1716C16.702 3 17.2107 3.21071 17.5858 3.58579L20.4142 6.41421C20.7893 6.78929 21 7.29799 21 7.82843V19C21 20.1046 20.1046 21 19 21H5C3.89543 21 3 20.1046 3 19Z" stroke="currentColor" stroke-width="1.5"/>
<path d="M8.6 9H15.4C15.7314 9 16 8.73137 16 8.4V3.6C16 3.26863 15.7314 3 15.4 3H8.6C8.26863 3 8 3.26863 8 3.6V8.4C8 8.73137 8.26863 9 8.6 9Z" stroke="currentColor" stroke-width="1.5"/>
<path d="M6 13.6V21H18V13.6C18 13.2686 17.7314 13 17.4 13H6.6C6.26863 13 6 13.2686 6 13.6Z" stroke="currentColor" stroke-width="1.5"/>
</svg>

After

Width:  |  Height:  |  Size: 691 B

@@ -0,0 +1,5 @@
<svg width="24" height="24" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18 6H20H22" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M21.4 20H2.6C2.26863 20 2 19.7314 2 19.4V11H21.4C21.7314 11 22 11.2686 22 11.6V19.4C22 19.7314 21.7314 20 21.4 20Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M2 11V4.6C2 4.26863 2.26863 4 2.6 4H8.77805C8.92127 4 9.05977 4.05124 9.16852 4.14445L12.3315 6.85555C12.4402 6.94876 12.5787 7 12.722 7H14" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 632 B

+5
View File
@@ -0,0 +1,5 @@
<svg width="24" height="24" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18 6H20M22 6H20M20 6V4M20 6V8" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M21.4 20H2.6C2.26863 20 2 19.7314 2 19.4V11H21.4C21.7314 11 22 11.2686 22 11.6V19.4C22 19.7314 21.7314 20 21.4 20Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M2 11V4.6C2 4.26863 2.26863 4 2.6 4H8.77805C8.92127 4 9.05977 4.05124 9.16852 4.14445L12.3315 6.85555C12.4402 6.94876 12.5787 7 12.722 7H14" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 651 B

+3
View File
@@ -0,0 +1,3 @@
<svg width="24" height="24" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2 11V4.6C2 4.26863 2.26863 4 2.6 4H8.77805C8.92127 4 9.05977 4.05124 9.16852 4.14445L12.3315 6.85555C12.4402 6.94876 12.5787 7 12.722 7H21.4C21.7314 7 22 7.26863 22 7.6V11M2 11V19.4C2 19.7314 2.26863 20 2.6 20H21.4C21.7314 20 22 19.7314 22 19.4V11M2 11H22" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 460 B

+7
View File
@@ -0,0 +1,7 @@
<svg width="24" height="24" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6 3L6 21" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M18 3L18 21" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3 6L21 6" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.6 9H14.4C14.7314 9 15 9.26863 15 9.6V14.4C15 14.7314 14.7314 15 14.4 15H9.6C9.26863 15 9 14.7314 9 14.4V9.6C9 9.26863 9.26863 9 9.6 9Z" fill="currentColor"/>
<path d="M3 18L21 18" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 700 B

+5
View File
@@ -0,0 +1,5 @@
<svg width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10 18V15C10 13.8954 10.8954 13 12 13V13C13.1046 13 14 13.8954 14 15V18" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M2 8L11.7317 3.13416C11.9006 3.04971 12.0994 3.0497 12.2683 3.13416L22 8" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M20 11V19C20 20.1046 19.1046 21 18 21H6C4.89543 21 4 20.1046 4 19V11" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 579 B

+4
View File
@@ -0,0 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2 8L11.7317 3.13416C11.9006 3.04971 12.0994 3.0497 12.2683 3.13416L22 8" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M20 11V19C20 20.1046 19.1046 21 18 21H6C4.89543 21 4 20.1046 4 19V11" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 426 B

+12
View File
@@ -0,0 +1,12 @@
<svg width="24" height="24" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21 13.5V16.5M13.5 21H16.5" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M16.5 9H9.6C9.26863 9 9 9.26863 9 9.6V16.5" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10.5 21H9.6C9.26863 21 9 20.7314 9 20.4V19.5" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M21 19.5V20.4C21 20.7314 20.7314 21 20.4 21H19.5" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M19.5 9H20.4C20.7314 9 21 9.26863 21 9.6V10.5" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3 10.5V7.5M7.5 3H10.5" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7.5 15H14.4C14.7314 15 15 14.7314 15 14.4V7.5" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4.5 15H3.6C3.26863 15 3 14.7314 3 14.4V13.5" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3 4.5V3.6C3 3.26863 3.26863 3 3.6 3H4.5" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13.5 3H14.4C14.7314 3 15 3.26863 15 3.6V4.5" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@@ -0,0 +1,4 @@
<svg width="24" height="24" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9 12H12H15" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M21 3.6V20.4C21 20.7314 20.7314 21 20.4 21H3.6C3.26863 21 3 20.7314 3 20.4V3.6C3 3.26863 3.26863 3 3.6 3H20.4C20.7314 3 21 3.26863 21 3.6Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 435 B

@@ -0,0 +1,9 @@
<svg width="24" height="24" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1 21L4.14286 3H19.8571L23 21H1Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M2 16.5H22" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3 12H21" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4 7.5H20" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 3V21" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8 3.5L6.5 20.5" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M16 3.5L17.5 20.5" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 795 B

+10
View File
@@ -0,0 +1,10 @@
<svg width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11 16L11 11L3 11" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M21 8H15V10" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M15 18L15 21" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11 19V21" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11 3V6" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M21 15H15L15 13" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11 9V11" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M21 3.6V20.4C21 20.7314 20.7314 21 20.4 21H3.6C3.26863 21 3 20.7314 3 20.4V3.6C3 3.26863 3.26863 3 3.6 3H20.4C20.7314 3 21 3.26863 21 3.6Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 995 B

+4
View File
@@ -0,0 +1,4 @@
<svg width="24" height="24" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9 12H12M15 12H12M12 12V9M12 12V15" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M21 3.6V20.4C21 20.7314 20.7314 21 20.4 21H3.6C3.26863 21 3 20.7314 3 20.4V3.6C3 3.26863 3.26863 3 3.6 3H20.4C20.7314 3 21 3.26863 21 3.6Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 458 B

@@ -0,0 +1,6 @@
<svg width="24" height="24" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21.1679 8C19.6247 4.46819 16.1006 2 11.9999 2C6.81459 2 2.55104 5.94668 2.04932 11" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M17 8H21.4C21.7314 8 22 7.73137 22 7.4V3" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M2.88146 16C4.42458 19.5318 7.94874 22 12.0494 22C17.2347 22 21.4983 18.0533 22 13" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7.04932 16H2.64932C2.31795 16 2.04932 16.2686 2.04932 16.6V21" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 717 B

@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg width="24px" height="24px" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" color="#000000"><path d="M21 7.35304L21 16.647C21 16.8649 20.8819 17.0656 20.6914 17.1715L12.2914 21.8381C12.1102 21.9388 11.8898 21.9388 11.7086 21.8381L3.30861 17.1715C3.11814 17.0656 3 16.8649 3 16.647L2.99998 7.35304C2.99998 7.13514 3.11812 6.93437 3.3086 6.82855L11.7086 2.16188C11.8898 2.06121 12.1102 2.06121 12.2914 2.16188L20.6914 6.82855C20.8818 6.93437 21 7.13514 21 7.35304Z" stroke="#000000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><path d="M12 21L12 12" stroke="#000000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><path d="M12.5 11V21C12.5 21.2761 12.2761 21.5 12 21.5C11.7239 21.5 11.5 21.2761 11.5 21V11C11.5 10.7239 11.7239 10.5 12 10.5C12.2761 10.5 12.5 10.7239 12.5 11Z" fill="#000000" stroke="#000000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg>

After

Width:  |  Height:  |  Size: 1020 B

@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg width="24px" height="24px" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" color="#000000"><path d="M21 7.35304L21 16.647C21 16.8649 20.8819 17.0656 20.6914 17.1715L12.2914 21.8381C12.1102 21.9388 11.8898 21.9388 11.7086 21.8381L3.30861 17.1715C3.11814 17.0656 3 16.8649 3 16.647L2.99998 7.35304C2.99998 7.13514 3.11812 6.93437 3.3086 6.82855L11.7086 2.16188C11.8898 2.06121 12.1102 2.06121 12.2914 2.16188L20.6914 6.82855C20.8818 6.93437 21 7.13514 21 7.35304Z" stroke="#000000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><path d="M3.52844 7.29357L11.7086 11.8381C11.8898 11.9388 12.1102 11.9388 12.2914 11.8381L20.5 7.27777" stroke="#000000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><path d="M12 21L12 12" stroke="#000000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><path d="M11.6914 11.8285L3.89139 7.49521C3.49147 7.27304 3 7.56222 3 8.01971V16.647C3 16.8649 3.11813 17.0656 3.30861 17.1715L11.1086 21.5048C11.5085 21.727 12 21.4378 12 20.9803V12.353C12 12.1351 11.8819 11.9344 11.6914 11.8285Z" fill="#000000" stroke="#000000" stroke-width="1.5" stroke-linejoin="round"></path></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg width="24px" height="24px" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" color="#000000"><path d="M12 13C12.5523 13 13 12.5523 13 12C13 11.4477 12.5523 11 12 11C11.4477 11 11 11.4477 11 12C11 12.5523 11.4477 13 12 13Z" fill="#000000" stroke="#000000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><path d="M21 7.35304L21 16.647C21 16.8649 20.8819 17.0656 20.6914 17.1715L12.2914 21.8381C12.1102 21.9388 11.8898 21.9388 11.7086 21.8381L3.30861 17.1715C3.11814 17.0656 3 16.8649 3 16.647L2.99998 7.35304C2.99998 7.13514 3.11812 6.93437 3.3086 6.82855L11.7086 2.16188C11.8898 2.06121 12.1102 2.06121 12.2914 2.16188L20.6914 6.82855C20.8818 6.93437 21 7.13514 21 7.35304Z" stroke="#000000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg>

After

Width:  |  Height:  |  Size: 875 B

+4
View File
@@ -0,0 +1,4 @@
<svg width="24" height="24" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 15C13.6569 15 15 13.6569 15 12C15 10.3431 13.6569 9 12 9C10.3431 9 9 10.3431 9 12C9 13.6569 10.3431 15 12 15Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M19.6224 10.3954L18.5247 7.7448L20 6L18 4L16.2647 5.48295L13.5578 4.36974L12.9353 2H10.981L10.3491 4.40113L7.70441 5.51596L6 4L4 6L5.45337 7.78885L4.3725 10.4463L2 11V13L4.40111 13.6555L5.51575 16.2997L4 18L6 20L7.79116 18.5403L10.397 19.6123L11 22H13L13.6045 19.6132L16.2551 18.5155C16.6969 18.8313 18 20 18 20L20 18L18.5159 16.2494L19.6139 13.598L21.9999 12.9772L22 11L19.6224 10.3954Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 786 B

@@ -0,0 +1,5 @@
<svg width="24" stroke-width="1.5" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M19 21L5 21C3.89543 21 3 20.1046 3 19L3 5C3 3.89543 3.89543 3 5 3L19 3C20.1046 3 21 3.89543 21 5L21 19C21 20.1046 20.1046 21 19 21Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9.5 21V3" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5.5 10L7.25 12L5.5 14" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 533 B

@@ -0,0 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 20.4V3.6C3 3.26863 3.26863 3 3.6 3H20.4C20.7314 3 21 3.26863 21 3.6V20.4C21 20.7314 20.7314 21 20.4 21H3.6C3.26863 21 3 20.7314 3 20.4Z" stroke="currentColor"/>
<path d="M12 13C12.5523 13 13 12.5523 13 12C13 11.4477 12.5523 11 12 11C11.4477 11 11 11.4477 11 12C11 12.5523 11.4477 13 12 13Z" fill="currentColor" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 516 B

@@ -0,0 +1,5 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 21C16.9706 21 21 16.9706 21 12C21 7.02944 16.9706 3 12 3C7.02944 3 3 7.02944 3 12C3 16.9706 7.02944 21 12 21Z" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9 9L15 15" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M15 9L9 15" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 545 B

@@ -0,0 +1,50 @@
<!-- This file was generated with the assistance of an AI coding tool. -->
<RCC>
<qresource prefix="/fonts">
<file alias="DMSans-VariableFont_opsz,wght.ttf">../ifctester/webapp/public/fonts/dmsans/DMSans-VariableFont_opsz,wght.ttf</file>
</qresource>
<qresource prefix="/icons">
<file alias="plus-square.svg">icons/plus-square.svg</file>
<file alias="download-square.svg">icons/download-square.svg</file>
<file alias="cloud-square.svg">icons/cloud-square.svg</file>
<file alias="clock-rotate-right.svg">icons/clock-rotate-right.svg</file>
<file alias="floppy-disk.svg">icons/floppy-disk.svg</file>
<file alias="floppy-disk-arrow-in.svg">icons/floppy-disk-arrow-in.svg</file>
<file alias="cube.svg">icons/cube.svg</file>
<file alias="refresh-double.svg">icons/refresh-double.svg</file>
<file alias="settings.svg">icons/settings.svg</file>
<file alias="home.svg">icons/home.svg</file>
<file alias="home-alt.svg">icons/home-alt.svg</file>
<file alias="cube-scan.svg">icons/cube-scan.svg</file>
<file alias="cube-scan-solid.svg">icons/cube-scan-solid.svg</file>
<file alias="planimetry.svg">icons/planimetry.svg</file>
<file alias="city.svg">icons/city.svg</file>
<file alias="building.svg">icons/building.svg</file>
<file alias="cellar.svg">icons/cellar.svg</file>
<file alias="perspective-view.svg">icons/perspective-view.svg</file>
<file alias="drone.svg">icons/drone.svg</file>
<file alias="cube-cut-with-curve.svg">icons/cube-cut-with-curve.svg</file>
<file alias="folder-plus.svg">icons/folder-plus.svg</file>
<file alias="folder-minus.svg">icons/folder-minus.svg</file>
<file alias="folder.svg">icons/folder.svg</file>
<file alias="minus-square.svg">icons/minus-square.svg</file>
<file alias="eye-closed.svg">icons/eye-closed.svg</file>
<file alias="eye.svg">icons/eye.svg</file>
<file alias="eye-solid.svg">icons/eye-solid.svg</file>
<file alias="intersect.svg">icons/intersect.svg</file>
<file alias="select-edge3d.svg">icons/select-edge3d.svg</file>
<file alias="select-face3d.svg">icons/select-face3d.svg</file>
<file alias="select-point3d.svg">icons/select-point3d.svg</file>
<file alias="frame-alt.svg">icons/frame-alt.svg</file>
<file alias="square3d-from-center.svg">icons/square3d-from-center.svg</file>
<file alias="filter.svg">icons/filter.svg</file>
<file alias="cube-dots.svg">icons/cube-dots.svg</file>
<file alias="cursor-pointer.svg">icons/cursor-pointer.svg</file>
<file alias="sidebar-expand.svg">icons/sidebar-expand.svg</file>
<file alias="check.svg">icons/check.svg</file>
<file alias="xmark-circle.svg">icons/xmark-circle.svg</file>
<file alias="database.svg">icons/database.svg</file>
<file alias="database-restore.svg">icons/database-restore.svg</file>
<file alias="cube-bandage.svg">icons/cube-bandage.svg</file>
</qresource>
</RCC>
+40 -35
View File
@@ -1,3 +1,4 @@
// This file was generated with the assistance of an AI coding tool.
/********************************************************************************
* *
* This file is part of IfcOpenShell. *
@@ -17,18 +18,40 @@
* *
********************************************************************************/
#include <QApplication>
#include <QSurfaceFormat>
#include <QCommandLineParser>
#include "MainWindow.h"
#include "InterfaceSettings.h"
#include "components/Style.h"
#include <QApplication>
#include <QCommandLineParser>
#include <QFont>
#include <QFontDatabase>
#include <QSurfaceFormat>
namespace {
void installUiFont() {
const int font_id = QFontDatabase::addApplicationFont(
":/fonts/DMSans-VariableFont_opsz,wght.ttf");
QString family;
if (font_id >= 0) {
const QStringList families = QFontDatabase::applicationFontFamilies(font_id);
if (!families.isEmpty()) {
family = families.front();
}
}
if (!family.isEmpty()) {
QApplication::setFont(QFont(family, 10));
}
}
} // namespace
int main(int argc, char* argv[]) {
QApplication app(argc, argv);
app.setApplicationName("IfcViewer");
app.setOrganizationName("IfcOpenShell");
// Request OpenGL 4.5 Core globally
QSurfaceFormat fmt;
fmt.setVersion(4, 5);
fmt.setProfile(QSurfaceFormat::CoreProfile);
@@ -38,39 +61,21 @@ int main(int argc, char* argv[]) {
QSurfaceFormat::setDefaultFormat(fmt);
QCommandLineParser parser;
parser.setApplicationDescription("IfcOpenShell IFC Viewer");
parser.setApplicationDescription("IfcOpenShell IFC viewer");
parser.addHelpOption();
parser.addPositionalArgument("files",
"IFC file(s) and/or one .ifcfed federation to open",
"[files...]");
parser.addOption({{"c", "camera"},
"Set camera: tx,ty,tz,dist,yaw,pitch", "params"});
parser.addOption({{"b", "benchmark"},
"Run N frames then print stats and exit", "frames"});
parser.process(app);
MainWindow window;
installUiFont();
const auto applyStyle = [&app]() {
app.setStyleSheet(ifcinterface::components::style::buildAppStyleSheet());
};
applyStyle();
QObject::connect(&ifcinterface::InterfaceSettings::instance(),
&ifcinterface::InterfaceSettings::themeChanged,
&app,
applyStyle);
ifcinterface::shell::MainWindow window;
window.show();
auto args = parser.positionalArguments();
QStringList file_args;
QString fed_arg;
for (const auto& a : args) {
if (fed_arg.isEmpty() && a.endsWith(".ifcfed", Qt::CaseInsensitive)) {
fed_arg = a;
} else {
file_args << a;
}
}
if (!fed_arg.isEmpty()) window.openFederation(fed_arg);
if (!file_args.isEmpty()) window.addFiles(file_args);
if (parser.isSet("camera")) {
window.setPendingCamera(parser.value("camera"));
}
if (parser.isSet("benchmark")) {
window.setPendingBenchmark(parser.value("benchmark").toInt());
}
return app.exec();
}
@@ -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
@@ -1,3 +1,4 @@
// This file was generated with the assistance of an AI coding tool.
/********************************************************************************
* *
* This file is part of IfcOpenShell. *
@@ -17,45 +18,33 @@
* *
********************************************************************************/
#ifndef FEDERATIONSETTINGSDIALOG_H
#define FEDERATIONSETTINGSDIALOG_H
#ifndef IFCINTERFACE_PANELS_ADDMODELDIALOG_H
#define IFCINTERFACE_PANELS_ADDMODELDIALOG_H
#include <QDialog>
#include "../../components/Dialog.h"
QT_BEGIN_NAMESPACE
class QComboBox;
class QDoubleSpinBox;
QT_END_NAMESPACE
namespace ifcinterface::modules::models {
class Federation;
enum class SourceMode {
None,
IfcFile,
IfcDatabase,
GeometryOnly,
};
// Edits federation-wide state: the display unit (used to interpret all
// FederatedFalseOrigin / ModelTransformation numeric inputs) and the
// FederatedFalseOrigin (XYZ + Z-axis rotation in that unit). On Ok,
// calls Federation::setConfig + setFederatedFalseOrigin, which fire the
// granular Federation signals and trigger the viewport to recompose.
class FederationSettingsDialog : public QDialog {
class AddModelDialog : public components::Dialog {
Q_OBJECT
public:
explicit FederationSettingsDialog(Federation* federation, QWidget* parent = nullptr);
explicit AddModelDialog(QWidget* parent = nullptr);
protected:
void showEvent(QShowEvent* event) override;
SourceMode selectedMode() const { return selected_mode_; }
private:
void setupUi();
void syncFromFederation();
void onAccepted();
Federation* federation_ = nullptr;
// The combobox encodes (prefix, name) pairs in user data; common length
// units are listed. itemData(idx) returns a QStringList { prefix, name }.
QComboBox* unit_combo_ = nullptr;
QDoubleSpinBox* xyz_x_ = nullptr;
QDoubleSpinBox* xyz_y_ = nullptr;
QDoubleSpinBox* xyz_z_ = nullptr;
QDoubleSpinBox* rz_deg_ = nullptr;
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
@@ -0,0 +1,207 @@
// 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 "../../ElementRegistry.h"
#include "../../SessionState.h"
#include "../models/Controller.h"
#include "../viewport/Controller.h"
#include "../../../ifcviewer/Federation.h"
#include "../../../ifcviewer/SceneLoader.h"
#include "../../../ifcviewer/ViewportWindow.h"
#include <QFileDialog>
#include <QFileInfo>
#include <QMessageBox>
namespace ifcinterface::modules::project {
ProjectController::ProjectController(QWidget* host,
Federation* federation,
ifcinterface::SessionState* session_state,
ifcinterface::ElementRegistry* element_registry,
ViewportWindow* viewport,
ifcinterface::modules::models::ModelsPanelController* models_controller,
ifcinterface::modules::viewport::ViewportController* viewport_controller,
QObject* parent)
: QObject(parent)
, host_(host)
, federation_(federation)
, session_state_(session_state)
, element_registry_(element_registry)
, viewport_(viewport)
, models_controller_(models_controller)
, viewport_controller_(viewport_controller)
{
}
bool ProjectController::newProject() {
SceneLoader* loader = session_state_->loader();
if (loader && loader->isLoading()) {
QMessageBox::information(
host_, "New Project",
"Wait until the current model load finishes before creating a new project.");
return false;
}
if (!confirmDiscardIfDirty()) return false;
clearScene();
federation_->clear();
viewport_controller_->applyFederatedFalseOrigin();
session_state_->setStatusMessage("Project", "Untitled");
session_state_->notifyProjectReset();
return true;
}
bool ProjectController::openProject() {
QFileDialog file_dialog(host_, "Open Project");
file_dialog.setFileMode(QFileDialog::ExistingFile);
file_dialog.setNameFilter("IFC Federation (*.ifcfed);;All Files (*)");
file_dialog.setOption(QFileDialog::DontUseNativeDialog, true);
if (file_dialog.exec() != QDialog::Accepted) return false;
const QString path = file_dialog.selectedFiles().value(0);
if (path.isEmpty()) return false;
return openProject(path);
}
bool ProjectController::openProject(const QString& path) {
SceneLoader* loader = session_state_->loader();
if (loader && loader->isLoading()) {
QMessageBox::information(
host_, "Open Project",
"Wait until the current model load finishes before opening another project.");
return false;
}
if (!confirmDiscardIfDirty()) return false;
QStringList warnings;
QString err;
if (!federation_->load(path, &warnings, &err)) {
QMessageBox::warning(host_, "Open Project",
QString("Could not open project:\n%1").arg(err));
return false;
}
clearScene();
QStringList paths;
QStringList fed_ids;
for (const auto& model : federation_->models()) {
if (model.source_kind != "local") continue;
if (!QFileInfo::exists(model.source_path)) {
warnings << QString("Source not found, kept in project: %1").arg(model.source_path);
continue;
}
paths << model.source_path;
fed_ids << model.id;
}
models_controller_->loadModels(paths, fed_ids);
if (!warnings.isEmpty()) {
QMessageBox::warning(host_, "Open Project",
"Project opened with warnings:\n\n" + warnings.join("\n"));
}
federation_->markClean();
viewport_controller_->applyFederatedFalseOrigin();
if (federation_->hasHomeView()) {
const auto& hv = federation_->homeView();
viewport_->setCamera(
hv.target.x(), hv.target.y(), hv.target.z(), hv.distance, hv.yaw, hv.pitch);
}
session_state_->setStatusMessage("Project", QFileInfo(path).fileName());
session_state_->notifyProjectOpened(path);
return true;
}
bool ProjectController::saveProject() {
if (federation_->filePath().isEmpty()) return saveProjectAs();
QString err;
if (!federation_->save(federation_->filePath(), &err)) {
QMessageBox::warning(host_, "Save Project",
QString("Could not save project:\n%1").arg(err));
return false;
}
session_state_->setStatusMessage("Project", QFileInfo(federation_->filePath()).fileName());
session_state_->notifyProjectSaved(federation_->filePath());
return true;
}
bool ProjectController::saveProjectAs() {
QString suggested = federation_->filePath();
if (suggested.isEmpty()) suggested = "project.ifcfed";
QFileDialog file_dialog(host_, "Save Project As", suggested);
file_dialog.setAcceptMode(QFileDialog::AcceptSave);
file_dialog.setFileMode(QFileDialog::AnyFile);
file_dialog.setNameFilter("IFC Federation (*.ifcfed);;All Files (*)");
file_dialog.setOption(QFileDialog::DontUseNativeDialog, true);
if (file_dialog.exec() != QDialog::Accepted) return false;
QString path = file_dialog.selectedFiles().value(0);
if (path.isEmpty()) return false;
if (!path.endsWith(".ifcfed", Qt::CaseInsensitive)) path += ".ifcfed";
return saveProjectAs(path);
}
bool ProjectController::saveProjectAs(const QString& path) {
QString err;
if (!federation_->save(path, &err)) {
QMessageBox::warning(host_, "Save Project",
QString("Could not save project:\n%1").arg(err));
return false;
}
session_state_->setStatusMessage("Project", QFileInfo(path).fileName());
session_state_->notifyProjectSaved(path);
return true;
}
void ProjectController::clearScene() {
viewport_->setSelectedObjectId(0);
session_state_->setSelectedObjectId(0);
session_state_->notifySelectionChanged();
const auto model_ids = session_state_->modelIds();
for (uint32_t mid : model_ids) {
viewport_->removeModel(mid);
session_state_->loader()->removeModel(mid);
}
session_state_->clearModelMappings();
element_registry_->clear();
session_state_->notifyModelsChanged();
}
bool ProjectController::confirmDiscardIfDirty() {
if (!federation_->isDirty()) return true;
const auto result = QMessageBox::question(
host_, "Unsaved Project",
"The current project has unsaved changes. Save before continuing?",
QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel,
QMessageBox::Save);
if (result == QMessageBox::Cancel) return false;
if (result == QMessageBox::Save) return saveProject();
return true;
}
} // namespace ifcinterface::modules::project
@@ -0,0 +1,71 @@
// 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_PROJECT_CONTROLLER_H
#define IFCINTERFACE_PANELS_PROJECT_CONTROLLER_H
#include <QObject>
class QWidget;
class Federation;
class ViewportWindow;
namespace ifcinterface { class ElementRegistry; }
namespace ifcinterface { class SessionState; }
namespace ifcinterface::modules::models { class ModelsPanelController; }
namespace ifcinterface::modules::viewport { class ViewportController; }
namespace ifcinterface::modules::project {
class ProjectController : public QObject {
Q_OBJECT
public:
explicit ProjectController(QWidget* host,
Federation* federation,
ifcinterface::SessionState* session_state,
ifcinterface::ElementRegistry* element_registry,
ViewportWindow* viewport,
ifcinterface::modules::models::ModelsPanelController* models_controller,
ifcinterface::modules::viewport::ViewportController* viewport_controller,
QObject* parent = nullptr);
bool newProject();
bool openProject();
bool saveProject();
bool saveProjectAs();
private:
bool openProject(const QString& path);
bool saveProjectAs(const QString& path);
void clearScene();
bool confirmDiscardIfDirty();
QWidget* host_ = nullptr;
Federation* federation_ = nullptr;
ifcinterface::SessionState* session_state_ = nullptr;
ifcinterface::ElementRegistry* element_registry_ = nullptr;
ViewportWindow* viewport_ = nullptr;
ifcinterface::modules::models::ModelsPanelController* models_controller_ = nullptr;
ifcinterface::modules::viewport::ViewportController* viewport_controller_ = nullptr;
};
} // namespace ifcinterface::modules::project
#endif
@@ -0,0 +1,237 @@
// 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/KeyValueTable.h"
#include "../../components/Section.h"
#include "../../components/Style.h"
#include "../../components/SvgIcon.h"
#include <QFrame>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QLineEdit>
#include <QToolButton>
#include <QVBoxLayout>
namespace {
QWidget* makePropertySetPanel(const ifcinterface::modules::properties::PropertySet& property_set, QWidget* parent = nullptr) {
auto* group = new QGroupBox(property_set.title, parent);
group->setObjectName("propertySetBox");
auto* layout = new QVBoxLayout(group);
layout->setContentsMargins(10, 10, 10, 10);
layout->setSpacing(0);
QList<ifcinterface::components::KeyValueTableRow> rows;
for (const auto& row : property_set.rows) {
rows.append({row.key, row.value, "keyValueValueLabel", "", "", 0});
}
layout->addWidget(new ifcinterface::components::KeyValueTable(rows, group));
return group;
}
QWidget* makeAttributeList(const QList<ifcinterface::modules::properties::KeyValueRow>& rows, QWidget* parent = nullptr) {
QList<ifcinterface::components::KeyValueTableRow> table_rows;
for (const auto& row : rows) {
table_rows.append({row.key, row.value, "keyValueValueLabel", "", "", 0});
}
return new ifcinterface::components::KeyValueTable(table_rows, parent);
}
QWidget* makeRelationshipList(const QList<ifcinterface::modules::properties::RelationshipRow>& rows, QWidget* parent = nullptr) {
QList<ifcinterface::components::KeyValueTableRow> table_rows;
for (const auto& row_data : rows) {
table_rows.append({row_data.key,
row_data.value,
"keyValueValueLabel",
":/icons/cursor-pointer.svg",
"keyValueTrailingIconLabel",
72});
}
return new ifcinterface::components::KeyValueTable(table_rows, parent);
}
QWidget* makeFilterWrapper(QLineEdit** field_out, QWidget* parent = nullptr) {
auto* wrapper = new QWidget(parent);
wrapper->setObjectName("panelSectionFilterWrapper");
auto* layout = new QVBoxLayout(wrapper);
layout->setContentsMargins(ifcinterface::components::style::metrics::section_body_padding,
0,
ifcinterface::components::style::metrics::section_body_padding,
0);
layout->setSpacing(0);
auto* field = new QLineEdit(wrapper);
field->setClearButtonEnabled(true);
field->addAction(ifcinterface::components::icons::makeSvgIcon(":/icons/filter.svg"), QLineEdit::LeadingPosition);
field->setVisible(false);
layout->addWidget(field);
if (field_out) *field_out = field;
return wrapper;
}
QFrame* makeEntityBox(const ifcinterface::modules::properties::EntitySummary& entity, QWidget* parent = nullptr) {
auto* entity_box = new QFrame(parent);
entity_box->setObjectName("entityClassBox");
auto* entity_layout = new QHBoxLayout(entity_box);
entity_layout->setContentsMargins(10, 8, 10, 8);
entity_layout->setSpacing(10);
auto* entity_icon = new QLabel(entity_box);
entity_icon->setPixmap(ifcinterface::components::icons::makeSvgPixmap(":/icons/cube-dots.svg", QSize(28, 28)));
entity_icon->setAlignment(Qt::AlignCenter);
auto* entity_text = new QWidget(entity_box);
auto* entity_text_layout = new QVBoxLayout(entity_text);
entity_text_layout->setContentsMargins(0, 0, 0, 0);
entity_text_layout->setSpacing(2);
auto* entity_class_label = new QLabel(entity.entity_class, entity_text);
entity_class_label->setObjectName("entityClassLabel");
auto* entity_type_label = new QLabel(entity.predefined_type, entity_text);
entity_type_label->setProperty("textRole", "secondary");
entity_text_layout->addWidget(entity_class_label);
entity_text_layout->addWidget(entity_type_label);
entity_layout->addWidget(entity_icon, 0, Qt::AlignVCenter);
entity_layout->addWidget(entity_text, 1, Qt::AlignVCenter);
return entity_box;
}
} // namespace
namespace ifcinterface::modules::properties {
PropertiesPanel::PropertiesPanel(QWidget* parent)
: components::Panel("Properties", nullptr, parent, false, true)
{
}
void PropertiesPanel::render(const PropertiesPanelState& state) {
clearBodyWidgets();
QList<QWidget*> property_set_widgets;
for (const auto& property_set : state.property_sets) {
property_set_widgets.append(makePropertySetPanel(property_set, this));
}
QList<QWidget*> quantity_set_widgets;
for (const auto& property_set : state.quantity_sets) {
quantity_set_widgets.append(makePropertySetPanel(property_set, this));
}
auto* entity_section = new components::Section("", components::SectionHeaderMode::Hidden, this);
entity_section->addBodyWidget(makeEntityBox(state.entity, this));
auto* attributes_section = new components::Section("Attributes", components::SectionHeaderMode::Visible, this);
attributes_section->addBodyWidget(makeAttributeList(state.attributes, this));
attributes_section->setExpanded(attributes_expanded_);
auto* relationships_section = new components::Section("Relationships", components::SectionHeaderMode::Visible, this);
relationships_section->addBodyWidget(makeRelationshipList(state.relationships, this));
relationships_section->setExpanded(relationships_expanded_);
auto* properties_section = new components::Section("Properties", components::SectionHeaderMode::Visible, this);
auto* properties_filter_toggle = new QToolButton(properties_section);
properties_filter_toggle->setObjectName("panelSectionFilterToggle");
properties_filter_toggle->setCheckable(true);
properties_filter_toggle->setIcon(components::icons::makeSvgIcon(":/icons/filter.svg"));
properties_filter_toggle->setAutoRaise(true);
properties_section->addHeaderWidget(properties_filter_toggle);
QLineEdit* properties_filter_field = nullptr;
auto* properties_filter_wrapper = makeFilterWrapper(&properties_filter_field, properties_section);
properties_filter_field->setPlaceholderText("Filter properties or sets");
properties_filter_field->setText(properties_filter_text_);
properties_filter_wrapper->setVisible(properties_filter_visible_);
properties_filter_field->setVisible(properties_filter_visible_);
connect(properties_filter_toggle, &QToolButton::toggled, properties_filter_field, [this, properties_filter_field, properties_filter_wrapper](bool visible) {
properties_filter_visible_ = visible;
properties_filter_field->setVisible(visible);
properties_filter_wrapper->setVisible(visible);
if (visible) properties_filter_field->setFocus();
});
connect(properties_filter_field, &QLineEdit::textChanged, this, [this](const QString& text) {
properties_filter_text_ = text;
});
properties_section->addBodyWidget(properties_filter_wrapper);
for (auto* widget : property_set_widgets) properties_section->addBodyWidget(widget);
properties_section->setExpanded(properties_expanded_);
properties_filter_toggle->setChecked(properties_filter_visible_);
auto* quantities_section = new components::Section("Quantities", components::SectionHeaderMode::Visible, this);
auto* quantities_filter_toggle = new QToolButton(quantities_section);
quantities_filter_toggle->setObjectName("panelSectionFilterToggle");
quantities_filter_toggle->setCheckable(true);
quantities_filter_toggle->setIcon(components::icons::makeSvgIcon(":/icons/filter.svg"));
quantities_filter_toggle->setAutoRaise(true);
quantities_section->addHeaderWidget(quantities_filter_toggle);
QLineEdit* quantities_filter_field = nullptr;
auto* quantities_filter_wrapper = makeFilterWrapper(&quantities_filter_field, quantities_section);
quantities_filter_field->setPlaceholderText("Filter quantities or sets");
quantities_filter_field->setText(quantities_filter_text_);
quantities_filter_wrapper->setVisible(quantities_filter_visible_);
quantities_filter_field->setVisible(quantities_filter_visible_);
connect(quantities_filter_toggle, &QToolButton::toggled, quantities_filter_field, [this, quantities_filter_field, quantities_filter_wrapper](bool visible) {
quantities_filter_visible_ = visible;
quantities_filter_field->setVisible(visible);
quantities_filter_wrapper->setVisible(visible);
if (visible) quantities_filter_field->setFocus();
});
connect(quantities_filter_field, &QLineEdit::textChanged, this, [this](const QString& text) {
quantities_filter_text_ = text;
});
quantities_section->addBodyWidget(quantities_filter_wrapper);
for (auto* widget : quantity_set_widgets) quantities_section->addBodyWidget(widget);
quantities_section->setExpanded(quantities_expanded_);
quantities_filter_toggle->setChecked(quantities_filter_visible_);
if (auto* button = attributes_section->findChild<QToolButton*>("panelSectionHeaderButton")) {
connect(button, &QToolButton::toggled, this, [this](bool expanded) {
attributes_expanded_ = expanded;
});
}
if (auto* button = relationships_section->findChild<QToolButton*>("panelSectionHeaderButton")) {
connect(button, &QToolButton::toggled, this, [this](bool expanded) {
relationships_expanded_ = expanded;
});
}
if (auto* button = properties_section->findChild<QToolButton*>("panelSectionHeaderButton")) {
connect(button, &QToolButton::toggled, this, [this](bool expanded) {
properties_expanded_ = expanded;
});
}
if (auto* button = quantities_section->findChild<QToolButton*>("panelSectionHeaderButton")) {
connect(button, &QToolButton::toggled, this, [this](bool expanded) {
quantities_expanded_ = expanded;
});
}
addBodyWidget(entity_section);
addBodyWidget(attributes_section);
addBodyWidget(relationships_section);
addBodyWidget(properties_section);
addBodyWidget(quantities_section);
}
} // namespace ifcinterface::modules::properties
@@ -0,0 +1,56 @@
// 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_PROPERTIES_PANEL_H
#define IFCINTERFACE_MODULES_PROPERTIES_PANEL_H
#include "Types.h"
#include "../../components/Panel.h"
#include <QString>
class QLabel;
class QLineEdit;
class QToolButton;
namespace ifcinterface::modules::properties {
class PropertiesPanel : public components::Panel {
Q_OBJECT
public:
explicit PropertiesPanel(QWidget* parent = nullptr);
void render(const PropertiesPanelState& state);
private:
bool attributes_expanded_ = true;
bool relationships_expanded_ = true;
bool properties_expanded_ = true;
bool quantities_expanded_ = true;
bool properties_filter_visible_ = false;
bool quantities_filter_visible_ = false;
QString properties_filter_text_;
QString quantities_filter_text_;
};
} // namespace ifcinterface::modules::properties
#endif
@@ -0,0 +1,60 @@
// 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_PROPERTIESPANELTYPES_H
#define IFCINTERFACE_PANELS_PROPERTIESPANELTYPES_H
#include <QList>
#include <QPair>
#include <QString>
namespace ifcinterface::modules::properties {
struct KeyValueRow {
QString key;
QString value;
};
struct RelationshipRow {
QString key;
QString value;
};
struct PropertySet {
QString title;
QList<KeyValueRow> rows;
};
struct EntitySummary {
QString entity_class;
QString predefined_type;
};
struct PropertiesPanelState {
EntitySummary entity;
QList<KeyValueRow> attributes;
QList<RelationshipRow> relationships;
QList<PropertySet> property_sets;
QList<PropertySet> quantity_sets;
};
} // namespace ifcinterface::modules::properties
#endif
@@ -0,0 +1,121 @@
// 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 "../../ElementRegistry.h"
#include "../../SessionState.h"
namespace ifcinterface::modules::properties {
PropertiesPanelView::PropertiesPanelView(PropertiesPanel* widget,
ifcinterface::SessionState* session_state,
QObject* parent)
: QObject(parent), widget_(widget), session_state_(session_state)
{
connect(session_state_, &ifcinterface::SessionState::selectionChanged, this, [this](uint32_t object_id) {
refresh(object_id);
});
connect(session_state_, &ifcinterface::SessionState::projectReset, this, [this]() {
refresh(0);
});
refresh(0);
}
void PropertiesPanelView::refresh(uint32_t object_id) {
auto* registry = session_state_->elementRegistry();
PropertiesPanelState state;
state.entity = {"IfcWall", "SOLIDWALL"};
state.attributes = {
{"GlobalId", "2Q$n5SLPP9Q8B7wQKjKfUQ"},
{"Name", "Core-EXT-204"},
{"Description", "External load-bearing wall"},
};
state.relationships = {
{"Type", "Basic Wall: Exterior - 200mm"},
{"Container", "Level 02"},
};
state.property_sets = {
{"Pset_WallCommon",
{{"Reference", "Core-EXT-204"},
{"Status", "Reviewed"},
{"Fire Rating", "120 min"},
{"LoadBearing", "True"}}},
{"Identity Data",
{{"Type", "IfcWall"},
{"Name", "Core-EXT-204"},
{"Owner", "Architecture"},
{"Phase", "Construction"}}},
{"BIM Collaboration",
{{"Issue Count", "2 open"},
{"Last Review", "2026-04-30"},
{"Assigned To", "Design Coordination"}}},
};
state.quantity_sets = {
{"BaseQuantities",
{{"Length", "6.20 m"},
{"Height", "3.45 m"},
{"Width", "0.30 m"},
{"Volume", "6.42 m3"}}},
{"Finish Quantities",
{{"NetSideArea", "21.39 m2"},
{"GrossArea", "22.10 m2"},
{"Paint Coverage", "42.78 m2"}}},
};
if (!registry) {
widget_->render(state);
return;
}
auto entity = registry->findEntity(object_id);
if (entity) {
state.entity.entity_class = QString::fromStdString(entity->declaration().name());
if (!state.property_sets.isEmpty() && !state.property_sets[1].rows.isEmpty()) {
state.property_sets[1].rows[0].value = state.entity.entity_class;
}
} else {
// No live IFC source for this object — typical when a pure-geometry
// .ifcview sidecar was loaded without its .ifc/.rdb sibling. Fall
// back to the basic info cached in the element registry so the
// panel still shows class / name / guid for visible elements.
auto info = registry->findBasicElementInfo(object_id);
if (info && !info->type.isEmpty()) {
state.entity.entity_class = info->type;
if (!state.property_sets.isEmpty() && !state.property_sets[1].rows.isEmpty()) {
state.property_sets[1].rows[0].value = info->type;
}
}
if (info && !info->name.isEmpty()) {
state.attributes[1].value = info->name;
if (state.property_sets.size() > 1 && state.property_sets[1].rows.size() > 1) {
state.property_sets[1].rows[1].value = info->name;
}
}
if (info && !info->guid.isEmpty()) {
state.attributes[0].value = info->guid;
}
}
widget_->render(state);
}
} // namespace ifcinterface::modules::properties
@@ -0,0 +1,49 @@
// 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_PROPERTIESPANELVIEW_H
#define IFCINTERFACE_PANELS_PROPERTIESPANELVIEW_H
#include "Types.h"
#include <QObject>
namespace ifcinterface { class SessionState; }
namespace ifcinterface::modules::properties {
class PropertiesPanel;
class PropertiesPanelView : public QObject {
Q_OBJECT
public:
explicit PropertiesPanelView(PropertiesPanel* widget,
ifcinterface::SessionState* session_state,
QObject* parent = nullptr);
private:
void refresh(uint32_t object_id);
PropertiesPanel* widget_ = nullptr;
ifcinterface::SessionState* session_state_ = nullptr;
};
} // namespace ifcinterface::modules::properties
#endif
@@ -0,0 +1,357 @@
// 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 "Dialog.h"
#include "../../InterfaceSettings.h"
#include "../../../ifcviewer/AppSettings.h"
#include "../../components/Dialog.h"
#include "../../components/Section.h"
#include "../../components/SvgIcon.h"
#include "../../components/Style.h"
#include <QCheckBox>
#include <QColorDialog>
#include <QComboBox>
#include <QDialogButtonBox>
#include <QDoubleSpinBox>
#include <QFrame>
#include <QFormLayout>
#include <QGridLayout>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QShowEvent>
#include <QSpinBox>
#include <QVBoxLayout>
namespace ifcinterface::modules::settings {
SettingsDialog::SettingsDialog(QWidget* parent)
: components::TabbedDialog(parent)
{
setObjectName("appDialog");
setWindowTitle("Settings");
setModal(true);
resize(520, 420);
setupUi();
}
void SettingsDialog::showEvent(QShowEvent* event) {
syncFromSettings();
QDialog::showEvent(event);
}
void SettingsDialog::setupUi() {
auto* graphics_tab = new QWidget(this);
auto* graphics_layout = new QVBoxLayout(graphics_tab);
graphics_layout->setContentsMargins(0, 0, 0, 0);
graphics_layout->setSpacing(components::style::metrics::padding);
graphics_layout->setAlignment(Qt::AlignTop);
auto* general_section = new components::Section("General", components::SectionHeaderMode::Visible, graphics_tab);
auto* general_body = new QWidget(general_section);
auto* general_form = new QFormLayout(general_body);
general_form->setContentsMargins(0, 0, 0, 0);
general_form->setHorizontalSpacing(16);
general_form->setVerticalSpacing(10);
geometry_library_edit_ = new QLineEdit(general_body);
geometry_library_edit_->setMinimumWidth(300);
general_form->addRow("Geometry Library", geometry_library_edit_);
show_stats_check_ = new QCheckBox(general_body);
general_form->addRow("Show Performance Stats", show_stats_check_);
backface_culling_check_ = new QCheckBox(general_body);
backface_culling_check_->setToolTip(
"Skip triangles facing away from the camera. Big FPS win on closed solids; "
"disable if you see holes in open geometry.");
general_form->addRow("Backface Culling", backface_culling_check_);
general_section->addBodyWidget(general_body);
auto* loading_section = new components::Section("Loading", components::SectionHeaderMode::Visible, graphics_tab);
auto* loading_body = new QWidget(loading_section);
auto* loading_form = new QFormLayout(loading_body);
loading_form->setContentsMargins(0, 0, 0, 0);
loading_form->setHorizontalSpacing(16);
loading_form->setVerticalSpacing(10);
void_limit_spin_ = new QSpinBox(loading_body);
void_limit_spin_->setRange(0, 100000);
loading_form->addRow("Void Limit", void_limit_spin_);
deflection_tolerance_spin_ = new QDoubleSpinBox(loading_body);
deflection_tolerance_spin_->setRange(0.000001, 1000.0);
deflection_tolerance_spin_->setDecimals(6);
deflection_tolerance_spin_->setSingleStep(0.001);
loading_form->addRow("Deflection Tolerance", deflection_tolerance_spin_);
angular_tolerance_spin_ = new QDoubleSpinBox(loading_body);
angular_tolerance_spin_->setRange(0.000001, 3.141592);
angular_tolerance_spin_->setDecimals(6);
angular_tolerance_spin_->setSingleStep(0.05);
loading_form->addRow("Angular Tolerance", angular_tolerance_spin_);
min_pixel_radius_spin_ = new QDoubleSpinBox(loading_body);
min_pixel_radius_spin_->setRange(0.0, 100.0);
min_pixel_radius_spin_->setDecimals(2);
min_pixel_radius_spin_->setSingleStep(0.5);
min_pixel_radius_spin_->setToolTip(
"Minimum projected sphere radius (in pixels) for an instance to "
"be drawn. Bigger = faster but more pop-in on small detail.");
loading_form->addRow("Min Pixel Radius", min_pixel_radius_spin_);
motion_min_pixel_radius_spin_ = new QDoubleSpinBox(loading_body);
motion_min_pixel_radius_spin_->setRange(0.0, 100.0);
motion_min_pixel_radius_spin_->setDecimals(2);
motion_min_pixel_radius_spin_->setSingleStep(1.0);
motion_min_pixel_radius_spin_->setToolTip(
"Aggressive cull threshold while the camera is moving. 0 = no "
"motion boost (motion uses the same threshold as still frames).");
loading_form->addRow("Motion Min Pixel Radius", motion_min_pixel_radius_spin_);
lod1_pixel_threshold_spin_ = new QDoubleSpinBox(loading_body);
lod1_pixel_threshold_spin_->setRange(0.0, 1000.0);
lod1_pixel_threshold_spin_->setDecimals(1);
lod1_pixel_threshold_spin_->setSingleStep(1.0);
lod1_pixel_threshold_spin_->setToolTip(
"Pixel radius below which an instance switches to its LOD1 "
"representation. 0 disables LOD1 entirely.");
loading_form->addRow("LOD1 Pixel Threshold", lod1_pixel_threshold_spin_);
hiz_enabled_check_ = new QCheckBox(loading_body);
hiz_enabled_check_->setToolTip(
"Enable HiZ (hierarchical Z) occlusion culling. Hides geometry "
"behind opaque blockers based on a downsampled depth pyramid.");
loading_form->addRow("HiZ Occlusion", hiz_enabled_check_);
hiz_resolution_spin_ = new QSpinBox(loading_body);
hiz_resolution_spin_->setRange(64, 4096);
hiz_resolution_spin_->setSingleStep(64);
hiz_resolution_spin_->setToolTip(
"Base HiZ pyramid width in texels (height tracks aspect). "
"Changes take effect on next viewport reinitialization.");
loading_form->addRow("HiZ Resolution", hiz_resolution_spin_);
loading_section->addBodyWidget(loading_body);
graphics_layout->addWidget(general_section);
graphics_layout->addWidget(loading_section);
graphics_layout->addStretch(1);
// Navigation tab: orbit / pan presets. Selection always stays on
// LMB so click + box-select keep working regardless of preset.
auto* interface_tab = new QWidget(this);
{
auto* layout = new QVBoxLayout(interface_tab);
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(components::style::metrics::padding);
layout->setAlignment(Qt::AlignTop);
auto* section = new components::Section(
"Navigation", components::SectionHeaderMode::Visible, interface_tab);
auto* body = new QWidget(section);
auto* form = new QFormLayout(body);
form->setContentsMargins(0, 0, 0, 0);
form->setHorizontalSpacing(16);
form->setVerticalSpacing(10);
nav_preset_combo_ = new QComboBox(body);
// Order must match AppSettings::NavPreset enum ordering — index
// is what we read back via currentIndex / setCurrentIndex.
nav_preset_combo_->addItem("Blender (Orbit MMB, Pan Shift+MMB)");
nav_preset_combo_->addItem("Rhino (Orbit RMB, Pan Shift+RMB)");
nav_preset_combo_->addItem("Revit (Orbit Shift+MMB, Pan MMB)");
nav_preset_combo_->setToolTip(
"Mouse-button mapping for orbit and pan. Selection stays on "
"left mouse button for every preset, so click + box-select "
"always work.");
form->addRow("Preset", nav_preset_combo_);
section->addBodyWidget(body);
layout->addWidget(section);
auto* theme_section = new components::Section(
"Theme", components::SectionHeaderMode::Visible, interface_tab);
auto* theme_body = new QWidget(theme_section);
auto* theme_layout = new QVBoxLayout(theme_body);
theme_layout->setContentsMargins(0, 0, 0, 0);
theme_layout->setSpacing(components::style::metrics::padding);
auto* theme_form = new QFormLayout();
theme_form->setContentsMargins(0, 0, 0, 0);
theme_form->setHorizontalSpacing(16);
theme_form->setVerticalSpacing(10);
theme_mode_combo_ = new QComboBox(theme_body);
theme_mode_combo_->addItem("Dark", static_cast<int>(ifcinterface::InterfaceSettings::ThemeMode::Dark));
theme_mode_combo_->addItem("Light", static_cast<int>(ifcinterface::InterfaceSettings::ThemeMode::Light));
theme_mode_combo_->addItem("Custom", static_cast<int>(ifcinterface::InterfaceSettings::ThemeMode::Custom));
theme_form->addRow("Preset", theme_mode_combo_);
theme_custom_body_ = new QWidget(theme_body);
auto* custom_grid = new QGridLayout(theme_custom_body_);
custom_grid->setContentsMargins(0, 0, 0, 0);
custom_grid->setHorizontalSpacing(12);
custom_grid->setVerticalSpacing(8);
int row = 0;
for (const auto& spec : ifcinterface::InterfaceSettings::themeColorSpecs()) {
auto* label = new QLabel(QString::fromUtf8(spec.label), theme_custom_body_);
auto* edit = new QLineEdit(theme_custom_body_);
edit->setPlaceholderText("#000000");
auto* pick = new QPushButton("Pick", theme_custom_body_);
connect(pick, &QPushButton::clicked, this, [this, edit]() { pickThemeColor(edit); });
custom_grid->addWidget(label, row, 0);
custom_grid->addWidget(edit, row, 1);
custom_grid->addWidget(pick, row, 2);
theme_color_editors_.push_back({QString::fromUtf8(spec.key), edit});
++row;
}
auto* theme_form_widget = new QWidget(theme_body);
theme_form_widget->setLayout(theme_form);
theme_layout->addWidget(theme_form_widget);
theme_layout->addWidget(theme_custom_body_);
theme_section->addBodyWidget(theme_body);
layout->addWidget(theme_section);
connect(theme_mode_combo_, &QComboBox::currentIndexChanged, this, [this](int) {
updateThemeEditorEnabled();
});
}
auto make_placeholder_tab = [this](const QString& title, const QString& detail) {
auto* tab = new QWidget(this);
auto* layout = new QVBoxLayout(tab);
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(components::style::metrics::padding);
layout->setAlignment(Qt::AlignTop);
auto* section = new components::Section(title, components::SectionHeaderMode::Visible, tab);
auto* body = new QWidget(section);
auto* body_layout = new QVBoxLayout(body);
body_layout->setContentsMargins(0, 0, 0, 0);
body_layout->setSpacing(8);
auto* heading = new QLabel(title, body);
auto* content = new QLabel(detail, body);
content->setProperty("textRole", "secondary");
content->setWordWrap(true);
body_layout->addWidget(heading);
body_layout->addWidget(content);
section->addBodyWidget(body);
layout->addWidget(section);
layout->addStretch(1);
return tab;
};
addTab("Interface", interface_tab);
addTab("Keybindings", make_placeholder_tab("Keybindings", "Shortcut presets and command bindings will live here."));
addTab("Graphics", graphics_tab);
addTab("About", make_placeholder_tab("About", "Version, credits, and environment information will live here."));
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, &SettingsDialog::onAccepted);
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
addFooterWidget(buttons);
}
void SettingsDialog::syncFromSettings() {
geometry_library_edit_->setText(AppSettings::instance().geometryLibrary());
show_stats_check_->setChecked(AppSettings::instance().showStats());
backface_culling_check_->setChecked(AppSettings::instance().backfaceCulling());
void_limit_spin_->setValue(AppSettings::instance().voidLimit());
deflection_tolerance_spin_->setValue(AppSettings::instance().deflectionTolerance());
angular_tolerance_spin_->setValue(AppSettings::instance().angularTolerance());
min_pixel_radius_spin_->setValue(AppSettings::instance().minPixelRadius());
motion_min_pixel_radius_spin_->setValue(AppSettings::instance().motionMinPixelRadius());
lod1_pixel_threshold_spin_->setValue(AppSettings::instance().lod1PixelThreshold());
hiz_enabled_check_->setChecked(AppSettings::instance().hizEnabled());
hiz_resolution_spin_->setValue(AppSettings::instance().hizResolution());
nav_preset_combo_->setCurrentIndex(static_cast<int>(AppSettings::instance().navPreset()));
syncThemeSettings();
}
void SettingsDialog::syncThemeSettings() {
const auto& settings = ifcinterface::InterfaceSettings::instance();
const int idx = theme_mode_combo_->findData(static_cast<int>(settings.themeMode()));
theme_mode_combo_->setCurrentIndex(idx >= 0 ? idx : 0);
for (auto& editor : theme_color_editors_) {
editor.edit->setText(settings.customColor(editor.key));
}
updateThemeEditorEnabled();
}
void SettingsDialog::updateThemeEditorEnabled() {
if (!theme_mode_combo_ || !theme_custom_body_) return;
const auto mode =
static_cast<ifcinterface::InterfaceSettings::ThemeMode>(theme_mode_combo_->currentData().toInt());
const bool is_custom = mode == ifcinterface::InterfaceSettings::ThemeMode::Custom;
theme_custom_body_->setVisible(is_custom);
theme_custom_body_->setEnabled(is_custom);
}
void SettingsDialog::pickThemeColor(QLineEdit* edit) {
QColorDialog dialog(QColor(edit->text()), this);
dialog.setObjectName("appDialog");
dialog.setWindowTitle("Choose Color");
dialog.setOption(QColorDialog::DontUseNativeDialog, true);
if (dialog.exec() != QDialog::Accepted) return;
const QColor color = dialog.selectedColor();
if (!color.isValid()) return;
edit->setText(color.name(QColor::HexRgb));
}
void SettingsDialog::onAccepted() {
AppSettings::instance().setGeometryLibrary(geometry_library_edit_->text());
AppSettings::instance().setShowStats(show_stats_check_->isChecked());
AppSettings::instance().setBackfaceCulling(backface_culling_check_->isChecked());
AppSettings::instance().setVoidLimit(void_limit_spin_->value());
AppSettings::instance().setDeflectionTolerance(deflection_tolerance_spin_->value());
AppSettings::instance().setAngularTolerance(angular_tolerance_spin_->value());
AppSettings::instance().setMinPixelRadius(min_pixel_radius_spin_->value());
AppSettings::instance().setMotionMinPixelRadius(motion_min_pixel_radius_spin_->value());
AppSettings::instance().setLod1PixelThreshold(lod1_pixel_threshold_spin_->value());
AppSettings::instance().setHizEnabled(hiz_enabled_check_->isChecked());
AppSettings::instance().setHizResolution(hiz_resolution_spin_->value());
AppSettings::instance().setNavPreset(
static_cast<AppSettings::NavPreset>(nav_preset_combo_->currentIndex()));
auto& interface_settings = ifcinterface::InterfaceSettings::instance();
interface_settings.setThemeMode(
static_cast<ifcinterface::InterfaceSettings::ThemeMode>(theme_mode_combo_->currentData().toInt()));
for (const auto& editor : theme_color_editors_) {
interface_settings.setCustomColor(editor.key, editor.edit->text());
}
accept();
}
} // namespace ifcinterface::modules::settings
@@ -1,3 +1,4 @@
// This file was generated with the assistance of an AI coding tool.
/********************************************************************************
* *
* This file is part of IfcOpenShell. *
@@ -17,31 +18,44 @@
* *
********************************************************************************/
#ifndef SETTINGSWINDOW_H
#define SETTINGSWINDOW_H
#ifndef IFCINTERFACE_PANELS_SETTINGSDIALOG_H
#define IFCINTERFACE_PANELS_SETTINGSDIALOG_H
#include <QDialog>
#include "../../components/Dialog.h"
#include <QString>
#include <vector>
class QCheckBox;
class QComboBox;
class QDoubleSpinBox;
class QLineEdit;
class QShowEvent;
class QSpinBox;
class QWidget;
class SettingsWindow : public QDialog {
namespace ifcinterface::modules::settings {
class SettingsDialog : public components::TabbedDialog {
Q_OBJECT
public:
explicit SettingsWindow(QWidget *parent = nullptr);
explicit SettingsDialog(QWidget* parent = nullptr);
protected:
void showEvent(QShowEvent* event) override;
private slots:
void onAccepted();
private:
void setupUi();
void syncFromSettings();
void syncThemeSettings();
void updateThemeEditorEnabled();
void pickThemeColor(QLineEdit* edit);
void onAccepted();
struct ThemeColorEditor {
QString key;
QLineEdit* edit = nullptr;
};
QLineEdit* geometry_library_edit_ = nullptr;
QCheckBox* show_stats_check_ = nullptr;
@@ -52,8 +66,14 @@ private:
QDoubleSpinBox* min_pixel_radius_spin_ = nullptr;
QDoubleSpinBox* motion_min_pixel_radius_spin_ = nullptr;
QDoubleSpinBox* lod1_pixel_threshold_spin_ = nullptr;
QSpinBox* hiz_resolution_spin_ = nullptr;
QCheckBox* hiz_enabled_check_ = nullptr;
QSpinBox* hiz_resolution_spin_ = nullptr;
QComboBox* nav_preset_combo_ = nullptr;
QComboBox* theme_mode_combo_ = nullptr;
QWidget* theme_custom_body_ = nullptr;
std::vector<ThemeColorEditor> theme_color_editors_;
};
} // namespace ifcinterface::modules::settings
#endif

Some files were not shown because too many files have changed in this diff Show More