Skip to content

Commit 37ffec2

Browse files
authored
Merge pull request #791 from openstudiocoalition/dev/anton/153
Fix #153 - Allow users to specifically import 99%,99.6%, 0.4%,1% and 2% annual design days
2 parents b49c5de + 219b9bd commit 37ffec2

22 files changed

+1366
-542
lines changed

.github/workflows/app_build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -502,7 +502,7 @@ jobs:
502502
set -x
503503
if [ "$RUNNER_OS" == "macOS" ]; then
504504
# Avoid "builtin __has_nothrow_assign is deprecated; use __is_nothrow_assignable instead" in boost/1.78 with recent clang
505-
conan install . --output-folder=./build --build=missing -c tools.cmake.cmaketoolchain:generator=Ninja -s compiler.cppstd=20 -s build_type=Release -c tools.build:cxxflags="['-D_LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION']"
505+
conan install . --output-folder=./build --build=missing -c tools.cmake.cmaketoolchain:generator=Ninja -s compiler.cppstd=20 -s build_type=Release -c tools.build:cxxflags="['-D_LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION', '-Wno-enum-constexpr-conversion']"
506506
else
507507
conan install . --output-folder=./build --build=missing -c tools.cmake.cmaketoolchain:generator=Ninja -s compiler.cppstd=20 -s build_type=Release
508508
fi

