From ae938d425bd196e40c18ab1e09bb399b8c293304 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Tue, 21 Apr 2026 14:06:44 +1000 Subject: [PATCH] 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 --- cmake/CMakeLists.txt | 2 + src/ifcviewer-full/CMakeLists.txt | 36 +++ .../MainWindow.cpp | 0 .../MainWindow.h | 0 .../SettingsWindow.cpp | 0 .../SettingsWindow.h | 0 src/{ifcviewer => ifcviewer-full}/main.cpp | 0 src/ifcviewer-minimal/CMakeLists.txt | 36 +++ src/ifcviewer-minimal/MinimalWindow.cpp | 278 ++++++++++++++++++ src/ifcviewer-minimal/MinimalWindow.h | 85 ++++++ src/ifcviewer-minimal/main.cpp | 65 ++++ src/ifcviewer/CMakeLists.txt | 24 +- 12 files changed, 518 insertions(+), 8 deletions(-) create mode 100644 src/ifcviewer-full/CMakeLists.txt rename src/{ifcviewer => ifcviewer-full}/MainWindow.cpp (100%) rename src/{ifcviewer => ifcviewer-full}/MainWindow.h (100%) rename src/{ifcviewer => ifcviewer-full}/SettingsWindow.cpp (100%) rename src/{ifcviewer => ifcviewer-full}/SettingsWindow.h (100%) rename src/{ifcviewer => ifcviewer-full}/main.cpp (100%) create mode 100644 src/ifcviewer-minimal/CMakeLists.txt create mode 100644 src/ifcviewer-minimal/MinimalWindow.cpp create mode 100644 src/ifcviewer-minimal/MinimalWindow.h create mode 100644 src/ifcviewer-minimal/main.cpp diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt index 69667eff07..660d55cf3d 100644 --- a/cmake/CMakeLists.txt +++ b/cmake/CMakeLists.txt @@ -674,6 +674,8 @@ if(BUILD_IFCGEOM) endif(BUILD_IFCGEOM) if(BUILD_IFCVIEWER) add_subdirectory(../src/ifcviewer ifcviewer) + add_subdirectory(../src/ifcviewer-minimal ifcviewer-minimal) + add_subdirectory(../src/ifcviewer-full ifcviewer-full) endif() # Cmake uninstall target diff --git a/src/ifcviewer-full/CMakeLists.txt b/src/ifcviewer-full/CMakeLists.txt new file mode 100644 index 0000000000..f578d96f70 --- /dev/null +++ b/src/ifcviewer-full/CMakeLists.txt @@ -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 . # +# # +################################################################################ + +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}) diff --git a/src/ifcviewer/MainWindow.cpp b/src/ifcviewer-full/MainWindow.cpp similarity index 100% rename from src/ifcviewer/MainWindow.cpp rename to src/ifcviewer-full/MainWindow.cpp diff --git a/src/ifcviewer/MainWindow.h b/src/ifcviewer-full/MainWindow.h similarity index 100% rename from src/ifcviewer/MainWindow.h rename to src/ifcviewer-full/MainWindow.h diff --git a/src/ifcviewer/SettingsWindow.cpp b/src/ifcviewer-full/SettingsWindow.cpp similarity index 100% rename from src/ifcviewer/SettingsWindow.cpp rename to src/ifcviewer-full/SettingsWindow.cpp diff --git a/src/ifcviewer/SettingsWindow.h b/src/ifcviewer-full/SettingsWindow.h similarity index 100% rename from src/ifcviewer/SettingsWindow.h rename to src/ifcviewer-full/SettingsWindow.h diff --git a/src/ifcviewer/main.cpp b/src/ifcviewer-full/main.cpp similarity index 100% rename from src/ifcviewer/main.cpp rename to src/ifcviewer-full/main.cpp diff --git a/src/ifcviewer-minimal/CMakeLists.txt b/src/ifcviewer-minimal/CMakeLists.txt new file mode 100644 index 0000000000..dda8743f13 --- /dev/null +++ b/src/ifcviewer-minimal/CMakeLists.txt @@ -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 . # +# # +################################################################################ + +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}) diff --git a/src/ifcviewer-minimal/MinimalWindow.cpp b/src/ifcviewer-minimal/MinimalWindow.cpp new file mode 100644 index 0000000000..2d3593165c --- /dev/null +++ b/src/ifcviewer-minimal/MinimalWindow.cpp @@ -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 . * + * * + ********************************************************************************/ + +#include "MinimalWindow.h" +#include "AppSettings.h" +#include "SidecarCache.h" + +#include +#include +#include +#include +#include + +#include +#include + +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(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::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; + } +} diff --git a/src/ifcviewer-minimal/MinimalWindow.h b/src/ifcviewer-minimal/MinimalWindow.h new file mode 100644 index 0000000000..12ea533c40 --- /dev/null +++ b/src/ifcviewer-minimal/MinimalWindow.h @@ -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 . * + * * + ********************************************************************************/ + +#ifndef MINIMALWINDOW_H +#define MINIMALWINDOW_H + +#include +#include +#include +#include + +#include +#include +#include + +#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 models_; + std::deque 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 diff --git a/src/ifcviewer-minimal/main.cpp b/src/ifcviewer-minimal/main.cpp new file mode 100644 index 0000000000..75db1b212b --- /dev/null +++ b/src/ifcviewer-minimal/main.cpp @@ -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 . * + * * + ********************************************************************************/ + +#include +#include +#include + +#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(); +} diff --git a/src/ifcviewer/CMakeLists.txt b/src/ifcviewer/CMakeLists.txt index 458c52d9f3..b488cd5050 100644 --- a/src/ifcviewer/CMakeLists.txt +++ b/src/ifcviewer/CMakeLists.txt @@ -20,9 +20,9 @@ message("Running CMakeLists.txt in /src/ifcviewer") set(QT_VERSION 6 CACHE STRING "Qt version") -# IfcViewer always needs OpenGL in addition to Core/Gui/Widgets. We don't use -# the CACHE'd QT_COMPONENTS here because it may have been set by another target -# (e.g. qtviewer) without the OpenGL component. +# IfcViewerLib always needs OpenGL in addition to Core/Gui/Widgets. We don't +# use the CACHE'd QT_COMPONENTS here because it may have been set by another +# target (e.g. qtviewer) without the OpenGL component. find_package(Qt${QT_VERSION} COMPONENTS Core Gui Widgets OpenGL REQUIRED PATHS ${QT_DIR}) 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) set(IFCVIEWER_FILES ${IFCVIEWER_CPP_FILES} ${IFCVIEWER_H_FILES}) -add_executable(IfcViewer ${IFCVIEWER_FILES}) +add_library(IfcViewer ${IFCVIEWER_FILES}) set_target_properties(IfcViewer PROPERTIES AUTOMOC ON - WIN32_EXECUTABLE ON - MACOSX_BUNDLE ON + VERSION "${PROJECT_VERSION}" + 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}) -target_link_libraries(IfcViewer PRIVATE +target_link_libraries(IfcViewer PUBLIC IfcGeom IfcParse ${OpenCASCADE_LIBRARIES} @@ -58,7 +62,11 @@ target_link_libraries(IfcViewer PRIVATE if(UNIX AND NOT APPLE) find_package(Threads REQUIRED) - target_link_libraries(IfcViewer PRIVATE Threads::Threads) + target_link_libraries(IfcViewer PUBLIC Threads::Threads) endif() install(TARGETS IfcViewer EXPORT ${IFCOPENSHELL_EXPORT_TARGETS}) + +install(FILES ${IFCVIEWER_H_FILES} + DESTINATION ${INCLUDEDIR}/ifcviewer +)