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
6674static constexpr auto NAME (" Name: " );
6775static constexpr auto LATITUDE (" Latitude: " );
@@ -74,6 +82,42 @@ static constexpr auto CHANGEWEATHERFILE("Change Weather File");
7482
7583namespace 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+
77121LocationTabView::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+
628836void 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
785950void 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
791956void LocationView::setDstEndDayOfWeekAndMonth (int newWeek, int newDay, int newMonth) {
@@ -797,7 +962,7 @@ void LocationView::setDstEndDayOfWeekAndMonth(int newWeek, int newDay, int newMo
797962void 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
803968void LocationView::onSelectItem () {
0 commit comments