src/openstudio_lib/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -792,6 +792,7 @@ set(${target_name}_test_src
792792
test/FacilityShading_GTest.cpp
793793
test/Geometry_GTest.cpp
794794
test/IconLibrary_GTest.cpp
795+
test/LocationTab_GTest.cpp
795796
test/ObjectSelector_GTest.cpp
796797
test/OSDropZone_GTest.cpp
797798
test/OSLineEdit_GTest.cpp

src/openstudio_lib/LocationTabView.cpp

Lines changed: 224 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -50,18 +50,26 @@
5050
#include <boost/smart_ptr.hpp>
5151

5252
#include <QBoxLayout>
53+
#include <QButtonGroup>
5354
#include <QComboBox>
5455
#include <QDateTime>
56+
#include <QCheckBox>
57+
#include <QDialogButtonBox>
5558
#include <QDir>
5659
#include <QFile>
5760
#include <QFileDialog>
61+
#include <QGridLayout>
5862
#include <QLabel>
5963
#include <QLineEdit>
6064
#include <QMessageBox>
6165
#include <QPushButton>
66+
#include <QRegExp>
6267
#include <QScrollArea>
6368
#include <QSettings>
6469
#include <QSizePolicy>
70+
#include <QCoreApplication>
71+
#include <QObject>
72+
#include <QPushButton>
6573

6674
static constexpr auto NAME("Name: ");
6775
static constexpr auto LATITUDE("Latitude: ");
@@ -74,6 +82,42 @@ static constexpr auto CHANGEWEATHERFILE("Change Weather File");
7482

7583
namespace openstudio {
7684

85+
SortableDesignDay::SortableDesignDay(const openstudio::model::DesignDay& designDay) : m_designDay(designDay) {
86+
QRegExp regex("^.*Ann.*([\\d\\.]+)[\\s]?%.*$", Qt::CaseInsensitive);
87+
if (regex.exactMatch(toQString(designDay.nameString())) && regex.captureCount() == 1) {
88+
m_permil = qstringToPermil(regex.capturedTexts()[1]);
89+
if (m_permil > 500) {
90+
m_type = "Heating";
91+
} else {
92+
m_type = "Cooling";
93+
}
94+
}
95+
}
96+
97+
int SortableDesignDay::qstringToPermil(const QString& str) {
98+
return (int)(str.toDouble() * 10.0);
99+
}
100+
101+
QString SortableDesignDay::permilToQString(int permil) {
102+
return QString::number((double)permil / 10.0, 'f', 1);
103+
}
104+
105+
QString SortableDesignDay::key(const QString& type, int sortablePermil) {
106+
return type + permilToQString(sortablePermil);
107+
}
108+
109+
QString SortableDesignDay::type() const {
110+
return m_type;
111+
}
112+
113+
int SortableDesignDay::permil() const {
114+
return m_permil;
115+
}
116+
117+
int SortableDesignDay::sortablePermil() const {
118+
return ((m_permil < 500) ? m_permil : 1000 - m_permil);
119+
}
120+
77121
LocationTabView::LocationTabView(const model::Model& model, const QString& modelTempDir, QWidget* parent)
78122
: MainTabView(tr("Site"), MainTabView::SUB_TAB, parent) {}
79123

@@ -625,6 +669,170 @@ void LocationView::onWeatherFileBtnClicked() {
625669
}
626670
}
627671

672+
/**
673+
* @brief Displays a dialog for selecting design days from a given list.
674+
*
675+
* This function creates and displays a modal dialog that allows the user to select specific design days
676+
* from a provided list of all available design days which are
677+
* heatingPercentages "99.6%", "99%"
678+
* and coolingPercentages "2%", "1%", "0.4%"
679+
*
680+
* . The dialog includes options for selecting heating
681+
* and cooling design days based on predefined percentages. The user can choose to import all design days,
682+
* select specific ones, or cancel the operation.
683+
*
684+
* @param allDesignDays A vector containing all available design days.
685+
* @return A vector of selected design days if the user confirms the selection, or an empty vector if the user cancels.
686+
*/
687+
std::vector<model::DesignDay> LocationView::showDesignDaySelectionDialog(const std::vector<openstudio::model::DesignDay>& allDesignDays) {
688+
689+
std::vector<model::DesignDay> result;
690+
691+
// parse out the design day names into SortableDesignDays and figure out the column and row names
692+
std::vector<SortableDesignDay> sortableDesignDays;
693+
std::set<QString> designDayTypes; // rows
694+
std::set<int> sortedDesignDayPermils; // columns
695+
696+
// key is designDayType + sortedDesignDayPermil, value is names of dds
697+
// each cell in the table has a unique key
698+
std::map<QString, std::vector<openstudio::model::DesignDay>> designDayMap;
699+
size_t numUnknownType = 0;
700+
for (const auto& dd : allDesignDays) {
701+
SortableDesignDay sdd(dd);
702+
703+
// skip Design Days with unknown type
704+
if (sdd.type().isEmpty()) {
705+
++numUnknownType;
706+
continue;
707+
}
708+
709+
sortableDesignDays.push_back(sdd);
710+
designDayTypes.insert(sdd.type());
711+
sortedDesignDayPermils.insert(sdd.sortablePermil());
712+
QString key = SortableDesignDay::key(sdd.type(), sdd.sortablePermil());
713+
if (!designDayMap.contains(key)) {
714+
designDayMap[key] = std::vector<openstudio::model::DesignDay>();
715+
}
716+
designDayMap[key].push_back(dd);
717+
}
718+
719+
// main dialog
720+
QDialog dialog(this, Qt::Dialog | Qt::WindowTitleHint | Qt::WindowCloseButtonHint);
721+
dialog.setWindowTitle(QCoreApplication::translate("LocationView", "Import Design Days"));
722+
dialog.setMinimumWidth(450);
723+
dialog.setModal(true);
724+
dialog.setStyleSheet("background: #E6E6E6;");
725+
726+
auto* layout = new QVBoxLayout(&dialog);
727+
728+
// grid view for the design day types and permils to import
729+
auto* gridLayout = new QGridLayout();
730+
731+
// first row is for headers
732+
int row = 0;
733+
734+
auto msg = tr("There are <span style=\"font-weight:bold;\">%1</span> Design Days available for import").arg(QString::number(allDesignDays.size()));
735+
if (numUnknownType > 0) {
736+
msg += tr(", %1 of which are unknown type").arg(QString::number(numUnknownType));
737+
}
738+
739+
auto* numInfo = new QLabel(msg);
740+
gridLayout->addWidget(numInfo, row, 0, 1, -1, Qt::AlignCenter);
741+
742+
++row;
743+
int column = 1;
744+
for (const auto& sddp : sortedDesignDayPermils) {
745+
auto* header = new QLabel(SortableDesignDay::permilToQString(sddp) + "%");
746+
header->setStyleSheet("font-weight: bold;");
747+
gridLayout->addWidget(header, row, column++, Qt::AlignCenter);
748+
}
749+
750+
// one row for each design day type
751+
++row;
752+
QVector<QRadioButton*> allRadioButtons;
753+
for (const auto& ddt : designDayTypes) {
754+
column = 0;
755+
bool checkedFirst = false;
756+
auto* rowHeader = new QLabel();
757+
if (ddt == "Heating") {
758+
rowHeader->setText(tr("Heating"));
759+
rowHeader->setStyleSheet("font-weight: bold; color: #EF1C21;");
760+
} else if (ddt == "Cooling") {
761+
rowHeader->setText(tr("Cooling"));
762+
rowHeader->setStyleSheet("font-weight: bold; color: #0071BD;");
763+
} else {
764+
rowHeader->setText(ddt);
765+
}
766+
gridLayout->addWidget(rowHeader, row, column++, Qt::AlignCenter);
767+
768+
auto* buttonGroup = new QButtonGroup(gridLayout);
769+
for (const auto& sddp : sortedDesignDayPermils) {
770+
QString key = SortableDesignDay::key(ddt, sddp);
771+
auto* radioButton = new QRadioButton();
772+
allRadioButtons.append(radioButton);
773+
if (!designDayMap.contains(key)) {
774+
radioButton->setEnabled(false);
775+
radioButton->setCheckable(false);
776+
radioButton->setToolTip(QString::number(0) + " " + tr("Design Days"));
777+
radioButton->setProperty("designDayKey", "");
778+
} else {
779+
radioButton->setEnabled(true);
780+
radioButton->setCheckable(true);
781+
if (!checkedFirst) {
782+
radioButton->setChecked(true);
783+
checkedFirst = true;
784+
}
785+
radioButton->setToolTip(QString::number(designDayMap[key].size()) + " " + tr("Design Days"));
786+
radioButton->setProperty("designDayKey", key);
787+
}
788+
buttonGroup->addButton(radioButton);
789+
gridLayout->addWidget(radioButton, row, column++, Qt::AlignCenter);
790+
}
791+
++row;
792+
}
793+
layout->addLayout(gridLayout);
794+
int columnCount = gridLayout->columnCount();
795+
int rowCount = gridLayout->rowCount();
796+
797+
// ok button only imports the checked design days
798+
auto* okButton = new QPushButton(tr("OK"), &dialog);
799+
connect(okButton, &QPushButton::clicked, [&dialog, &result, &allRadioButtons, &designDayMap]() {
800+
for (const auto& rb : allRadioButtons) {
801+
if (rb->isChecked()) {
802+
QString key = rb->property("designDayKey").toString();
803+
if (!key.isEmpty() && designDayMap.contains(key)) {
804+
for (const auto& dd : designDayMap[key]) {
805+
result.push_back(dd);
806+
}
807+
}
808+
}
809+
}
810+
dialog.accept();
811+
});
812+
813+
// cancel button imports nothing
814+
auto* cancelButton = new QPushButton(tr("Cancel"), &dialog);
815+
connect(cancelButton, &QPushButton::clicked, &dialog, &QDialog::reject);
816+
817+
// import all imports everything
818+
auto* importAllButton = new QPushButton(tr("Import all"), &dialog);
819+
connect(importAllButton, &QPushButton::clicked, [&dialog, &result, &allDesignDays]() {
820+
result = allDesignDays;
821+
dialog.accept();
822+
});
823+
824+
// add all the buttons in a button box
825+
auto* buttonBox = new QDialogButtonBox(Qt::Horizontal);
826+
buttonBox->addButton(okButton, QDialogButtonBox::AcceptRole);
827+
buttonBox->addButton(cancelButton, QDialogButtonBox::RejectRole);
828+
buttonBox->addButton(importAllButton, QDialogButtonBox::YesRole);
829+
layout->addWidget(buttonBox);
830+
831+
// Execute the dialog and wait for user interaction
832+
dialog.exec();
833+
return result;
834+
}
835+
628836
void LocationView::onDesignDayBtnClicked() {
629837
QString fileTypes("Files (*.ddy)");
630838

@@ -644,87 +852,44 @@ void LocationView::onDesignDayBtnClicked() {
644852
if (ddyIdfFile) {
645853

646854
openstudio::Workspace ddyWorkspace(StrictnessLevel::None, IddFileType::EnergyPlus);
855+
647856
for (const IdfObject& idfObject : ddyIdfFile->objects()) {
648857
IddObjectType iddObjectType = idfObject.iddObject().type();
649858
if ((iddObjectType == IddObjectType::SizingPeriod_DesignDay) || (iddObjectType == IddObjectType::SizingPeriod_WeatherFileDays)
650859
|| (iddObjectType == IddObjectType::SizingPeriod_WeatherFileConditionType)) {
651-
652860
ddyWorkspace.addObject(idfObject);
653861
}
654862
}
655863

656-
energyplus::ReverseTranslator reverseTranslator;
864+
openstudio::energyplus::ReverseTranslator reverseTranslator;
657865
model::Model ddyModel = reverseTranslator.translateWorkspace(ddyWorkspace);
658866

659867
// Use a heuristic based on the ddy files provided by EnergyPlus
660868
// Filter out the days that are not helpful.
661869
if (!ddyModel.objects().empty()) {
662-
// Containers to hold 99%, 99.6%, 2%, 1%, and 0.4% design points
663-
std::vector<model::DesignDay> days99;
664-
std::vector<model::DesignDay> days99_6;
665-
std::vector<model::DesignDay> days2;
666-
std::vector<model::DesignDay> days1;
667-
std::vector<model::DesignDay> days0_4;
668-
669-
bool unknownDay = false;
670-
671-
for (const model::DesignDay& designDay : ddyModel.getConcreteModelObjects<model::DesignDay>()) {
672-
boost::optional<std::string> name;
673-
name = designDay.name();
674-
675-
if (name) {
676-
QString qname = QString::fromStdString(name.get());
677-
678-
if (qname.contains("99%")) {
679-
days99.push_back(designDay);
680-
} else if (qname.contains("99.6%")) {
681-
days99_6.push_back(designDay);
682-
} else if (qname.contains("2%")) {
683-
days2.push_back(designDay);
684-
} else if (qname.contains("1%")) {
685-
days1.push_back(designDay);
686-
} else if (qname.contains(".4%")) {
687-
days0_4.push_back(designDay);
688-
} else {
689-
unknownDay = true;
690-
}
691-
}
692-
}
693-
694-
// Pick only the most stringent design points
695-
if (!unknownDay) {
696-
if (!days99_6.empty()) {
697-
for (model::DesignDay designDay : days99) {
698-
designDay.remove();
699-
}
700-
}
701-
702-
if (!days0_4.empty()) {
703-
for (model::DesignDay designDay : days1) {
704-
designDay.remove();
705-
}
706-
for (model::DesignDay designDay : days2) {
707-
designDay.remove();
708-
}
709-
} else if (!days1.empty()) {
710-
for (model::DesignDay designDay : days2) {
711-
designDay.remove();
712-
}
713-
}
714-
}
715870

716871
// Evan note: do not remove existing design days
717872
//for (model::SizingPeriod sizingPeriod : m_model.getModelObjects<model::SizingPeriod>()){
718873
// sizingPeriod.remove();
719874
//}
720875

876+
//m_model.insertObjects(ddyModel.objects());
877+
878+
std::vector<openstudio::model::DesignDay> designDaysToInsert =
879+
showDesignDaySelectionDialog(ddyModel.getConcreteModelObjects<model::DesignDay>());
880+
881+
// Remove design days from ddyModel that are not in designDaysToInsert
882+
for (auto& designDay : ddyModel.getConcreteModelObjects<model::DesignDay>()) {
883+
if (std::find(designDaysToInsert.begin(), designDaysToInsert.end(), designDay) == designDaysToInsert.end()) {
884+
designDay.remove();
885+
}
886+
}
887+
721888
m_model.insertObjects(ddyModel.objects());
722889

723890
m_lastDdyPathOpened = QFileInfo(fileName).absoluteFilePath();
724891
}
725892
}
726-
727-
QTimer::singleShot(0, this, &LocationView::checkNumDesignDays);
728893
}
729894
}
730895

@@ -785,7 +950,7 @@ void LocationView::setDstStartDayOfWeekAndMonth(int newWeek, int newDay, int new
785950
void LocationView::setDstStartDate(const QDate& newdate) {
786951
auto dst = m_model.getUniqueModelObject<model::RunPeriodControlDaylightSavingTime>();
787952

788-
dst.setStartDate(monthOfYear(newdate.month()), newdate.day());
953+
dst.setStartDate(MonthOfYear(newdate.month()), newdate.day());
789954
}
790955

791956
void LocationView::setDstEndDayOfWeekAndMonth(int newWeek, int newDay, int newMonth) {
@@ -797,7 +962,7 @@ void LocationView::setDstEndDayOfWeekAndMonth(int newWeek, int newDay, int newMo
797962
void LocationView::setDstEndDate(const QDate& newdate) {
798963
auto dst = m_model.getUniqueModelObject<model::RunPeriodControlDaylightSavingTime>();
799964

800-
dst.setEndDate(monthOfYear(newdate.month()), newdate.day());
965+
dst.setEndDate(MonthOfYear(newdate.month()), newdate.day());
801966
}
802967

803968
void LocationView::onSelectItem() {

0 commit comments

Comments
 (0)