mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-26 10:11:46 +00:00
ifcviewer: split into shared library and two app executables
Turn src/ifcviewer into libIfcViewer.so holding the rendering engine + geometry pipeline (ViewportWindow, GeometryStreamer, BvhAccel, InstancedGeometry, SidecarCache, LodBuilder, AppSettings). Move the existing UI shell (MainWindow, SettingsWindow, main.cpp) into src/ifcviewer-full as the IfcViewerFull executable. Add a new src/ifcviewer-minimal target with a MinimalWindow that hosts only the viewport and reuses the sidecar fast-path for benchmark/debug runs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -674,6 +674,8 @@ if(BUILD_IFCGEOM)
|
|||||||
endif(BUILD_IFCGEOM)
|
endif(BUILD_IFCGEOM)
|
||||||
if(BUILD_IFCVIEWER)
|
if(BUILD_IFCVIEWER)
|
||||||
add_subdirectory(../src/ifcviewer ifcviewer)
|
add_subdirectory(../src/ifcviewer ifcviewer)
|
||||||
|
add_subdirectory(../src/ifcviewer-minimal ifcviewer-minimal)
|
||||||
|
add_subdirectory(../src/ifcviewer-full ifcviewer-full)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Cmake uninstall target
|
# Cmake uninstall target
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
################################################################################
|
||||||
|
# #
|
||||||
|
# This file is part of IfcOpenShell. #
|
||||||
|
# #
|
||||||
|
# IfcOpenShell is free software: you can redistribute it and/or modify #
|
||||||
|
# it under the terms of the Lesser GNU General Public License as published by #
|
||||||
|
# the Free Software Foundation, either version 3.0 of the License, or #
|
||||||
|
# (at your option) any later version. #
|
||||||
|
# #
|
||||||
|
# IfcOpenShell is distributed in the hope that it will be useful, #
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||||
|
# Lesser GNU General Public License for more details. #
|
||||||
|
# #
|
||||||
|
# You should have received a copy of the Lesser GNU General Public License #
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
|
||||||
|
# #
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
message("Running CMakeLists.txt in /src/ifcviewer-full")
|
||||||
|
|
||||||
|
file(GLOB IFCVIEWER_FULL_CPP_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
|
||||||
|
file(GLOB IFCVIEWER_FULL_H_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.h)
|
||||||
|
set(IFCVIEWER_FULL_FILES ${IFCVIEWER_FULL_CPP_FILES} ${IFCVIEWER_FULL_H_FILES})
|
||||||
|
|
||||||
|
add_executable(IfcViewerFull ${IFCVIEWER_FULL_FILES})
|
||||||
|
|
||||||
|
set_target_properties(IfcViewerFull PROPERTIES
|
||||||
|
AUTOMOC ON
|
||||||
|
WIN32_EXECUTABLE ON
|
||||||
|
MACOSX_BUNDLE ON
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(IfcViewerFull PRIVATE IfcViewer)
|
||||||
|
|
||||||
|
install(TARGETS IfcViewerFull EXPORT ${IFCOPENSHELL_EXPORT_TARGETS})
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
################################################################################
|
||||||
|
# #
|
||||||
|
# This file is part of IfcOpenShell. #
|
||||||
|
# #
|
||||||
|
# IfcOpenShell is free software: you can redistribute it and/or modify #
|
||||||
|
# it under the terms of the Lesser GNU General Public License as published by #
|
||||||
|
# the Free Software Foundation, either version 3.0 of the License, or #
|
||||||
|
# (at your option) any later version. #
|
||||||
|
# #
|
||||||
|
# IfcOpenShell is distributed in the hope that it will be useful, #
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||||
|
# Lesser GNU General Public License for more details. #
|
||||||
|
# #
|
||||||
|
# You should have received a copy of the Lesser GNU General Public License #
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
|
||||||
|
# #
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
message("Running CMakeLists.txt in /src/ifcviewer-minimal")
|
||||||
|
|
||||||
|
file(GLOB IFCVIEWER_MINIMAL_CPP_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
|
||||||
|
file(GLOB IFCVIEWER_MINIMAL_H_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.h)
|
||||||
|
set(IFCVIEWER_MINIMAL_FILES ${IFCVIEWER_MINIMAL_CPP_FILES} ${IFCVIEWER_MINIMAL_H_FILES})
|
||||||
|
|
||||||
|
add_executable(IfcViewerMinimal ${IFCVIEWER_MINIMAL_FILES})
|
||||||
|
|
||||||
|
set_target_properties(IfcViewerMinimal PROPERTIES
|
||||||
|
AUTOMOC ON
|
||||||
|
WIN32_EXECUTABLE ON
|
||||||
|
MACOSX_BUNDLE ON
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(IfcViewerMinimal PRIVATE IfcViewer)
|
||||||
|
|
||||||
|
install(TARGETS IfcViewerMinimal EXPORT ${IFCOPENSHELL_EXPORT_TARGETS})
|
||||||
@@ -0,0 +1,278 @@
|
|||||||
|
/********************************************************************************
|
||||||
|
* *
|
||||||
|
* This file is part of IfcOpenShell. *
|
||||||
|
* *
|
||||||
|
* IfcOpenShell is free software: you can redistribute it and/or modify *
|
||||||
|
* it under the terms of the Lesser GNU General Public License as published by *
|
||||||
|
* the Free Software Foundation, either version 3.0 of the License, or *
|
||||||
|
* (at your option) any later version. *
|
||||||
|
* *
|
||||||
|
* IfcOpenShell is distributed in the hope that it will be useful, *
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||||
|
* Lesser GNU General Public License for more details. *
|
||||||
|
* *
|
||||||
|
* You should have received a copy of the Lesser GNU General Public License *
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||||
|
* *
|
||||||
|
********************************************************************************/
|
||||||
|
|
||||||
|
#include "MinimalWindow.h"
|
||||||
|
#include "AppSettings.h"
|
||||||
|
#include "SidecarCache.h"
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QStatusBar>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
MinimalWindow::MinimalWindow(QWidget* parent)
|
||||||
|
: QMainWindow(parent)
|
||||||
|
{
|
||||||
|
viewport_ = new ViewportWindow();
|
||||||
|
viewport_container_ = QWidget::createWindowContainer(viewport_, this);
|
||||||
|
viewport_container_->setMinimumSize(400, 300);
|
||||||
|
viewport_container_->setFocusPolicy(Qt::StrongFocus);
|
||||||
|
setCentralWidget(viewport_container_);
|
||||||
|
|
||||||
|
status_label_ = new QLabel("Ready");
|
||||||
|
stats_label_ = new QLabel();
|
||||||
|
stats_label_->setVisible(AppSettings::instance().showStats());
|
||||||
|
statusBar()->addWidget(status_label_, 1);
|
||||||
|
statusBar()->addPermanentWidget(stats_label_);
|
||||||
|
|
||||||
|
connect(viewport_, &ViewportWindow::frameStatsUpdated, this,
|
||||||
|
[this](const ViewportWindow::FrameStats& s) {
|
||||||
|
if (!stats_label_->isVisible()) return;
|
||||||
|
stats_label_->setText(
|
||||||
|
QString("%1 fps | %2 ms | %3/%4 obj | %5/%6 tri | %7 gl_draws (%8 sub)")
|
||||||
|
.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(s.indirect_sub_draws));
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(&AppSettings::instance(), &AppSettings::showStatsChanged, this, [this](bool show) {
|
||||||
|
stats_label_->setVisible(show);
|
||||||
|
if (!show) stats_label_->clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Periodically drain the streamer's pending-elements buffer so it doesn't
|
||||||
|
// grow unbounded on large models. We don't use the element data here —
|
||||||
|
// this window has no tree — but the buffer must still be flushed.
|
||||||
|
connect(&element_drain_timer_, &QTimer::timeout, this, &MinimalWindow::drainStreamerElements);
|
||||||
|
element_drain_timer_.setInterval(250);
|
||||||
|
|
||||||
|
setWindowTitle("IfcViewerMinimal");
|
||||||
|
resize(1200, 800);
|
||||||
|
}
|
||||||
|
|
||||||
|
MinimalWindow::~MinimalWindow() {
|
||||||
|
joinSidecarThread();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MinimalWindow::joinSidecarThread() {
|
||||||
|
if (sidecar_read_thread_.joinable())
|
||||||
|
sidecar_read_thread_.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MinimalWindow::addFiles(const QStringList& paths) {
|
||||||
|
for (const auto& path : paths) {
|
||||||
|
uint32_t id = next_model_id_++;
|
||||||
|
ModelEntry entry;
|
||||||
|
entry.id = id;
|
||||||
|
entry.file_path = path;
|
||||||
|
entry.display_name = QFileInfo(path).fileName();
|
||||||
|
entry.streamer = new GeometryStreamer(this);
|
||||||
|
models_[id] = entry;
|
||||||
|
load_queue_.push_back(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loading_model_id_ == 0) {
|
||||||
|
QTimer::singleShot(0, this, &MinimalWindow::startNextLoad);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MinimalWindow::connectStreamer(GeometryStreamer* streamer) {
|
||||||
|
connect(streamer, &GeometryStreamer::meshReady,
|
||||||
|
this, &MinimalWindow::onMeshReady, Qt::QueuedConnection);
|
||||||
|
connect(streamer, &GeometryStreamer::instanceReady,
|
||||||
|
this, &MinimalWindow::onInstanceReady, Qt::QueuedConnection);
|
||||||
|
connect(streamer, &GeometryStreamer::finished,
|
||||||
|
this, &MinimalWindow::onStreamingFinished, Qt::QueuedConnection);
|
||||||
|
connect(streamer, &GeometryStreamer::errorOccurred,
|
||||||
|
this, &MinimalWindow::onErrorOccurred, Qt::QueuedConnection);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MinimalWindow::startNextLoad() {
|
||||||
|
if (load_queue_.empty()) {
|
||||||
|
loading_model_id_ = 0;
|
||||||
|
status_label_->setText(QString("Loaded %1 model(s)").arg(models_.size()));
|
||||||
|
applyPendingBenchmark();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
loading_model_id_ = load_queue_.front();
|
||||||
|
load_queue_.pop_front();
|
||||||
|
|
||||||
|
auto& model = models_[loading_model_id_];
|
||||||
|
|
||||||
|
load_timer_.restart();
|
||||||
|
status_label_->setText("Loading: " + model.display_name);
|
||||||
|
|
||||||
|
std::string ifc_path = model.file_path.toStdString();
|
||||||
|
uint64_t file_size = static_cast<uint64_t>(QFileInfo(model.file_path).size());
|
||||||
|
uint32_t mid = loading_model_id_;
|
||||||
|
|
||||||
|
joinSidecarThread();
|
||||||
|
sidecar_read_thread_ = std::thread([this, ifc_path, file_size, mid]() {
|
||||||
|
QElapsedTimer rt; rt.start();
|
||||||
|
auto cached = readSidecar(ifc_path, file_size);
|
||||||
|
qDebug(" Sidecar read: %lld ms (%s)", rt.elapsed(), ifc_path.c_str());
|
||||||
|
auto result = std::make_shared<std::optional<SidecarData>>(std::move(cached));
|
||||||
|
QMetaObject::invokeMethod(this, [this, mid, result]() {
|
||||||
|
if (*result && !(*result)->instances.empty()) {
|
||||||
|
applySidecarData(mid, std::move(**result));
|
||||||
|
} else {
|
||||||
|
auto it = models_.find(mid);
|
||||||
|
if (it == models_.end()) return;
|
||||||
|
auto& m = it->second;
|
||||||
|
connectStreamer(m.streamer);
|
||||||
|
element_drain_timer_.start();
|
||||||
|
m.streamer->loadFile(
|
||||||
|
m.file_path.toStdString(), next_object_id_, loading_model_id_);
|
||||||
|
}
|
||||||
|
}, Qt::QueuedConnection);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void MinimalWindow::applySidecarData(uint32_t mid, SidecarData data) {
|
||||||
|
auto it = models_.find(mid);
|
||||||
|
if (it == models_.end()) return;
|
||||||
|
auto& model = it->second;
|
||||||
|
|
||||||
|
qDebug("Sidecar hit: %s (%zu verts, %zu indices, %zu meshes, %zu instances, %zu elements)",
|
||||||
|
model.file_path.toStdString().c_str(),
|
||||||
|
data.vertices.size() / INSTANCED_VERTEX_STRIDE_BYTES,
|
||||||
|
data.indices.size(),
|
||||||
|
data.meshes.size(),
|
||||||
|
data.instances.size(),
|
||||||
|
data.elements.size());
|
||||||
|
|
||||||
|
// Rebase object/model IDs onto the current session's ID space. Matches
|
||||||
|
// MainWindow::applySidecarData — two cached models both starting at
|
||||||
|
// object_id=1 would collide otherwise.
|
||||||
|
uint32_t min_oid = UINT32_MAX;
|
||||||
|
for (const auto& pe : data.elements) {
|
||||||
|
if (pe.object_id < min_oid) min_oid = pe.object_id;
|
||||||
|
}
|
||||||
|
uint32_t oid_offset = 0;
|
||||||
|
if (!data.elements.empty() && min_oid < UINT32_MAX) {
|
||||||
|
oid_offset = next_object_id_ - min_oid;
|
||||||
|
}
|
||||||
|
for (auto& pe : data.elements) {
|
||||||
|
pe.object_id += oid_offset;
|
||||||
|
pe.model_id = mid;
|
||||||
|
if (pe.object_id >= next_object_id_)
|
||||||
|
next_object_id_ = pe.object_id + 1;
|
||||||
|
}
|
||||||
|
for (auto& inst : data.instances) {
|
||||||
|
inst.object_id += oid_offset;
|
||||||
|
inst.model_id = mid;
|
||||||
|
}
|
||||||
|
|
||||||
|
viewport_->applyCachedModel(mid, std::move(data));
|
||||||
|
|
||||||
|
qint64 ms = load_timer_.elapsed();
|
||||||
|
QString elapsed = (ms >= 1000)
|
||||||
|
? QString::number(ms / 1000.0, 'f', 2) + " s"
|
||||||
|
: QString::number(ms) + " ms";
|
||||||
|
status_label_->setText(QString("%1 loaded from cache in %2")
|
||||||
|
.arg(model.display_name).arg(elapsed));
|
||||||
|
|
||||||
|
loading_model_id_ = 0;
|
||||||
|
QTimer::singleShot(0, this, &MinimalWindow::startNextLoad);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MinimalWindow::onMeshReady(MeshChunk chunk) {
|
||||||
|
viewport_->uploadMeshChunk(chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MinimalWindow::onInstanceReady(InstanceChunk chunk) {
|
||||||
|
viewport_->uploadInstanceChunk(chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MinimalWindow::drainStreamerElements() {
|
||||||
|
if (loading_model_id_ == 0) return;
|
||||||
|
auto it = models_.find(loading_model_id_);
|
||||||
|
if (it == models_.end()) return;
|
||||||
|
(void)it->second.streamer->drainElements();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MinimalWindow::onStreamingFinished() {
|
||||||
|
element_drain_timer_.stop();
|
||||||
|
drainStreamerElements();
|
||||||
|
|
||||||
|
if (loading_model_id_ != 0) {
|
||||||
|
auto it = models_.find(loading_model_id_);
|
||||||
|
if (it != models_.end()) {
|
||||||
|
next_object_id_ = it->second.streamer->lastObjectId();
|
||||||
|
viewport_->finalizeModel(loading_model_id_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
qint64 ms = load_timer_.elapsed();
|
||||||
|
QString elapsed = (ms >= 1000)
|
||||||
|
? QString::number(ms / 1000.0, 'f', 2) + " s"
|
||||||
|
: QString::number(ms) + " ms";
|
||||||
|
|
||||||
|
auto it = models_.find(loading_model_id_);
|
||||||
|
QString name = (it != models_.end()) ? it->second.display_name : QString();
|
||||||
|
status_label_->setText(QString("%1 streamed in %2").arg(name).arg(elapsed));
|
||||||
|
|
||||||
|
startNextLoad();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MinimalWindow::onErrorOccurred(const QString& message) {
|
||||||
|
qWarning("IfcViewerMinimal error: %s", qPrintable(message));
|
||||||
|
status_label_->setText("Error: " + message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MinimalWindow::setPendingCamera(const QString& params) {
|
||||||
|
pending_camera_ = params;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MinimalWindow::setPendingBenchmark(int frames) {
|
||||||
|
pending_benchmark_ = frames;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MinimalWindow::applyPendingBenchmark() {
|
||||||
|
if (pending_camera_.isEmpty() && pending_benchmark_ <= 0) return;
|
||||||
|
|
||||||
|
if (!pending_camera_.isEmpty()) {
|
||||||
|
QStringList parts = pending_camera_.split(',');
|
||||||
|
if (parts.size() == 6) {
|
||||||
|
viewport_->setCamera(
|
||||||
|
parts[0].toFloat(), parts[1].toFloat(), parts[2].toFloat(),
|
||||||
|
parts[3].toFloat(), parts[4].toFloat(), parts[5].toFloat());
|
||||||
|
qDebug("Camera set: %s", qPrintable(pending_camera_));
|
||||||
|
} else {
|
||||||
|
qWarning("--camera expects 6 comma-separated values: tx,ty,tz,dist,yaw,pitch");
|
||||||
|
}
|
||||||
|
pending_camera_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pending_benchmark_ > 0) {
|
||||||
|
qDebug("Starting benchmark: %d frames", pending_benchmark_);
|
||||||
|
viewport_->setBenchmarkFrames(pending_benchmark_);
|
||||||
|
pending_benchmark_ = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
/********************************************************************************
|
||||||
|
* *
|
||||||
|
* This file is part of IfcOpenShell. *
|
||||||
|
* *
|
||||||
|
* IfcOpenShell is free software: you can redistribute it and/or modify *
|
||||||
|
* it under the terms of the Lesser GNU General Public License as published by *
|
||||||
|
* the Free Software Foundation, either version 3.0 of the License, or *
|
||||||
|
* (at your option) any later version. *
|
||||||
|
* *
|
||||||
|
* IfcOpenShell is distributed in the hope that it will be useful, *
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||||
|
* Lesser GNU General Public License for more details. *
|
||||||
|
* *
|
||||||
|
* You should have received a copy of the Lesser GNU General Public License *
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||||
|
* *
|
||||||
|
********************************************************************************/
|
||||||
|
|
||||||
|
#ifndef MINIMALWINDOW_H
|
||||||
|
#define MINIMALWINDOW_H
|
||||||
|
|
||||||
|
#include <QMainWindow>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QElapsedTimer>
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <deque>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#include "ViewportWindow.h"
|
||||||
|
#include "GeometryStreamer.h"
|
||||||
|
|
||||||
|
class MinimalWindow : public QMainWindow {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit MinimalWindow(QWidget* parent = nullptr);
|
||||||
|
~MinimalWindow();
|
||||||
|
|
||||||
|
void addFiles(const QStringList& paths);
|
||||||
|
void setPendingCamera(const QString& params);
|
||||||
|
void setPendingBenchmark(int frames);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onMeshReady(MeshChunk chunk);
|
||||||
|
void onInstanceReady(InstanceChunk chunk);
|
||||||
|
void onStreamingFinished();
|
||||||
|
void onErrorOccurred(const QString& message);
|
||||||
|
void drainStreamerElements();
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct ModelEntry {
|
||||||
|
uint32_t id = 0;
|
||||||
|
QString file_path;
|
||||||
|
QString display_name;
|
||||||
|
GeometryStreamer* streamer = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
void startNextLoad();
|
||||||
|
void connectStreamer(GeometryStreamer* streamer);
|
||||||
|
void joinSidecarThread();
|
||||||
|
void applySidecarData(uint32_t mid, SidecarData data);
|
||||||
|
void applyPendingBenchmark();
|
||||||
|
|
||||||
|
ViewportWindow* viewport_ = nullptr;
|
||||||
|
QWidget* viewport_container_ = nullptr;
|
||||||
|
QLabel* status_label_ = nullptr;
|
||||||
|
QLabel* stats_label_ = nullptr;
|
||||||
|
|
||||||
|
std::map<uint32_t, ModelEntry> models_;
|
||||||
|
std::deque<uint32_t> load_queue_;
|
||||||
|
uint32_t next_model_id_ = 1;
|
||||||
|
uint32_t next_object_id_ = 1;
|
||||||
|
uint32_t loading_model_id_ = 0;
|
||||||
|
std::thread sidecar_read_thread_;
|
||||||
|
|
||||||
|
QTimer element_drain_timer_;
|
||||||
|
QElapsedTimer load_timer_;
|
||||||
|
|
||||||
|
QString pending_camera_;
|
||||||
|
int pending_benchmark_ = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // MINIMALWINDOW_H
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
/********************************************************************************
|
||||||
|
* *
|
||||||
|
* This file is part of IfcOpenShell. *
|
||||||
|
* *
|
||||||
|
* IfcOpenShell is free software: you can redistribute it and/or modify *
|
||||||
|
* it under the terms of the Lesser GNU General Public License as published by *
|
||||||
|
* the Free Software Foundation, either version 3.0 of the License, or *
|
||||||
|
* (at your option) any later version. *
|
||||||
|
* *
|
||||||
|
* IfcOpenShell is distributed in the hope that it will be useful, *
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||||
|
* Lesser GNU General Public License for more details. *
|
||||||
|
* *
|
||||||
|
* You should have received a copy of the Lesser GNU General Public License *
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||||
|
* *
|
||||||
|
********************************************************************************/
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QSurfaceFormat>
|
||||||
|
#include <QCommandLineParser>
|
||||||
|
|
||||||
|
#include "MinimalWindow.h"
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
QApplication app(argc, argv);
|
||||||
|
app.setApplicationName("IfcViewerMinimal");
|
||||||
|
app.setOrganizationName("IfcOpenShell");
|
||||||
|
|
||||||
|
QSurfaceFormat fmt;
|
||||||
|
fmt.setVersion(4, 5);
|
||||||
|
fmt.setProfile(QSurfaceFormat::CoreProfile);
|
||||||
|
fmt.setDepthBufferSize(24);
|
||||||
|
fmt.setSwapBehavior(QSurfaceFormat::DoubleBuffer);
|
||||||
|
fmt.setSamples(4);
|
||||||
|
QSurfaceFormat::setDefaultFormat(fmt);
|
||||||
|
|
||||||
|
QCommandLineParser parser;
|
||||||
|
parser.setApplicationDescription("IfcOpenShell minimal IFC viewer — benchmarking and debugging");
|
||||||
|
parser.addHelpOption();
|
||||||
|
parser.addPositionalArgument("files", "IFC file(s) to open", "[files...]");
|
||||||
|
parser.addOption({{"c", "camera"},
|
||||||
|
"Set camera: tx,ty,tz,dist,yaw,pitch", "params"});
|
||||||
|
parser.addOption({{"b", "benchmark"},
|
||||||
|
"Run N frames then print stats and exit", "frames"});
|
||||||
|
parser.process(app);
|
||||||
|
|
||||||
|
MinimalWindow window;
|
||||||
|
window.show();
|
||||||
|
|
||||||
|
auto args = parser.positionalArguments();
|
||||||
|
if (!args.isEmpty()) {
|
||||||
|
window.addFiles(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parser.isSet("camera")) {
|
||||||
|
window.setPendingCamera(parser.value("camera"));
|
||||||
|
}
|
||||||
|
if (parser.isSet("benchmark")) {
|
||||||
|
window.setPendingBenchmark(parser.value("benchmark").toInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
return app.exec();
|
||||||
|
}
|
||||||
@@ -20,9 +20,9 @@
|
|||||||
message("Running CMakeLists.txt in /src/ifcviewer")
|
message("Running CMakeLists.txt in /src/ifcviewer")
|
||||||
|
|
||||||
set(QT_VERSION 6 CACHE STRING "Qt version")
|
set(QT_VERSION 6 CACHE STRING "Qt version")
|
||||||
# IfcViewer always needs OpenGL in addition to Core/Gui/Widgets. We don't use
|
# IfcViewerLib always needs OpenGL in addition to Core/Gui/Widgets. We don't
|
||||||
# the CACHE'd QT_COMPONENTS here because it may have been set by another target
|
# use the CACHE'd QT_COMPONENTS here because it may have been set by another
|
||||||
# (e.g. qtviewer) without the OpenGL component.
|
# target (e.g. qtviewer) without the OpenGL component.
|
||||||
find_package(Qt${QT_VERSION} COMPONENTS Core Gui Widgets OpenGL REQUIRED PATHS ${QT_DIR})
|
find_package(Qt${QT_VERSION} COMPONENTS Core Gui Widgets OpenGL REQUIRED PATHS ${QT_DIR})
|
||||||
|
|
||||||
find_package(OpenGL REQUIRED)
|
find_package(OpenGL REQUIRED)
|
||||||
@@ -32,17 +32,21 @@ file(GLOB IFCVIEWER_CPP_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
|
|||||||
file(GLOB IFCVIEWER_H_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.h)
|
file(GLOB IFCVIEWER_H_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.h)
|
||||||
set(IFCVIEWER_FILES ${IFCVIEWER_CPP_FILES} ${IFCVIEWER_H_FILES})
|
set(IFCVIEWER_FILES ${IFCVIEWER_CPP_FILES} ${IFCVIEWER_H_FILES})
|
||||||
|
|
||||||
add_executable(IfcViewer ${IFCVIEWER_FILES})
|
add_library(IfcViewer ${IFCVIEWER_FILES})
|
||||||
|
|
||||||
set_target_properties(IfcViewer PROPERTIES
|
set_target_properties(IfcViewer PROPERTIES
|
||||||
AUTOMOC ON
|
AUTOMOC ON
|
||||||
WIN32_EXECUTABLE ON
|
VERSION "${PROJECT_VERSION}"
|
||||||
MACOSX_BUNDLE ON
|
SOVERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Consumers include headers as `#include "ViewportWindow.h"`, so expose this
|
||||||
|
# directory on the public interface.
|
||||||
|
target_include_directories(IfcViewer PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
|
||||||
add_dependencies(IfcViewer ${kernel_libraries} ${mapping_libraries})
|
add_dependencies(IfcViewer ${kernel_libraries} ${mapping_libraries})
|
||||||
|
|
||||||
target_link_libraries(IfcViewer PRIVATE
|
target_link_libraries(IfcViewer PUBLIC
|
||||||
IfcGeom
|
IfcGeom
|
||||||
IfcParse
|
IfcParse
|
||||||
${OpenCASCADE_LIBRARIES}
|
${OpenCASCADE_LIBRARIES}
|
||||||
@@ -58,7 +62,11 @@ target_link_libraries(IfcViewer PRIVATE
|
|||||||
|
|
||||||
if(UNIX AND NOT APPLE)
|
if(UNIX AND NOT APPLE)
|
||||||
find_package(Threads REQUIRED)
|
find_package(Threads REQUIRED)
|
||||||
target_link_libraries(IfcViewer PRIVATE Threads::Threads)
|
target_link_libraries(IfcViewer PUBLIC Threads::Threads)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
install(TARGETS IfcViewer EXPORT ${IFCOPENSHELL_EXPORT_TARGETS})
|
install(TARGETS IfcViewer EXPORT ${IFCOPENSHELL_EXPORT_TARGETS})
|
||||||
|
|
||||||
|
install(FILES ${IFCVIEWER_H_FILES}
|
||||||
|
DESTINATION ${INCLUDEDIR}/ifcviewer
|
||||||
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user