mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-12 10:33:20 +00:00
Merge branch 'ifcviewer-wgpu' of https://github.com/IfcOpenShell/IfcOpenShell into ifcviewer-wgpu
This commit is contained in:
@@ -1,19 +1,19 @@
|
||||
# This file was generated with the assistance of an AI coding tool.
|
||||
################################################################################
|
||||
# #
|
||||
# This file is part of IfcOpenShell. #
|
||||
# This file is part of Bonsai. #
|
||||
# #
|
||||
# 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 #
|
||||
# Bonsai is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the 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, #
|
||||
# Bonsai 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. #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the Lesser GNU General Public License #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
################################################################################
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
@@ -88,37 +88,35 @@ std::optional<express::Base> ElementRegistry::findEntity(uint32_t object_id) con
|
||||
}
|
||||
}
|
||||
|
||||
void ElementRegistry::onSidecarElementsReady(uint32_t /*mid*/,
|
||||
std::vector<PackedElementInfo> elements,
|
||||
void ElementRegistry::onSidecarElementsReady(uint32_t /*model_id*/,
|
||||
std::vector<ElementTableRecord> elements,
|
||||
std::string string_table) {
|
||||
auto str = [&](uint32_t offset, uint32_t length) -> QString {
|
||||
auto string_from_table = [&](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) {
|
||||
for (const auto& packed_element : 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);
|
||||
info.object_id = packed_element.object_id;
|
||||
info.model_id = packed_element.model_id;
|
||||
info.ifc_id = packed_element.ifc_id;
|
||||
info.guid = string_from_table(packed_element.guid_offset, packed_element.guid_length);
|
||||
info.name = string_from_table(packed_element.name_offset, packed_element.name_length);
|
||||
info.type = string_from_table(packed_element.type_offset, packed_element.type_length);
|
||||
elements_[info.object_id] = info;
|
||||
}
|
||||
}
|
||||
|
||||
void ElementRegistry::onStreamedElementsReady(uint32_t /*mid*/, std::vector<ElementInfo> elements) {
|
||||
for (const auto& e : elements) {
|
||||
void ElementRegistry::onStreamedElementsReady(uint32_t /*model_id*/, std::vector<ElementInfo> elements) {
|
||||
for (const auto& element : 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);
|
||||
info.object_id = element.object_id;
|
||||
info.model_id = element.model_id;
|
||||
info.ifc_id = element.ifc_id;
|
||||
info.guid = QString::fromStdString(element.guid);
|
||||
info.name = QString::fromStdString(element.name);
|
||||
info.type = QString::fromStdString(element.type);
|
||||
elements_[info.object_id] = info;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
@@ -30,7 +30,7 @@
|
||||
#include <vector>
|
||||
|
||||
class SceneLoader;
|
||||
struct PackedElementInfo;
|
||||
struct ElementTableRecord;
|
||||
struct ElementInfo;
|
||||
|
||||
namespace bonsaiviewer {
|
||||
@@ -39,7 +39,6 @@ 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;
|
||||
@@ -58,10 +57,10 @@ public:
|
||||
std::optional<express::Base> findEntity(uint32_t object_id) const;
|
||||
|
||||
private:
|
||||
void onSidecarElementsReady(uint32_t mid,
|
||||
std::vector<PackedElementInfo> elements,
|
||||
void onSidecarElementsReady(uint32_t model_id,
|
||||
std::vector<ElementTableRecord> elements,
|
||||
std::string string_table);
|
||||
void onStreamedElementsReady(uint32_t mid, std::vector<ElementInfo> elements);
|
||||
void onStreamedElementsReady(uint32_t model_id, std::vector<ElementInfo> elements);
|
||||
|
||||
SceneLoader* loader_ = nullptr;
|
||||
std::unordered_map<uint32_t, BasicElementInfo> elements_;
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
@@ -100,10 +100,10 @@ void MainWindow::setupChrome() {
|
||||
QMainWindow::AllowTabbedDocks |
|
||||
QMainWindow::GroupedDragging);
|
||||
|
||||
auto bind_shortcut = [this](const QKeySequence& sequence, auto fn) {
|
||||
auto bind_shortcut = [this](const QKeySequence& sequence, auto handler) {
|
||||
auto* shortcut = new QShortcut(sequence, this);
|
||||
shortcut->setContext(Qt::WindowShortcut);
|
||||
connect(shortcut, &QShortcut::activated, this, fn);
|
||||
connect(shortcut, &QShortcut::activated, this, handler);
|
||||
};
|
||||
bind_shortcut(QKeySequence("Ctrl+Shift+L"), [this]() {
|
||||
modules::viewport::commands::toggleDistance(*viewport_widget_->viewport());
|
||||
@@ -496,6 +496,20 @@ void MainWindow::setupStatus() {
|
||||
status_perf_label_->setVisible(show);
|
||||
if (!show) status_perf_label_->clear();
|
||||
});
|
||||
|
||||
// Nav mouse preset. The WGPU_NAV_PRESET env var is a dev override applied at
|
||||
// ViewportWindow construction; otherwise apply the persisted Settings choice
|
||||
// here, and re-apply live whenever the user changes it in the dialog.
|
||||
if (!std::getenv("WGPU_NAV_PRESET")) {
|
||||
if (auto* vp = viewport_widget_->viewport())
|
||||
vp->applyNavPreset(AppSettings::navPresetName(AppSettings::instance().navPreset()));
|
||||
}
|
||||
connect(&AppSettings::instance(), &AppSettings::navPresetChanged, this,
|
||||
[this](AppSettings::NavPreset preset) {
|
||||
if (auto* vp = viewport_widget_->viewport())
|
||||
vp->applyNavPreset(AppSettings::navPresetName(preset));
|
||||
});
|
||||
|
||||
connect(session_state_, &bonsaiviewer::SessionState::statusMessageChanged,
|
||||
this, [this](const QString& mode, const QString& detail) {
|
||||
status_mode_label_->setText(mode);
|
||||
@@ -530,17 +544,17 @@ void MainWindow::setupLoader() {
|
||||
});
|
||||
|
||||
connect(viewport_widget_->viewport(), &ViewportWindow::frameStatsUpdated, this,
|
||||
[this](const ViewportWindow::FrameStats& s) {
|
||||
[this](const ViewportWindow::FrameStats& stats) {
|
||||
if (!status_perf_label_->isVisible()) return;
|
||||
status_perf_label_->setText(
|
||||
QString("%1 fps | %2 ms | %3/%4 obj | %5/%6 tri | %7 draws")
|
||||
.arg(s.fps, 0, 'f', 1)
|
||||
.arg(s.frame_time_ms, 0, 'f', 1)
|
||||
.arg(s.visible_objects)
|
||||
.arg(s.total_objects)
|
||||
.arg(s.visible_triangles)
|
||||
.arg(s.total_triangles)
|
||||
.arg(s.gl_draw_calls));
|
||||
.arg(stats.fps, 0, 'f', 1)
|
||||
.arg(stats.frame_time_ms, 0, 'f', 1)
|
||||
.arg(stats.visible_objects)
|
||||
.arg(stats.total_objects)
|
||||
.arg(stats.visible_triangles)
|
||||
.arg(stats.total_triangles)
|
||||
.arg(stats.gl_draw_calls));
|
||||
});
|
||||
connect(viewport_widget_->viewport(), &ViewportWindow::objectPicked,
|
||||
this, [this](uint32_t object_id) {
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
@@ -64,25 +64,25 @@ void SessionState::createLoader(ViewportWindow* viewport) {
|
||||
});
|
||||
connect(loader_, &SceneLoader::progressChanged, this, &SessionState::setProgress);
|
||||
connect(loader_, &SceneLoader::loadedFromSidecar, this,
|
||||
[this, format_elapsed](uint32_t mid, qint64 elapsed_ms) {
|
||||
[this, format_elapsed](uint32_t model_id, qint64 elapsed_ms) {
|
||||
setStatusMessage("Loaded",
|
||||
QString("%1 from cache in %2")
|
||||
.arg(loader_->displayName(mid))
|
||||
.arg(loader_->displayName(model_id))
|
||||
.arg(format_elapsed(elapsed_ms)));
|
||||
endProgress();
|
||||
emit modelGeometryReady(mid);
|
||||
emit modelGeometryReady(model_id);
|
||||
});
|
||||
connect(loader_, &SceneLoader::loadedFromStream, this,
|
||||
[this, format_elapsed](uint32_t mid, qint64 elapsed_ms) {
|
||||
[this, format_elapsed](uint32_t model_id, qint64 elapsed_ms) {
|
||||
setStatusMessage("Loaded",
|
||||
QString("%1 streamed in %2")
|
||||
.arg(loader_->displayName(mid))
|
||||
.arg(loader_->displayName(model_id))
|
||||
.arg(format_elapsed(elapsed_ms)));
|
||||
endProgress();
|
||||
emit modelGeometryReady(mid);
|
||||
emit modelGeometryReady(model_id);
|
||||
});
|
||||
connect(loader_, &SceneLoader::loadCancelled, this, [this](uint32_t mid) {
|
||||
setStatusMessage("Cancelled", loader_->displayName(mid));
|
||||
connect(loader_, &SceneLoader::loadCancelled, this, [this](uint32_t model_id) {
|
||||
setStatusMessage("Cancelled", loader_->displayName(model_id));
|
||||
endProgress();
|
||||
});
|
||||
connect(loader_, &SceneLoader::loadError, this,
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
.. This file was generated with the assistance of an AI coding tool.
|
||||
|
||||
Capturing debug output
|
||||
======================
|
||||
|
||||
When BonsaiViewer misbehaves — a crash, a black viewport, a hang — the
|
||||
single most useful thing you can hand off to a developer is a
|
||||
**stack trace at the point of failure**. This page covers how to capture
|
||||
one on Linux, macOS, and Windows.
|
||||
|
||||
Pre-flight: always run from a terminal
|
||||
--------------------------------------
|
||||
|
||||
GUI launches discard everything BonsaiViewer prints. Double-clicking
|
||||
``BonsaiViewer`` (or ``open -a`` on macOS, or a Start Menu shortcut on
|
||||
Windows) hides every ``qWarning``, ``[wgpu] …`` line, and runtime error
|
||||
message. **Always launch from a terminal first** when something is
|
||||
wrong:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Linux
|
||||
/path/to/BonsaiViewer
|
||||
|
||||
# macOS
|
||||
/Applications/BonsaiViewer.app/Contents/MacOS/BonsaiViewer
|
||||
|
||||
.. code-block:: bat
|
||||
|
||||
:: Windows
|
||||
"C:\path\to\BonsaiViewer.exe"
|
||||
|
||||
If the problem reproduces just by running from a terminal, copy the
|
||||
console output verbatim — it's usually enough on its own. If the
|
||||
process disappears without printing anything useful, you need a
|
||||
debugger; pick the section below for your OS.
|
||||
|
||||
Linux
|
||||
-----
|
||||
|
||||
GDB is the standard debugger and ships with every distribution.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
gdb --args /path/to/BonsaiViewer <any args you'd normally pass>
|
||||
(gdb) run
|
||||
# ... reproduce the crash ...
|
||||
(gdb) bt 30 # backtrace of the crashing thread, top 30 frames
|
||||
(gdb) thread apply all bt # all threads, full backtraces
|
||||
|
||||
If BonsaiViewer crashed *without* a debugger and you have core dumps
|
||||
enabled (``ulimit -c unlimited``), open the core file directly:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
gdb /path/to/BonsaiViewer /path/to/core.<pid>
|
||||
(gdb) bt 30
|
||||
|
||||
Most distributions hand crashes to ``systemd-coredump``; ``coredumpctl
|
||||
list`` shows recent dumps and ``coredumpctl debug BonsaiViewer`` jumps
|
||||
straight into GDB.
|
||||
|
||||
For runtime memory errors (use-after-free, buffer overflows) that don't
|
||||
crash immediately, run under ``valgrind`` or rebuild with ASAN
|
||||
(``-fsanitize=address``).
|
||||
|
||||
macOS
|
||||
-----
|
||||
|
||||
LLDB is bundled with the Xcode Command Line Tools (``xcode-select
|
||||
--install`` if you don't already have it).
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
lldb -- /Applications/BonsaiViewer.app/Contents/MacOS/BonsaiViewer
|
||||
(lldb) run
|
||||
# ... reproduce the crash ...
|
||||
(lldb) bt 30 # backtrace of the crashing thread
|
||||
(lldb) thread backtrace all # all threads
|
||||
|
||||
macOS also writes automatic crash reports (``.ips`` files since macOS
|
||||
12) at ``~/Library/Logs/DiagnosticReports/``. ``ls -lt
|
||||
~/Library/Logs/DiagnosticReports/ | head`` shows the most recent ones.
|
||||
Open the newest ``BonsaiViewer-*.ips`` in Console.app or any text
|
||||
editor — the JSON contains a full symbolicated stack.
|
||||
|
||||
Obj-C use-after-free aborts (``message sent to deallocated instance
|
||||
…``) often show up as a vague segfault with NSZombieEnabled disabled.
|
||||
Re-run with zombies turned on to get the real recipient of the dead
|
||||
message:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
NSZombieEnabled=YES lldb -- /Applications/BonsaiViewer.app/Contents/MacOS/BonsaiViewer
|
||||
(lldb) run
|
||||
|
||||
Windows
|
||||
-------
|
||||
|
||||
There's no preinstalled debugger on Windows; install **WinDbg Preview**
|
||||
from the Microsoft Store (free) or grab **ProcDump** as a single
|
||||
standalone ``.exe`` from
|
||||
https://learn.microsoft.com/en-us/sysinternals/downloads/procdump.
|
||||
|
||||
**Live debugging with WinDbg Preview**
|
||||
|
||||
1. Install ``WinDbg Preview`` from the Microsoft Store.
|
||||
2. ``File → Launch executable → Browse`` to ``BonsaiViewer.exe``.
|
||||
3. Press F5 to start. Reproduce the crash.
|
||||
4. When WinDbg breaks in, type ``kn30`` in the command bar at the
|
||||
bottom — that's the stack trace with frame numbers.
|
||||
|
||||
**Post-mortem dump with ProcDump (no install)**
|
||||
|
||||
Useful when the crash is hard to reach inside a debugger (e.g. it
|
||||
happens during a Bonsai-launched child process):
|
||||
|
||||
.. code-block:: bat
|
||||
|
||||
procdump -ma -e BonsaiViewer.exe -accepteula
|
||||
|
||||
In a second terminal, run ``BonsaiViewer.exe``. When it crashes,
|
||||
ProcDump writes ``BonsaiViewer.exe_YYMMDD_HHMMSS.dmp`` in the current
|
||||
directory. Open that ``.dmp`` in WinDbg Preview
|
||||
(``File → Open dump file``) and type ``kn30``.
|
||||
|
||||
**Getting symbolicated frames**
|
||||
|
||||
The shipping ``BonsaiViewer.exe`` doesn't include ``.pdb`` symbols. The
|
||||
stack will show DLL boundaries (which is often enough to narrow a bug
|
||||
down to ``BonsaiViewer.exe`` vs. ``wgpu_native.dll`` vs.
|
||||
``KERNELBASE.dll``), but function names will be addresses. To get
|
||||
symbol names:
|
||||
|
||||
1. Find the matching ``BonsaiViewer.pdb`` in the build artifacts of the
|
||||
workflow run that produced your ``.exe`` (under "Upload Build
|
||||
Logs").
|
||||
2. Drop it next to ``BonsaiViewer.exe``.
|
||||
3. Re-launch under WinDbg; it'll pick up the symbols automatically.
|
||||
|
||||
For runtime memory errors that don't crash immediately, the
|
||||
**Application Verifier** (in the Windows SDK) is the closest analogue
|
||||
to valgrind / ASAN.
|
||||
|
||||
What to send the developer
|
||||
--------------------------
|
||||
|
||||
A stack trace alone is usually enough. If the crash is reliable but
|
||||
needs specific input to trigger, also include:
|
||||
|
||||
1. The exact command line you launched with (env vars matter — e.g.
|
||||
``WGPU_PRESENT_MODE=immediate``).
|
||||
2. The model file (or its publicly-shareable equivalent) that triggers
|
||||
the crash, plus the smallest model that *doesn't* trigger it.
|
||||
3. The terminal output from before the crash (the ``[wgpu …]`` /
|
||||
``Sidecar metadata read: …`` / ``Streamer done: …`` lines tell us
|
||||
which subsystem was active when things went south).
|
||||
4. Your GPU + driver version and OS version. On Linux: ``glxinfo |
|
||||
grep "OpenGL renderer"`` and ``uname -r``. On macOS: Apple menu →
|
||||
About this Mac. On Windows: ``dxdiag``.
|
||||
@@ -1,277 +1,438 @@
|
||||
Viewport architecture
|
||||
=====================
|
||||
|
||||
This page documents the IFC viewer renderer that lives at
|
||||
``src/ifcviewer/`` and underpins both BonsaiViewer and the standalone
|
||||
``IfcViewerMinimal`` testbed.
|
||||
This page documents the WebGPU IFC viewer renderer in ``src/ifcviewer``. The
|
||||
same renderer is used by BonsaiViewer, IfcViewerMinimal, and the web build. It
|
||||
explains the path from an IFC model or ``.ifcview`` cache to rendered triangles,
|
||||
including the optimisation stages that decide what is loaded, what is resident
|
||||
in GPU memory, and what is drawn.
|
||||
|
||||
Consumers
|
||||
---------
|
||||
Overview
|
||||
--------
|
||||
|
||||
``src/ifcviewer/`` is a static library (``IfcViewer``) — it does not
|
||||
ship as an executable on its own. Two consumers link against it:
|
||||
|
||||
* **BonsaiViewer** (``src/bonsaiviewer/``) — full project shell with
|
||||
side panels, federation tree, settings dialog, connector picker,
|
||||
ribbon. The end-user product.
|
||||
* **IfcViewerMinimal** (``src/ifcviewer-minimal/``) — single-file
|
||||
testbed that opens an IFC, a ``.ifcview`` sidecar, or a federation
|
||||
``.ifcfed`` and shows just the viewport. Used for screenshot
|
||||
regression tests, benchmarks, and isolating renderer-only issues
|
||||
from the Bonsai Viewer shell.
|
||||
|
||||
Stack
|
||||
-----
|
||||
|
||||
* **wgpu-native v29** for rendering, fetched as a pre-built binary in
|
||||
``src/ifcviewer/CMakeLists.txt``. WebGPU API on top of Vulkan
|
||||
(Linux/Windows) or Metal (macOS, via the ``MetalSurface_mac``
|
||||
Cocoa bridge).
|
||||
* **Qt6** for windowing (a raw ``QWindow``, not ``QOpenGLWidget``)
|
||||
and the surface integration with the OS. Qt is also the source of
|
||||
``QSettings`` for persisted user preferences (``AppSettings``),
|
||||
the event loop driving render scheduling, and the timer/elapsed
|
||||
primitives used in instrumentation.
|
||||
* **IfcOpenShell C++ libs** (IfcParse / IfcGeom) for IFC parsing and
|
||||
geometry generation, plus **helpers** for the schema-agnostic
|
||||
helpers (``unit``, ``geolocation``, ``placement``).
|
||||
* **Eigen3** for 4×4 matrices and small linear algebra.
|
||||
* **meshoptimizer** at sidecar-build time only — decimates each
|
||||
unique mesh into an LOD1 slice. Not pulled at runtime.
|
||||
|
||||
Five core ideas
|
||||
---------------
|
||||
|
||||
The renderer is built around five decisions worth knowing about
|
||||
before reading the code:
|
||||
|
||||
**1. Unique-mesh GPU instancing.** IFC scenes are dominated by
|
||||
repeated geometry (identical doors, windows, studs, pipes, …). The
|
||||
``IfcGeom::Iterator`` surfaces representation identity, so each
|
||||
unique mesh is uploaded once into a per-model vertex buffer slice
|
||||
and every placement becomes a tiny ``InstanceCpu`` record
|
||||
(transform + ``object_id`` + optional colour override). On a 1 M-
|
||||
placement BIM scene this collapses tens of millions of duplicate
|
||||
vertices into a few hundred MB of unique meshes.
|
||||
|
||||
**2. Quantized 12-byte vertex format.** Each vertex is exactly 12
|
||||
bytes:
|
||||
The renderer is built around a small number of data transformations:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
offset 0 pos 3 × uint16 normalised → mix(mesh.aabb_min, mesh.aabb_max, t)
|
||||
offset 6 normal 2 × int8 normalised → [-1,1]; octahedral-decoded
|
||||
offset 8 color 4 × uint8 normalised → [0,1]; alpha is real (see "Transparency")
|
||||
IFC / .ifcview input
|
||||
-> SceneLoader
|
||||
-> GeometryStreamer or sidecar metadata reader
|
||||
-> SidecarData-shaped model metadata
|
||||
-> ViewportCore::applyCachedModel()
|
||||
-> per-model chunks, mesh table, instance table
|
||||
-> cullModelCpuCompute()
|
||||
-> visible_draws + prefix_sums
|
||||
-> driveStreamingLoads()
|
||||
-> applyStreamedChunk()
|
||||
-> WebGPU draw() calls
|
||||
-> WGSL expands visible draws into indexed triangles
|
||||
|
||||
The dequantisation basis is per-mesh, uploaded once in a
|
||||
``MeshGpu`` SSBO at binding 2; the vertex shader reads it via the
|
||||
mesh index that comes through the visible-draws table. ``int8``
|
||||
normals carry ~1.4° worst-case angular error, invisible for the
|
||||
overwhelmingly axis-aligned faces (walls, floors, slabs) BIM models
|
||||
produce.
|
||||
The important split is between **metadata** and **geometry bytes**. Metadata
|
||||
describes meshes, instances, bounds, transforms, element ids, and chunk layout.
|
||||
Geometry bytes are the quantised vertices and indices. A sidecar load reads
|
||||
metadata first and leaves geometry chunks non-resident until the camera needs
|
||||
them. A direct IFC load builds the same structures from the geometry iterator,
|
||||
then uploads all chunks at finalisation.
|
||||
|
||||
**3. Streaming chunked storage backed by a probed VRAM pool.** Per-
|
||||
model state isn't loaded all at once. The sidecar's vertex/index
|
||||
data is split into spatially-coherent chunks (Morton-sorted at bake
|
||||
time, packed greedily); ``ViewportWindow`` keeps each chunk's bytes
|
||||
resident in a single GPU buffer pool whose size is determined at
|
||||
startup by ``wgpuDevicePushErrorScope(OutOfMemory)`` probing. As
|
||||
the camera moves, chunks page in and out via the
|
||||
``StreamingThread`` background worker; the render thread never
|
||||
issues a blocking disk read.
|
||||
Main components
|
||||
---------------
|
||||
|
||||
**4. Sidecar cache as the fast path.** Loading an IFC the first
|
||||
time runs the ``IfcGeom::Iterator`` (expensive); the result is
|
||||
serialised to a ``.ifcview`` file (``SIDECAR_VERSION = 13``) next
|
||||
to the source. Subsequent opens go straight from disk to GPU
|
||||
buffers — no iteration, no geometry-engine cost. The sidecar
|
||||
format embeds chunked vertex sections with a byte-range table of
|
||||
contents so the streaming thread can fetch any chunk by ``pread``
|
||||
without scanning the file.
|
||||
``SceneLoader``
|
||||
Orchestrates model loading. It first tries to read ``.ifcview`` metadata
|
||||
when sidecar reads are enabled. If that fails, it starts the geometry
|
||||
streamer. It also keeps the ``ifcopenshell::file`` used by property and UI
|
||||
code.
|
||||
|
||||
**5. Event-driven rendering.** No render timer. Frames are
|
||||
scheduled only via ``QWindow::requestUpdate()`` when something
|
||||
actually changes — camera motion, streaming chunk arrival, hover,
|
||||
selection, settings edits. When the camera is idle, the cull pass
|
||||
short-circuits and the main thread blocks in the Qt event loop;
|
||||
the viewer costs zero CPU/GPU on a static scene.
|
||||
``GeometryStreamer``
|
||||
Runs ``IfcGeom::Iterator`` on a worker thread for raw IFC loads. It emits a
|
||||
``StreamedMesh`` once for each unique representation mesh and a
|
||||
``StreamedInstance`` for each placed occurrence.
|
||||
|
||||
Per-frame pipeline
|
||||
------------------
|
||||
``SidecarBuilder``
|
||||
Optionally mirrors the streamer's output while a raw IFC is loading. It
|
||||
writes a new ``.ifcview`` cache after the stream finishes, so the next open
|
||||
can use the sidecar fast path.
|
||||
|
||||
Each call to ``ViewportWindow::render()`` runs the same six phases:
|
||||
``ViewportWindow``
|
||||
The Qt-facing window wrapper. It owns UI/window integration and forwards
|
||||
renderer work to ``ViewportCore``.
|
||||
|
||||
``ViewportCore``
|
||||
The renderer core. It owns the WebGPU device-facing model state, pipelines,
|
||||
chunk residency, culling, streaming, picking, screenshot capture, and render
|
||||
loop.
|
||||
|
||||
``ModelGpuData``
|
||||
Per-model runtime state: mesh metadata, instances, chunks, GPU buffers,
|
||||
per-chunk visibility scratch, object lookup maps, and CPU-side caches used
|
||||
by tools.
|
||||
|
||||
``BufferPool``
|
||||
Sub-allocates chunk vertex and index bytes from one or more WebGPU buffers.
|
||||
Chunks enter and leave this pool as the streaming system pages geometry in
|
||||
and out.
|
||||
|
||||
``StreamingThread`` and web range loading
|
||||
Desktop sidecar streaming uses a background worker to read and decompress
|
||||
chunk frames. The web build uses asynchronous byte-range reads from a
|
||||
registered file or URL source.
|
||||
|
||||
Load path 1: sidecar hit
|
||||
------------------------
|
||||
|
||||
The fast path starts when ``SceneLoader`` finds a readable ``.ifcview`` cache.
|
||||
The reader validates the sidecar header, skips the compressed geometry section,
|
||||
and reads the metadata blocks.
|
||||
|
||||
For desktop loading, ``readSidecarMetadataOnly()`` returns a
|
||||
``StreamingSidecar`` containing:
|
||||
|
||||
- the sidecar file path
|
||||
- the geometry section offset
|
||||
- mesh records
|
||||
- instance records
|
||||
- georeferencing state
|
||||
- the baked chunk table of contents
|
||||
- element metadata
|
||||
|
||||
For web loading, ``ViewportCore::loadSidecarMetadataWeb()`` reads only the
|
||||
header and critical metadata before creating the model. It records the location
|
||||
of the deferred element metadata block and fetches that later only when UI code
|
||||
needs it.
|
||||
|
||||
``ViewportCore::applyCachedModel()`` then converts sidecar metadata into
|
||||
runtime model state:
|
||||
|
||||
1. It creates the chunk list. If the sidecar contains a baked
|
||||
``SidecarChunk`` table, that table is authoritative. Each chunk is a
|
||||
consecutive mesh range with compressed vertex and index frame locations. If
|
||||
the model came from a direct in-memory load and has no table, the chunk plan
|
||||
is derived from mesh centroids with Morton sorting and greedy packing.
|
||||
2. It creates per-chunk scratch buffers: ``visible_draws``, ``prefix_sums``,
|
||||
and a small uniform with draw counts.
|
||||
3. It uploads the per-mesh quantisation table, ``MeshGpu``.
|
||||
4. It uploads the per-instance GPU table, ``InstanceGpu``.
|
||||
5. It builds CPU lookup tables: object id to instance, instance to chunk,
|
||||
mesh-local offsets inside each chunk, chunk AABBs, and chunk instance lists.
|
||||
6. It leaves the actual chunk vertex and index bytes non-resident. They are
|
||||
fetched later by ``driveStreamingLoads()``.
|
||||
|
||||
At this point the viewer knows the model's structure and bounds, but a sidecar
|
||||
model has not necessarily uploaded any triangles yet.
|
||||
|
||||
Load path 2: direct IFC stream
|
||||
------------------------------
|
||||
|
||||
When no usable sidecar exists, ``SceneLoader`` starts ``GeometryStreamer``.
|
||||
Streamer signals are queued from the worker thread into the UI/render thread:
|
||||
|
||||
``StreamedMesh``
|
||||
A unique mesh in transfer form. Vertices are seven floats per vertex:
|
||||
position, normal, and packed colour.
|
||||
|
||||
``StreamedInstance``
|
||||
One placed occurrence of a mesh, with object id, colour override,
|
||||
placement transform, and world AABB.
|
||||
|
||||
During the stream, ``ViewportCore::uploadStreamedMesh()`` and
|
||||
``ViewportCore::uploadStreamedInstance()`` stage the data in a ``SidecarData``
|
||||
shape. Mesh vertices are converted to the renderer's packed 12-byte format,
|
||||
indices are stored, and instance records are accumulated. The viewport does not
|
||||
incrementally draw these raw streamed mesh records one by one.
|
||||
|
||||
When the streamer finishes, ``SceneLoader::onStreamerFinished()`` calls
|
||||
``ViewportCore::finalizeModel()``. Finalisation wraps the staged data in a
|
||||
``StreamingSidecar`` object and calls the same ``applyCachedModel()`` path used
|
||||
by sidecar loads. It then gathers the already-staged vertex and index bytes per
|
||||
chunk and calls ``applyStreamedChunk()`` for each chunk. Direct IFC loads
|
||||
therefore become resident after finalisation rather than streaming from disk.
|
||||
|
||||
If sidecar writes are enabled, ``SidecarBuilder`` finalises its mirrored copy,
|
||||
builds LOD1 where available, reorders geometry into streaming chunk order, and
|
||||
writes the ``.ifcview`` file for the next open.
|
||||
|
||||
Mesh and vertex representation
|
||||
------------------------------
|
||||
|
||||
IFC geometry is represented as unique meshes plus instances. Repeated IFC
|
||||
representations are uploaded once, and every placement becomes an instance that
|
||||
references a mesh.
|
||||
|
||||
The GPU vertex format is 12 bytes per vertex:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
offset 0 position uint16[3] quantised against the mesh local AABB
|
||||
offset 6 normal int8[2] octahedral encoded
|
||||
offset 8 colour uint8[4] RGBA
|
||||
|
||||
The mesh's ``MeshGpu`` record stores ``aabb_min`` and ``aabb_max``. The vertex
|
||||
shader reconstructs local positions by mapping the three 16-bit coordinates
|
||||
back into that local AABB. The instance transform then moves the vertex to
|
||||
world space.
|
||||
|
||||
Indices are ``uint32``. Each mesh has an LOD0 index range and may have an LOD1
|
||||
index range. LOD1 reuses the same vertices with a smaller index list generated
|
||||
at sidecar-build time.
|
||||
|
||||
Chunk layout and residency
|
||||
--------------------------
|
||||
|
||||
Chunks are the unit of streaming, residency, culling priority, and rendering
|
||||
bind groups. A chunk owns:
|
||||
|
||||
- a list of mesh ids
|
||||
- a list of instance ids
|
||||
- a world AABB
|
||||
- compressed sidecar frame locations, for sidecar-backed models
|
||||
- local vertex and index offsets for meshes inside the chunk
|
||||
- GPU pool slices when resident
|
||||
- cull output buffers
|
||||
|
||||
For sidecar-backed models, chunks start non-resident. They have metadata and
|
||||
small cull buffers, but no vertex/index pool slices. A chunk becomes resident
|
||||
when ``applyStreamedChunk()`` receives its decompressed vertex and index bytes.
|
||||
|
||||
``applyStreamedChunk()``:
|
||||
|
||||
1. Allocates vertex and index slices from ``BufferPool``.
|
||||
2. Uploads chunk bytes with ``wgpuQueueWriteBuffer``.
|
||||
3. Builds the chunk bind group.
|
||||
4. Marks the chunk resident.
|
||||
5. Scans alpha bytes so culling can route transparent meshes to the transparent
|
||||
pass.
|
||||
6. Computes mesh-local volumes and triangle CPU shadows used by measurement
|
||||
tools.
|
||||
|
||||
``unloadChunk()`` releases the chunk's bind group and returns its pool slices
|
||||
to ``BufferPool``. Metadata and cull scratch remain allocated so the chunk can
|
||||
be loaded again later.
|
||||
|
||||
Per-frame render pipeline
|
||||
-------------------------
|
||||
|
||||
Each frame in ``ViewportCore::render()`` follows this order:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
render():
|
||||
1. fpsIntegrate() ← advance fly-mode camera by wall-clock dt
|
||||
2. drainHizReadbacks() ← absorb HiZ readback completions
|
||||
3. uploadSelectionFlagsIfDirty()
|
||||
4. cullModelCpuCompute() ← parallel per-model frustum+contrib+HiZ+LOD
|
||||
cullModelCpuUpload() ← writeBuffer visible-draws + prefix-sums
|
||||
5. driveStreamingLoads() ← enqueue chunk fetches, apply completions
|
||||
6. encode passes:
|
||||
opaque main (depthWriteEnabled=True, no blend)
|
||||
transparent main (depthWriteEnabled=True, SrcAlpha/InvSrcAlpha blend)
|
||||
edge silhouette (samples depth buffer)
|
||||
overlay (HUD, labels, gizmos, measurements, marquee)
|
||||
wgpuSurfacePresent()
|
||||
drain completed HiZ readbacks
|
||||
upload selection flags if dirty
|
||||
acquire surface texture
|
||||
update frame uniforms
|
||||
cull visible instances and upload visible draw tables
|
||||
drive chunk streaming and eviction
|
||||
encode opaque main pass
|
||||
encode transparent main pass
|
||||
encode in-pass overlays
|
||||
encode edge silhouette pass
|
||||
encode HiZ resolve if enabled
|
||||
encode post-main overlays
|
||||
submit command buffer
|
||||
present surface
|
||||
|
||||
The two-pass alpha split (#6) routes each visible instance into
|
||||
either the opaque half or the transparent half of the per-chunk
|
||||
``visible_draws_scratch`` based on a per-mesh ``has_alpha`` flag +
|
||||
the instance's ``color_override_rgba8`` alpha byte. The transparent
|
||||
pass uses the same buffers, just with ``firstVertex`` offset to the
|
||||
opaque half's end. ``Alt+X`` forces every instance through the
|
||||
transparent pass via the ``xray_alpha_cap`` frame uniform, giving a
|
||||
free X-ray mode.
|
||||
Streaming runs after culling. That means a chunk that becomes resident during
|
||||
``driveStreamingLoads()`` is usually first visible on a later frame. The
|
||||
renderer requests a short burst of follow-up frames while streaming work is
|
||||
settling so an event-driven render loop does not stop before newly resident
|
||||
chunks are drawn.
|
||||
|
||||
Cull
|
||||
~~~~
|
||||
Culling
|
||||
-------
|
||||
|
||||
CPU-side, runs parallel per model via ``std::async``. The cascade
|
||||
per instance is:
|
||||
Culling is CPU-side and model-parallel on desktop. The web build currently uses
|
||||
the serial path because the WebAssembly build is not wired for pthread-backed
|
||||
``std::async``.
|
||||
|
||||
1. **Chunk-level frustum cull** — each chunk's AABB is tested first;
|
||||
off-frustum chunks skip every instance inside them in one shot.
|
||||
2. **Per-instance frustum cull** — for chunks that pass.
|
||||
3. **Contribution cull** — instances whose sphere projects below
|
||||
``viewport/min_pixel_radius`` (stationary) or
|
||||
``viewport/motion_min_pixel_radius`` (during motion) get dropped.
|
||||
4. **HiZ occlusion** — projected AABB tested against the previous
|
||||
frame's depth pyramid, when the VP matches. The pyramid is
|
||||
captured at end-of-frame, downsampled, read back to CPU, and
|
||||
queried with conservative all-fine-texels-agree semantics. Off
|
||||
by default; ``WGPU_HIZ=1`` re-enables.
|
||||
5. **LOD selection** — instances with sub-``viewport/lod1_pixel_threshold``
|
||||
projected size and an available LOD1 slice route through the
|
||||
LOD1 index range. Same vertex buffer, different ``firstIndex``.
|
||||
``cullModelCpuCompute()`` is chunk-driven:
|
||||
|
||||
Survivors are appended to the chunk's ``visible_draws_scratch`` and
|
||||
cumulative ``prefix_sums_scratch`` — the vertex shader uses a
|
||||
binary search on prefix_sums to translate ``vertex_index`` into a
|
||||
``(draw_index, vertex_in_draw)`` pair.
|
||||
1. Reset each chunk's per-frame scratch and counters.
|
||||
2. Frustum-test the chunk AABB. If the chunk is outside the frustum, every
|
||||
instance inside it is skipped.
|
||||
3. For frustum-passing chunks, process each instance in the chunk.
|
||||
4. Skip hidden object ids.
|
||||
5. Frustum-test the instance AABB.
|
||||
6. Estimate projected pixel radius and projected AABB area.
|
||||
7. Accumulate projected AABB area into the chunk's streaming priority.
|
||||
8. Apply contribution culling. Instances below the current pixel-radius
|
||||
threshold are dropped.
|
||||
9. Mark the chunk contribution-visible. This is the signal that
|
||||
``driveStreamingLoads()`` uses to decide whether a non-resident chunk is
|
||||
worth fetching.
|
||||
10. Optionally apply HiZ occlusion using the previous frame's depth pyramid.
|
||||
11. Choose LOD0 or LOD1 based on projected size and LOD1 availability.
|
||||
12. Classify the draw as opaque or transparent.
|
||||
13. Append a ``VisibleDrawGpu`` record and extend the chunk prefix sum.
|
||||
|
||||
Streaming
|
||||
~~~~~~~~~
|
||||
The contribution threshold is higher while the camera is moving when
|
||||
``viewport/motion_min_pixel_radius`` exceeds the still threshold. This drops
|
||||
more sub-pixel work during navigation and reduces cull and streaming pressure.
|
||||
|
||||
The buffer pool (``BufferPool``) is a free-list sub-allocator over
|
||||
one or more wgpu buffers, sized at startup by probing for
|
||||
``WGPUErrorType_OutOfMemory`` on a series of growing allocations.
|
||||
``StreamingThread`` is one worker thread that owns disk I/O:
|
||||
``driveStreamingLoads()`` posts requests for the highest-priority
|
||||
non-resident chunks and drains completions. The pool's eviction
|
||||
policy is "evict the lowest-priority resident chunk whose priority
|
||||
is below ``candidate_priority / EVICT_PRIORITY_RATIO``" — strict
|
||||
enough to prevent thrash, lax enough that a single panning frame
|
||||
doesn't refuse the move.
|
||||
HiZ occlusion is optional. When enabled, the renderer uses a depth pyramid from
|
||||
a previous compatible view-projection matrix. Contribution culling runs before
|
||||
HiZ because it is much cheaper and reduces the number of HiZ tests.
|
||||
|
||||
Priorities come from a per-chunk screen-space score computed during
|
||||
cull (chunk AABB projected to a screen-rect area). Spatial sort at
|
||||
bake time means screen-adjacent chunks are byte-adjacent on disk,
|
||||
so the typical fetch is a single ``pread`` range, not scatter-
|
||||
gather.
|
||||
Visible draw tables
|
||||
-------------------
|
||||
|
||||
Overlay / picking / section cuts
|
||||
--------------------------------
|
||||
Culling does not issue draw calls directly. It writes compact per-chunk tables:
|
||||
|
||||
* ``OverlayRenderer`` runs after the main passes. Bundles the HUD
|
||||
text, world-anchored labels (measurement readouts), the
|
||||
marquee-select rect, the corner axis gizmo, the orbit pivot
|
||||
indicator, and section-plane outline rendering. Has its own
|
||||
pipelines with the usual ``SrcAlpha/OneMinusSrcAlpha`` blend.
|
||||
* **Picking** is a second render pass with a dedicated pipeline
|
||||
writing object IDs into an ``R32UInt`` framebuffer. Clicking
|
||||
triggers a ``wgpuQueueOnSubmittedWorkDone`` + ``mapAsync``
|
||||
readback of one pixel. No CPU-side raycasting.
|
||||
* **Section cuts** (the ``K`` tool) push up to six clipping planes
|
||||
into the frame uniform; ``is_section_clipped()`` in WGSL discards
|
||||
fragments on the positive side. The plane gizmo and visible
|
||||
cross-section outline come from OverlayRenderer.
|
||||
``visible_draws``
|
||||
One record per visible instance draw. It stores mesh id, instance index,
|
||||
first index, and base vertex.
|
||||
|
||||
Federation
|
||||
----------
|
||||
``prefix_sums``
|
||||
A monotonic array that maps a flat vertex id inside one chunk draw call to
|
||||
a visible draw record.
|
||||
|
||||
``Federation`` (in ``IfcViewer``, not Bonsai) is the multi-model
|
||||
data model. Each model in a federation has a fed_id, optional
|
||||
group membership, an explicit ``ModelTransformation``, a
|
||||
``CoordinateOperation`` (from the IFC's IfcMapConversion), and a
|
||||
visibility flag. The viewport composes a single
|
||||
``transform_meters`` per instance as:
|
||||
``per_chunk_uniform``
|
||||
Four counters: total visible draws, total visible vertices, opaque visible
|
||||
vertices, and opaque visible draws.
|
||||
|
||||
After culling, ``cullModelCpuUpload()`` uploads those arrays to the chunk's GPU
|
||||
buffers. Chunks with zero visible draws have their uniform zeroed and are
|
||||
skipped by the render pass.
|
||||
|
||||
Triangle rendering
|
||||
------------------
|
||||
|
||||
The main render uses one WebGPU ``draw()`` per resident visible chunk, not one
|
||||
draw per IFC element. The CPU has already compacted the visible instances into
|
||||
the chunk's ``visible_draws`` table.
|
||||
|
||||
The vertex shader receives a flat ``vertex_index`` from the draw call. It:
|
||||
|
||||
1. Binary-searches ``prefix_sums`` to find the visible draw record.
|
||||
2. Computes the local vertex within that draw.
|
||||
3. Reads the mesh-local index from the chunk index buffer.
|
||||
4. Adds the draw's base vertex to get the packed vertex record.
|
||||
5. Decodes quantised position, octahedral normal, and colour.
|
||||
6. Reads the instance transform and object id.
|
||||
7. Transforms the local vertex to world and clip space.
|
||||
|
||||
The fragment shader applies lighting, selection/active-object highlighting,
|
||||
x-ray alpha cap, section clipping, and transparency output. Section clipping is
|
||||
also used by picking so selected section planes match what the user sees.
|
||||
|
||||
Opaque and transparent passes
|
||||
-----------------------------
|
||||
|
||||
Culling partitions visible draws into opaque and transparent halves. Opaque
|
||||
draws are written first in each chunk's arrays; transparent draws are appended
|
||||
after them.
|
||||
|
||||
The main pass then renders:
|
||||
|
||||
1. Opaque pipeline with depth writes and no blending, drawing
|
||||
``opaque_visible_vertices``.
|
||||
2. Transparent pipeline with blending, drawing the remaining vertices starting
|
||||
at ``opaque_visible_vertices``.
|
||||
|
||||
Transparency is chosen from the baked mesh alpha flag, an instance colour
|
||||
override alpha, or forced x-ray mode. The transparent pass uses the same chunk
|
||||
bind group and buffers as the opaque pass.
|
||||
|
||||
Streaming and eviction
|
||||
----------------------
|
||||
|
||||
``driveStreamingLoads()`` decides which non-resident sidecar chunks should be
|
||||
loaded and which resident chunks may be evicted.
|
||||
|
||||
Each frame it:
|
||||
|
||||
1. Drains completed desktop worker reads and applies successful chunks.
|
||||
2. Updates resident chunk visibility history.
|
||||
3. Builds candidates from non-resident chunks that passed contribution
|
||||
visibility in the current cull.
|
||||
4. Sorts candidates by current projected-area priority.
|
||||
5. Loads up to a fixed number of chunks per frame.
|
||||
6. Ensures the buffer pool can fit the chunk before issuing the load.
|
||||
7. Evicts lower-value resident chunks when the pool is full.
|
||||
|
||||
The first eviction pass drops the least-recently-visible resident chunk that
|
||||
was not visible this frame. If every resident chunk is currently visible, a
|
||||
second pass may evict the lowest-priority resident chunk, but only when the
|
||||
candidate has meaningfully higher priority. This hysteresis prevents simple
|
||||
swap loops while still allowing a saturated pool to follow the camera.
|
||||
|
||||
Desktop sidecar chunks are read by ``StreamingThread``. Each request contains
|
||||
the sidecar path, geometry section offset, compressed vertex frame location,
|
||||
and compressed index frame location. The worker reads and decompresses those
|
||||
frames, then ``driveStreamingLoads()`` applies the result on the render thread.
|
||||
|
||||
Web sidecar chunks are fetched asynchronously by byte range. Vertex and index
|
||||
frames are requested separately and joined before decompression and upload.
|
||||
The web path caps concurrent chunk downloads so the highest-priority chunks can
|
||||
arrive and render progressively instead of sharing bandwidth across the entire
|
||||
visible set.
|
||||
|
||||
Sidecar format relationship
|
||||
---------------------------
|
||||
|
||||
The ``.ifcview`` file exists to feed this renderer. Its critical metadata block
|
||||
contains enough data to build ``ModelGpuData`` without reading geometry:
|
||||
|
||||
- mesh table
|
||||
- instance table
|
||||
- georeferencing cache
|
||||
- chunk table of contents
|
||||
|
||||
The geometry section contains one compressed vertex frame and one compressed
|
||||
index frame per chunk. The chunk table lets the streaming system jump directly
|
||||
to the frames for a visible chunk without scanning the file or reading
|
||||
unrelated geometry.
|
||||
|
||||
The sidecar version documented by the current code is version 16.
|
||||
|
||||
Picking, overlays, and tools
|
||||
----------------------------
|
||||
|
||||
Picking is a GPU pass, not CPU raycasting. The pick pipeline uses the same
|
||||
``visible_draws`` and chunk buffers as the main pass and writes object ids to
|
||||
an integer texture. A click reads back the selected pixel. The section tool also
|
||||
uses a normal and world-position pick target so it can create planes from the
|
||||
actual rendered surface.
|
||||
|
||||
Overlay rendering is host-provided. ``ViewportCore`` encodes the shared section
|
||||
gizmo and calls host hooks for in-pass and post-main overlays. The Qt host
|
||||
forwards those hooks to the desktop overlay renderer; the web host can no-op or
|
||||
provide its own implementation.
|
||||
|
||||
Edges are drawn after the main pass using the depth buffer. HiZ resolve, when
|
||||
enabled, also happens after the main pass so a later frame can use the captured
|
||||
depth pyramid for occlusion culling.
|
||||
|
||||
Federation and transforms
|
||||
-------------------------
|
||||
|
||||
Federated models compose several transforms before upload:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
federated_false_origin · model_transformation · coord_op · placement
|
||||
federated_false_origin
|
||||
* model_transformation
|
||||
* coordinate_operation
|
||||
* placement_transformation
|
||||
|
||||
``federated_false_origin`` is shared across all models in the
|
||||
session and cancels the bulk of the big surveyor coordinates BIM
|
||||
files carry, so float32 vertex math stays well within precision.
|
||||
The first-added model's first geometry point auto-seeds the false
|
||||
origin via ``ViewportView::guessFederatedFalseOriginFromFirstModel``
|
||||
when the add-model command arms the guess; see that function for
|
||||
why the arm/consume mechanism exists (it replaced a stack-
|
||||
overflowing slot-emit-in-slot recursion).
|
||||
The sidecar stores double-precision placement transforms so large IFC
|
||||
coordinates can be combined with coordinate operation and false-origin matrices
|
||||
before being narrowed to the float transform used by the GPU. This protects
|
||||
rendering precision for survey-coordinate models.
|
||||
|
||||
Files map
|
||||
---------
|
||||
What to remember
|
||||
----------------
|
||||
|
||||
Render core (the wgpu side):
|
||||
|
||||
* ``ViewportWindow.{h,cpp}`` — the main render window. Owns the
|
||||
wgpu surface, device, queue, all pipelines, the per-model
|
||||
``ModelGpuData``, the cull pass, the frame uniforms. ~7500 lines;
|
||||
the bulk of the renderer.
|
||||
* ``ModelGpuData.h`` — per-model GPU state (chunks, instances,
|
||||
meshes, pool slices, alpha flags, scratch buffers used by cull).
|
||||
* ``InstancedGeometry.h`` — wire structs shared by the streamer
|
||||
and the viewport (``MeshInfo``, ``InstanceCpu``, ``InstanceGpu``,
|
||||
``MeshChunk``, ``InstanceChunk``, vertex layout constants).
|
||||
* ``VertexQuantization.h`` — pack/unpack helpers for the 12-byte
|
||||
vertex format.
|
||||
* ``BufferPool.{h,cpp}`` — VRAM sub-allocator with multi-sub-buffer
|
||||
growth.
|
||||
* ``StreamingThread.{h,cpp}`` — background ``pread`` worker.
|
||||
* ``StreamingLoader.{h,cpp}`` — chunk-priority planner driving the
|
||||
thread; owns the eviction policy and the residency map.
|
||||
* ``OverlayRenderer.{h,cpp}`` — HUD / labels / gizmos / section-cut
|
||||
outline / marquee. Separate pipelines + WGSL shader strings.
|
||||
* ``MetalSurface_mac.{h,mm}`` — Cocoa bridge for the CAMetalLayer
|
||||
surface attach on macOS. Compiled only on Apple via the
|
||||
``OBJCXX`` language CMake enables.
|
||||
* ``SelectionState.h``, ``VisibilityState.h`` — pure CPU state
|
||||
machines for the selection set and the hidden-objects set;
|
||||
header-only; covered by ``ifcviewer/tests/test_selection.cpp``
|
||||
and ``test_visibility.cpp``.
|
||||
* ``AreaMeasurement.{h,cpp}``, ``LengthMeasurement.{h,cpp}`` — the
|
||||
measurement tools (triangle-area accumulation; world-space
|
||||
polyline distance).
|
||||
|
||||
IFC ingestion and on-disk format:
|
||||
|
||||
* ``GeometryStreamer.{h,cpp}`` — wraps ``IfcGeom::Iterator`` on a
|
||||
background thread; emits ``MeshChunk`` (unique geometry) and
|
||||
``InstanceChunk`` (one per placement) to ``SceneLoader``.
|
||||
* ``SceneLoader.{h,cpp}`` — orchestrates load. Owns the per-model
|
||||
``ifcopenshell::file`` for property lookup; detects sidecar
|
||||
presence; serialises queue of pending loads.
|
||||
* ``SidecarCache.{h,cpp}`` — binary read/write of ``.ifcview``
|
||||
files. Magic ``IFVW``, version 13 (see the per-version comment
|
||||
block at the top of the header for the schema evolution).
|
||||
* ``SidecarBuilder.{h,cpp}`` — live-load sidecar writer. Accumulates
|
||||
the data as the streamer emits it, writes the sidecar after
|
||||
``finalizeModel``.
|
||||
* ``LodBuilder.{h,cpp}`` — meshoptimizer wrapper that builds each
|
||||
mesh's LOD1 index slice at sidecar-bake time. Gated behind
|
||||
``WITH_MESH_OPTIMIZER``; otherwise the LOD1 slot is left empty
|
||||
and the renderer always uses LOD0.
|
||||
|
||||
Federation + settings:
|
||||
|
||||
* ``Federation.{h,cpp}`` — multi-model session: groups, model
|
||||
transforms, coordinate operations, the federated false origin.
|
||||
Persisted as ``.ifcfed``. Covered by
|
||||
``ifcviewer/tests/test_federation.cpp``.
|
||||
* ``AppSettings.{h,cpp}`` — ``QSettings``-backed preferences:
|
||||
``viewport/min_pixel_radius``, ``viewport/lod1_pixel_threshold``,
|
||||
``viewport/hiz_enabled``, ``viewport/nav_preset``, etc. See
|
||||
:doc:`env-vars` for the full key list.
|
||||
- Raw IFC streaming produces mesh and instance chunks, but the WebGPU viewport
|
||||
does not draw each emitted mesh immediately. It stages them, finalises a model,
|
||||
then uses the same chunk path as sidecar loads.
|
||||
- Sidecar loads create a renderable model from metadata first. Geometry becomes
|
||||
visible only as chunks become resident.
|
||||
- Culling produces compact visible draw tables per chunk.
|
||||
- The renderer draws one flat vertex stream per visible chunk. The shader
|
||||
expands that stream back into indexed, instanced triangles using
|
||||
``visible_draws`` and ``prefix_sums``.
|
||||
- Streaming priority comes from the current view. Chunks are fetched only when
|
||||
their instances are large enough on screen to be worth drawing.
|
||||
- The buffer pool is finite, so residency is dynamic. The renderer keeps the
|
||||
chunks that matter most for the current view and evicts lower-value chunks
|
||||
under memory pressure.
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
@@ -60,9 +60,9 @@ ConnectorPickerDialog::ConnectorPickerDialog(const std::vector<ConnectorManifest
|
||||
row->setSpacing(components::style::metrics::padding);
|
||||
|
||||
QList<QToolButton*> buttons;
|
||||
for (const auto& m : manifests) {
|
||||
auto* button = components::buttons::makeButton(m.name, ":/icons/cloud-square.svg", choices);
|
||||
const QString id = m.id;
|
||||
for (const auto& manifest : manifests) {
|
||||
auto* button = components::buttons::makeButton(manifest.name, ":/icons/cloud-square.svg", choices);
|
||||
const QString id = manifest.id;
|
||||
connect(button, &QToolButton::clicked, this, [this, id]() {
|
||||
selected_id_ = id;
|
||||
accept();
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
@@ -189,8 +189,8 @@ void ConnectorProcess::dispatchLine(const QByteArray& line) {
|
||||
void ConnectorProcess::failPendingAndClear(int code, const QString& message) {
|
||||
QHash<QString, Pending> snapshot;
|
||||
snapshot.swap(pending_);
|
||||
for (const auto& p : snapshot) {
|
||||
if (p.on_error) p.on_error(code, message);
|
||||
for (const auto& pending_request : snapshot) {
|
||||
if (pending_request.on_error) pending_request.on_error(code, message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
@@ -48,14 +48,14 @@ void ConnectorRegistry::refresh() {
|
||||
// exec changed under us. Surviving entries keep their running process.
|
||||
for (auto it = processes_.begin(); it != processes_.end();) {
|
||||
const QString id = it.key();
|
||||
ConnectorProcess* p = it.value();
|
||||
const ConnectorManifest* now = manifestFor(id);
|
||||
const bool stale = !now || !p ||
|
||||
p->manifest().exec_path != now->exec_path;
|
||||
ConnectorProcess* process = it.value();
|
||||
const ConnectorManifest* current_manifest = manifestFor(id);
|
||||
const bool stale = !current_manifest || !process ||
|
||||
process->manifest().exec_path != current_manifest->exec_path;
|
||||
if (stale) {
|
||||
if (p) {
|
||||
p->shutdown();
|
||||
p->deleteLater();
|
||||
if (process) {
|
||||
process->shutdown();
|
||||
process->deleteLater();
|
||||
}
|
||||
it = processes_.erase(it);
|
||||
} else {
|
||||
@@ -65,8 +65,8 @@ void ConnectorRegistry::refresh() {
|
||||
}
|
||||
|
||||
const ConnectorManifest* ConnectorRegistry::manifestFor(const QString& id) const {
|
||||
for (const auto& m : manifests_) {
|
||||
if (m.id == id) return &m;
|
||||
for (const auto& manifest : manifests_) {
|
||||
if (manifest.id == id) return &manifest;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
@@ -90,7 +90,7 @@ ConnectorProcess* ConnectorRegistry::get(const QString& id) {
|
||||
processes_.insert(id, proc);
|
||||
connect(proc, &ConnectorProcess::crashed, this, [this, id](const QString& message) {
|
||||
qWarning() << "ifcviewer connectors:" << message;
|
||||
if (auto* p = processes_.take(id)) p->deleteLater();
|
||||
if (auto* process = processes_.take(id)) process->deleteLater();
|
||||
});
|
||||
return proc;
|
||||
}
|
||||
@@ -99,9 +99,9 @@ void ConnectorRegistry::shutdownAll() {
|
||||
const auto procs = processes_;
|
||||
processes_.clear();
|
||||
for (auto it = procs.begin(); it != procs.end(); ++it) {
|
||||
if (auto* p = it.value()) {
|
||||
p->shutdown();
|
||||
p->deleteLater();
|
||||
if (auto* process = it.value()) {
|
||||
process->shutdown();
|
||||
process->deleteLater();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
@@ -91,24 +91,24 @@ QString formatElapsed(qint64 ms) {
|
||||
|
||||
} // namespace
|
||||
|
||||
void toggleVisibility(SessionState& s, ItemKind kind, const QString& id) {
|
||||
Federation* fed = s.federation();
|
||||
void toggleVisibility(SessionState& session, ItemKind kind, const QString& id) {
|
||||
Federation* federation = session.federation();
|
||||
if (kind == ItemKind::Group) {
|
||||
const Federation::Group* group = fed->findGroupById(id);
|
||||
const Federation::Group* group = federation->findGroupById(id);
|
||||
if (!group) return;
|
||||
fed->setGroupVisible(id, !group->visible);
|
||||
s.notifyVisibilityChanged();
|
||||
s.setStatusMessage("Models", group->visible ? "Group hidden" : "Group shown");
|
||||
federation->setGroupVisible(id, !group->visible);
|
||||
session.notifyVisibilityChanged();
|
||||
session.setStatusMessage("Models", group->visible ? "Group hidden" : "Group shown");
|
||||
} else {
|
||||
const Federation::Model* model = fed->findById(id);
|
||||
const Federation::Model* model = federation->findById(id);
|
||||
if (!model) return;
|
||||
fed->setModelVisible(id, !model->visible);
|
||||
s.notifyVisibilityChanged();
|
||||
s.setStatusMessage("Models", model->visible ? "Model hidden" : "Model shown");
|
||||
federation->setModelVisible(id, !model->visible);
|
||||
session.notifyVisibilityChanged();
|
||||
session.setStatusMessage("Models", model->visible ? "Model hidden" : "Model shown");
|
||||
}
|
||||
}
|
||||
|
||||
void addGroup(SessionState& s, QWidget& host, const QString& parent_group_id) {
|
||||
void addGroup(SessionState& session, QWidget& host, const QString& parent_group_id) {
|
||||
bool ok = false;
|
||||
const QString name = QInputDialog::getText(
|
||||
&host, "New Group", "Group name:", QLineEdit::Normal, "Group", &ok);
|
||||
@@ -116,13 +116,13 @@ void addGroup(SessionState& s, QWidget& host, const QString& parent_group_id) {
|
||||
const QString trimmed = name.trimmed();
|
||||
if (trimmed.isEmpty()) return;
|
||||
|
||||
s.federation()->addGroup(trimmed, parent_group_id);
|
||||
s.notifyFederationChanged();
|
||||
s.setStatusMessage("Models", "Group added");
|
||||
session.federation()->addGroup(trimmed, parent_group_id);
|
||||
session.notifyFederationChanged();
|
||||
session.setStatusMessage("Models", "Group added");
|
||||
}
|
||||
|
||||
void renameGroup(SessionState& s, QWidget& host, const QString& group_id) {
|
||||
const Federation::Group* group = s.federation()->findGroupById(group_id);
|
||||
void renameGroup(SessionState& session, QWidget& host, const QString& group_id) {
|
||||
const Federation::Group* group = session.federation()->findGroupById(group_id);
|
||||
if (!group) return;
|
||||
|
||||
bool ok = false;
|
||||
@@ -132,27 +132,27 @@ void renameGroup(SessionState& s, QWidget& host, const QString& group_id) {
|
||||
const QString trimmed = name.trimmed();
|
||||
if (trimmed.isEmpty()) return;
|
||||
|
||||
s.federation()->setGroupName(group_id, trimmed);
|
||||
s.notifyFederationChanged();
|
||||
s.setStatusMessage("Models", "Group renamed");
|
||||
session.federation()->setGroupName(group_id, trimmed);
|
||||
session.notifyFederationChanged();
|
||||
session.setStatusMessage("Models", "Group renamed");
|
||||
}
|
||||
|
||||
void moveGroup(SessionState& s, const QString& id, const QString& parent_group_id) {
|
||||
s.federation()->setGroupParent(id, parent_group_id);
|
||||
s.notifyFederationChanged();
|
||||
s.setStatusMessage("Models", parent_group_id.isEmpty() ? "Group moved to root" : "Group moved");
|
||||
void moveGroup(SessionState& session, const QString& id, const QString& parent_group_id) {
|
||||
session.federation()->setGroupParent(id, parent_group_id);
|
||||
session.notifyFederationChanged();
|
||||
session.setStatusMessage("Models", parent_group_id.isEmpty() ? "Group moved to root" : "Group moved");
|
||||
}
|
||||
|
||||
void moveModels(SessionState& s, const QStringList& ids, const QString& parent_group_id) {
|
||||
void moveModels(SessionState& session, const QStringList& ids, const QString& parent_group_id) {
|
||||
for (const auto& id : ids) {
|
||||
s.federation()->setModelGroup(id, parent_group_id);
|
||||
session.federation()->setModelGroup(id, parent_group_id);
|
||||
}
|
||||
s.notifyFederationChanged();
|
||||
s.setStatusMessage("Models", parent_group_id.isEmpty() ? "Model(s) moved to root" : "Model(s) moved");
|
||||
session.notifyFederationChanged();
|
||||
session.setStatusMessage("Models", parent_group_id.isEmpty() ? "Model(s) moved to root" : "Model(s) moved");
|
||||
}
|
||||
|
||||
void removeGroup(SessionState& s, QWidget& host, const QString& group_id) {
|
||||
const Federation::Group* group = s.federation()->findGroupById(group_id);
|
||||
void removeGroup(SessionState& session, QWidget& host, const QString& group_id) {
|
||||
const Federation::Group* group = session.federation()->findGroupById(group_id);
|
||||
if (!group) return;
|
||||
|
||||
const auto choice = QMessageBox::question(
|
||||
@@ -161,13 +161,13 @@ void removeGroup(SessionState& s, QWidget& host, const QString& group_id) {
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
|
||||
if (choice != QMessageBox::Yes) return;
|
||||
|
||||
s.federation()->removeGroup(group_id);
|
||||
s.notifyFederationChanged();
|
||||
s.setStatusMessage("Models", "Group removed");
|
||||
session.federation()->removeGroup(group_id);
|
||||
session.notifyFederationChanged();
|
||||
session.setStatusMessage("Models", "Group removed");
|
||||
}
|
||||
|
||||
void removeModel(SessionState& s, ViewportWindow& vp, QWidget& host, const QString& fed_id) {
|
||||
const Federation::Model* model = s.federation()->findById(fed_id);
|
||||
void removeModel(SessionState& session, ViewportWindow& viewport, QWidget& host, const QString& fed_id) {
|
||||
const Federation::Model* model = session.federation()->findById(fed_id);
|
||||
const QString label = model ? model->display_name : fed_id;
|
||||
const auto choice = QMessageBox::question(
|
||||
&host, "Remove Model",
|
||||
@@ -175,41 +175,41 @@ void removeModel(SessionState& s, ViewportWindow& vp, QWidget& host, const QStri
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
|
||||
if (choice != QMessageBox::Yes) return;
|
||||
|
||||
const uint32_t mid = s.modelIdForFedId(fed_id);
|
||||
if (mid == 0) {
|
||||
s.federation()->removeModel(fed_id);
|
||||
s.notifyFederationChanged();
|
||||
s.setStatusMessage("Models", "Model removed");
|
||||
const uint32_t model_id = session.modelIdForFedId(fed_id);
|
||||
if (model_id == 0) {
|
||||
session.federation()->removeModel(fed_id);
|
||||
session.notifyFederationChanged();
|
||||
session.setStatusMessage("Models", "Model removed");
|
||||
return;
|
||||
}
|
||||
if (s.loader()->isLoadingModel(mid)) return;
|
||||
if (session.loader()->isLoadingModel(model_id)) return;
|
||||
|
||||
vp.setSelectedObjectId(0);
|
||||
s.setSelectedObjectId(0);
|
||||
s.federation()->removeModel(fed_id);
|
||||
vp.removeModel(mid);
|
||||
s.loader()->removeModel(mid);
|
||||
s.elementRegistry()->removeModel(mid);
|
||||
s.removeModelMappingByFedId(fed_id);
|
||||
s.notifySelectionChanged();
|
||||
s.notifyModelsChanged();
|
||||
s.setStatusMessage("Models", "Model removed");
|
||||
viewport.setSelectedObjectId(0);
|
||||
session.setSelectedObjectId(0);
|
||||
session.federation()->removeModel(fed_id);
|
||||
viewport.removeModel(model_id);
|
||||
session.loader()->removeModel(model_id);
|
||||
session.elementRegistry()->removeModel(model_id);
|
||||
session.removeModelMappingByFedId(fed_id);
|
||||
session.notifySelectionChanged();
|
||||
session.notifyModelsChanged();
|
||||
session.setStatusMessage("Models", "Model removed");
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
|
||||
void loadModels(SessionState& s, const QStringList& paths, const QStringList& fed_ids) {
|
||||
void loadModels(SessionState& session, const QStringList& paths, const QStringList& fed_ids) {
|
||||
if (paths.isEmpty()) return;
|
||||
|
||||
const auto ids = s.loader()->addFiles(paths);
|
||||
for (int i = 0; i < paths.size() && i < static_cast<int>(ids.size()) && i < fed_ids.size(); ++i) {
|
||||
s.setModelMapping(fed_ids[i], ids[i]);
|
||||
const auto model_ids = session.loader()->addFiles(paths);
|
||||
for (int i = 0; i < paths.size() && i < static_cast<int>(model_ids.size()) && i < fed_ids.size(); ++i) {
|
||||
session.setModelMapping(fed_ids[i], model_ids[i]);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
void addModel(SessionState& s, QWidget& host) {
|
||||
void addModel(SessionState& session, QWidget& host) {
|
||||
AddModelDialog dialog(&host);
|
||||
if (dialog.exec() != QDialog::Accepted) return;
|
||||
|
||||
@@ -253,13 +253,13 @@ void addModel(SessionState& s, QWidget& host) {
|
||||
break;
|
||||
}
|
||||
case SourceMode::CloudModel:
|
||||
addModelFromCloud(s, host);
|
||||
addModelFromCloud(session, host);
|
||||
return;
|
||||
case SourceMode::ConvertToDatabase:
|
||||
convertIfcToDatabase(s, host);
|
||||
convertIfcToDatabase(session, host);
|
||||
return;
|
||||
case SourceMode::ExportGeometryDatabase:
|
||||
exportGeometryDatabase(s, host);
|
||||
exportGeometryDatabase(session, host);
|
||||
return;
|
||||
case SourceMode::None:
|
||||
return;
|
||||
@@ -270,24 +270,24 @@ void addModel(SessionState& s, QWidget& host) {
|
||||
// origin via ViewportView. Checked here (before federation->addModel)
|
||||
// because federation->addModel doesn't yet populate SessionState's
|
||||
// model mapping; modelIds() reflects pre-add state at this point.
|
||||
if (s.modelIds().isEmpty()) {
|
||||
if (session.modelIds().isEmpty()) {
|
||||
armFederatedFalseOriginGuess();
|
||||
}
|
||||
|
||||
QStringList accepted_paths;
|
||||
QStringList accepted_fed_ids;
|
||||
for (const auto& path : paths) {
|
||||
const QString fed_id = s.federation()->addModel(path);
|
||||
const QString fed_id = session.federation()->addModel(path);
|
||||
if (fed_id.isEmpty()) continue;
|
||||
accepted_paths << path;
|
||||
accepted_fed_ids << fed_id;
|
||||
}
|
||||
detail::loadModels(s, accepted_paths, accepted_fed_ids);
|
||||
s.notifyModelsChanged();
|
||||
detail::loadModels(session, accepted_paths, accepted_fed_ids);
|
||||
session.notifyModelsChanged();
|
||||
}
|
||||
|
||||
void addModelFromCloud(SessionState& s, QWidget& host) {
|
||||
auto* registry = s.connectorRegistry();
|
||||
void addModelFromCloud(SessionState& session, QWidget& host) {
|
||||
auto* registry = session.connectorRegistry();
|
||||
const auto& manifests = registry->available();
|
||||
if (manifests.empty()) {
|
||||
QMessageBox::information(&host, "Add From Cloud",
|
||||
@@ -310,9 +310,9 @@ void addModelFromCloud(SessionState& s, QWidget& host) {
|
||||
return;
|
||||
}
|
||||
|
||||
s.setStatusMessage("Cloud", QString("Browsing %1...").arg(connector_id));
|
||||
session.setStatusMessage("Cloud", QString("Browsing %1...").arg(connector_id));
|
||||
|
||||
QPointer<SessionState> sguard(&s);
|
||||
QPointer<SessionState> sguard(&session);
|
||||
|
||||
proc->call("pull_models_interactive", QJsonValue(),
|
||||
[sguard, connector_id](const QJsonValue& result) {
|
||||
@@ -327,9 +327,9 @@ void addModelFromCloud(SessionState& s, QWidget& host) {
|
||||
QStringList paths;
|
||||
QStringList fed_ids;
|
||||
int added = 0;
|
||||
for (const QJsonValue& v : arr) {
|
||||
if (v.isNull() || !v.isObject()) continue;
|
||||
const QJsonObject entry = v.toObject();
|
||||
for (const QJsonValue& value : arr) {
|
||||
if (value.isNull() || !value.isObject()) continue;
|
||||
const QJsonObject entry = value.toObject();
|
||||
const QString display_name = entry.value("display_name").toString();
|
||||
const QString path = entry.value("path").toString();
|
||||
if (path.isEmpty()) continue;
|
||||
@@ -368,35 +368,35 @@ void addModelFromCloud(SessionState& s, QWidget& host) {
|
||||
namespace {
|
||||
|
||||
// Shared "local path on disk" lookup for the right-click cloud commands:
|
||||
// the loader keeps the path keyed by mid (set when a file or pull_models
|
||||
// the loader keeps the path keyed by model_id (set when a file or pull_models
|
||||
// path was queued). Both local-sourced and resolved cloud-sourced models
|
||||
// have one; only un-resolved cloud models (where pull_models hasn't
|
||||
// returned yet) won't.
|
||||
QString localPathForModel(SessionState& s, const QString& fed_id) {
|
||||
const uint32_t mid = s.modelIdForFedId(fed_id);
|
||||
if (mid == 0 || !s.loader()) return {};
|
||||
return s.loader()->filePath(mid);
|
||||
QString localPathForModel(SessionState& session, const QString& fed_id) {
|
||||
const uint32_t model_id = session.modelIdForFedId(fed_id);
|
||||
if (model_id == 0 || !session.loader()) return {};
|
||||
return session.loader()->filePath(model_id);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void saveModelToCloud(SessionState& s, QWidget& host, const QString& fed_id) {
|
||||
auto* fed = s.federation();
|
||||
const Federation::Model* model = fed->findById(fed_id);
|
||||
void saveModelToCloud(SessionState& session, QWidget& host, const QString& fed_id) {
|
||||
auto* federation = session.federation();
|
||||
const Federation::Model* model = federation->findById(fed_id);
|
||||
if (!model) return;
|
||||
if (model->source_connector == "local") {
|
||||
QMessageBox::information(&host, "Save Model To Cloud",
|
||||
"This model has no cloud target. Use \"Save As To Cloud\" first.");
|
||||
return;
|
||||
}
|
||||
const QString local_path = localPathForModel(s, fed_id);
|
||||
const QString local_path = localPathForModel(session, fed_id);
|
||||
if (local_path.isEmpty()) {
|
||||
QMessageBox::warning(&host, "Save Model To Cloud",
|
||||
"Cannot find a local copy of this model to push.");
|
||||
return;
|
||||
}
|
||||
const QString connector_id = model->source_connector;
|
||||
auto* registry = s.connectorRegistry();
|
||||
auto* registry = session.connectorRegistry();
|
||||
auto* proc = registry->get(connector_id);
|
||||
if (!proc) {
|
||||
QMessageBox::warning(&host, "Save Model To Cloud",
|
||||
@@ -411,10 +411,10 @@ void saveModelToCloud(SessionState& s, QWidget& host, const QString& fed_id) {
|
||||
params["path"] = local_path;
|
||||
params["source"] = source;
|
||||
|
||||
s.setStatusMessage("Cloud",
|
||||
session.setStatusMessage("Cloud",
|
||||
QString("Saving %1 to %2...").arg(model->display_name, connector_id));
|
||||
|
||||
QPointer<SessionState> sguard(&s);
|
||||
QPointer<SessionState> sguard(&session);
|
||||
proc->call("push_model", params,
|
||||
[sguard, fed_id, connector_id](const QJsonValue& result) {
|
||||
if (!sguard) return;
|
||||
@@ -438,17 +438,17 @@ void saveModelToCloud(SessionState& s, QWidget& host, const QString& fed_id) {
|
||||
});
|
||||
}
|
||||
|
||||
void saveModelAsToCloud(SessionState& s, QWidget& host, const QString& fed_id) {
|
||||
const Federation::Model* model = s.federation()->findById(fed_id);
|
||||
void saveModelAsToCloud(SessionState& session, QWidget& host, const QString& fed_id) {
|
||||
const Federation::Model* model = session.federation()->findById(fed_id);
|
||||
if (!model) return;
|
||||
const QString local_path = localPathForModel(s, fed_id);
|
||||
const QString local_path = localPathForModel(session, fed_id);
|
||||
if (local_path.isEmpty()) {
|
||||
QMessageBox::warning(&host, "Save Model As To Cloud",
|
||||
"Cannot find a local copy of this model to push.");
|
||||
return;
|
||||
}
|
||||
|
||||
auto* registry = s.connectorRegistry();
|
||||
auto* registry = session.connectorRegistry();
|
||||
const auto& manifests = registry->available();
|
||||
if (manifests.empty()) {
|
||||
QMessageBox::information(&host, "Save Model As To Cloud",
|
||||
@@ -475,10 +475,10 @@ void saveModelAsToCloud(SessionState& s, QWidget& host, const QString& fed_id) {
|
||||
QJsonObject params;
|
||||
params["path"] = local_path;
|
||||
|
||||
s.setStatusMessage("Cloud",
|
||||
session.setStatusMessage("Cloud",
|
||||
QString("Pushing %1 to %2...").arg(model->display_name, connector_id));
|
||||
|
||||
QPointer<SessionState> sguard(&s);
|
||||
QPointer<SessionState> sguard(&session);
|
||||
proc->call("push_model_interactive", params,
|
||||
[sguard, fed_id, connector_id](const QJsonValue& result) {
|
||||
if (!sguard) return;
|
||||
@@ -507,7 +507,7 @@ void saveModelAsToCloud(SessionState& s, QWidget& host, const QString& fed_id) {
|
||||
});
|
||||
}
|
||||
|
||||
void convertIfcToDatabase(SessionState& s, QWidget& host) {
|
||||
void convertIfcToDatabase(SessionState& session, QWidget& host) {
|
||||
QFileDialog input_dialog(&host, "Select IFC File to Convert");
|
||||
input_dialog.setFileMode(QFileDialog::ExistingFile);
|
||||
input_dialog.setNameFilter("IFC Files (*.ifc);;All Files (*)");
|
||||
@@ -545,10 +545,10 @@ void convertIfcToDatabase(SessionState& s, QWidget& host) {
|
||||
}
|
||||
}
|
||||
|
||||
s.beginProgress(QString("Converting %1 to %2…")
|
||||
session.beginProgress(QString("Converting %1 to %2…")
|
||||
.arg(QFileInfo(input_path).fileName(),
|
||||
QFileInfo(output_path).fileName()));
|
||||
s.setStatusMessage("Converting",
|
||||
session.setStatusMessage("Converting",
|
||||
QString("%1 → %2").arg(QFileInfo(input_path).fileName(), QFileInfo(output_path).fileName()));
|
||||
|
||||
auto timer = std::make_shared<QElapsedTimer>();
|
||||
@@ -583,20 +583,20 @@ void convertIfcToDatabase(SessionState& s, QWidget& host) {
|
||||
});
|
||||
|
||||
QObject::connect(thread, &QThread::finished, &host,
|
||||
[&s, host_ptr = &host, thread, timer, error_message, input_path, output_path]() {
|
||||
[&session, host_ptr = &host, thread, timer, error_message, input_path, output_path]() {
|
||||
const qint64 elapsed = timer->elapsed();
|
||||
|
||||
s.endProgress();
|
||||
session.endProgress();
|
||||
thread->deleteLater();
|
||||
|
||||
if (!error_message->isEmpty()) {
|
||||
s.setStatusMessage("Error", *error_message);
|
||||
session.setStatusMessage("Error", *error_message);
|
||||
QMessageBox::warning(host_ptr, "Convert IFC to Database",
|
||||
QString("Conversion failed:\n%1").arg(*error_message));
|
||||
return;
|
||||
}
|
||||
|
||||
s.setStatusMessage(
|
||||
session.setStatusMessage(
|
||||
"Converted",
|
||||
QString("%1 → %2 in %3")
|
||||
.arg(QFileInfo(input_path).fileName(),
|
||||
@@ -609,7 +609,7 @@ void convertIfcToDatabase(SessionState& s, QWidget& host) {
|
||||
thread->start();
|
||||
}
|
||||
|
||||
void exportGeometryDatabase(SessionState& s, QWidget& host) {
|
||||
void exportGeometryDatabase(SessionState& session, QWidget& host) {
|
||||
QFileDialog input_dialog(&host, "Select IFC File to Export");
|
||||
input_dialog.setFileMode(QFileDialog::ExistingFile);
|
||||
input_dialog.setNameFilter("IFC Files (*.ifc);;All Files (*)");
|
||||
@@ -637,10 +637,10 @@ void exportGeometryDatabase(SessionState& s, QWidget& host) {
|
||||
output_path += ".rdbview";
|
||||
}
|
||||
|
||||
s.beginProgress(QString("Exporting %1 to %2…")
|
||||
session.beginProgress(QString("Exporting %1 to %2…")
|
||||
.arg(QFileInfo(input_path).fileName(),
|
||||
QFileInfo(output_path).fileName()));
|
||||
s.setStatusMessage("Exporting",
|
||||
session.setStatusMessage("Exporting",
|
||||
QString("%1 → %2").arg(QFileInfo(input_path).fileName(), QFileInfo(output_path).fileName()));
|
||||
|
||||
auto timer = std::make_shared<QElapsedTimer>();
|
||||
@@ -695,13 +695,13 @@ void exportGeometryDatabase(SessionState& s, QWidget& host) {
|
||||
|
||||
// Write to a sibling `.tmp` then rename so a partial file never
|
||||
// appears at the destination (matters for cloud-sync folders).
|
||||
const QString tmp_zip = output_path + ".tmp";
|
||||
QFile::remove(tmp_zip);
|
||||
const QString temporary_zip = output_path + ".tmp";
|
||||
QFile::remove(temporary_zip);
|
||||
{
|
||||
QZipWriter writer(tmp_zip);
|
||||
QZipWriter writer(temporary_zip);
|
||||
if (writer.status() != QZipWriter::NoError) {
|
||||
throw ifcopenshell::exception(
|
||||
("Failed to open " + tmp_zip + " for writing").toStdString());
|
||||
("Failed to open " + temporary_zip + " for writing").toStdString());
|
||||
}
|
||||
writer.setCompressionPolicy(QZipWriter::AutoCompress);
|
||||
|
||||
@@ -731,15 +731,15 @@ void exportGeometryDatabase(SessionState& s, QWidget& host) {
|
||||
writer.close();
|
||||
if (writer.status() != QZipWriter::NoError) {
|
||||
throw ifcopenshell::exception(
|
||||
("Failed to finalize " + tmp_zip).toStdString());
|
||||
("Failed to finalize " + temporary_zip).toStdString());
|
||||
}
|
||||
}
|
||||
|
||||
QFile::remove(output_path);
|
||||
if (!QFile::rename(tmp_zip, output_path)) {
|
||||
QFile::remove(tmp_zip);
|
||||
if (!QFile::rename(temporary_zip, output_path)) {
|
||||
QFile::remove(temporary_zip);
|
||||
throw ifcopenshell::exception(
|
||||
("Failed to move " + tmp_zip + " to " + output_path).toStdString());
|
||||
("Failed to move " + temporary_zip + " to " + output_path).toStdString());
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
*error_message = QString::fromUtf8(e.what());
|
||||
@@ -751,20 +751,20 @@ void exportGeometryDatabase(SessionState& s, QWidget& host) {
|
||||
});
|
||||
|
||||
QObject::connect(thread, &QThread::finished, &host,
|
||||
[&s, host_ptr = &host, thread, timer, error_message, input_path, output_path]() {
|
||||
[&session, host_ptr = &host, thread, timer, error_message, input_path, output_path]() {
|
||||
const qint64 elapsed = timer->elapsed();
|
||||
|
||||
s.endProgress();
|
||||
session.endProgress();
|
||||
thread->deleteLater();
|
||||
|
||||
if (!error_message->isEmpty()) {
|
||||
s.setStatusMessage("Error", *error_message);
|
||||
session.setStatusMessage("Error", *error_message);
|
||||
QMessageBox::warning(host_ptr, "Export Geometry Database",
|
||||
QString("Export failed:\n%1").arg(*error_message));
|
||||
return;
|
||||
}
|
||||
|
||||
s.setStatusMessage(
|
||||
session.setStatusMessage(
|
||||
"Exported",
|
||||
QString("%1 → %2 in %3")
|
||||
.arg(QFileInfo(input_path).fileName(),
|
||||
@@ -777,8 +777,8 @@ void exportGeometryDatabase(SessionState& s, QWidget& host) {
|
||||
thread->start();
|
||||
}
|
||||
|
||||
void openSettings(SessionState& s, QWidget& host) {
|
||||
SettingsDialog dialog(&s, &host);
|
||||
void openSettings(SessionState& session, QWidget& host) {
|
||||
SettingsDialog dialog(&session, &host);
|
||||
dialog.exec();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
@@ -52,35 +52,35 @@ namespace bonsaiviewer::modules::models::commands {
|
||||
|
||||
// User-facing commands. Each one is responsible for emitting any notify()
|
||||
// signals exactly once, at the end of its execution.
|
||||
void toggleVisibility(SessionState& s, ItemKind kind, const QString& id);
|
||||
void addGroup(SessionState& s, QWidget& host, const QString& parent_group_id);
|
||||
void renameGroup(SessionState& s, QWidget& host, const QString& group_id);
|
||||
void moveGroup(SessionState& s, const QString& id, const QString& parent_group_id);
|
||||
void moveModels(SessionState& s, const QStringList& ids, const QString& parent_group_id);
|
||||
void removeGroup(SessionState& s, QWidget& host, const QString& group_id);
|
||||
void removeModel(SessionState& s, ViewportWindow& vp, QWidget& host, const QString& fed_id);
|
||||
void addModel(SessionState& s, QWidget& host);
|
||||
void toggleVisibility(SessionState& session, ItemKind kind, const QString& id);
|
||||
void addGroup(SessionState& session, QWidget& host, const QString& parent_group_id);
|
||||
void renameGroup(SessionState& session, QWidget& host, const QString& group_id);
|
||||
void moveGroup(SessionState& session, const QString& id, const QString& parent_group_id);
|
||||
void moveModels(SessionState& session, const QStringList& ids, const QString& parent_group_id);
|
||||
void removeGroup(SessionState& session, QWidget& host, const QString& group_id);
|
||||
void removeModel(SessionState& session, ViewportWindow& viewport, QWidget& host, const QString& fed_id);
|
||||
void addModel(SessionState& session, QWidget& host);
|
||||
// Connector picker → pull_models_interactive → addCloudModel + load.
|
||||
// Reachable from AddModelDialog's CloudModel button; the underlying call
|
||||
// is async, so addModelFromCloud returns immediately after kicking it off.
|
||||
void addModelFromCloud(SessionState& s, QWidget& host);
|
||||
void addModelFromCloud(SessionState& session, QWidget& host);
|
||||
// push_model: push a cloud-sourced model back to its existing target.
|
||||
// Only valid when model.source_connector != "local". Async.
|
||||
void saveModelToCloud(SessionState& s, QWidget& host, const QString& fed_id);
|
||||
void saveModelToCloud(SessionState& session, QWidget& host, const QString& fed_id);
|
||||
// push_model_interactive: pick a connector and push to a fresh cloud
|
||||
// target. Valid for any model (local or already cloud-sourced). Async.
|
||||
void saveModelAsToCloud(SessionState& s, QWidget& host, const QString& fed_id);
|
||||
void convertIfcToDatabase(SessionState& s, QWidget& host);
|
||||
void exportGeometryDatabase(SessionState& s, QWidget& host);
|
||||
void openSettings(SessionState& s, QWidget& host);
|
||||
void saveModelAsToCloud(SessionState& session, QWidget& host, const QString& fed_id);
|
||||
void convertIfcToDatabase(SessionState& session, QWidget& host);
|
||||
void exportGeometryDatabase(SessionState& session, QWidget& host);
|
||||
void openSettings(SessionState& session, QWidget& host);
|
||||
|
||||
// Internal building blocks shared by commands here and by ProjectController.
|
||||
// These NEVER call notify*() — the caller is responsible for emitting once
|
||||
// at the end of its execution.
|
||||
namespace detail {
|
||||
|
||||
// Queues already-federated models on the loader and maps their fed-ids to mids.
|
||||
void loadModels(SessionState& s, const QStringList& paths, const QStringList& fed_ids);
|
||||
// Queues already-federated models on the loader and maps their federation-ids to mids.
|
||||
void loadModels(SessionState& session, const QStringList& paths, const QStringList& fed_ids);
|
||||
|
||||
} // namespace detail
|
||||
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
@@ -164,8 +164,8 @@ void FederationItemModel::refreshSubtreeVisibility(QStandardItem* root) {
|
||||
const auto kind = static_cast<ItemKind>(item->data(KindRole).toInt());
|
||||
bool visible = true;
|
||||
if (kind == ItemKind::Group) {
|
||||
const Federation::Group* g = federation_->findGroupById(id);
|
||||
visible = g && g->visible;
|
||||
const Federation::Group* group = federation_->findGroupById(id);
|
||||
visible = group && group->visible;
|
||||
} else {
|
||||
visible = federation_->isModelEffectivelyVisible(id);
|
||||
}
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
@@ -197,8 +197,9 @@ private:
|
||||
if (!target_index.isValid()) return true;
|
||||
if (kindOf(target_index) != ItemKind::Group) return false;
|
||||
if (group_id == target_group_id) return false;
|
||||
for (QModelIndex cur = target_index; cur.isValid(); cur = cur.parent()) {
|
||||
if (idOf(cur) == group_id) return false;
|
||||
for (QModelIndex ancestor_index = target_index; ancestor_index.isValid();
|
||||
ancestor_index = ancestor_index.parent()) {
|
||||
if (idOf(ancestor_index) == group_id) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
@@ -166,10 +166,10 @@ void SettingsDialog::setupUi() {
|
||||
federation_unit_form->setHorizontalSpacing(16);
|
||||
federation_unit_form->setVerticalSpacing(10);
|
||||
unit_combo_ = new QComboBox(federation_unit_body);
|
||||
for (const auto& uc : kUnitChoices) {
|
||||
for (const auto& unit_choice : kUnitChoices) {
|
||||
QStringList data;
|
||||
data << QString::fromUtf8(uc.prefix) << QString::fromUtf8(uc.name);
|
||||
unit_combo_->addItem(uc.label, data);
|
||||
data << QString::fromUtf8(unit_choice.prefix) << QString::fromUtf8(unit_choice.name);
|
||||
unit_combo_->addItem(unit_choice.label, data);
|
||||
}
|
||||
federation_unit_form->addRow("Unit", unit_combo_);
|
||||
federation_unit_section->addBodyWidget(unit_hint);
|
||||
@@ -341,13 +341,13 @@ void SettingsDialog::setupUi() {
|
||||
void SettingsDialog::syncFromFederation() {
|
||||
if (!federation_) return;
|
||||
|
||||
const auto& cfg = federation_->config();
|
||||
const auto& config = 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) {
|
||||
data[0].toStdString() == config.unit_prefix &&
|
||||
data[1].toStdString() == config.unit_name) {
|
||||
idx = i;
|
||||
break;
|
||||
}
|
||||
@@ -370,7 +370,7 @@ void SettingsDialog::populateModelTable() {
|
||||
|
||||
int row = 0;
|
||||
for (const auto& model : federation_->models()) {
|
||||
const auto& xf = model.model_transformation;
|
||||
const auto& transformation = model.model_transformation;
|
||||
model_table_->insertRow(row);
|
||||
|
||||
auto* model_item = new QTableWidgetItem(model.display_name.isEmpty() ? model.id : model.display_name);
|
||||
@@ -383,13 +383,13 @@ void SettingsDialog::populateModelTable() {
|
||||
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);
|
||||
widgets.frame->setCurrentIndex(transformation.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));
|
||||
widgets.from_point = new QTableWidgetItem(formatVector3(transformation.a));
|
||||
widgets.to_point = new QTableWidgetItem(formatVector3(transformation.b));
|
||||
widgets.rotate = new QTableWidgetItem(formatVector3(transformation.rxyz_deg));
|
||||
widgets.pivot = new QTableWidgetItem(formatVector3(transformation.pivot));
|
||||
model_table_->setItem(row, 2, widgets.from_point);
|
||||
model_table_->setItem(row, 3, widgets.to_point);
|
||||
model_table_->setItem(row, 4, widgets.rotate);
|
||||
@@ -454,12 +454,12 @@ void SettingsDialog::updateSelectedModelGeoref() {
|
||||
void SettingsDialog::onAccepted() {
|
||||
if (federation_) {
|
||||
const QStringList data = unit_combo_->currentData().toStringList();
|
||||
FederationConfig cfg;
|
||||
FederationConfig config;
|
||||
if (data.size() == 2) {
|
||||
cfg.unit_prefix = data[0].toStdString();
|
||||
cfg.unit_name = data[1].toStdString();
|
||||
config.unit_prefix = data[0].toStdString();
|
||||
config.unit_name = data[1].toStdString();
|
||||
}
|
||||
federation_->setConfig(cfg);
|
||||
federation_->setConfig(config);
|
||||
|
||||
FederatedFalseOrigin origin;
|
||||
origin.xyz = Eigen::Vector3d(parseNumber(xyz_x_), parseNumber(xyz_y_), parseNumber(xyz_z_));
|
||||
@@ -467,13 +467,13 @@ void SettingsDialog::onAccepted() {
|
||||
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);
|
||||
ModelTransformation transformation;
|
||||
transformation.a_frame = static_cast<AFrame>(row.frame->currentData().toInt());
|
||||
transformation.a = parseVector3(row.from_point->text());
|
||||
transformation.b = parseVector3(row.to_point->text());
|
||||
transformation.rxyz_deg = parseVector3(row.rotate->text());
|
||||
transformation.pivot = parseVector3(row.pivot->text());
|
||||
federation_->setModelTransformation(row.fed_id, transformation);
|
||||
}
|
||||
if (session_state_) {
|
||||
session_state_->notifyFederationChanged();
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
@@ -39,12 +39,16 @@ QString formatNumber(double value) {
|
||||
|
||||
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 int degree_part = static_cast<int>(absolute);
|
||||
const double minutes_total = (absolute - static_cast<double>(degree_part)) * 60.0;
|
||||
const int minute_part = static_cast<int>(minutes_total);
|
||||
const double second_part = (minutes_total - static_cast<double>(minute_part)) * 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));
|
||||
return QString("%1%2° %3' %4\"")
|
||||
.arg(sign)
|
||||
.arg(degree_part)
|
||||
.arg(minute_part, 2, 10, QChar('0'))
|
||||
.arg(formatNumber(second_part));
|
||||
}
|
||||
|
||||
SelectedModelGeorefState unknownState(const QString& georef, const QString& type) {
|
||||
@@ -74,8 +78,8 @@ QString formatCachedUnitScale(double 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() : "");
|
||||
enumeration_reference enumeration = av;
|
||||
return std::string(enumeration.value() ? enumeration.value() : "");
|
||||
}
|
||||
|
||||
QString formatNamedUnit(const express::Base& unit) {
|
||||
@@ -224,18 +228,18 @@ void SettingsView::refresh(const QString& fed_id) const {
|
||||
return;
|
||||
}
|
||||
|
||||
const uint32_t mid = session_state_->modelIdForFedId(fed_id);
|
||||
if (mid == 0) {
|
||||
const uint32_t model_id = session_state_->modelIdForFedId(fed_id);
|
||||
if (model_id == 0) {
|
||||
widget_->renderSelectedModelGeoref(unknownState("Not loaded", "No live model"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (auto* ifc_file = loader->ifcFile(mid)) {
|
||||
if (auto* ifc_file = loader->ifcFile(model_id)) {
|
||||
widget_->renderSelectedModelGeoref(stateFromLiveFile(ifc_file));
|
||||
return;
|
||||
}
|
||||
|
||||
const ModelGeoref* georef = loader->modelGeoref(mid);
|
||||
const ModelGeoref* georef = loader->modelGeoref(model_id);
|
||||
if (!georef) {
|
||||
widget_->renderSelectedModelGeoref(unknownState("Not available yet", "No data source"));
|
||||
return;
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
@@ -53,28 +53,28 @@ namespace {
|
||||
// Pure helper — clears the loaded scene without emitting any signals. The
|
||||
// caller (newProject / openProject) emits projectReset / projectOpened once
|
||||
// the whole flow finishes.
|
||||
void clearScene(SessionState& s, ViewportWindow& vp) {
|
||||
vp.setSelectedObjectId(0);
|
||||
s.setSelectedObjectId(0);
|
||||
for (uint32_t mid : s.modelIds()) {
|
||||
vp.removeModel(mid);
|
||||
s.loader()->removeModel(mid);
|
||||
void clearScene(SessionState& session, ViewportWindow& viewport) {
|
||||
viewport.setSelectedObjectId(0);
|
||||
session.setSelectedObjectId(0);
|
||||
for (uint32_t model_id : session.modelIds()) {
|
||||
viewport.removeModel(model_id);
|
||||
session.loader()->removeModel(model_id);
|
||||
}
|
||||
s.clearModelMappings();
|
||||
s.elementRegistry()->clear();
|
||||
session.clearModelMappings();
|
||||
session.elementRegistry()->clear();
|
||||
}
|
||||
|
||||
// Returns false if the user cancelled (i.e. don't proceed with the destructive
|
||||
// op). Handles the Save → Discard → Cancel branch including a follow-on save.
|
||||
bool confirmDiscardIfDirty(SessionState& s, QWidget& host) {
|
||||
if (!s.federation()->isDirty()) return true;
|
||||
bool confirmDiscardIfDirty(SessionState& session, QWidget& host) {
|
||||
if (!session.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(s, host);
|
||||
if (result == QMessageBox::Save) return saveProject(session, host);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -88,18 +88,18 @@ bool confirmDiscardIfDirty(SessionState& s, QWidget& host) {
|
||||
// - if no scene entry exists yet (initial open), queue a load.
|
||||
// Per spec, connector errors are not surfaced to the user; the connector
|
||||
// has already shown its own UI.
|
||||
void resolveCloudModels(SessionState& s, ViewportWindow& vp) {
|
||||
auto* fed = s.federation();
|
||||
void resolveCloudModels(SessionState& session, ViewportWindow& viewport) {
|
||||
auto* federation = session.federation();
|
||||
QHash<QString, QStringList> connector_to_fed_ids;
|
||||
for (const auto& m : fed->models()) {
|
||||
if (m.source_connector == "local") continue;
|
||||
connector_to_fed_ids[m.source_connector].push_back(m.id);
|
||||
for (const auto& model : federation->models()) {
|
||||
if (model.source_connector == "local") continue;
|
||||
connector_to_fed_ids[model.source_connector].push_back(model.id);
|
||||
}
|
||||
if (connector_to_fed_ids.isEmpty()) return;
|
||||
|
||||
auto* registry = s.connectorRegistry();
|
||||
QPointer<SessionState> sguard(&s);
|
||||
QPointer<ViewportWindow> vguard(&vp);
|
||||
auto* registry = session.connectorRegistry();
|
||||
QPointer<SessionState> sguard(&session);
|
||||
QPointer<ViewportWindow> vguard(&viewport);
|
||||
|
||||
for (auto it = connector_to_fed_ids.constBegin();
|
||||
it != connector_to_fed_ids.constEnd(); ++it) {
|
||||
@@ -115,13 +115,13 @@ void resolveCloudModels(SessionState& s, ViewportWindow& vp) {
|
||||
|
||||
QJsonArray params;
|
||||
for (const QString& fed_id : fed_ids) {
|
||||
const Federation::Model* m = fed->findById(fed_id);
|
||||
if (!m) continue;
|
||||
QJsonObject source = m->source_data;
|
||||
source["connector"] = m->source_connector;
|
||||
const Federation::Model* model = federation->findById(fed_id);
|
||||
if (!model) continue;
|
||||
QJsonObject source = model->source_data;
|
||||
source["connector"] = model->source_connector;
|
||||
QJsonObject entry;
|
||||
entry["display_name"] = m->display_name;
|
||||
entry["id"] = m->id;
|
||||
entry["display_name"] = model->display_name;
|
||||
entry["id"] = model->id;
|
||||
entry["source"] = source;
|
||||
params.append(entry);
|
||||
}
|
||||
@@ -187,7 +187,7 @@ void resolveCloudModels(SessionState& s, ViewportWindow& vp) {
|
||||
it != connector_to_fed_ids.constEnd(); ++it) {
|
||||
total += it.value().size();
|
||||
}
|
||||
s.setStatusMessage("Cloud", QString("Resolving %1 model(s)...").arg(total));
|
||||
session.setStatusMessage("Cloud", QString("Resolving %1 model(s)...").arg(total));
|
||||
}
|
||||
|
||||
// Byte-equality check for "is this .ifcfed the same as what we have loaded?"
|
||||
@@ -203,29 +203,29 @@ bool isIfcfedUnchanged(const QString& current_path, const QString& candidate_pat
|
||||
return a.readAll() == b.readAll();
|
||||
}
|
||||
|
||||
bool openProjectAt(SessionState& s, QWidget& host, ViewportWindow& vp, const QString& path) {
|
||||
SceneLoader* loader = s.loader();
|
||||
bool openProjectAt(SessionState& session, QWidget& host, ViewportWindow& viewport, const QString& path) {
|
||||
SceneLoader* loader = session.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(s, host)) return false;
|
||||
if (!confirmDiscardIfDirty(session, host)) return false;
|
||||
|
||||
QStringList warnings;
|
||||
QString err;
|
||||
if (!s.federation()->load(path, &warnings, &err)) {
|
||||
if (!session.federation()->load(path, &warnings, &err)) {
|
||||
QMessageBox::warning(&host, "Open Project",
|
||||
QString("Could not open project:\n%1").arg(err));
|
||||
return false;
|
||||
}
|
||||
|
||||
clearScene(s, vp);
|
||||
clearScene(session, viewport);
|
||||
|
||||
QStringList paths;
|
||||
QStringList fed_ids;
|
||||
for (const auto& model : s.federation()->models()) {
|
||||
for (const auto& model : session.federation()->models()) {
|
||||
if (model.source_connector != "local") continue;
|
||||
if (!QFileInfo::exists(model.source_path)) {
|
||||
warnings << QString("Source not found, kept in project: %1").arg(model.source_path);
|
||||
@@ -234,58 +234,58 @@ bool openProjectAt(SessionState& s, QWidget& host, ViewportWindow& vp, const QSt
|
||||
paths << model.source_path;
|
||||
fed_ids << model.id;
|
||||
}
|
||||
modules::models::commands::detail::loadModels(s, paths, fed_ids);
|
||||
modules::models::commands::detail::loadModels(session, paths, fed_ids);
|
||||
|
||||
if (!warnings.isEmpty()) {
|
||||
QMessageBox::warning(&host, "Open Project",
|
||||
"Project opened with warnings:\n\n" + warnings.join("\n"));
|
||||
}
|
||||
|
||||
s.federation()->markClean();
|
||||
if (s.federation()->hasHomeView()) {
|
||||
const auto& hv = s.federation()->homeView();
|
||||
vp.setCamera(hv.target.x(), hv.target.y(), hv.target.z(),
|
||||
hv.distance, hv.yaw, hv.pitch);
|
||||
session.federation()->markClean();
|
||||
if (session.federation()->hasHomeView()) {
|
||||
const auto& home_view = session.federation()->homeView();
|
||||
viewport.setCamera(home_view.target.x(), home_view.target.y(), home_view.target.z(),
|
||||
home_view.distance, home_view.yaw, home_view.pitch);
|
||||
}
|
||||
s.setStatusMessage("Project", QFileInfo(path).fileName());
|
||||
s.notifyProjectOpened(path);
|
||||
session.setStatusMessage("Project", QFileInfo(path).fileName());
|
||||
session.notifyProjectOpened(path);
|
||||
|
||||
resolveCloudModels(s, vp);
|
||||
resolveCloudModels(session, viewport);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool saveProjectTo(SessionState& s, QWidget& host, const QString& path) {
|
||||
bool saveProjectTo(SessionState& session, QWidget& host, const QString& path) {
|
||||
QString err;
|
||||
if (!s.federation()->save(path, &err)) {
|
||||
if (!session.federation()->save(path, &err)) {
|
||||
QMessageBox::warning(&host, "Save Project",
|
||||
QString("Could not save project:\n%1").arg(err));
|
||||
return false;
|
||||
}
|
||||
s.setStatusMessage("Project", QFileInfo(path).fileName());
|
||||
s.notifyProjectSaved(path);
|
||||
session.setStatusMessage("Project", QFileInfo(path).fileName());
|
||||
session.notifyProjectSaved(path);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool newProject(SessionState& s, QWidget& host, ViewportWindow& vp) {
|
||||
SceneLoader* loader = s.loader();
|
||||
bool newProject(SessionState& session, QWidget& host, ViewportWindow& viewport) {
|
||||
SceneLoader* loader = session.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(s, host)) return false;
|
||||
if (!confirmDiscardIfDirty(session, host)) return false;
|
||||
|
||||
clearScene(s, vp);
|
||||
s.federation()->clear();
|
||||
s.setStatusMessage("Project", "Untitled");
|
||||
s.notifyProjectReset();
|
||||
clearScene(session, viewport);
|
||||
session.federation()->clear();
|
||||
session.setStatusMessage("Project", "Untitled");
|
||||
session.notifyProjectReset();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool openProject(SessionState& s, QWidget& host, ViewportWindow& vp) {
|
||||
bool openProject(SessionState& session, QWidget& host, ViewportWindow& viewport) {
|
||||
QFileDialog file_dialog(&host, "Open Project");
|
||||
file_dialog.setFileMode(QFileDialog::ExistingFile);
|
||||
file_dialog.setNameFilter("IFC Federation (*.ifcfed);;All Files (*)");
|
||||
@@ -294,25 +294,25 @@ bool openProject(SessionState& s, QWidget& host, ViewportWindow& vp) {
|
||||
|
||||
const QString path = file_dialog.selectedFiles().value(0);
|
||||
if (path.isEmpty()) return false;
|
||||
return openProjectAt(s, host, vp, path);
|
||||
return openProjectAt(session, host, viewport, path);
|
||||
}
|
||||
|
||||
bool openProjectPath(SessionState& s, QWidget& host, ViewportWindow& vp, const QString& path) {
|
||||
bool openProjectPath(SessionState& session, QWidget& host, ViewportWindow& viewport, const QString& path) {
|
||||
if (path.isEmpty()) return false;
|
||||
return openProjectAt(s, host, vp, path);
|
||||
return openProjectAt(session, host, viewport, path);
|
||||
}
|
||||
|
||||
bool openCloudProject(SessionState& s, QWidget& host, ViewportWindow& vp) {
|
||||
SceneLoader* loader = s.loader();
|
||||
bool openCloudProject(SessionState& session, QWidget& host, ViewportWindow& viewport) {
|
||||
SceneLoader* loader = session.loader();
|
||||
if (loader && loader->isLoading()) {
|
||||
QMessageBox::information(
|
||||
&host, "Open from Cloud",
|
||||
"Wait until the current model load finishes before opening another project.");
|
||||
return false;
|
||||
}
|
||||
if (!confirmDiscardIfDirty(s, host)) return false;
|
||||
if (!confirmDiscardIfDirty(session, host)) return false;
|
||||
|
||||
auto* registry = s.connectorRegistry();
|
||||
auto* registry = session.connectorRegistry();
|
||||
const auto& manifests = registry->available();
|
||||
if (manifests.empty()) {
|
||||
QMessageBox::information(&host, "Open from Cloud",
|
||||
@@ -336,12 +336,12 @@ bool openCloudProject(SessionState& s, QWidget& host, ViewportWindow& vp) {
|
||||
return false;
|
||||
}
|
||||
|
||||
s.beginProgress(QString("Opening project from %1...").arg(connector_id));
|
||||
s.setStatusMessage("Cloud", QString("Browsing %1...").arg(connector_id));
|
||||
session.beginProgress(QString("Opening project from %1...").arg(connector_id));
|
||||
session.setStatusMessage("Cloud", QString("Browsing %1...").arg(connector_id));
|
||||
|
||||
QPointer<SessionState> sguard(&s);
|
||||
QPointer<SessionState> sguard(&session);
|
||||
QPointer<QWidget> hguard(&host);
|
||||
QPointer<ViewportWindow> vguard(&vp);
|
||||
QPointer<ViewportWindow> vguard(&viewport);
|
||||
|
||||
proc->call("pull_ifcfed_interactive", QJsonValue(),
|
||||
[sguard, hguard, vguard, connector_id](const QJsonValue& result) {
|
||||
@@ -371,16 +371,16 @@ bool openCloudProject(SessionState& s, QWidget& host, ViewportWindow& vp) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool syncCloudProject(SessionState& s, QWidget& host, ViewportWindow& vp) {
|
||||
auto* fed = s.federation();
|
||||
bool syncCloudProject(SessionState& session, QWidget& host, ViewportWindow& viewport) {
|
||||
auto* federation = session.federation();
|
||||
|
||||
// Per spec, sync has two independent phases — refreshing the .ifcfed
|
||||
// (requires manifest) and refreshing cloud models (requires any
|
||||
// non-local source). Either is sufficient.
|
||||
const bool has_manifest = fed->hasManifest();
|
||||
const bool has_manifest = federation->hasManifest();
|
||||
bool has_cloud_models = false;
|
||||
for (const auto& m : fed->models()) {
|
||||
if (m.source_connector != "local") { has_cloud_models = true; break; }
|
||||
for (const auto& model : federation->models()) {
|
||||
if (model.source_connector != "local") { has_cloud_models = true; break; }
|
||||
}
|
||||
if (!has_manifest && !has_cloud_models) {
|
||||
QMessageBox::information(&host, "Sync From Cloud",
|
||||
@@ -388,7 +388,7 @@ bool syncCloudProject(SessionState& s, QWidget& host, ViewportWindow& vp) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SceneLoader* loader = s.loader();
|
||||
SceneLoader* loader = session.loader();
|
||||
if (loader && loader->isLoading()) {
|
||||
QMessageBox::information(&host, "Sync From Cloud",
|
||||
"Wait until the current model load finishes before syncing.");
|
||||
@@ -399,23 +399,23 @@ bool syncCloudProject(SessionState& s, QWidget& host, ViewportWindow& vp) {
|
||||
// skipped). Just refresh cloud-sourced models against the .ifcfed
|
||||
// already on disk. Federation state is preserved, so no dirty prompt.
|
||||
if (!has_manifest) {
|
||||
s.setStatusMessage("Cloud", "Refreshing cloud models...");
|
||||
resolveCloudModels(s, vp);
|
||||
session.setStatusMessage("Cloud", "Refreshing cloud models...");
|
||||
resolveCloudModels(session, viewport);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Manifest path: the .ifcfed itself may be replaced. Confirm dirty —
|
||||
// even though we'll attempt to preserve the session if the returned
|
||||
// .ifcfed is byte-equal, that's not known until after the round-trip.
|
||||
if (!confirmDiscardIfDirty(s, host)) return false;
|
||||
if (!confirmDiscardIfDirty(session, host)) return false;
|
||||
|
||||
const QString connector_id = fed->manifestConnectorId();
|
||||
const QString connector_id = federation->manifestConnectorId();
|
||||
if (connector_id.isEmpty()) {
|
||||
QMessageBox::warning(&host, "Sync From Cloud",
|
||||
"The project's manifest does not name a connector.");
|
||||
return false;
|
||||
}
|
||||
auto* registry = s.connectorRegistry();
|
||||
auto* registry = session.connectorRegistry();
|
||||
auto* proc = registry->get(connector_id);
|
||||
if (!proc) {
|
||||
QMessageBox::warning(&host, "Sync From Cloud",
|
||||
@@ -424,15 +424,15 @@ bool syncCloudProject(SessionState& s, QWidget& host, ViewportWindow& vp) {
|
||||
return false;
|
||||
}
|
||||
|
||||
s.beginProgress(QString("Syncing from %1...").arg(connector_id));
|
||||
s.setStatusMessage("Cloud", QString("Syncing from %1...").arg(connector_id));
|
||||
session.beginProgress(QString("Syncing from %1...").arg(connector_id));
|
||||
session.setStatusMessage("Cloud", QString("Syncing from %1...").arg(connector_id));
|
||||
|
||||
QPointer<SessionState> sguard(&s);
|
||||
QPointer<SessionState> sguard(&session);
|
||||
QPointer<QWidget> hguard(&host);
|
||||
QPointer<ViewportWindow> vguard(&vp);
|
||||
const QString current_path = fed->filePath();
|
||||
QPointer<ViewportWindow> vguard(&viewport);
|
||||
const QString current_path = federation->filePath();
|
||||
|
||||
proc->call("pull_ifcfed", fed->manifest(),
|
||||
proc->call("pull_ifcfed", federation->manifest(),
|
||||
[sguard, hguard, vguard, connector_id, current_path](const QJsonValue& result) {
|
||||
if (!sguard) return;
|
||||
sguard->endProgress();
|
||||
@@ -475,13 +475,13 @@ bool syncCloudProject(SessionState& s, QWidget& host, ViewportWindow& vp) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool saveProject(SessionState& s, QWidget& host) {
|
||||
if (s.federation()->filePath().isEmpty()) return saveProjectAs(s, host);
|
||||
return saveProjectTo(s, host, s.federation()->filePath());
|
||||
bool saveProject(SessionState& session, QWidget& host) {
|
||||
if (session.federation()->filePath().isEmpty()) return saveProjectAs(session, host);
|
||||
return saveProjectTo(session, host, session.federation()->filePath());
|
||||
}
|
||||
|
||||
bool saveProjectAs(SessionState& s, QWidget& host) {
|
||||
QString suggested = s.federation()->filePath();
|
||||
bool saveProjectAs(SessionState& session, QWidget& host) {
|
||||
QString suggested = session.federation()->filePath();
|
||||
if (suggested.isEmpty()) suggested = "project.ifcfed";
|
||||
|
||||
QFileDialog file_dialog(&host, "Save Project As", suggested);
|
||||
@@ -494,7 +494,7 @@ bool saveProjectAs(SessionState& s, QWidget& host) {
|
||||
QString path = file_dialog.selectedFiles().value(0);
|
||||
if (path.isEmpty()) return false;
|
||||
if (!path.endsWith(".ifcfed", Qt::CaseInsensitive)) path += ".ifcfed";
|
||||
return saveProjectTo(s, host, path);
|
||||
return saveProjectTo(session, host, path);
|
||||
}
|
||||
|
||||
namespace {
|
||||
@@ -507,7 +507,7 @@ struct TempProjectFile {
|
||||
QString path;
|
||||
};
|
||||
|
||||
TempProjectFile writeProjectToTemp(SessionState& s, QWidget& host, const QString& op_title) {
|
||||
TempProjectFile writeProjectToTemp(SessionState& session, QWidget& host, const QString& op_title) {
|
||||
TempProjectFile out;
|
||||
out.dir = std::make_shared<QTemporaryDir>();
|
||||
if (!out.dir->isValid()) {
|
||||
@@ -517,12 +517,12 @@ TempProjectFile writeProjectToTemp(SessionState& s, QWidget& host, const QString
|
||||
out.dir.reset();
|
||||
return out;
|
||||
}
|
||||
const QString name = s.federation()->filePath().isEmpty()
|
||||
const QString name = session.federation()->filePath().isEmpty()
|
||||
? "project.ifcfed"
|
||||
: QFileInfo(s.federation()->filePath()).fileName();
|
||||
: QFileInfo(session.federation()->filePath()).fileName();
|
||||
const QString tmp_path = QDir(out.dir->path()).filePath(name);
|
||||
QString err;
|
||||
if (!s.federation()->writeCopyTo(tmp_path, &err)) {
|
||||
if (!session.federation()->writeCopyTo(tmp_path, &err)) {
|
||||
QMessageBox::warning(&host, op_title,
|
||||
QString("Failed to write temporary project:\n%1").arg(err));
|
||||
out.dir.reset();
|
||||
@@ -534,12 +534,12 @@ TempProjectFile writeProjectToTemp(SessionState& s, QWidget& host, const QString
|
||||
|
||||
// Shared continuation for push_ifcfed[_interactive]: on success, repoint
|
||||
// Federation to the returned path and notify; on error, log + status.
|
||||
void onPushIfcfedResult(SessionState& s,
|
||||
void onPushIfcfedResult(SessionState& session,
|
||||
QWidget& host,
|
||||
const QString& op_title,
|
||||
const QString& connector_id,
|
||||
const QJsonValue& result) {
|
||||
s.endProgress();
|
||||
session.endProgress();
|
||||
const QString new_path = result.toObject().value("path").toString();
|
||||
if (new_path.isEmpty()) {
|
||||
QMessageBox::warning(&host, op_title,
|
||||
@@ -547,24 +547,24 @@ void onPushIfcfedResult(SessionState& s,
|
||||
return;
|
||||
}
|
||||
QStringList warnings;
|
||||
s.federation()->repointTo(new_path, &warnings);
|
||||
s.setStatusMessage("Cloud",
|
||||
session.federation()->repointTo(new_path, &warnings);
|
||||
session.setStatusMessage("Cloud",
|
||||
QString("Saved to %1 via %2")
|
||||
.arg(QFileInfo(new_path).fileName(), connector_id));
|
||||
s.notifyProjectSaved(new_path);
|
||||
session.notifyProjectSaved(new_path);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool saveCloudProject(SessionState& s, QWidget& host) {
|
||||
auto* fed = s.federation();
|
||||
if (!fed->hasManifest()) {
|
||||
bool saveCloudProject(SessionState& session, QWidget& host) {
|
||||
auto* federation = session.federation();
|
||||
if (!federation->hasManifest()) {
|
||||
QMessageBox::information(&host, "Save To Cloud",
|
||||
"This project has no cloud target. Use \"Save As To Cloud\" first.");
|
||||
return false;
|
||||
}
|
||||
const QString connector_id = fed->manifestConnectorId();
|
||||
auto* registry = s.connectorRegistry();
|
||||
const QString connector_id = federation->manifestConnectorId();
|
||||
auto* registry = session.connectorRegistry();
|
||||
auto* proc = registry->get(connector_id);
|
||||
if (!proc) {
|
||||
QMessageBox::warning(&host, "Save To Cloud",
|
||||
@@ -573,26 +573,26 @@ bool saveCloudProject(SessionState& s, QWidget& host) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto tmp = writeProjectToTemp(s, host, "Save To Cloud");
|
||||
if (!tmp.dir) return false;
|
||||
auto temporary_project = writeProjectToTemp(session, host, "Save To Cloud");
|
||||
if (!temporary_project.dir) return false;
|
||||
|
||||
QJsonObject params;
|
||||
params["path"] = tmp.path;
|
||||
params["manifest"] = fed->manifest();
|
||||
params["path"] = temporary_project.path;
|
||||
params["manifest"] = federation->manifest();
|
||||
|
||||
s.beginProgress(QString("Saving to %1...").arg(connector_id));
|
||||
s.setStatusMessage("Cloud", QString("Saving to %1...").arg(connector_id));
|
||||
session.beginProgress(QString("Saving to %1...").arg(connector_id));
|
||||
session.setStatusMessage("Cloud", QString("Saving to %1...").arg(connector_id));
|
||||
|
||||
QPointer<SessionState> sguard(&s);
|
||||
QPointer<SessionState> sguard(&session);
|
||||
QPointer<QWidget> hguard(&host);
|
||||
|
||||
proc->call("push_ifcfed", params,
|
||||
[sguard, hguard, connector_id, tmp_keepalive = tmp.dir](const QJsonValue& result) {
|
||||
[sguard, hguard, connector_id, tmp_keepalive = temporary_project.dir](const QJsonValue& result) {
|
||||
(void)tmp_keepalive;
|
||||
if (!sguard || !hguard) return;
|
||||
onPushIfcfedResult(*sguard, *hguard, "Save To Cloud", connector_id, result);
|
||||
},
|
||||
[sguard, connector_id, tmp_keepalive = tmp.dir](int code, const QString& message) {
|
||||
[sguard, connector_id, tmp_keepalive = temporary_project.dir](int code, const QString& message) {
|
||||
(void)tmp_keepalive;
|
||||
qWarning() << "push_ifcfed to" << connector_id
|
||||
<< "failed:" << code << message;
|
||||
@@ -605,8 +605,8 @@ bool saveCloudProject(SessionState& s, QWidget& host) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool saveAsCloudProject(SessionState& s, QWidget& host) {
|
||||
auto* registry = s.connectorRegistry();
|
||||
bool saveAsCloudProject(SessionState& session, QWidget& host) {
|
||||
auto* registry = session.connectorRegistry();
|
||||
const auto& manifests = registry->available();
|
||||
if (manifests.empty()) {
|
||||
QMessageBox::information(&host, "Save As To Cloud",
|
||||
@@ -629,25 +629,25 @@ bool saveAsCloudProject(SessionState& s, QWidget& host) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto tmp = writeProjectToTemp(s, host, "Save As To Cloud");
|
||||
if (!tmp.dir) return false;
|
||||
auto temporary_project = writeProjectToTemp(session, host, "Save As To Cloud");
|
||||
if (!temporary_project.dir) return false;
|
||||
|
||||
QJsonObject params;
|
||||
params["path"] = tmp.path;
|
||||
params["path"] = temporary_project.path;
|
||||
|
||||
s.beginProgress(QString("Pushing to %1...").arg(connector_id));
|
||||
s.setStatusMessage("Cloud", QString("Pushing to %1...").arg(connector_id));
|
||||
session.beginProgress(QString("Pushing to %1...").arg(connector_id));
|
||||
session.setStatusMessage("Cloud", QString("Pushing to %1...").arg(connector_id));
|
||||
|
||||
QPointer<SessionState> sguard(&s);
|
||||
QPointer<SessionState> sguard(&session);
|
||||
QPointer<QWidget> hguard(&host);
|
||||
|
||||
proc->call("push_ifcfed_interactive", params,
|
||||
[sguard, hguard, connector_id, tmp_keepalive = tmp.dir](const QJsonValue& result) {
|
||||
[sguard, hguard, connector_id, tmp_keepalive = temporary_project.dir](const QJsonValue& result) {
|
||||
(void)tmp_keepalive;
|
||||
if (!sguard || !hguard) return;
|
||||
onPushIfcfedResult(*sguard, *hguard, "Save As To Cloud", connector_id, result);
|
||||
},
|
||||
[sguard, connector_id, tmp_keepalive = tmp.dir](int code, const QString& message) {
|
||||
[sguard, connector_id, tmp_keepalive = temporary_project.dir](int code, const QString& message) {
|
||||
(void)tmp_keepalive;
|
||||
qWarning() << "push_ifcfed_interactive to" << connector_id
|
||||
<< "failed:" << code << message;
|
||||
@@ -660,14 +660,14 @@ bool saveAsCloudProject(SessionState& s, QWidget& host) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool saveProjectDialog(SessionState& s, QWidget& host) {
|
||||
SaveProjectDialog dialog(s.federation()->hasManifest(), &host);
|
||||
bool saveProjectDialog(SessionState& session, QWidget& host) {
|
||||
SaveProjectDialog dialog(session.federation()->hasManifest(), &host);
|
||||
if (dialog.exec() != QDialog::Accepted) return false;
|
||||
switch (dialog.selectedTarget()) {
|
||||
case SaveTarget::Local: return saveProject(s, host);
|
||||
case SaveTarget::LocalAs: return saveProjectAs(s, host);
|
||||
case SaveTarget::Cloud: return saveCloudProject(s, host);
|
||||
case SaveTarget::CloudAs: return saveAsCloudProject(s, host);
|
||||
case SaveTarget::Local: return saveProject(session, host);
|
||||
case SaveTarget::LocalAs: return saveProjectAs(session, host);
|
||||
case SaveTarget::Cloud: return saveCloudProject(session, host);
|
||||
case SaveTarget::CloudAs: return saveAsCloudProject(session, host);
|
||||
case SaveTarget::None: return false;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
@@ -32,35 +32,35 @@ namespace bonsaiviewer::modules::project::commands {
|
||||
// User-facing commands. Each owns its own dialogs and confirmations; each
|
||||
// emits exactly one notify() at the end (projectReset / projectOpened /
|
||||
// projectSaved) so views refresh once per command.
|
||||
bool newProject(SessionState& s, QWidget& host, ViewportWindow& vp);
|
||||
bool openProject(SessionState& s, QWidget& host, ViewportWindow& vp);
|
||||
bool newProject(SessionState& session, QWidget& host, ViewportWindow& viewport);
|
||||
bool openProject(SessionState& session, QWidget& host, ViewportWindow& viewport);
|
||||
// Open a specific .ifcfed by path, bypassing the file dialog. Used by the
|
||||
// "Open Recent" menu. Same dirty-check / load / cloud-resolve flow as
|
||||
// openProject; returns false if the load failed or was cancelled.
|
||||
bool openProjectPath(SessionState& s, QWidget& host, ViewportWindow& vp, const QString& path);
|
||||
bool openProjectPath(SessionState& session, QWidget& host, ViewportWindow& viewport, const QString& path);
|
||||
// Pick a connector, then call pull_ifcfed_interactive and open the resulting
|
||||
// .ifcfed as a fresh project. Non-local models in the loaded federation are
|
||||
// resolved asynchronously via pull_models.
|
||||
bool openCloudProject(SessionState& s, QWidget& host, ViewportWindow& vp);
|
||||
bool openCloudProject(SessionState& session, QWidget& host, ViewportWindow& viewport);
|
||||
// pull_ifcfed using the current project's .ifcfed.manifest. Re-downloads
|
||||
// the .ifcfed from the same cloud target it came from (typically without
|
||||
// user interaction), then opens it like a fresh project — discarding any
|
||||
// local edits after the usual dirty-check prompt.
|
||||
bool syncCloudProject(SessionState& s, QWidget& host, ViewportWindow& vp);
|
||||
bool saveProject(SessionState& s, QWidget& host);
|
||||
bool saveProjectAs(SessionState& s, QWidget& host);
|
||||
bool syncCloudProject(SessionState& session, QWidget& host, ViewportWindow& viewport);
|
||||
bool saveProject(SessionState& session, QWidget& host);
|
||||
bool saveProjectAs(SessionState& session, QWidget& host);
|
||||
// Push the current federation to the cloud target named in its manifest
|
||||
// (push_ifcfed). No user prompt for destination. Caller is responsible for
|
||||
// gating this on Federation::hasManifest.
|
||||
bool saveCloudProject(SessionState& s, QWidget& host);
|
||||
bool saveCloudProject(SessionState& session, QWidget& host);
|
||||
// Pick a connector and push the current federation to a fresh cloud target
|
||||
// (push_ifcfed_interactive). The connector returns a new path + manifest;
|
||||
// Federation repoints to that location.
|
||||
bool saveAsCloudProject(SessionState& s, QWidget& host);
|
||||
bool saveAsCloudProject(SessionState& session, QWidget& host);
|
||||
// Show the four-way Save dialog (Local / Save As Local / To Cloud / Save
|
||||
// As To Cloud) and dispatch to one of the above. This is what the "Save
|
||||
// Project" ribbon button is wired to.
|
||||
bool saveProjectDialog(SessionState& s, QWidget& host);
|
||||
bool saveProjectDialog(SessionState& session, QWidget& host);
|
||||
|
||||
} // namespace bonsaiviewer::modules::project::commands
|
||||
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
@@ -188,10 +188,11 @@ void SettingsDialog::setupUi() {
|
||||
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_->addItem("Web (Orbit LMB, Pan MMB, Select RMB)");
|
||||
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.");
|
||||
"Mouse-button mapping for orbit, pan, and selection. Selection is "
|
||||
"on the left mouse button for Blender/Rhino/Revit and on the right "
|
||||
"for Web; click + box-select use whichever the preset assigns.");
|
||||
form->addRow("Preset", nav_preset_combo_);
|
||||
|
||||
section->addBodyWidget(body);
|
||||
@@ -273,11 +274,84 @@ void SettingsDialog::setupUi() {
|
||||
return tab;
|
||||
};
|
||||
|
||||
auto make_about_tab = [this]() {
|
||||
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 make_rich_label = [](const QString& text, QWidget* parent) {
|
||||
const QString link_color = bonsaiviewer::ViewerSettings::instance().color("icon_accent_color");
|
||||
auto* label = new QLabel(QString("<style>a { color: %1; }</style>%2").arg(link_color, text), parent);
|
||||
label->setTextFormat(Qt::RichText);
|
||||
label->setTextInteractionFlags(Qt::TextBrowserInteraction);
|
||||
label->setOpenExternalLinks(true);
|
||||
label->setWordWrap(true);
|
||||
return label;
|
||||
};
|
||||
|
||||
auto* about_section = new components::Section(
|
||||
"About Bonsai Viewer", components::SectionHeaderMode::Visible, tab);
|
||||
auto* about_body = new QWidget(about_section);
|
||||
auto* about_layout = new QVBoxLayout(about_body);
|
||||
about_layout->setContentsMargins(0, 0, 0, 0);
|
||||
about_layout->setSpacing(8);
|
||||
about_layout->addWidget(make_rich_label(
|
||||
"<b>Bonsai Viewer</b><br>"
|
||||
"A high performance IFC viewing and coordination tool for large OpenBIM projects.<br><br>"
|
||||
"<a href=\"https://bonsaibim.org\">bonsaibim.org</a> | "
|
||||
"<a href=\"https://github.com/IfcOpenShell/IfcOpenShell\">Source code</a>",
|
||||
about_body));
|
||||
about_section->addBodyWidget(about_body);
|
||||
layout->addWidget(about_section);
|
||||
|
||||
auto* license_section = new components::Section("License", components::SectionHeaderMode::Visible, tab);
|
||||
auto* license_body = new QWidget(license_section);
|
||||
auto* license_layout = new QVBoxLayout(license_body);
|
||||
license_layout->setContentsMargins(0, 0, 0, 0);
|
||||
license_layout->setSpacing(8);
|
||||
license_layout->addWidget(make_rich_label(
|
||||
"Bonsai Viewer is free software: you can redistribute it and/or modify it under the terms of "
|
||||
"the <b>GNU General Public License</b> as published by the Free Software Foundation, either "
|
||||
"version 3.0 of the License, or (at your option) any later version.<br><br>"
|
||||
"Bonsai Viewer 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 <a href=\"https://www.gnu.org/licenses/gpl-3.0.html\">GNU General Public License</a> "
|
||||
"for more details.",
|
||||
license_body));
|
||||
license_section->addBodyWidget(license_body);
|
||||
layout->addWidget(license_section);
|
||||
|
||||
auto* dependencies_section = new components::Section(
|
||||
"Third-party licenses", components::SectionHeaderMode::Visible, tab);
|
||||
auto* dependencies_body = new QWidget(dependencies_section);
|
||||
auto* dependencies_layout = new QVBoxLayout(dependencies_body);
|
||||
dependencies_layout->setContentsMargins(0, 0, 0, 0);
|
||||
dependencies_layout->setSpacing(8);
|
||||
dependencies_layout->addWidget(make_rich_label(
|
||||
"<ul>"
|
||||
"<li>IfcOpenShell and IfcViewer - LGPL-3.0-or-later</li>"
|
||||
"<li>Qt - LGPL-3.0-only, GPL-3.0-only, or commercial license depending on distribution</li>"
|
||||
"<li>Open CASCADE Technology - LGPL-2.1-only with OCCT exception</li>"
|
||||
"<li>Eigen - MPL-2.0</li>"
|
||||
"<li>wgpu-native - MPL-2.0</li>"
|
||||
"<li>zstd - BSD-3-Clause</li>"
|
||||
"<li>meshoptimizer - MIT, when enabled</li>"
|
||||
"</ul>"
|
||||
"See the bundled dependency notices and source distributions for the complete license texts.",
|
||||
dependencies_body));
|
||||
dependencies_section->addBodyWidget(dependencies_body);
|
||||
layout->addWidget(dependencies_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("Connectors", buildConnectorsTab());
|
||||
addTab("About", make_placeholder_tab("About", "Version, credits, and environment information will live here."));
|
||||
addTab("About", make_about_tab());
|
||||
|
||||
auto* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
|
||||
if (auto* ok = buttons->button(QDialogButtonBox::Ok)) {
|
||||
@@ -390,20 +464,20 @@ QWidget* SettingsDialog::buildConnectorsTab() {
|
||||
body_layout->addWidget(empty);
|
||||
}
|
||||
|
||||
for (const auto& m : manifests) {
|
||||
for (const auto& manifest : manifests) {
|
||||
auto* row = new QWidget(body);
|
||||
auto* row_layout = new QHBoxLayout(row);
|
||||
row_layout->setContentsMargins(0, 0, 0, 0);
|
||||
row_layout->setSpacing(8);
|
||||
|
||||
auto* name = new QLabel(m.name, row);
|
||||
auto* name = new QLabel(manifest.name, row);
|
||||
auto* version = new QLabel(
|
||||
m.version.isEmpty() ? QString() : QString("v%1").arg(m.version), row);
|
||||
manifest.version.isEmpty() ? QString() : QString("v%1").arg(manifest.version), row);
|
||||
version->setProperty("textRole", "secondary");
|
||||
|
||||
auto* settings_button = new QPushButton("Settings…", row);
|
||||
settings_button->setIcon(components::icons::makeSvgIcon(":/icons/settings.svg"));
|
||||
const QString connector_id = m.id;
|
||||
const QString connector_id = manifest.id;
|
||||
connect(settings_button, &QPushButton::clicked, this,
|
||||
[this, connector_id, settings_button]() {
|
||||
if (!session_state_) return;
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
@@ -26,8 +26,8 @@
|
||||
|
||||
namespace bonsaiviewer::modules::viewport::commands {
|
||||
|
||||
void setHome(SessionState& session, ViewportWindow& vp) {
|
||||
auto camera = vp.cameraState();
|
||||
void setHome(SessionState& session, ViewportWindow& viewport) {
|
||||
auto camera = viewport.cameraState();
|
||||
Federation::HomeView home_view;
|
||||
home_view.target = camera.target;
|
||||
home_view.distance = camera.distance;
|
||||
@@ -37,66 +37,66 @@ void setHome(SessionState& session, ViewportWindow& vp) {
|
||||
session.setStatusMessage("Camera", "Home view updated");
|
||||
}
|
||||
|
||||
void goHome(SessionState& session, ViewportWindow& vp) {
|
||||
void goHome(SessionState& session, ViewportWindow& viewport) {
|
||||
Federation* federation = session.federation();
|
||||
if (!federation->hasHomeView()) {
|
||||
session.setStatusMessage("Camera", "No home view set for this project");
|
||||
return;
|
||||
}
|
||||
const auto& home_view = federation->homeView();
|
||||
vp.setCamera(
|
||||
viewport.setCamera(
|
||||
home_view.target.x(), home_view.target.y(), home_view.target.z(),
|
||||
home_view.distance, home_view.yaw, home_view.pitch);
|
||||
session.setStatusMessage("Camera", "Home view restored");
|
||||
}
|
||||
|
||||
void viewSelected(ViewportWindow& vp) {
|
||||
vp.focusOnSelectedObject();
|
||||
void viewSelected(ViewportWindow& viewport) {
|
||||
viewport.focusOnSelectedObject();
|
||||
}
|
||||
|
||||
void fly(SessionState& session, ViewportWindow& vp) {
|
||||
vp.requestActivate();
|
||||
vp.enterFpsMode();
|
||||
void fly(SessionState& session, ViewportWindow& viewport) {
|
||||
viewport.requestActivate();
|
||||
viewport.enterFpsMode();
|
||||
session.setStatusMessage("Mode", "Fly mode active");
|
||||
}
|
||||
|
||||
void toggleSection(SessionState& session, ViewportWindow& vp) {
|
||||
vp.toggleSectionTool();
|
||||
void toggleSection(SessionState& session, ViewportWindow& viewport) {
|
||||
viewport.toggleSectionTool();
|
||||
session.setStatusMessage("Section",
|
||||
vp.sectionToolActive() ? "Section tool active" : "Section tool off");
|
||||
viewport.sectionToolActive() ? "Section tool active" : "Section tool off");
|
||||
}
|
||||
|
||||
void clearSection(SessionState& session, ViewportWindow& vp) {
|
||||
vp.clearSectionPlanes();
|
||||
void clearSection(SessionState& session, ViewportWindow& viewport) {
|
||||
viewport.clearSectionPlanes();
|
||||
session.setStatusMessage("Section", "Section planes cleared");
|
||||
}
|
||||
|
||||
void toggleDistance(ViewportWindow& vp) {
|
||||
vp.toggleLengthTool();
|
||||
void toggleDistance(ViewportWindow& viewport) {
|
||||
viewport.toggleLengthTool();
|
||||
}
|
||||
|
||||
void toggleArea(ViewportWindow& vp) {
|
||||
vp.toggleAreaTool();
|
||||
void toggleArea(ViewportWindow& viewport) {
|
||||
viewport.toggleAreaTool();
|
||||
}
|
||||
|
||||
void toggleVolume(ViewportWindow& vp) {
|
||||
vp.toggleVolumeTool();
|
||||
void toggleVolume(ViewportWindow& viewport) {
|
||||
viewport.toggleVolumeTool();
|
||||
}
|
||||
|
||||
void hideSelected(ViewportWindow& vp) {
|
||||
vp.hideSelectedElements();
|
||||
void hideSelected(ViewportWindow& viewport) {
|
||||
viewport.hideSelectedElements();
|
||||
}
|
||||
|
||||
void isolateSelected(ViewportWindow& vp) {
|
||||
vp.isolateSelectedElements();
|
||||
void isolateSelected(ViewportWindow& viewport) {
|
||||
viewport.isolateSelectedElements();
|
||||
}
|
||||
|
||||
void showAll(ViewportWindow& vp) {
|
||||
vp.showAllElements();
|
||||
void showAll(ViewportWindow& viewport) {
|
||||
viewport.showAllElements();
|
||||
}
|
||||
|
||||
void invertVisibility(ViewportWindow& vp) {
|
||||
vp.invertElementVisibility();
|
||||
void invertVisibility(ViewportWindow& viewport) {
|
||||
viewport.invertElementVisibility();
|
||||
}
|
||||
|
||||
} // namespace bonsaiviewer::modules::viewport::commands
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
@@ -26,22 +26,22 @@ namespace bonsaiviewer { class SessionState; }
|
||||
|
||||
namespace bonsaiviewer::modules::viewport::commands {
|
||||
|
||||
void setHome(SessionState& session, ViewportWindow& vp);
|
||||
void goHome(SessionState& session, ViewportWindow& vp);
|
||||
void viewSelected(ViewportWindow& vp);
|
||||
void setHome(SessionState& session, ViewportWindow& viewport);
|
||||
void goHome(SessionState& session, ViewportWindow& viewport);
|
||||
void viewSelected(ViewportWindow& viewport);
|
||||
|
||||
void fly(SessionState& session, ViewportWindow& vp);
|
||||
void toggleSection(SessionState& session, ViewportWindow& vp);
|
||||
void clearSection(SessionState& session, ViewportWindow& vp);
|
||||
void fly(SessionState& session, ViewportWindow& viewport);
|
||||
void toggleSection(SessionState& session, ViewportWindow& viewport);
|
||||
void clearSection(SessionState& session, ViewportWindow& viewport);
|
||||
|
||||
void toggleDistance(ViewportWindow& vp);
|
||||
void toggleArea(ViewportWindow& vp);
|
||||
void toggleVolume(ViewportWindow& vp);
|
||||
void toggleDistance(ViewportWindow& viewport);
|
||||
void toggleArea(ViewportWindow& viewport);
|
||||
void toggleVolume(ViewportWindow& viewport);
|
||||
|
||||
void hideSelected(ViewportWindow& vp);
|
||||
void isolateSelected(ViewportWindow& vp);
|
||||
void showAll(ViewportWindow& vp);
|
||||
void invertVisibility(ViewportWindow& vp);
|
||||
void hideSelected(ViewportWindow& viewport);
|
||||
void isolateSelected(ViewportWindow& viewport);
|
||||
void showAll(ViewportWindow& viewport);
|
||||
void invertVisibility(ViewportWindow& viewport);
|
||||
|
||||
} // namespace bonsaiviewer::modules::viewport::commands
|
||||
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
@@ -67,9 +67,9 @@ ViewportView::ViewportView(bonsaiviewer::SessionState* session_state,
|
||||
// first geometry-ready consumes the arm). refresh() stays terminal —
|
||||
// any federation mutation from the guess propagates through
|
||||
// SessionState's federatedFalseOriginChanged relay.
|
||||
connect(session_state_, &SessionState::modelGeometryReady, this, [this](uint32_t mid) {
|
||||
connect(session_state_, &SessionState::modelGeometryReady, this, [this](uint32_t model_id) {
|
||||
if (modules::models::consumeFederatedFalseOriginGuess()) {
|
||||
guessFederatedFalseOriginFromFirstModel(mid);
|
||||
guessFederatedFalseOriginFromFirstModel(model_id);
|
||||
}
|
||||
refresh();
|
||||
});
|
||||
@@ -136,34 +136,34 @@ void ViewportView::refresh() {
|
||||
viewport_->setFederatedFalseOrigin(
|
||||
composeFederatedFalseOrigin(federation->federatedFalseOrigin(), federation->config()));
|
||||
|
||||
for (uint32_t mid : session_state_->modelIds()) {
|
||||
applyCoordinateOperation(mid);
|
||||
applyModelVisibility(mid);
|
||||
for (uint32_t model_id : session_state_->modelIds()) {
|
||||
applyCoordinateOperation(model_id);
|
||||
applyModelVisibility(model_id);
|
||||
}
|
||||
}
|
||||
|
||||
void ViewportView::applyCoordinateOperation(uint32_t mid) {
|
||||
void ViewportView::applyCoordinateOperation(uint32_t model_id) {
|
||||
SceneLoader* loader = session_state_->loader();
|
||||
Eigen::Matrix4d matrix = Eigen::Matrix4d::Identity();
|
||||
if (const ModelGeoref* georef = loader->modelGeoref(mid)) {
|
||||
if (const ModelGeoref* georef = loader->modelGeoref(model_id)) {
|
||||
if (georef->has_coordinate_operation) {
|
||||
matrix = georef->coordinate_operation_meters;
|
||||
}
|
||||
}
|
||||
viewport_->setModelCoordinateOperation(mid, matrix);
|
||||
applyModelTransformation(mid);
|
||||
viewport_->setModelCoordinateOperation(model_id, matrix);
|
||||
applyModelTransformation(model_id);
|
||||
}
|
||||
|
||||
void ViewportView::applyModelTransformation(uint32_t mid) {
|
||||
void ViewportView::applyModelTransformation(uint32_t model_id) {
|
||||
Federation* federation = session_state_->federation();
|
||||
SceneLoader* loader = session_state_->loader();
|
||||
Eigen::Matrix4d matrix = Eigen::Matrix4d::Identity();
|
||||
const QString fed_id = session_state_->fedIdForModelId(mid);
|
||||
const QString fed_id = session_state_->fedIdForModelId(model_id);
|
||||
if (!fed_id.isEmpty()) {
|
||||
if (const Federation::Model* model = federation->findById(fed_id)) {
|
||||
ModelUnits units;
|
||||
Eigen::Matrix4d coordinate_operation = Eigen::Matrix4d::Identity();
|
||||
if (const ModelGeoref* georef = loader->modelGeoref(mid)) {
|
||||
if (const ModelGeoref* georef = loader->modelGeoref(model_id)) {
|
||||
units = georef->units;
|
||||
if (georef->has_coordinate_operation) {
|
||||
coordinate_operation = georef->coordinate_operation_meters;
|
||||
@@ -173,18 +173,18 @@ void ViewportView::applyModelTransformation(uint32_t mid) {
|
||||
model->model_transformation, federation->config(), units, coordinate_operation);
|
||||
}
|
||||
}
|
||||
viewport_->setModelTransformation(mid, matrix);
|
||||
viewport_->setModelTransformation(model_id, matrix);
|
||||
}
|
||||
|
||||
void ViewportView::applyModelVisibility(uint32_t mid) {
|
||||
void ViewportView::applyModelVisibility(uint32_t model_id) {
|
||||
Federation* federation = session_state_->federation();
|
||||
const QString fed_id = session_state_->fedIdForModelId(mid);
|
||||
const QString fed_id = session_state_->fedIdForModelId(model_id);
|
||||
if (fed_id.isEmpty()) return;
|
||||
|
||||
if (federation->isModelEffectivelyVisible(fed_id)) {
|
||||
viewport_->showModel(mid);
|
||||
viewport_->showModel(model_id);
|
||||
} else {
|
||||
viewport_->hideModel(mid);
|
||||
viewport_->hideModel(model_id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,7 +209,7 @@ void ViewportView::applyModelVisibility(uint32_t mid) {
|
||||
// mutation here propagates through SessionState's federation relay
|
||||
// (federatedFalseOriginChanged → notifyFederationChanged) without
|
||||
// re-entering this function.
|
||||
void ViewportView::guessFederatedFalseOriginFromFirstModel(uint32_t mid) {
|
||||
void ViewportView::guessFederatedFalseOriginFromFirstModel(uint32_t model_id) {
|
||||
Federation* federation = session_state_->federation();
|
||||
if (!federation->filePath().isEmpty()) return;
|
||||
|
||||
@@ -218,10 +218,10 @@ void ViewportView::guessFederatedFalseOriginFromFirstModel(uint32_t mid) {
|
||||
if (current.xyz != defaults.xyz || current.rz_deg != defaults.rz_deg) return;
|
||||
|
||||
Eigen::Vector3d first_geometry_point_m;
|
||||
if (!viewport_->firstGeometryPointWorldM(mid, first_geometry_point_m)) return;
|
||||
if (!viewport_->firstGeometryPointWorldM(model_id, first_geometry_point_m)) return;
|
||||
|
||||
SceneLoader* loader = session_state_->loader();
|
||||
const ModelGeoref* georef = loader->modelGeoref(mid);
|
||||
const ModelGeoref* georef = loader->modelGeoref(model_id);
|
||||
if (georef == nullptr) return;
|
||||
|
||||
federation->setFederatedFalseOrigin(::guessFederatedFalseOrigin(
|
||||
@@ -236,42 +236,42 @@ void ViewportView::guessFederatedFalseOriginFromFirstModel(uint32_t mid) {
|
||||
// (0,0,0) — the federated false origin in render space — capped at
|
||||
// 100 m so a model with crazy-coord geometry can't pull the camera
|
||||
// back into nothing.
|
||||
viewport_->frameOnFederatedOrigin(mid, 100.0f);
|
||||
viewport_->frameOnFederatedOrigin(model_id, 100.0f);
|
||||
}
|
||||
|
||||
void ViewportView::updateVolumeReadout() {
|
||||
if (viewport_->toolMode() != ViewportWindow::ToolMode::Volume) return;
|
||||
|
||||
const auto& sel = viewport_->selection().selectionIds();
|
||||
if (sel.empty()) {
|
||||
const auto& selection_ids = viewport_->selection().selectionIds();
|
||||
if (selection_ids.empty()) {
|
||||
viewport_->setHudText(std::string());
|
||||
viewport_->setOverlayLabels({});
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<uint32_t> ids(sel.begin(), sel.end());
|
||||
const auto per_obj = volumesPerObject(*viewport_, ids);
|
||||
std::vector<uint32_t> object_ids(selection_ids.begin(), selection_ids.end());
|
||||
const auto volumes_by_object = volumesPerObject(*viewport_, object_ids);
|
||||
|
||||
double total = 0.0;
|
||||
std::vector<OverlayRenderer::Label> labels;
|
||||
labels.reserve(per_obj.size());
|
||||
for (const auto& [oid, v] : per_obj) {
|
||||
total += v;
|
||||
labels.reserve(volumes_by_object.size());
|
||||
for (const auto& [object_id, volume] : volumes_by_object) {
|
||||
total += volume;
|
||||
Eigen::Vector3f mn, mx;
|
||||
if (!viewport_->computeObjectAabb(oid, mn, mx)) continue;
|
||||
if (!viewport_->computeObjectAabb(object_id, mn, mx)) continue;
|
||||
OverlayRenderer::Label lbl;
|
||||
const Eigen::Vector3f c = (mn + mx) * 0.5f;
|
||||
lbl.world_pos[0] = c.x();
|
||||
lbl.world_pos[1] = c.y();
|
||||
lbl.world_pos[2] = c.z();
|
||||
lbl.text = QString::number(v, 'f', 4) + " m³";
|
||||
const Eigen::Vector3f center = (mn + mx) * 0.5f;
|
||||
lbl.world_pos[0] = center.x();
|
||||
lbl.world_pos[1] = center.y();
|
||||
lbl.world_pos[2] = center.z();
|
||||
lbl.text = QString::number(volume, 'f', 4) + " m³";
|
||||
labels.push_back(std::move(lbl));
|
||||
}
|
||||
|
||||
viewport_->setHudText(QString("Volume: %1 m³ (%2 object%3)")
|
||||
.arg(total, 0, 'f', 4)
|
||||
.arg(per_obj.size())
|
||||
.arg(per_obj.size() == 1 ? "" : "s").toStdString());
|
||||
.arg(volumes_by_object.size())
|
||||
.arg(volumes_by_object.size() == 1 ? "" : "s").toStdString());
|
||||
viewport_->setOverlayLabels(labels);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// This file was generated with the assistance of an AI coding tool.
|
||||
/********************************************************************************
|
||||
* *
|
||||
* This file is part of IfcOpenShell. *
|
||||
* This file is part of Bonsai. *
|
||||
* *
|
||||
* 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 *
|
||||
* Bonsai is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the 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, *
|
||||
* Bonsai 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. *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
@@ -50,10 +50,10 @@ public:
|
||||
|
||||
private:
|
||||
void refresh();
|
||||
void applyCoordinateOperation(uint32_t mid);
|
||||
void applyModelTransformation(uint32_t mid);
|
||||
void applyModelVisibility(uint32_t mid);
|
||||
void guessFederatedFalseOriginFromFirstModel(uint32_t mid);
|
||||
void applyCoordinateOperation(uint32_t model_id);
|
||||
void applyModelTransformation(uint32_t model_id);
|
||||
void applyModelVisibility(uint32_t model_id);
|
||||
void guessFederatedFalseOriginFromFirstModel(uint32_t model_id);
|
||||
void updateVolumeReadout();
|
||||
|
||||
bonsaiviewer::SessionState* session_state_ = nullptr;
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
.. This file was generated with the assistance of an AI coding tool.
|
||||
|
||||
Bonsai Viewer
|
||||
=============
|
||||
|
||||
Bonsai Viewer is a high performance viewing and coordination tool for IFC
|
||||
models. It is designed for quickly opening large projects, inspecting model
|
||||
geometry and metadata, and coordinating issues across federated project data.
|
||||
|
||||
For more information, visit the `Bonsai website <https://bonsaibim.org>`_.
|
||||
@@ -1,5 +1,7 @@
|
||||
Bonsai
|
||||
======
|
||||
|
||||
Bonsai lets you analyse, create, and modify OpenBIM with Blender. For more
|
||||
information, visit the `Bonsai website <https://bonsaibim.org>`_.
|
||||
Bonsai is a native IFC authoring platform to analyse, create, and modify
|
||||
OpenBIM with Blender.
|
||||
|
||||
For more information, visit the `Bonsai website <https://bonsaibim.org>`_.
|
||||
|
||||
@@ -19,3 +19,4 @@ capabilities of the C++ core are available in Python.
|
||||
ifcopenshell-python/selector_syntax
|
||||
ifcopenshell-python/schema_querying
|
||||
ifcopenshell-python/validation
|
||||
ifcopenshell-python/running_tests
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
.. This file was generated with the assistance of an AI coding tool.
|
||||
|
||||
Running tests
|
||||
=============
|
||||
|
||||
IfcOpenShell-Python tests live in ``src/ifcopenshell-python/test`` and use
|
||||
``pytest``. From the repository root, enter the Python package directory before
|
||||
running the test suite:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
cd src/ifcopenshell-python
|
||||
|
||||
Install the test runner in the Python environment you are using for
|
||||
development:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pip install pytest
|
||||
|
||||
Full test suite
|
||||
---------------
|
||||
|
||||
Use the Makefile target for the default test suite:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
make test
|
||||
|
||||
This runs:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pytest -p no:pytest-blender test --ignore=test/util/test_shape_builder.py
|
||||
|
||||
The ``pytest-blender`` plugin is disabled because these are IfcOpenShell-Python
|
||||
tests, not Bonsai Blender tests. The shape builder tests are split into a
|
||||
separate target because they require Blender's ``mathutils`` package.
|
||||
|
||||
Parallel tests
|
||||
--------------
|
||||
|
||||
For a faster local run, install ``pytest-xdist`` and use the parallel target:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pip install pytest-xdist
|
||||
make test-parallel
|
||||
|
||||
This automatically uses the available CPU count and runs the same tests as
|
||||
``make test``.
|
||||
|
||||
Shape builder tests
|
||||
-------------------
|
||||
|
||||
The shape builder tests require ``mathutils``. Run them separately:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pip install mathutils
|
||||
make test-mathutils
|
||||
|
||||
Running individual tests
|
||||
------------------------
|
||||
|
||||
You can run an individual file or test directly with ``pytest``:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pytest -p no:pytest-blender test/test_file.py
|
||||
pytest -p no:pytest-blender test/util/test_unit.py
|
||||
pytest -p no:pytest-blender test/test_file.py::TestFile::test_creating_a_new_file
|
||||
|
||||
Coverage
|
||||
--------
|
||||
|
||||
To generate an HTML coverage report, install ``coverage`` and run:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pip install coverage
|
||||
make coverage
|
||||
|
||||
The report is written to ``htmlcov``.
|
||||
@@ -101,6 +101,11 @@ without repeating tessellation, packing, quantisation, and instance rebuilds.
|
||||
It is not a general interchange format or a full semantic storage backend. For
|
||||
model data, pair it with the original IFC file or with RocksDB.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
ifcview_format
|
||||
|
||||
RDBVIEW
|
||||
-------
|
||||
|
||||
|
||||
@@ -0,0 +1,366 @@
|
||||
IfcView format
|
||||
==============
|
||||
|
||||
``.ifcview`` is a lossy binary geometry cache used by the Bonsai Viewer and
|
||||
IfcViewer. It is a geometry cache, not an IFC exchange format. It stores
|
||||
quantised, compressed, spatially sorted streamable chunks of tessellated
|
||||
geometry, instancing, LODs, georeferencing, and basic element identifiers.
|
||||
|
||||
The format is optimised for three requirements:
|
||||
|
||||
- fast load and reuse of model geometry for analysis or visualisation
|
||||
- direct upload to a viewer's GPU-facing mesh and instance layout
|
||||
- compressed chunked streaming, especially for web loading by byte range
|
||||
|
||||
It intentionally does not store non-geometric IFC data or original IFC
|
||||
parametric geometry. It is recommended to be combined with the original
|
||||
``.ifc`` file, a ``.ifcdb``/``.rdb`` model store, or an ``.rdbview`` package
|
||||
when additional data is needed.
|
||||
|
||||
The format writes native C++ structs directly for several tables. This makes it
|
||||
compact and cheap to load, but it also means that ``.ifcview`` should not be
|
||||
treated as a stable, language-neutral interchange specification.
|
||||
|
||||
The source of truth for the format is the C++ sidecar implementation in
|
||||
``src/ifcviewer``. The current on-disk version is version 17.
|
||||
|
||||
Top-level layout
|
||||
----------------
|
||||
|
||||
.. csv-table::
|
||||
:header: "Section", "Description"
|
||||
|
||||
"SidecarHeader", "Versioning and format metadata"
|
||||
"uint64 geometry_section_size", "Number of bytes from start of geometry section to byte immediately before the geometry metadata block. May be used by streaming readers to skip bulk geometry and first fetch metadata."
|
||||
"Geometry section", "Streamable compressed chunks of geometry"
|
||||
"Geometry metadata block", "Mesh records, instance records, georeferencing data, and chunk frame offsets"
|
||||
"Element metadata block", "Element IDs, GUIDs, names, and IFC classes"
|
||||
|
||||
Both metadata blocks use the same compressed block wrapper:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
uint64 compressed_size
|
||||
uint64 raw_size
|
||||
byte[compressed_size] zstd_frame
|
||||
|
||||
The geometry section is also compressed with zstd, but it is not one large
|
||||
frame. Each streaming chunk has its own vertex frame and index frame so a
|
||||
loader can fetch and decompress only the chunks it needs.
|
||||
|
||||
For web streaming, the loader would read:
|
||||
|
||||
1. the 20-byte head, consisting of the 12-byte header and 8-byte geometry size
|
||||
2. the geometry metadata block header and compressed payload
|
||||
3. the element metadata block header, so it can remember where the UI metadata
|
||||
lives
|
||||
|
||||
After that, visible chunks are loaded asynchronously by byte range. The
|
||||
element metadata payload is fetched only when UI code asks for element
|
||||
metadata.
|
||||
|
||||
|
||||
Sidecar header
|
||||
--------------
|
||||
|
||||
``SidecarHeader`` is 12 bytes:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
struct SidecarHeader {
|
||||
uint32 magic;
|
||||
uint32 version;
|
||||
uint32 endian;
|
||||
};
|
||||
|
||||
The current values are:
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
|
||||
* - Field
|
||||
- Value
|
||||
- Purpose
|
||||
* - Magic
|
||||
- ``0x49465657``
|
||||
- Identifies an ``IFVW`` sidecar.
|
||||
* - Version
|
||||
- ``17``
|
||||
- Selects the current layout version. Versions may not be compatible.
|
||||
* - Endian marker
|
||||
- ``0x01020304``
|
||||
- Rejects files written with a different byte order.
|
||||
|
||||
If any of these values do not match, the ifcview cache must be rejected.
|
||||
|
||||
Geometry section
|
||||
----------------
|
||||
|
||||
The geometry section is the streamable part of the file. It contains, for each
|
||||
chunk, two zstd frames:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
chunk 0 vertex frame
|
||||
chunk 0 index frame
|
||||
chunk 1 vertex frame
|
||||
chunk 1 index frame
|
||||
...
|
||||
|
||||
The metadata does not discover these frames by scanning. Instead, the geometry
|
||||
metadata block stores the ``SidecarChunk`` table that locates each chunk's
|
||||
compressed vertex and index frames.
|
||||
|
||||
Inside a decompressed chunk, data is chunk-local:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
vertices for mesh first_mesh
|
||||
vertices for mesh first_mesh + 1
|
||||
...
|
||||
LOD0 indices for mesh first_mesh
|
||||
LOD0 indices for mesh first_mesh + 1
|
||||
...
|
||||
LOD1 indices for mesh first_mesh
|
||||
LOD1 indices for mesh first_mesh + 1
|
||||
...
|
||||
|
||||
This order matches the renderer's streamed chunk upload path. Vertex offsets
|
||||
and index offsets in ``MeshInfo`` describe the whole-model logical layout, but
|
||||
the chunk upload path computes chunk-local offsets when it builds runtime chunk
|
||||
state.
|
||||
|
||||
Vertex format
|
||||
-------------
|
||||
|
||||
Each stored vertex is 12 bytes:
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
|
||||
* - Byte offset
|
||||
- Type
|
||||
- Meaning
|
||||
* - ``0``
|
||||
- ``uint16[3]``
|
||||
- Quantised local position.
|
||||
* - ``6``
|
||||
- ``int8[2]``
|
||||
- Octahedral-encoded normal.
|
||||
* - ``8``
|
||||
- ``uint8[4]``
|
||||
- RGBA colour.
|
||||
|
||||
The position is quantised against the mesh's local axis-aligned bounding box,
|
||||
stored in ``MeshInfo.local_aabb_min`` and ``MeshInfo.local_aabb_max``. This
|
||||
bounding box is the quantisation basis. In other words, the format stores each
|
||||
coordinate as a normalised integer within the mesh-local min/max range, rather
|
||||
than storing the original float coordinate.
|
||||
|
||||
Conceptually, encoding does this per axis:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
t = (position - local_aabb_min) / (local_aabb_max - local_aabb_min)
|
||||
stored = round(clamp(t, 0, 1) * 65535)
|
||||
|
||||
Decoding does the inverse:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
t = stored / 65535
|
||||
position = mix(local_aabb_min, local_aabb_max, t)
|
||||
|
||||
The implementation stores ``extent_recip`` while encoding, which is simply
|
||||
``1 / (local_aabb_max - local_aabb_min)`` for each axis. Degenerate axes use
|
||||
``0`` so all coordinates on that axis quantise to the same value.
|
||||
|
||||
Normals use two signed bytes with octahedral encoding. This is less precise
|
||||
than storing three floats, but is small and adequate for typical BIM geometry,
|
||||
which is dominated by planar and axis-aligned surfaces. Colour is stored as the
|
||||
four bytes used by the viewer's packed RGBA path.
|
||||
|
||||
Geometry metadata block
|
||||
-----------------------
|
||||
|
||||
The geometry metadata block is the minimum metadata required to create the
|
||||
runtime model and begin painting geometry. Its raw, decompressed order is:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
vector<MeshInfo> meshes
|
||||
vector<InstanceInfo> instances
|
||||
uint32 has_coordinate_operation
|
||||
double[16] coordinate_operation_meters
|
||||
double project_length_to_meters
|
||||
double map_unit_to_meters
|
||||
vector<SidecarChunk> chunks
|
||||
|
||||
``MeshInfo`` is 56 bytes and describes one reusable mesh:
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
|
||||
* - Field
|
||||
- Meaning
|
||||
* - ``vbo_byte_offset``
|
||||
- Byte offset of the mesh's vertices in the logical whole-model vertex buffer.
|
||||
* - ``vertex_count``
|
||||
- Number of 12-byte vertices.
|
||||
* - ``ebo_byte_offset``
|
||||
- Byte offset of the mesh's LOD0 indices in the logical index buffer.
|
||||
* - ``index_count``
|
||||
- Number of LOD0 ``uint32`` indices.
|
||||
* - ``local_aabb_min`` / ``local_aabb_max``
|
||||
- Mesh-local bounds and the quantisation basis for vertex positions.
|
||||
* - ``first_instance`` / ``instance_count``
|
||||
- Range of instances that reference this mesh after sidecar layout.
|
||||
* - ``lod1_ebo_byte_offset`` / ``lod1_index_count``
|
||||
- Optional decimated index range for LOD1. A count of ``0`` means LOD1 is unavailable.
|
||||
|
||||
``InstanceInfo`` is 232 bytes and records one placed occurrence of a mesh. The
|
||||
important fields are:
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
|
||||
* - Field
|
||||
- Meaning
|
||||
* - ``mesh_id``
|
||||
- Index into the mesh table.
|
||||
* - ``object_id``
|
||||
- Viewer object identifier used for selection and lookup.
|
||||
* - ``color_override_rgba8``
|
||||
- Optional per-instance colour override. ``0`` means use the baked vertex colour.
|
||||
* - ``model_id``
|
||||
- Source model identifier within the viewer.
|
||||
* - ``placement_transformation``
|
||||
- Double-precision placement emitted by the geometry streamer, before final federation and false-origin composition.
|
||||
* - ``transform``
|
||||
- Float render transform for the default stage state. Loaders may recompute it from ``placement_transformation`` and current stage matrices.
|
||||
* - ``world_aabb_min`` / ``world_aabb_max``
|
||||
- World-space instance bounds used for chunk bounds, culling, and view fitting.
|
||||
|
||||
Both transform forms are stored for precision and reuse. The double placement
|
||||
keeps large IFC coordinates intact until the viewer has applied coordinate
|
||||
operation, model transformation, and false-origin matrices. The float transform
|
||||
is the GPU-facing result for the default composition.
|
||||
|
||||
The georeferencing fields cache enough of the model's coordinate operation and
|
||||
unit scale to load a sidecar without reparsing the IFC source solely to recover
|
||||
map conversion state. If the source map conversion changes, the sidecar must be
|
||||
deleted and rebuilt.
|
||||
|
||||
``SidecarChunk`` is 56 bytes and records one streamable range of meshes. Each
|
||||
chunk maps to two compressed geometry frames in the geometry section:
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
|
||||
* - Field
|
||||
- Meaning
|
||||
* - ``first_mesh``
|
||||
- First mesh index covered by the chunk.
|
||||
* - ``mesh_count``
|
||||
- Number of consecutive meshes covered by the chunk.
|
||||
* - ``v_comp_off`` / ``v_comp_size``
|
||||
- Byte offset and compressed byte size of the chunk's vertex frame, relative to the start of the geometry section.
|
||||
* - ``v_raw_size``
|
||||
- Decompressed byte size of the chunk's vertex data.
|
||||
* - ``i_comp_off`` / ``i_comp_size``
|
||||
- Byte offset and compressed byte size of the chunk's index frame, relative to the start of the geometry section.
|
||||
* - ``i_raw_size``
|
||||
- Decompressed byte size of the chunk's index data.
|
||||
|
||||
The chunk table is part of the geometry metadata because the viewer needs it
|
||||
before it can request geometry. On the web path, the loader reads the header
|
||||
and geometry metadata block, creates a model with non-resident chunks, and then
|
||||
starts fetching visible chunk frames by byte range.
|
||||
|
||||
Element metadata block
|
||||
-----------------------
|
||||
|
||||
The element metadata block stores non-geometric element lookup data used by UI
|
||||
features such as picking, tree display, object labels, and search.
|
||||
|
||||
Its raw, decompressed order is:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
vector<ElementTableRecord> elements
|
||||
uint32 string_table_bytes
|
||||
char[string_table_bytes] string_table
|
||||
|
||||
Each ``ElementTableRecord`` is a fixed-size 36-byte record:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
struct ElementTableRecord {
|
||||
uint32 object_id;
|
||||
uint32 model_id;
|
||||
int32 ifc_id;
|
||||
uint32 guid_offset;
|
||||
uint32 guid_length;
|
||||
uint32 name_offset;
|
||||
uint32 name_length;
|
||||
uint32 type_offset;
|
||||
uint32 type_length;
|
||||
};
|
||||
|
||||
Strings are stored once in the string table and referenced by offset and
|
||||
length.
|
||||
|
||||
Splitting this block from geometry metadata is important for first paint. A
|
||||
large model can have substantial names, GlobalIds, and type strings. The web
|
||||
viewer can show geometry after the geometry metadata block is available, then
|
||||
fetch this metadata later when the UI needs it.
|
||||
|
||||
Chunk planning
|
||||
--------------
|
||||
|
||||
Chunks are spatial groups of meshes. During sidecar creation, the builder:
|
||||
|
||||
1. Computes a centroid for each mesh from the average of its instance
|
||||
world-AABB centres.
|
||||
2. Sorts mesh ids by a 3D Morton code, also known as Z-order.
|
||||
3. Greedily packs the sorted meshes into 4 MiB vertex-byte chunks. A single
|
||||
mesh larger than that limit is kept whole in an oversized chunk. Meshes are
|
||||
not split.
|
||||
4. Reorders meshes, vertices, indices, and instances so each chunk is a
|
||||
consecutive mesh range.
|
||||
5. Writes these offsets into the ``SidecarChunk`` table so that loaders can
|
||||
directly jump to the compressed chunk as needed.
|
||||
|
||||
How the file is produced
|
||||
------------------------
|
||||
|
||||
When no usable sidecar is available, the viewer falls back to the geometry
|
||||
streamer. The streamer emits:
|
||||
|
||||
- ``StreamedMesh`` once for each unique representation mesh
|
||||
- ``StreamedInstance`` for every placed occurrence of a mesh
|
||||
- ``ElementInfo`` records for viewer metadata
|
||||
|
||||
The sidecar builder consumes those streams. Mesh chunks are converted from the
|
||||
streamer transfer layout, which is seven floats per vertex
|
||||
(``position.xyz``, ``normal.xyz``, packed colour), into the 12-byte quantised
|
||||
vertex layout. Instance chunks become ``InstanceInfo`` records. Element info
|
||||
records become ``ElementTableRecord`` plus string table entries.
|
||||
|
||||
At finalisation, the builder adds LOD1 index buffers where useful, caches
|
||||
georeferencing state, lays out meshes in streaming chunk order, builds the chunk
|
||||
table, and writes the file.
|
||||
|
||||
Binary conventions
|
||||
------------------
|
||||
|
||||
The current writer uses these conventions:
|
||||
|
||||
- Multi-byte scalar values are written in native byte order and validated by
|
||||
the endian marker.
|
||||
- Counted vectors are written as ``uint32 count`` followed by
|
||||
``count * sizeof(T)`` bytes of raw table entries.
|
||||
- Metadata blocks are zstd-compressed as complete raw metadata buffers.
|
||||
- Geometry chunks store vertex bytes and index bytes as separate zstd frames.
|
||||
- Offsets stored in the chunk table are relative to the start of the geometry
|
||||
section, not relative to the start of the file.
|
||||
@@ -0,0 +1,16 @@
|
||||
.. This file was generated with the assistance of an AI coding tool.
|
||||
|
||||
IfcViewer
|
||||
=========
|
||||
|
||||
IfcViewer is the WebGPU-based IFC viewer used by Bonsai Viewer and the
|
||||
standalone viewer targets. It includes shared loading, sidecar, streaming,
|
||||
selection, visibility, and viewport state code used by both desktop and web
|
||||
frontends.
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
:maxdepth: 1
|
||||
:caption: Contents:
|
||||
|
||||
ifcviewer/running_tests
|
||||
@@ -0,0 +1,128 @@
|
||||
.. This file was generated with the assistance of an AI coding tool.
|
||||
|
||||
Running tests
|
||||
=============
|
||||
|
||||
IfcViewer has two test layers:
|
||||
|
||||
1. **Desktop tests**: C++ unit tests for the shared viewer core and desktop
|
||||
support code.
|
||||
2. **Web tests**: headless-browser smoke tests for the Emscripten/WebGPU
|
||||
frontend.
|
||||
|
||||
The desktop tests are in ``src/ifcviewer/tests``. The web tests are in
|
||||
``src/ifcviewer-web/tests``.
|
||||
|
||||
Desktop tests
|
||||
-------------
|
||||
|
||||
The desktop tests are Catch2 executables registered with CTest. They cover
|
||||
pure viewer logic such as sidecar layout, streaming loaders, selection,
|
||||
visibility, instance composition, buffer-pool allocation, and viewport camera
|
||||
state.
|
||||
|
||||
Configure a desktop build with viewer tests enabled:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
cmake -S cmake -B build-viewer-wgpu \
|
||||
-G Ninja \
|
||||
-DBUILD_BONSAIVIEWER=ON \
|
||||
-DBUILD_BONSAIVIEWER_TESTS=ON
|
||||
|
||||
Build and run all registered tests:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
cmake --build build-viewer-wgpu
|
||||
ctest --test-dir build-viewer-wgpu --output-on-failure
|
||||
|
||||
To run only the IfcViewer tests, filter by test name:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
ctest --test-dir build-viewer-wgpu -R "test_(sidecar|streaming|selection|visibility|buffer|viewport|federation|instance|chunk|lod)" --output-on-failure
|
||||
|
||||
You can also build or run a single test executable directly:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
cmake --build build-viewer-wgpu --target test_sidecar_cache
|
||||
./build-viewer-wgpu/ifcviewer/tests/test_sidecar_cache
|
||||
|
||||
Common test targets include:
|
||||
|
||||
* ``test_sidecar_compress``
|
||||
* ``test_sidecar_cache``
|
||||
* ``test_streaming_loader``
|
||||
* ``test_instanced_geometry``
|
||||
* ``test_chunk_planner``
|
||||
* ``test_sidecar_layout``
|
||||
* ``test_instance_compose``
|
||||
* ``test_selection``
|
||||
* ``test_visibility``
|
||||
* ``test_buffer_pool``
|
||||
* ``test_viewport_camera``
|
||||
* ``test_federation``
|
||||
|
||||
Web tests
|
||||
---------
|
||||
|
||||
The web tests are Playwright smoke tests for the WebGPU/Emscripten build. They
|
||||
load the built page in Chrome, wait for WebGPU initialisation, and assert that
|
||||
the embedded sample renders non-blank, interactions change the framebuffer,
|
||||
and no uncaptured WebGPU errors are logged.
|
||||
|
||||
First build the web viewer from the repository root. This requires an
|
||||
Emscripten environment:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
source /path/to/emsdk_env.sh
|
||||
emcmake cmake -S src/ifcviewer-web -B build-web
|
||||
ninja -C build-web IfcViewerWeb
|
||||
|
||||
Install the web test dependencies once:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
cd src/ifcviewer-web/tests
|
||||
npm install
|
||||
|
||||
Run the web smoke tests:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
npm test
|
||||
|
||||
The Playwright configuration starts ``serve.mjs`` automatically. The server
|
||||
serves ``build-web`` on ``http://localhost:8124``. Override the build directory
|
||||
or port with environment variables:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
WEB_BUILD_DIR=/path/to/build-web PORT=9000 npm test
|
||||
|
||||
Chrome requirements
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The test configuration uses the system Chrome channel, so a system Chrome such
|
||||
as ``google-chrome-stable`` must be installed. You do not need to run
|
||||
``npx playwright install`` unless you change the Playwright browser channel.
|
||||
|
||||
Headless and CI runs
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
WebGPU in headless Linux environments can be sensitive to the GPU and browser
|
||||
configuration. The default configuration runs headed against the machine's real
|
||||
GPU. On a headless machine, use Xvfb:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
xvfb-run -a npm test
|
||||
|
||||
For a GPU-less runner, change ``headless`` to ``true`` in
|
||||
``playwright.config.mjs`` and provide a SwiftShader Vulkan ICD, for example via
|
||||
``VK_ICD_FILENAMES=/path/to/vk_swiftshader_icd.json`` together with a Chrome
|
||||
``--use-angle=swiftshader`` argument. This is slower than a real GPU but is
|
||||
enough for render-non-blank smoke checks.
|
||||
@@ -12,7 +12,9 @@ Let's learn IfcOpenShell!
|
||||
ifcopenshell
|
||||
ifcopenshell-python
|
||||
ifcconvert
|
||||
ifcviewer
|
||||
bonsai
|
||||
bonsai-viewer
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
|
||||
@@ -3,7 +3,7 @@ Introduction
|
||||
|
||||
**IfcOpenShell** is an open source software library for software developers and BIM powerusers working with Industry Foundation Classes (`IFC <https://technical.buildingsmart.org/standards/ifc/>`_).
|
||||
|
||||
In addition to a C++ and Python API, **IfcOpenShell** comes with an ecosystem of tools, notably including **IfcConvert** (an application to convert IFC models to other formats), **Bonsai** (an add-on to Blender providing a graphical IFC authoring platform), and many other libraries, CLI apps, and more. Support is also provided for auxiliary standards such as BCF, bSDD, and IDS.
|
||||
In addition to a C++ and Python API, **IfcOpenShell** comes with an ecosystem of tools, notably including **IfcConvert** (an application to convert IFC models to other formats), **Bonsai** (an add-on to Blender providing a graphical IFC authoring platform and viewer), and many other libraries, CLI apps, and more. Support is also provided for auxiliary standards such as BCF, bSDD, and IDS.
|
||||
|
||||
Things you can do
|
||||
-----------------
|
||||
@@ -58,6 +58,9 @@ IfcOpenShell is a modular ecosystem of tools that work together, where each tool
|
||||
"`IfcOpenShell-Python <https://docs.ifcopenshell.org/ifcopenshell-python.html>`_", "Python bindings to the core IfcOpenShell C++ system, as well as high level analysis and authoring functions."
|
||||
"`IfcConvert <https://docs.ifcopenshell.org/ifcconvert.html>`_", "A command-line application for converting IFC geometry into file formats such as OBJ, DAE, GLB, STP, IGS, XML, SVG, H5, and IFC itself."
|
||||
"`Bonsai <https://docs.ifcopenshell.org/bonsai.html>`_", "A graphical add-on for Blender that lets you analyse, author, and modify IFC with Blender. Graphically create BIM models from scratch!"
|
||||
"`Bonsai Viewer <https://docs.ifcopenshell.org/bonsai-viewer.html>`_", "A high performance viewing and coordination tool for opening and inspecting IFC models."
|
||||
"`IfcViewer <https://docs.ifcopenshell.org/ifcviewer.html>`_", "A WebGPU-based desktop IFC viewer and shared viewer core used by the standalone viewer targets."
|
||||
"IfcViewerWeb", "A WebAssembly and WebGPU viewer target for viewing IFC models in a web browser."
|
||||
"`BCF <https://docs.ifcopenshell.org/bcf.html>`_", "BIM Collaboration Format (BCF) is a standard to manage and exchange coordination topics between disciplines collaborating on a project by changing XML files or querying an API."
|
||||
"`BIMServer-Plugin <https://docs.ifcopenshell.org/bimserver-plugin.html>`_", "A plugin to the open source BIMServer CDE to allow you to use IfcOpenShell to parse, view, and audit models."
|
||||
"`BIMTester <https://docs.ifcopenshell.org/bimtester.html>`_", "A utility that allows you to write Gherkin-based tests for models."
|
||||
@@ -81,7 +84,9 @@ IfcOpenShell is a modular ecosystem of tools that work together, where each tool
|
||||
|
||||
.. note::
|
||||
|
||||
**IfcOpenShell** and all of its libraries are licensed under LGPL-3.0-or-later. Two exceptions to this are **Bonsai** and **IfcSverchok**, which are both licensed under GPL-3.0-or-later.
|
||||
**IfcOpenShell** and its libraries and viewer targets are licensed under
|
||||
LGPL-3.0-or-later. Exceptions include **Bonsai**, **Bonsai Viewer**, and
|
||||
**IfcSverchok**, which are licensed under GPL-3.0-or-later.
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
|
||||
@@ -82,10 +82,11 @@ int main(int argc, char* argv[]) {
|
||||
const QStringList parts = parser.value("camera").split(',');
|
||||
if (parts.size() == 6) {
|
||||
bool ok = true;
|
||||
float v[6];
|
||||
for (int i = 0; i < 6 && ok; ++i) v[i] = parts[i].toFloat(&ok);
|
||||
float camera_values[6];
|
||||
for (int i = 0; i < 6 && ok; ++i) camera_values[i] = parts[i].toFloat(&ok);
|
||||
if (ok) {
|
||||
viewport->setCamera(v[0], v[1], v[2], v[3], v[4], v[5]);
|
||||
viewport->setCamera(camera_values[0], camera_values[1], camera_values[2],
|
||||
camera_values[3], camera_values[4], camera_values[5]);
|
||||
} else {
|
||||
Log::warn() << "--camera: failed to parse "
|
||||
<< parser.value("camera");
|
||||
|
||||
@@ -104,7 +104,7 @@ target_link_options(IfcViewerWeb PRIVATE
|
||||
# EMSCRIPTEN_KEEPALIVE alone keeps the symbols in the binary but doesn't
|
||||
# add them to Module. ccall lets shell.html pass a JS string (the ?model
|
||||
# URL) to load_sidecar_from_url_c without manual heap marshalling.
|
||||
"-sEXPORTED_FUNCTIONS=['_main','_raf_tick_c','_load_sidecar_from_source_c','_clear_scene_c','_ifcv_on_range_done','_ifcv_chunks_resident_c','_ifcv_chunks_total_c','_ifcv_model_count_c','_ifcv_model_resident_c','_ifcv_model_total_c','_ifcv_bytes_total_c','_ifcv_bytes_needed_c','_ifcv_bytes_loaded_c','_view_all_c','_frame_selection_c','_toggle_projection_c','_projection_is_ortho_c','_standard_view_c','_toggle_fly_c','_fly_is_active_c','_hide_selected_c','_isolate_selected_c','_show_all_c','_toggle_xray_c','_xray_is_active_c']"
|
||||
"-sEXPORTED_FUNCTIONS=['_main','_raf_tick_c','_load_sidecar_from_source_c','_clear_scene_c','_ifcv_on_range_done','_ifcv_chunks_resident_c','_ifcv_chunks_total_c','_ifcv_model_count_c','_ifcv_model_resident_c','_ifcv_model_total_c','_ifcv_bytes_total_c','_ifcv_bytes_needed_c','_ifcv_bytes_loaded_c','_view_all_c','_frame_selection_c','_toggle_projection_c','_projection_is_ortho_c','_standard_view_c','_toggle_fly_c','_fly_is_active_c','_hide_selected_c','_isolate_selected_c','_show_all_c','_toggle_xray_c','_xray_is_active_c','_toggle_section_c','_clear_section_c','_section_is_active_c']"
|
||||
# ccall: shell.html passes the ?model URL string to load_sidecar_from_url_c.
|
||||
# HEAPU8: lets tooling/tests read the wasm heap size (e.g. to verify a large
|
||||
# sidecar streams by range instead of loading whole). Standard, zero-cost.
|
||||
|
||||
+202
-53
@@ -35,9 +35,12 @@
|
||||
#include <emscripten/emscripten.h>
|
||||
#include <emscripten/html5.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -45,6 +48,9 @@ namespace {
|
||||
// WebViewportHost selector below.
|
||||
constexpr const char* kCanvasSelector = "#viewer-canvas";
|
||||
|
||||
// What a mouse drag drives, decided against the active nav preset's bindings.
|
||||
enum class NavKind { None, Orbit, Pan, Select };
|
||||
|
||||
struct AppState {
|
||||
WebViewportHost host{ kCanvasSelector };
|
||||
ViewportCore core{ &host };
|
||||
@@ -56,15 +62,19 @@ struct AppState {
|
||||
bool ready = false;
|
||||
|
||||
// ---- Mouse navigation state ----
|
||||
// A drag is armed on mousedown and released on mouseup. button is the
|
||||
// DOM button code (0 left, 1 middle, 2 right). Left orbits; middle or
|
||||
// right pans — matches common web 3D-viewer bindings and covers both
|
||||
// three-button mice and trackpad (right-drag) users.
|
||||
bool nav_active = false;
|
||||
int nav_button = 0;
|
||||
// Accumulated |movement| since mousedown, in CSS px. A left release under
|
||||
// the click threshold (no real drag) is treated as a pick instead of an
|
||||
// orbit. Captures the down position (canvas-relative CSS px) for the pick.
|
||||
// A drag is armed on mousedown and released on mouseup. What the press
|
||||
// drives (orbit / pan / select) is decided against the active nav preset's
|
||||
// bindings (ViewportCore::navBindings), so any preset works on web too.
|
||||
bool nav_active = false;
|
||||
NavKind nav_kind = NavKind::None;
|
||||
// Section-cut tool: while active, a select-button click drops a clip plane
|
||||
// at the picked surface (K toggles, Shift+K clears — matches desktop).
|
||||
bool section_tool_active = false;
|
||||
// True while dragging a section-plane gizmo (LMB down on the arrow → slide).
|
||||
bool section_dragging = false;
|
||||
// Accumulated |movement| since mousedown, in CSS px. A select-button release
|
||||
// under the click threshold (no real drag) is treated as a pick; a drag will
|
||||
// become a marquee. Captures the down position (canvas-relative CSS px).
|
||||
float nav_drag_px = 0.0f;
|
||||
long down_x = 0;
|
||||
long down_y = 0;
|
||||
@@ -78,7 +88,13 @@ struct AppState {
|
||||
// Distinguishes "lock lost" (Esc/click-out → exit fly) from "lock denied on
|
||||
// entry" (headless / permission) where fly stays keyboard-drivable.
|
||||
bool fly_locked = false;
|
||||
bool k_w = false, k_a = false, k_s = false, k_d = false, k_q = false, k_e = false, k_shift = false;
|
||||
bool key_w_pressed = false;
|
||||
bool key_a_pressed = false;
|
||||
bool key_s_pressed = false;
|
||||
bool key_d_pressed = false;
|
||||
bool key_q_pressed = false;
|
||||
bool key_e_pressed = false;
|
||||
bool key_shift_pressed = false;
|
||||
double fly_last_ms = 0.0;
|
||||
};
|
||||
|
||||
@@ -102,13 +118,52 @@ int canvasCssHeight() {
|
||||
return (h > 1.0) ? int(h) : 1;
|
||||
}
|
||||
|
||||
// Marquee rectangle overlay. The rubber-band is a plain DOM <div> (shell.html)
|
||||
// positioned in CSS px — the canvas fills the viewport, so canvas-relative
|
||||
// coords are viewport coords. Cheaper + pixel-perfect vs a GPU overlay pass
|
||||
// (which the web lib doesn't have anyway).
|
||||
void showMarquee(int x, int y, int w, int h) {
|
||||
EM_ASM({
|
||||
var m = document.getElementById('marquee');
|
||||
if (m) {
|
||||
m.style.display = 'block';
|
||||
m.style.left = $0 + 'px'; m.style.top = $1 + 'px';
|
||||
m.style.width = $2 + 'px'; m.style.height = $3 + 'px';
|
||||
}
|
||||
}, x, y, w, h);
|
||||
}
|
||||
void hideMarquee() {
|
||||
EM_ASM({ var m = document.getElementById('marquee'); if (m) m.style.display = 'none'; });
|
||||
}
|
||||
|
||||
NavKind classifyPress(const ViewportCore::NavBindings& b, int em_button,
|
||||
bool shift, bool ctrl, bool alt) {
|
||||
using MB = ViewportCore::MouseBtn; using M = ViewportCore::NavMod;
|
||||
const MB btn = (em_button == 0) ? MB::Left : (em_button == 1) ? MB::Middle : MB::Right;
|
||||
const M mod = shift ? M::Shift : ctrl ? M::Ctrl : alt ? M::Alt : M::Plain;
|
||||
if (btn == b.orbit && mod == b.orbit_mod) return NavKind::Orbit;
|
||||
if (btn == b.pan && mod == b.pan_mod) return NavKind::Pan;
|
||||
if (btn == b.select) return NavKind::Select; // Shift/Ctrl = add/remove
|
||||
return NavKind::None;
|
||||
}
|
||||
|
||||
EM_BOOL onMouseDown(int, const EmscriptenMouseEvent* e, void* user) {
|
||||
auto* app = static_cast<AppState*>(user);
|
||||
// In fly mode a click exits (matches the desktop app).
|
||||
if (app->fly_mode) { setFlyMode(app, false); return EM_TRUE; }
|
||||
if (e->button == 0 || e->button == 1 || e->button == 2) {
|
||||
// Section tool: LMB on a plane's gizmo arrow grabs it to slide (logical px).
|
||||
if (app->section_tool_active && e->button == 0) {
|
||||
const int hit = app->core.hitTestSectionGizmo(int(e->targetX), int(e->targetY));
|
||||
if (hit >= 0 && app->core.beginSectionDrag(hit, int(e->targetX), int(e->targetY))) {
|
||||
app->section_dragging = true;
|
||||
return EM_TRUE; // claim the press — don't orbit
|
||||
}
|
||||
}
|
||||
const NavKind kind = classifyPress(app->core.navBindings(), e->button,
|
||||
e->shiftKey, e->ctrlKey, e->altKey);
|
||||
if (kind != NavKind::None) {
|
||||
app->nav_active = true;
|
||||
app->nav_button = e->button;
|
||||
app->nav_kind = kind;
|
||||
app->nav_drag_px = 0.0f;
|
||||
app->down_x = e->targetX; // canvas-relative CSS px
|
||||
app->down_y = e->targetY;
|
||||
@@ -124,42 +179,85 @@ EM_BOOL onMouseMove(int, const EmscriptenMouseEvent* e, void* user) {
|
||||
app->core.flyLook(float(e->movementX), float(e->movementY));
|
||||
return EM_TRUE;
|
||||
}
|
||||
// Section gizmo drag: slide the grabbed plane along its normal (logical px).
|
||||
if (app->section_dragging) {
|
||||
app->core.updateSectionDrag(int(e->targetX), int(e->targetY));
|
||||
return EM_TRUE;
|
||||
}
|
||||
if (!app->nav_active) return EM_FALSE;
|
||||
|
||||
const float dx = float(e->movementX);
|
||||
const float dy = float(e->movementY);
|
||||
app->nav_drag_px += std::abs(dx) + std::abs(dy);
|
||||
if (app->nav_button == 0) {
|
||||
app->core.orbitBy(dx, dy);
|
||||
} else {
|
||||
app->core.panBy(dx, dy, canvasCssHeight());
|
||||
if (app->nav_kind == NavKind::Orbit) app->core.orbitBy(dx, dy);
|
||||
else if (app->nav_kind == NavKind::Pan) app->core.panBy(dx, dy, canvasCssHeight());
|
||||
else if (app->nav_kind == NavKind::Select && app->nav_drag_px > kClickDragThresholdPx) {
|
||||
// Select-button drag → draw the marquee rubber-band (CSS px).
|
||||
const long x0 = std::min<long>(app->down_x, e->targetX);
|
||||
const long y0 = std::min<long>(app->down_y, e->targetY);
|
||||
showMarquee(int(x0), int(y0),
|
||||
int(std::labs(long(e->targetX) - app->down_x)),
|
||||
int(std::labs(long(e->targetY) - app->down_y)));
|
||||
}
|
||||
return EM_TRUE;
|
||||
}
|
||||
|
||||
EM_BOOL onMouseUp(int, const EmscriptenMouseEvent* e, void* user) {
|
||||
auto* app = static_cast<AppState*>(user);
|
||||
const bool was_active = app->nav_active;
|
||||
const int button = app->nav_button;
|
||||
const bool was_active = app->nav_active;
|
||||
const NavKind kind = app->nav_kind;
|
||||
app->nav_active = false;
|
||||
app->nav_kind = NavKind::None;
|
||||
|
||||
// Left release with no real drag → pick the object under the cursor and
|
||||
// route it through selection (Shift add, Ctrl remove, plain replace).
|
||||
// Async readback: the highlight appears a frame after the result lands.
|
||||
if (was_active && button == 0 && app->ready &&
|
||||
app->nav_drag_px <= kClickDragThresholdPx) {
|
||||
const double dpr = emscripten_get_device_pixel_ratio();
|
||||
const int px = int(app->down_x * dpr);
|
||||
const int py = int(app->down_y * dpr);
|
||||
const bool add = e->shiftKey;
|
||||
const bool remove = e->ctrlKey;
|
||||
app->core.pickObjectAtAsync(px, py, [app, add, remove](std::uint32_t id) {
|
||||
app->core.applyPickToSelection(id, add, remove);
|
||||
// Demo the v15 on-demand deferred fetch: log the picked object's
|
||||
// IFC GUID (first pick fetches the property block off the network).
|
||||
if (id != 0) app->core.logSelectedObjectGuidWeb(id);
|
||||
// End a section-gizmo drag (took over the press; no pick/orbit on release).
|
||||
if (app->section_dragging) {
|
||||
app->core.endSectionDrag();
|
||||
app->section_dragging = false;
|
||||
return EM_TRUE;
|
||||
}
|
||||
|
||||
if (!was_active || !app->ready) return EM_TRUE;
|
||||
const double dpr = emscripten_get_device_pixel_ratio();
|
||||
const bool no_drag = app->nav_drag_px <= kClickDragThresholdPx;
|
||||
|
||||
// Section tool claims a LEFT-button click ("click a surface to cut"); LMB
|
||||
// drag still orbits. Takes priority over nav while the tool is active.
|
||||
if (app->section_tool_active && e->button == 0 && no_drag) {
|
||||
const int px = int(app->down_x * dpr), py = int(app->down_y * dpr);
|
||||
app->core.pickSurfaceAtAsync(px, py, [app](ViewportCore::SurfaceHit hit) {
|
||||
if (hit.found) // pad past the AABB so the cut reads as a cap
|
||||
app->core.addSectionPlaneAtSurface(hit.world_pos, hit.world_normal,
|
||||
hit.aabb_radius * 1.5f);
|
||||
app->host.requestFrame();
|
||||
});
|
||||
return EM_TRUE;
|
||||
}
|
||||
|
||||
if (kind == NavKind::Select) {
|
||||
const bool add = e->shiftKey, remove = e->ctrlKey;
|
||||
if (!no_drag) {
|
||||
// Marquee drag → box-pick the rect (device px) and apply to selection.
|
||||
hideMarquee();
|
||||
const long x0 = std::min<long>(app->down_x, e->targetX);
|
||||
const long y0 = std::min<long>(app->down_y, e->targetY);
|
||||
const int rx = int(x0 * dpr), ry = int(y0 * dpr);
|
||||
const int rw = int(std::labs(long(e->targetX) - app->down_x) * dpr);
|
||||
const int rh = int(std::labs(long(e->targetY) - app->down_y) * dpr);
|
||||
app->core.picksInRectAsync(rx, ry, rw, rh,
|
||||
[app, add, remove](std::vector<std::uint32_t> ids) {
|
||||
app->core.applyMarqueeToSelection(ids, add, remove);
|
||||
app->host.requestFrame();
|
||||
});
|
||||
} else {
|
||||
// Single pick under the cursor (Shift add, Ctrl remove, plain replace).
|
||||
const int px = int(app->down_x * dpr), py = int(app->down_y * dpr);
|
||||
app->core.pickObjectAtAsync(px, py, [app, add, remove](std::uint32_t id) {
|
||||
app->core.applyPickToSelection(id, add, remove);
|
||||
// v15 on-demand element metadata fetch: log the picked object's IFC GUID.
|
||||
if (id != 0) app->core.logSelectedObjectGuidWeb(id);
|
||||
app->host.requestFrame();
|
||||
});
|
||||
}
|
||||
}
|
||||
return EM_TRUE;
|
||||
}
|
||||
@@ -183,14 +281,14 @@ EM_BOOL onWheel(int, const EmscriptenWheelEvent* e, void* user) {
|
||||
// Track a held fly movement key (W/A/S/D/Q/E/Shift). Returns true if `code` was
|
||||
// one. Physical `.code` so it's keymap-independent.
|
||||
bool setFlyKey(AppState* app, const char* code, bool down) {
|
||||
if (!std::strcmp(code, "KeyW")) app->k_w = down;
|
||||
else if (!std::strcmp(code, "KeyA")) app->k_a = down;
|
||||
else if (!std::strcmp(code, "KeyS")) app->k_s = down;
|
||||
else if (!std::strcmp(code, "KeyD")) app->k_d = down;
|
||||
else if (!std::strcmp(code, "KeyQ")) app->k_q = down;
|
||||
else if (!std::strcmp(code, "KeyE")) app->k_e = down;
|
||||
if (!std::strcmp(code, "KeyW")) app->key_w_pressed = down;
|
||||
else if (!std::strcmp(code, "KeyA")) app->key_a_pressed = down;
|
||||
else if (!std::strcmp(code, "KeyS")) app->key_s_pressed = down;
|
||||
else if (!std::strcmp(code, "KeyD")) app->key_d_pressed = down;
|
||||
else if (!std::strcmp(code, "KeyQ")) app->key_q_pressed = down;
|
||||
else if (!std::strcmp(code, "KeyE")) app->key_e_pressed = down;
|
||||
else if (!std::strcmp(code, "ShiftLeft") || !std::strcmp(code, "ShiftRight"))
|
||||
app->k_shift = down;
|
||||
app->key_shift_pressed = down;
|
||||
else return false;
|
||||
return true;
|
||||
}
|
||||
@@ -205,7 +303,9 @@ void setFlyMode(AppState* app, bool on) {
|
||||
emscripten_request_pointerlock(kCanvasSelector, EM_TRUE);
|
||||
Log::info() << "[fly] on — WASD/QE move, mouse looks, Shift boosts, wheel = speed, Esc exits";
|
||||
} else {
|
||||
app->k_w = app->k_a = app->k_s = app->k_d = app->k_q = app->k_e = app->k_shift = false;
|
||||
app->key_w_pressed = app->key_a_pressed = app->key_s_pressed = false;
|
||||
app->key_d_pressed = app->key_q_pressed = app->key_e_pressed = false;
|
||||
app->key_shift_pressed = false;
|
||||
app->fly_locked = false;
|
||||
emscripten_exit_pointerlock();
|
||||
Log::info() << "[fly] off";
|
||||
@@ -237,6 +337,25 @@ EM_BOOL onKeyDown(int, const EmscriptenKeyboardEvent* e, void* user) {
|
||||
return EM_TRUE;
|
||||
}
|
||||
if (!std::strcmp(code, "KeyX") && alt) { app->core.toggleXray(); return EM_TRUE; }
|
||||
// Section tool: K toggles drop-a-plane mode, Shift+K clears all cuts.
|
||||
if (!std::strcmp(code, "KeyK")) {
|
||||
if (shift) app->core.clearSectionPlanes();
|
||||
else {
|
||||
app->section_tool_active = !app->section_tool_active;
|
||||
Log::info() << "[section] tool "
|
||||
<< (app->section_tool_active ? "active — click a surface" : "off");
|
||||
}
|
||||
app->host.requestFrame();
|
||||
return EM_TRUE;
|
||||
}
|
||||
// Del/Backspace removes the most recent cut while the tool is active.
|
||||
if (app->section_tool_active &&
|
||||
(!std::strcmp(code, "Delete") || !std::strcmp(code, "Backspace"))) {
|
||||
const int n = app->core.sectionPlaneCount();
|
||||
if (n > 0) app->core.removeSectionPlane(n - 1);
|
||||
app->host.requestFrame();
|
||||
return EM_TRUE;
|
||||
}
|
||||
using SV = ViewportCore::StandardView;
|
||||
if (!std::strcmp(code, "Home")) app->core.viewAll();
|
||||
else if (!std::strcmp(code, "KeyF") && !shift) app->core.frameSelection();
|
||||
@@ -312,8 +431,10 @@ extern "C" EMSCRIPTEN_KEEPALIVE void raf_tick_c(void* user) {
|
||||
const double now = emscripten_get_now();
|
||||
const float dt = float((now - app->fly_last_ms) / 1000.0);
|
||||
app->fly_last_ms = now;
|
||||
app->core.flyMove(app->k_w, app->k_s, app->k_d, app->k_a,
|
||||
app->k_e, app->k_q, app->k_shift, dt);
|
||||
app->core.flyMove(app->key_w_pressed, app->key_s_pressed,
|
||||
app->key_d_pressed, app->key_a_pressed,
|
||||
app->key_e_pressed, app->key_q_pressed,
|
||||
app->key_shift_pressed, dt);
|
||||
}
|
||||
|
||||
if (app->host.consumeFrameRequest()) {
|
||||
@@ -373,6 +494,21 @@ extern "C" EMSCRIPTEN_KEEPALIVE void show_all_c() { if (g_app && g_app->
|
||||
extern "C" EMSCRIPTEN_KEEPALIVE void toggle_xray_c() { if (g_app && g_app->ready) g_app->core.toggleXray(); }
|
||||
extern "C" EMSCRIPTEN_KEEPALIVE int xray_is_active_c() { return (g_app && g_app->ready && g_app->core.xrayActive()) ? 1 : 0; }
|
||||
|
||||
// Section-cut tool: toggle the drop-a-plane mode, clear all planes, query state.
|
||||
extern "C" EMSCRIPTEN_KEEPALIVE void toggle_section_c() {
|
||||
if (!g_app || !g_app->ready) return;
|
||||
g_app->section_tool_active = !g_app->section_tool_active;
|
||||
Log::info() << "[section] tool "
|
||||
<< (g_app->section_tool_active ? "active — click a surface to cut" : "off");
|
||||
g_app->host.requestFrame();
|
||||
}
|
||||
extern "C" EMSCRIPTEN_KEEPALIVE void clear_section_c() {
|
||||
if (g_app && g_app->ready) { g_app->core.clearSectionPlanes(); g_app->host.requestFrame(); }
|
||||
}
|
||||
extern "C" EMSCRIPTEN_KEEPALIVE int section_is_active_c() {
|
||||
return (g_app && g_app->ready && g_app->section_tool_active) ? 1 : 0;
|
||||
}
|
||||
|
||||
// id: 0 Front, 1 Back, 2 Left, 3 Right, 4 Top, 5 Bottom.
|
||||
extern "C" EMSCRIPTEN_KEEPALIVE void standard_view_c(int id) {
|
||||
if (!g_app || !g_app->ready || id < 0 || id > 5) return;
|
||||
@@ -387,11 +523,15 @@ extern "C" EMSCRIPTEN_KEEPALIVE void standard_view_c(int id) {
|
||||
// geometry chunks arrive.
|
||||
extern "C" EMSCRIPTEN_KEEPALIVE int ifcv_chunks_resident_c() {
|
||||
if (!g_app) return 0;
|
||||
int r = 0, t = 0; g_app->core.streamingProgress(r, t); return r;
|
||||
int resident_chunks = 0, total_chunks = 0;
|
||||
g_app->core.streamingProgress(resident_chunks, total_chunks);
|
||||
return resident_chunks;
|
||||
}
|
||||
extern "C" EMSCRIPTEN_KEEPALIVE int ifcv_chunks_total_c() {
|
||||
if (!g_app) return 0;
|
||||
int r = 0, t = 0; g_app->core.streamingProgress(r, t); return t;
|
||||
int resident_chunks = 0, total_chunks = 0;
|
||||
g_app->core.streamingProgress(resident_chunks, total_chunks);
|
||||
return total_chunks;
|
||||
}
|
||||
|
||||
// Per-model progress for the federation loading panel: how many models are in
|
||||
@@ -401,11 +541,15 @@ extern "C" EMSCRIPTEN_KEEPALIVE int ifcv_model_count_c() {
|
||||
}
|
||||
extern "C" EMSCRIPTEN_KEEPALIVE int ifcv_model_resident_c(int idx) {
|
||||
if (!g_app) return 0;
|
||||
int r = 0, t = 0; g_app->core.streamingModelProgress(idx, r, t); return r;
|
||||
int resident_chunks = 0, total_chunks = 0;
|
||||
g_app->core.streamingModelProgress(idx, resident_chunks, total_chunks);
|
||||
return resident_chunks;
|
||||
}
|
||||
extern "C" EMSCRIPTEN_KEEPALIVE int ifcv_model_total_c(int idx) {
|
||||
if (!g_app) return 0;
|
||||
int r = 0, t = 0; g_app->core.streamingModelProgress(idx, r, t); return t;
|
||||
int resident_chunks = 0, total_chunks = 0;
|
||||
g_app->core.streamingModelProgress(idx, resident_chunks, total_chunks);
|
||||
return total_chunks;
|
||||
}
|
||||
|
||||
// Combined byte progress for the loading bar: total geometry, bytes the current
|
||||
@@ -413,23 +557,28 @@ extern "C" EMSCRIPTEN_KEEPALIVE int ifcv_model_total_c(int idx) {
|
||||
// JS gets exact byte counts well past 2 GB.
|
||||
extern "C" EMSCRIPTEN_KEEPALIVE double ifcv_bytes_total_c() {
|
||||
if (!g_app) return 0.0;
|
||||
std::uint64_t tot = 0, need = 0, load = 0;
|
||||
g_app->core.streamingByteProgress(tot, need, load); return double(tot);
|
||||
std::uint64_t total_bytes = 0, needed_bytes = 0, loaded_bytes = 0;
|
||||
g_app->core.streamingByteProgress(total_bytes, needed_bytes, loaded_bytes);
|
||||
return double(total_bytes);
|
||||
}
|
||||
extern "C" EMSCRIPTEN_KEEPALIVE double ifcv_bytes_needed_c() {
|
||||
if (!g_app) return 0.0;
|
||||
std::uint64_t tot = 0, need = 0, load = 0;
|
||||
g_app->core.streamingByteProgress(tot, need, load); return double(need);
|
||||
std::uint64_t total_bytes = 0, needed_bytes = 0, loaded_bytes = 0;
|
||||
g_app->core.streamingByteProgress(total_bytes, needed_bytes, loaded_bytes);
|
||||
return double(needed_bytes);
|
||||
}
|
||||
extern "C" EMSCRIPTEN_KEEPALIVE double ifcv_bytes_loaded_c() {
|
||||
if (!g_app) return 0.0;
|
||||
std::uint64_t tot = 0, need = 0, load = 0;
|
||||
g_app->core.streamingByteProgress(tot, need, load); return double(load);
|
||||
std::uint64_t total_bytes = 0, needed_bytes = 0, loaded_bytes = 0;
|
||||
g_app->core.streamingByteProgress(total_bytes, needed_bytes, loaded_bytes);
|
||||
return double(loaded_bytes);
|
||||
}
|
||||
|
||||
int main(int /*argc*/, char** /*argv*/) {
|
||||
Log::info() << "ifcviewer-web: starting";
|
||||
g_app = new AppState();
|
||||
// Default to the web mouse scheme: LMB orbit, MMB pan, RMB select/marquee.
|
||||
g_app->core.setNavPreset("web");
|
||||
g_app->core.initWgpuAsyncWeb([](bool ok) {
|
||||
if (!ok) {
|
||||
Log::warn() << "ifcviewer-web: wgpu init failed";
|
||||
|
||||
Binary file not shown.
@@ -8,6 +8,10 @@
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; }
|
||||
#viewer-canvas { display: block; width: 100vw; height: 100vh; outline: none;
|
||||
background: #1a1d24; }
|
||||
/* Marquee (box-select) rubber-band. Positioned in CSS px by main_web; never
|
||||
eats pointer events so the drag keeps reaching the canvas. */
|
||||
#marquee { position: fixed; display: none; z-index: 50; pointer-events: none;
|
||||
border: 1px solid #4a9eff; background: rgba(74, 158, 255, 0.15); }
|
||||
/* Log overlay sits bottom-left and never eats pointer events (so it
|
||||
can't block orbit drags over the canvas). It auto-scrolls to the
|
||||
newest line. Capped small; collapses further once the app is live. */
|
||||
@@ -63,6 +67,7 @@
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="viewer-canvas" width="1280" height="800"></canvas>
|
||||
<div id="marquee"></div>
|
||||
<div id="progress"><div id="progress-fill"></div></div>
|
||||
<div id="progress-panel">
|
||||
<div id="progress-summary"></div>
|
||||
@@ -91,6 +96,9 @@
|
||||
<button data-act="isolate" title="Isolate selected (Shift+H)">Isolate</button>
|
||||
<button data-act="showall" title="Show all (Alt+H)">Show all</button>
|
||||
<button data-act="xray" id="xray-btn" title="X-ray — translucent everything (Alt+X)">X-ray</button>
|
||||
<span class="sep"></span>
|
||||
<button data-act="section" id="section-btn" title="Section tool — click a surface to cut (K)">Section</button>
|
||||
<button data-act="clearcut" title="Clear all section cuts (Shift+K)">Clear cuts</button>
|
||||
</div>
|
||||
<div id="status">Starting…</div>
|
||||
<script>
|
||||
@@ -171,6 +179,10 @@
|
||||
var xb = document.getElementById('xray-btn');
|
||||
if (xb) xb.classList.toggle('active', !!Module._xray_is_active_c());
|
||||
}
|
||||
if (Module._section_is_active_c) {
|
||||
var sb = document.getElementById('section-btn');
|
||||
if (sb) sb.classList.toggle('active', !!Module._section_is_active_c());
|
||||
}
|
||||
Module._raf_tick_c(Module._app_ptr);
|
||||
}
|
||||
requestAnimationFrame(shellTick);
|
||||
@@ -302,6 +314,14 @@
|
||||
// the current scene (federation). Multiple files can be picked at once. Each
|
||||
// File is registered as its own byte-source (kept alive in __ifcvSources for
|
||||
// lazy Blob.slice reads) and streamed independently.
|
||||
// RMB is the select/marquee button in the Web nav preset, so suppress the
|
||||
// browser context menu over the canvas. (Firefox forces its native menu on
|
||||
// Shift+RightClick regardless — a browser escape hatch pages can't override.)
|
||||
var viewerCanvas = document.getElementById('viewer-canvas');
|
||||
if (viewerCanvas) {
|
||||
viewerCanvas.addEventListener('contextmenu', function(ev) { ev.preventDefault(); });
|
||||
}
|
||||
|
||||
var openBtn = document.getElementById('open-btn');
|
||||
var addBtn = document.getElementById('add-btn');
|
||||
var fileInput = document.getElementById('file-input');
|
||||
@@ -351,6 +371,8 @@
|
||||
else if (act === 'isolate') Module._isolate_selected_c();
|
||||
else if (act === 'showall') Module._show_all_c();
|
||||
else if (act === 'xray') Module._toggle_xray_c();
|
||||
else if (act === 'section') Module._toggle_section_c();
|
||||
else if (act === 'clearcut') Module._clear_section_c();
|
||||
else if (view !== null) Module._standard_view_c(parseInt(view, 10));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
# Web smoke tests
|
||||
|
||||
Headless-browser smoke tests for the WebGPU/Emscripten ifcviewer build.
|
||||
They load the built page in a real Chrome, wait for wgpu init, and assert
|
||||
the embedded sample **renders non-blank**, an **orbit drag changes the
|
||||
framebuffer**, and **no uncaptured WebGPU errors** are logged. Every web
|
||||
bring-up bug so far (blank render, error-buffer cascade, an overlay
|
||||
swallowing mouse input) is this shape.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- The web build must exist at `build-web/` (repo root):
|
||||
```sh
|
||||
source /path/to/emsdk_env.sh
|
||||
emcmake cmake -S src/ifcviewer-web -B build-web
|
||||
ninja -C build-web IfcViewerWeb
|
||||
```
|
||||
- Node + a system Chrome (`google-chrome-stable`). The config uses
|
||||
`channel: 'chrome'`, so you do **not** need `npx playwright install`.
|
||||
|
||||
## Run
|
||||
|
||||
```sh
|
||||
cd src/ifcviewer-web/tests
|
||||
npm install # one-time: pulls @playwright/test
|
||||
npm test
|
||||
```
|
||||
|
||||
`serve.mjs` statically serves `build-web` on :8124 (override with
|
||||
`WEB_BUILD_DIR=/path PORT=...`). Playwright starts it automatically.
|
||||
|
||||
## Headless / CI
|
||||
|
||||
WebGPU + headless on Linux is finicky, so the default config runs
|
||||
**headed** against the machine's real GPU. On a headless box:
|
||||
|
||||
```sh
|
||||
xvfb-run -a npm test
|
||||
```
|
||||
|
||||
For a GPU-less runner, flip `headless: true` in
|
||||
`playwright.config.mjs` and provide a SwiftShader Vulkan ICD
|
||||
(`VK_ICD_FILENAMES=.../vk_swiftshader_icd.json`) plus
|
||||
`--use-angle=swiftshader`. Browser WebGPU over SwiftShader is slow but
|
||||
adequate for a render-non-blank assertion.
|
||||
@@ -261,7 +261,7 @@ test('click selects an object and the highlight renders (async pick)', async ({
|
||||
const cx = box.x + box.width / 2;
|
||||
const cy = box.y + box.height / 2;
|
||||
const before = await shot(page);
|
||||
await page.mouse.click(cx, cy);
|
||||
await page.mouse.click(cx, cy, { button: 'right' }); // Web preset: RMB selects
|
||||
await page.waitForTimeout(600); // async pick result + flush + render
|
||||
const after = await shot(page);
|
||||
expect(
|
||||
@@ -330,8 +330,8 @@ test('hide selected removes geometry after a pick', async ({ page }) => {
|
||||
|
||||
const box = await page.locator('#viewer-canvas').boundingBox();
|
||||
const before = await shot(page);
|
||||
// select whatever is under the centre, then hide it
|
||||
await page.mouse.click(box.x + box.width / 2, box.y + box.height / 2);
|
||||
// select whatever is under the centre (Web preset: RMB selects), then hide it
|
||||
await page.mouse.click(box.x + box.width / 2, box.y + box.height / 2, { button: 'right' });
|
||||
await page.waitForTimeout(400);
|
||||
await page.evaluate(() => window.Module._hide_selected_c());
|
||||
await page.waitForTimeout(400);
|
||||
@@ -343,3 +343,54 @@ test('hide selected removes geometry after a pick', async ({ page }) => {
|
||||
await page.waitForTimeout(400);
|
||||
expect(gpuErrors, gpuErrors.join('\n')).toEqual([]);
|
||||
});
|
||||
|
||||
test('RMB marquee drag box-selects (Web preset)', async ({ page }) => {
|
||||
const gpuErrors = [];
|
||||
page.on('console', (m) => { if (/Uncaptured WebGPU error|is invalid/i.test(m.text())) gpuErrors.push(m.text()); });
|
||||
await ready(page);
|
||||
|
||||
const box = await page.locator('#viewer-canvas').boundingBox();
|
||||
const cx = box.x + box.width / 2, cy = box.y + box.height / 2;
|
||||
const before = await shot(page);
|
||||
await page.mouse.move(cx - 120, cy - 90);
|
||||
await page.mouse.down({ button: 'right' });
|
||||
await page.mouse.move(cx - 40, cy - 30, { steps: 4 });
|
||||
const midDragVisible = await page.evaluate(() => {
|
||||
const m = document.getElementById('marquee');
|
||||
return !!(m && getComputedStyle(m).display !== 'none');
|
||||
});
|
||||
await page.mouse.move(cx + 120, cy + 90, { steps: 6 });
|
||||
await page.mouse.up({ button: 'right' });
|
||||
await page.waitForTimeout(600); // async box-pick + apply + render
|
||||
const after = await shot(page);
|
||||
const hiddenAfter = await page.evaluate(() => {
|
||||
const m = document.getElementById('marquee');
|
||||
return !!(m && getComputedStyle(m).display === 'none');
|
||||
});
|
||||
expect(midDragVisible, 'marquee rubber-band was not shown during the drag').toBe(true);
|
||||
expect(Buffer.compare(before, after), 'box-select did not change the canvas').not.toBe(0);
|
||||
expect(hiddenAfter, 'marquee was not hidden after release').toBe(true);
|
||||
expect(gpuErrors, gpuErrors.join('\n')).toEqual([]);
|
||||
});
|
||||
|
||||
test('section tool: click a surface cuts geometry, clear restores', async ({ page }) => {
|
||||
const gpuErrors = [];
|
||||
page.on('console', (m) => { if (/Uncaptured WebGPU error|is invalid/i.test(m.text())) gpuErrors.push(m.text()); });
|
||||
await ready(page);
|
||||
|
||||
const box = await page.locator('#viewer-canvas').boundingBox();
|
||||
const cx = box.x + box.width / 2, cy = box.y + box.height / 2;
|
||||
const before = await shot(page);
|
||||
await page.evaluate(() => window.Module._toggle_section_c());
|
||||
expect(await page.evaluate(() => window.Module._section_is_active_c())).toBe(1);
|
||||
// Section tool claims LMB: a left click on the model drops a cut.
|
||||
await page.mouse.click(cx, cy);
|
||||
await page.waitForTimeout(700); // async surface pick + add plane + render
|
||||
const cut = await shot(page);
|
||||
expect(Buffer.compare(before, cut), 'section cut did not change the render').not.toBe(0);
|
||||
await page.evaluate(() => window.Module._clear_section_c());
|
||||
await page.waitForTimeout(400);
|
||||
const cleared = await shot(page);
|
||||
expect(Buffer.compare(cleared, cut), 'clearing cuts did not change the render').not.toBe(0);
|
||||
expect(gpuErrors, gpuErrors.join('\n')).toEqual([]);
|
||||
});
|
||||
|
||||
@@ -186,6 +186,16 @@ AppSettings::NavPreset AppSettings::navPreset() const {
|
||||
return nav_preset_;
|
||||
}
|
||||
|
||||
const char* AppSettings::navPresetName(NavPreset preset) {
|
||||
switch (preset) {
|
||||
case NavPreset::Rhino: return "rhino";
|
||||
case NavPreset::Revit: return "revit";
|
||||
case NavPreset::Web: return "web";
|
||||
case NavPreset::Blender: break;
|
||||
}
|
||||
return "blender";
|
||||
}
|
||||
|
||||
void AppSettings::setNavPreset(NavPreset value) {
|
||||
if (nav_preset_ == value) return;
|
||||
nav_preset_ = value;
|
||||
@@ -220,7 +230,7 @@ void AppSettings::load() {
|
||||
static_cast<int>(NavPreset::Blender)).toInt();
|
||||
// Clamp to known values so a stale config doesn't drop us into
|
||||
// an undefined preset slot.
|
||||
if (raw < 0 || raw > static_cast<int>(NavPreset::Revit)) {
|
||||
if (raw < 0 || raw > static_cast<int>(NavPreset::Web)) {
|
||||
nav_preset_ = NavPreset::Blender;
|
||||
} else {
|
||||
nav_preset_ = static_cast<NavPreset>(raw);
|
||||
|
||||
@@ -37,13 +37,18 @@ public:
|
||||
// Blender — Orbit MMB, Pan Shift+MMB (current default)
|
||||
// Rhino — Orbit RMB, Pan Shift+RMB
|
||||
// Revit — Orbit Shift+MMB, Pan MMB
|
||||
// Web — Orbit LMB, Pan MMB, Select RMB
|
||||
enum class NavPreset {
|
||||
Blender = 0,
|
||||
Rhino = 1,
|
||||
Revit = 2,
|
||||
Web = 3,
|
||||
};
|
||||
Q_ENUM(NavPreset)
|
||||
|
||||
// Preset → the lowercase name ViewportCore::setNavPreset / applyNavPreset take.
|
||||
static const char* navPresetName(NavPreset preset);
|
||||
|
||||
static AppSettings& instance();
|
||||
|
||||
QString geometryLibrary() const;
|
||||
|
||||
@@ -41,8 +41,8 @@ void BufferPool::configure(WGPUInstance instance, WGPUDevice device,
|
||||
}
|
||||
|
||||
void BufferPool::destroy() {
|
||||
for (auto& sp : sub_pools_) {
|
||||
if (sp.buffer) wgpuBufferRelease(sp.buffer);
|
||||
for (auto& sub_pool : sub_pools_) {
|
||||
if (sub_pool.buffer) wgpuBufferRelease(sub_pool.buffer);
|
||||
}
|
||||
sub_pools_.clear();
|
||||
device_ = nullptr;
|
||||
@@ -140,7 +140,7 @@ bool BufferPool::addSubBuffer() {
|
||||
WGPUBuffer buf = wgpuDeviceCreateBuffer(device_, &desc);
|
||||
|
||||
struct PopResult { bool done = false; bool error = false; };
|
||||
auto pop = [&](PopResult& pr) {
|
||||
auto pop = [&](PopResult& pop_result) {
|
||||
WGPUPopErrorScopeCallbackInfo pcb = {};
|
||||
pcb.mode = WGPUCallbackMode_AllowProcessEvents;
|
||||
pcb.callback = [](WGPUPopErrorScopeStatus, WGPUErrorType type,
|
||||
@@ -149,9 +149,9 @@ bool BufferPool::addSubBuffer() {
|
||||
p->done = true;
|
||||
p->error = (type != WGPUErrorType_NoError);
|
||||
};
|
||||
pcb.userdata1 = ≺
|
||||
pcb.userdata1 = &pop_result;
|
||||
wgpuDevicePopErrorScope(device_, pcb);
|
||||
while (!pr.done) wgpuInstanceProcessEvents(instance_);
|
||||
while (!pop_result.done) wgpuInstanceProcessEvents(instance_);
|
||||
};
|
||||
PopResult oom_pop, validation_pop;
|
||||
pop(oom_pop);
|
||||
@@ -205,11 +205,12 @@ void BufferPool::resolveProvisionalGrowth(bool failed) {
|
||||
(unsigned long long)(total_capacity_bytes() / (1024 * 1024)),
|
||||
sub_pools_.size());
|
||||
} else {
|
||||
sub_pools_[i].provisional = false;
|
||||
SubPool& sub_pool = sub_pools_[i];
|
||||
sub_pool.provisional = false;
|
||||
std::fprintf(stderr,
|
||||
"[wgpu pool] added sub-buffer %zu (%llu MB); pool total now %llu MB\n",
|
||||
i,
|
||||
(unsigned long long)(sub_pools_[i].capacity / (1024 * 1024)),
|
||||
(unsigned long long)(sub_pool.capacity / (1024 * 1024)),
|
||||
(unsigned long long)(total_capacity_bytes() / (1024 * 1024)));
|
||||
}
|
||||
return;
|
||||
@@ -225,34 +226,34 @@ BufferPool::Slice BufferPool::alloc(uint64_t size, uint64_t align) {
|
||||
// adding another sub-buffer and retry once.
|
||||
for (int attempt = 0; attempt < 2; ++attempt) {
|
||||
for (size_t sp_idx = 0; sp_idx < sub_pools_.size(); ++sp_idx) {
|
||||
SubPool& sp = sub_pools_[sp_idx];
|
||||
SubPool& sub_pool = sub_pools_[sp_idx];
|
||||
// Web: never allocate out of a sub-buffer still awaiting OOM
|
||||
// validation — its handle may be a Dawn error buffer.
|
||||
if (sp.provisional) continue;
|
||||
for (size_t i = 0; i < sp.free_ranges.size(); ++i) {
|
||||
const FreeRange& r = sp.free_ranges[i];
|
||||
const uint64_t aligned = (r.offset + (align - 1)) & ~(align - 1);
|
||||
const uint64_t pad = aligned - r.offset;
|
||||
if (pad >= r.size) continue;
|
||||
if (size > r.size - pad) continue;
|
||||
if (sub_pool.provisional) continue;
|
||||
for (size_t i = 0; i < sub_pool.free_ranges.size(); ++i) {
|
||||
const FreeRange& free_range = sub_pool.free_ranges[i];
|
||||
const uint64_t aligned = (free_range.offset + (align - 1)) & ~(align - 1);
|
||||
const uint64_t alignment_padding = aligned - free_range.offset;
|
||||
if (alignment_padding >= free_range.size) continue;
|
||||
if (size > free_range.size - alignment_padding) continue;
|
||||
|
||||
const uint64_t post_off = aligned + size;
|
||||
const uint64_t post_size = (r.offset + r.size) - post_off;
|
||||
const uint64_t post_size = (free_range.offset + free_range.size) - post_off;
|
||||
|
||||
if (pad == 0 && post_size == 0) {
|
||||
sp.free_ranges.erase(sp.free_ranges.begin() + i);
|
||||
} else if (pad == 0) {
|
||||
sp.free_ranges[i] = {post_off, post_size};
|
||||
if (alignment_padding == 0 && post_size == 0) {
|
||||
sub_pool.free_ranges.erase(sub_pool.free_ranges.begin() + i);
|
||||
} else if (alignment_padding == 0) {
|
||||
sub_pool.free_ranges[i] = {post_off, post_size};
|
||||
} else if (post_size == 0) {
|
||||
sp.free_ranges[i] = {r.offset, pad};
|
||||
sub_pool.free_ranges[i] = {free_range.offset, alignment_padding};
|
||||
} else {
|
||||
sp.free_ranges[i] = {r.offset, pad};
|
||||
sp.free_ranges.insert(sp.free_ranges.begin() + i + 1,
|
||||
sub_pool.free_ranges[i] = {free_range.offset, alignment_padding};
|
||||
sub_pool.free_ranges.insert(sub_pool.free_ranges.begin() + i + 1,
|
||||
{post_off, post_size});
|
||||
}
|
||||
|
||||
sp.used += size;
|
||||
out.buffer = sp.buffer;
|
||||
sub_pool.used += size;
|
||||
out.buffer = sub_pool.buffer;
|
||||
out.offset = aligned;
|
||||
out.size = size;
|
||||
out.sub_idx = int(sp_idx);
|
||||
@@ -270,50 +271,56 @@ BufferPool::Slice BufferPool::alloc(uint64_t size, uint64_t align) {
|
||||
void BufferPool::free(const Slice& s) {
|
||||
if (!s.valid()) return;
|
||||
if (s.sub_idx < 0 || size_t(s.sub_idx) >= sub_pools_.size()) return;
|
||||
SubPool& sp = sub_pools_[size_t(s.sub_idx)];
|
||||
assert(s.offset + s.size <= sp.capacity);
|
||||
SubPool& sub_pool = sub_pools_[size_t(s.sub_idx)];
|
||||
assert(s.offset + s.size <= sub_pool.capacity);
|
||||
|
||||
size_t i = 0;
|
||||
while (i < sp.free_ranges.size() && sp.free_ranges[i].offset < s.offset) ++i;
|
||||
sp.free_ranges.insert(sp.free_ranges.begin() + i, {s.offset, s.size});
|
||||
sp.used -= s.size;
|
||||
while (i < sub_pool.free_ranges.size() && sub_pool.free_ranges[i].offset < s.offset) ++i;
|
||||
sub_pool.free_ranges.insert(sub_pool.free_ranges.begin() + i, {s.offset, s.size});
|
||||
sub_pool.used -= s.size;
|
||||
|
||||
if (i + 1 < sp.free_ranges.size()
|
||||
&& sp.free_ranges[i].offset + sp.free_ranges[i].size == sp.free_ranges[i + 1].offset) {
|
||||
sp.free_ranges[i].size += sp.free_ranges[i + 1].size;
|
||||
sp.free_ranges.erase(sp.free_ranges.begin() + i + 1);
|
||||
if (i + 1 < sub_pool.free_ranges.size()
|
||||
&& sub_pool.free_ranges[i].offset + sub_pool.free_ranges[i].size
|
||||
== sub_pool.free_ranges[i + 1].offset) {
|
||||
sub_pool.free_ranges[i].size += sub_pool.free_ranges[i + 1].size;
|
||||
sub_pool.free_ranges.erase(sub_pool.free_ranges.begin() + i + 1);
|
||||
}
|
||||
if (i > 0
|
||||
&& sp.free_ranges[i - 1].offset + sp.free_ranges[i - 1].size == sp.free_ranges[i].offset) {
|
||||
sp.free_ranges[i - 1].size += sp.free_ranges[i].size;
|
||||
sp.free_ranges.erase(sp.free_ranges.begin() + i);
|
||||
&& sub_pool.free_ranges[i - 1].offset + sub_pool.free_ranges[i - 1].size
|
||||
== sub_pool.free_ranges[i].offset) {
|
||||
sub_pool.free_ranges[i - 1].size += sub_pool.free_ranges[i].size;
|
||||
sub_pool.free_ranges.erase(sub_pool.free_ranges.begin() + i);
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t BufferPool::total_capacity_bytes() const {
|
||||
uint64_t s = 0;
|
||||
uint64_t total_capacity = 0;
|
||||
// Skip provisional sub-pools (web, awaiting OOM validation) — their
|
||||
// capacity isn't usable yet, so counting it would mislead the
|
||||
// evictor's "is there room?" heuristics.
|
||||
for (const auto& sp : sub_pools_) if (!sp.provisional) s += sp.capacity;
|
||||
return s;
|
||||
for (const auto& sub_pool : sub_pools_) {
|
||||
if (!sub_pool.provisional) total_capacity += sub_pool.capacity;
|
||||
}
|
||||
return total_capacity;
|
||||
}
|
||||
|
||||
uint64_t BufferPool::total_used_bytes() const {
|
||||
uint64_t s = 0;
|
||||
for (const auto& sp : sub_pools_) if (!sp.provisional) s += sp.used;
|
||||
return s;
|
||||
uint64_t total_used = 0;
|
||||
for (const auto& sub_pool : sub_pools_) {
|
||||
if (!sub_pool.provisional) total_used += sub_pool.used;
|
||||
}
|
||||
return total_used;
|
||||
}
|
||||
|
||||
uint64_t BufferPool::largest_free_run_bytes() const {
|
||||
uint64_t m = 0;
|
||||
for (const auto& sp : sub_pools_) {
|
||||
if (sp.provisional) continue;
|
||||
for (const auto& r : sp.free_ranges) {
|
||||
if (r.size > m) m = r.size;
|
||||
uint64_t largest_free_run = 0;
|
||||
for (const auto& sub_pool : sub_pools_) {
|
||||
if (sub_pool.provisional) continue;
|
||||
for (const auto& free_range : sub_pool.free_ranges) {
|
||||
if (free_range.size > largest_free_run) largest_free_run = free_range.size;
|
||||
}
|
||||
}
|
||||
return m;
|
||||
return largest_free_run;
|
||||
}
|
||||
|
||||
void BufferPool::addSubBufferForTesting(WGPUBuffer fake_buffer, uint64_t capacity) {
|
||||
|
||||
@@ -146,6 +146,7 @@ set(IFCVIEWER_CORE_SOURCES
|
||||
SidecarCompress.cpp
|
||||
StreamingLoader.cpp
|
||||
StreamingThread.cpp
|
||||
SectionGizmoRenderer.cpp
|
||||
ViewportCore.cpp
|
||||
)
|
||||
# Web needs a zstd DECODER (Emscripten has no zstd port; the desktop links the
|
||||
@@ -195,6 +196,7 @@ set(IFCVIEWER_CORE_HEADERS
|
||||
ModelGpuData.h
|
||||
FrameStats.h
|
||||
OverlayFrame.h
|
||||
SectionGizmoRenderer.h
|
||||
SectionPlane.h
|
||||
SelectionState.h
|
||||
SidecarCache.h
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user