Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -564,13 +564,11 @@ find_file(qweb_resources_200 NAMES qtwebengine_resources_200p.pak PATHS "${QT_IN
list(APPEND QT_WEB_LIBS Qt6::WebEngineCore)
list(APPEND QT_WEB_LIBS Qt6::WebEngineWidgets)
list(APPEND QT_WEB_LIBS Qt6::Charts)
list(APPEND QT_WEB_LIBS Qt6::WebChannel)
set_target_properties(${QT_WEB_LIBS} PROPERTIES INTERFACE_LINK_LIBRARIES "")

if(NOT APPLE)

find_package(Qt6WebChannel ${QT_VERSION} REQUIRED PATHS ${QT_INSTALL_DIR} NO_DEFAULT_PATH)
list(APPEND QT_WEB_LIBS Qt6::WebChannel)

find_package(Qt6Quick ${QT_VERSION} REQUIRED PATHS ${QT_INSTALL_DIR} NO_DEFAULT_PATH)
list(APPEND QT_WEB_LIBS Qt6::Quick)

Expand Down
166 changes: 165 additions & 1 deletion src/openstudio_lib/GeometryPreviewView.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,149 @@
#include "../model_editor/Application.hpp"

#include <openstudio/model/Model_Impl.hpp>
#include <openstudio/model/PlanarSurface.hpp>
#include <openstudio/model/PlanarSurface_Impl.hpp>
#include <openstudio/model/BuildingStory.hpp>
#include <openstudio/model/BuildingStory_Impl.hpp>
#include <openstudio/model/ConstructionBase.hpp>
#include <openstudio/model/ConstructionBase_Impl.hpp>
#include <openstudio/model/Space.hpp>
#include <openstudio/model/Space_Impl.hpp>
#include <openstudio/model/SpaceType.hpp>
#include <openstudio/model/SpaceType_Impl.hpp>
#include <openstudio/model/SubSurface.hpp>
#include <openstudio/model/Surface.hpp>
#include <openstudio/model/ThermalZone.hpp>
#include <openstudio/model/ThermalZone_Impl.hpp>
#include <openstudio/model/Surface_Impl.hpp>
#include <openstudio/model/ThreeJSForwardTranslator.hpp>

#include <algorithm>

#include <openstudio/utilities/core/Assert.hpp>
#include <openstudio/utilities/idd/IddEnums.hxx>

#include <QJsonArray>
#include <QJsonDocument>
#include <QStackedWidget>
#include <QVBoxLayout>
#include <QLabel>
#include <QPushButton>
#include <QFile>
#include <QWebEngineScriptCollection>
#include <QWebChannel>
#include <QtConcurrent>
#include <QCheckBox>

using namespace std::placeholders;

namespace openstudio {

GeometryBridge::GeometryBridge(model::Model& model, QObject* parent) : QObject(parent), m_model(model) {}

void GeometryBridge::reverseSurfaceVertices(const QString& surfaceName) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Set surface type could be useful as well.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that one definitely makes sense.

if (auto surface = m_model.getModelObjectByName<model::PlanarSurface>(surfaceName.toStdString())) {
auto vertices = surface->vertices();
std::reverse(vertices.begin(), vertices.end());
surface->setVertices(vertices);
emit modelChanged();
}
}

void GeometryBridge::triangulateSurface(const QString& surfaceName) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious, do you have a use case for this?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For sun-related things like shading, having a non convex surfaces (eg a L, H shaped roof) is a problem, so I could see myself wanting to do this. I don't know, I was just in the flow and thought about adding it so I just did.

auto planarSurface_ = m_model.getModelObjectByName<model::PlanarSurface>(surfaceName.toStdString());
if (!planarSurface_) {
return;
}

// Skip the case where the Surface has SubSurfaces, since we'd have to deal with triangulation of the subSurfaces(s) then intersection etc
if (auto surface_ = planarSurface_->optionalCast<model::Surface>()) {
if (!surface_->subSurfaces().empty()) {
return;
}
}
const auto triangles = planarSurface_->triangulation();
if (triangles.size() < 2) {
return;
}

const std::string origName = planarSurface_->nameString();
for (int i = 0; const auto& points : triangles) {
++i;
auto clone = planarSurface_->clone().cast<model::PlanarSurface>();
clone.setVertices(points);
clone.setName(origName + " (triangulation " + std::to_string(i) + ")");
}
planarSurface_->remove();

emit modelChanged();
}

void GeometryBridge::setSunExposure(const QString& surfaceName, const QString& value) {
if (auto surface = m_model.getModelObjectByName<model::Surface>(surfaceName.toStdString())) {
surface->setSunExposure(value.toStdString());
emit modelChanged();
}
}

void GeometryBridge::setWindExposure(const QString& surfaceName, const QString& value) {
if (auto surface = m_model.getModelObjectByName<model::Surface>(surfaceName.toStdString())) {
surface->setWindExposure(value.toStdString());
emit modelChanged();
}
}

void GeometryBridge::setOutsideBoundaryCondition(const QString& surfaceName, const QString& value) {
Copy link
Copy Markdown
Collaborator

@macumber macumber Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting the adjacent surface could be cool, although I guess you can do that from the surface tab too.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that's a valid point.

You can also set the outside boundary condition and all from the surface tab too, it's just more tedious especially if you have auto-named surfaces like "Surface 114", which makes it quite hard to remember what you're touching.

if (auto surface = m_model.getModelObjectByName<model::Surface>(surfaceName.toStdString())) {
surface->setOutsideBoundaryCondition(value.toStdString());
emit modelChanged();
}
}

void GeometryBridge::setConstruction(const QString& surfaceName, const QString& constructionName) {
if (auto surface = m_model.getModelObjectByName<model::PlanarSurface>(surfaceName.toStdString())) {
if (constructionName.isEmpty()) {
surface->resetConstruction();
} else if (auto construction = m_model.getModelObjectByName<model::ConstructionBase>(constructionName.toStdString())) {
surface->setConstruction(*construction);
}
emit modelChanged();
}
}

void GeometryBridge::setThermalZone(const QString& spaceName, const QString& thermalZoneName) {
if (auto space = m_model.getModelObjectByName<model::Space>(spaceName.toStdString())) {
if (thermalZoneName.isEmpty()) {
space->resetThermalZone();
} else if (auto thermalZone = m_model.getModelObjectByName<model::ThermalZone>(thermalZoneName.toStdString())) {
space->setThermalZone(*thermalZone);
}
emit modelChanged();
}
}

void GeometryBridge::setBuildingStory(const QString& spaceName, const QString& buildingStoryName) {
if (auto space = m_model.getModelObjectByName<model::Space>(spaceName.toStdString())) {
if (buildingStoryName.isEmpty()) {
space->resetBuildingStory();
} else if (auto story = m_model.getModelObjectByName<model::BuildingStory>(buildingStoryName.toStdString())) {
space->setBuildingStory(*story);
}
emit modelChanged();
}
}

void GeometryBridge::setSpaceType(const QString& spaceName, const QString& spaceTypeName) {
if (auto space = m_model.getModelObjectByName<model::Space>(spaceName.toStdString())) {
if (spaceTypeName.isEmpty()) {
space->resetSpaceType();
} else if (auto spaceType = m_model.getModelObjectByName<model::SpaceType>(spaceTypeName.toStdString())) {
space->setSpaceType(*spaceType);
}
emit modelChanged();
}
}

GeometryPreviewView::GeometryPreviewView(bool isIP, const openstudio::model::Model& model, QWidget* parent) : QWidget(parent) {
// TODO: DLM implement units switching
//connect(this, &GeometryPreviewView::toggleUnitsClicked, modelObjectInspectorView(), &ModelObjectInspectorView::toggleUnitsClicked);
Expand Down Expand Up @@ -77,6 +202,16 @@ PreviewWebView::PreviewWebView(bool isIP, const model::Model& model, QWidget* t_
m_page = new OSWebEnginePage(m_view);
m_view->setPage(m_page); // note, view does not take ownership of page

auto* channel = new QWebChannel(m_page);
m_bridge = new GeometryBridge(m_model, this);
channel->registerObject(QStringLiteral("bridge"), m_bridge);
m_page->setWebChannel(channel);
connect(m_bridge, &GeometryBridge::modelChanged, this, [this]() {
m_json = QString();
// Save current view settings and camera state to sessionStorage before the page reload
m_view->page()->runJavaScript("saveViewStateToSessionStorage();", [this](const QVariant& /*v*/) { refreshClicked(); });
});

auto* mainWindow = OSAppBase::instance()->currentDocument()->mainWindow();
const bool verboseOutput = mainWindow->geometryDiagnostics();
m_geometryDiagnosticsBox = new QCheckBox();
Expand Down Expand Up @@ -172,8 +307,37 @@ void PreviewWebView::onLoadFinished(bool ok) {
// disable doc
m_document->disable();

// build lists of available names for the JS context menu
QJsonArray spaceTypeNamesArray;
for (const auto& st : m_model.getConcreteModelObjects<model::SpaceType>()) {
spaceTypeNamesArray.append(QString::fromStdString(st.nameString()));
}
const QString spaceTypeNamesJson = QJsonDocument(spaceTypeNamesArray).toJson(QJsonDocument::Compact);

QJsonArray constructionNamesArray;
for (const auto& c : m_model.getModelObjects<model::ConstructionBase>()) {
constructionNamesArray.append(QString::fromStdString(c.nameString()));
}
const QString constructionNamesJson = QJsonDocument(constructionNamesArray).toJson(QJsonDocument::Compact);

QJsonArray thermalZoneNamesArray;
for (const auto& tz : m_model.getConcreteModelObjects<model::ThermalZone>()) {
thermalZoneNamesArray.append(QString::fromStdString(tz.nameString()));
}
const QString thermalZoneNamesJson = QJsonDocument(thermalZoneNamesArray).toJson(QJsonDocument::Compact);

QJsonArray buildingStoryNamesArray;
for (const auto& bs : m_model.getConcreteModelObjects<model::BuildingStory>()) {
buildingStoryNamesArray.append(QString::fromStdString(bs.nameString()));
}
const QString buildingStoryNamesJson = QJsonDocument(buildingStoryNamesArray).toJson(QJsonDocument::Compact);

// call init and animate
const QString javascript = QString("runFromJSON(%1, %2);").arg(m_json, m_geometryDiagnosticsBox->isChecked() ? "true" : "false");
const QString javascript = QString("var availableSpaceTypeNames = %1; var availableConstructionNames = %2;"
" var availableThermalZoneNames = %3; var availableBuildingStoryNames = %4;"
" runFromJSON(%5, %6);")
.arg(spaceTypeNamesJson, constructionNamesJson, thermalZoneNamesJson, buildingStoryNamesJson, m_json,
m_geometryDiagnosticsBox->isChecked() ? "true" : "false");
m_view->page()->runJavaScript(javascript, [this](const QVariant& v) { onJavaScriptFinished(v); });

//javascript = QString("os_data.metadata.version");
Expand Down
27 changes: 27 additions & 0 deletions src/openstudio_lib/GeometryPreviewView.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

#include "../shared_gui_components/ProgressBarWithError.hpp"

#include <QObject>
#include <QWidget>
#include <QWebEngineView>

Expand All @@ -25,6 +26,31 @@ namespace openstudio {

class OSDocument;

class GeometryBridge : public QObject
{
Q_OBJECT

public:
explicit GeometryBridge(model::Model& model, QObject* parent = nullptr);

public slots:
Q_INVOKABLE void reverseSurfaceVertices(const QString& surfaceName);
Q_INVOKABLE void triangulateSurface(const QString& surfaceName);
Q_INVOKABLE void setSunExposure(const QString& surfaceName, const QString& value);
Q_INVOKABLE void setWindExposure(const QString& surfaceName, const QString& value);
Q_INVOKABLE void setOutsideBoundaryCondition(const QString& surfaceName, const QString& value);
Q_INVOKABLE void setSpaceType(const QString& spaceName, const QString& spaceTypeName);
Q_INVOKABLE void setConstruction(const QString& surfaceName, const QString& constructionName);
Q_INVOKABLE void setThermalZone(const QString& spaceName, const QString& thermalZoneName);
Q_INVOKABLE void setBuildingStory(const QString& spaceName, const QString& buildingStoryName);

signals:
void modelChanged();

private:
model::Model& m_model;
};

class GeometryPreviewView : public QWidget
{
Q_OBJECT
Expand Down Expand Up @@ -75,6 +101,7 @@ class PreviewWebView : public QWidget
QWebEngineView* m_view;
OSWebEnginePage* m_page;
std::shared_ptr<OSDocument> m_document;
GeometryBridge* m_bridge;

QString m_json;
};
Expand Down
Loading
Loading