diff --git a/resources/model/OpenStudio.idd b/resources/model/OpenStudio.idd index edffc20040..3a6a323995 100644 --- a/resources/model/OpenStudio.idd +++ b/resources/model/OpenStudio.idd @@ -38660,6 +38660,53 @@ OS:EnergyManagementSystem:ConstructionIndexVariable, \group Python Plugin System +OS:PythonPlugin:SearchPaths, + \memo Add directories to the search path for Python plugin modules + \memo The directory containing the EnergyPlus executable file is + \memo automatically added so that the Python interpreter can find the + \memo packaged up pyenergyplus Python package. + \memo By default, the current working directory and input file directory + \memo are also added to the search path. However, this object allows + \memo modifying this behavior. With this object, searching these directories + \memo can be disabled, and users can add supplemental search paths + \memo that point to libraries of plugin scripts. + \unique-object + \min-fields 3 + \extensible:1 + A1, \field Handle + \type handle + \required-field + A2, \field Add Current Working Directory to Search Path + \note Adding the current working directory allows Python to find + \note plugin scripts in the current directory. + \note required-field disabled as it has a default + \type choice + \key Yes + \key No + \required-field + A3, \field Add Input File Directory to Search Path + \note Enabling this will allow Python to find plugin scripts in the + \note same directory as the running input file, even if that is not + \note the current working directory. + \note required-field disabled as it has a default + \type choice + \key Yes + \key No + \required-field + A4, \field Add epin Environment Variable to Search Path + \note The "epin" environment variable is set by some EnergyPlus interfaces + \note in order to let EnergyPlus find external files in special locations. + \note If this is enabled, and that variable is set, the value of the variable + \note will be added to the Python search path. + \type choice + \key Yes + \key No + \required-field + A5; \field Search Path 1 + \type alpha + \begin-extensible + \retaincase + OS:PythonPlugin:Instance, \memo A single plugin to be executed during the simulation, which can contain multiple calling points \memo for the same class instance by overriding multiple calling point methods. diff --git a/src/energyplus/CMakeLists.txt b/src/energyplus/CMakeLists.txt index 48aa9f11c1..2f8488943f 100644 --- a/src/energyplus/CMakeLists.txt +++ b/src/energyplus/CMakeLists.txt @@ -302,6 +302,7 @@ set(${target_name}_src ForwardTranslator/ForwardTranslatePythonPluginVariable.cpp ForwardTranslator/ForwardTranslatePythonPluginTrendVariable.cpp ForwardTranslator/ForwardTranslatePythonPluginOutputVariable.cpp + ForwardTranslator/ForwardTranslatePythonPluginSearchPaths.cpp ForwardTranslator/ForwardTranslateRefractionExtinctionGlazing.cpp ForwardTranslator/ForwardTranslateRefrigerationAirChiller.cpp ForwardTranslator/ForwardTranslateRefrigerationCase.cpp @@ -791,6 +792,7 @@ set(${target_name}_test_src Test/PythonPluginVariable_GTest.cpp Test/PythonPluginOutputVariable_GTest.cpp Test/PythonPluginTrendVariable_GTest.cpp + Test/PythonPluginSearchPaths_GTest.cpp Test/RunPeriod_GTest.cpp Test/RunPeriodControlDaylightSavingTime_GTest.cpp Test/RunPeriodControlSpecialDays_GTest.cpp diff --git a/src/energyplus/ForwardTranslator.cpp b/src/energyplus/ForwardTranslator.cpp index 93b639954d..10432603e3 100644 --- a/src/energyplus/ForwardTranslator.cpp +++ b/src/energyplus/ForwardTranslator.cpp @@ -2433,6 +2433,11 @@ namespace energyplus { retVal = translatePythonPluginOutputVariable(obj); break; } + case openstudio::IddObjectType::OS_PythonPlugin_SearchPaths: { + auto obj = modelObject.cast(); + retVal = translatePythonPluginSearchPaths(obj); + break; + } case openstudio::IddObjectType::OS_RadianceParameters: { // no-op break; @@ -3602,6 +3607,8 @@ namespace energyplus { IddObjectType::OS_ExternalInterface_FunctionalMockupUnitImport_To_Schedule, IddObjectType::OS_ExternalInterface_FunctionalMockupUnitImport_To_Variable, + IddObjectType:: + OS_PythonPlugin_SearchPaths, // this FT intentionally happens before PythonPlugin_Instance so that we can't end up with two PythonPlugin_SearchPaths objects IddObjectType::OS_PythonPlugin_Instance, IddObjectType::OS_PythonPlugin_Variable, IddObjectType::OS_PythonPlugin_TrendVariable, diff --git a/src/energyplus/ForwardTranslator.hpp b/src/energyplus/ForwardTranslator.hpp index 976de7e606..b14c46dabe 100644 --- a/src/energyplus/ForwardTranslator.hpp +++ b/src/energyplus/ForwardTranslator.hpp @@ -337,6 +337,7 @@ namespace model { class PythonPluginVariable; class PythonPluginTrendVariable; class PythonPluginOutputVariable; + class PythonPluginSearchPaths; class RefractionExtinctionGlazing; class RefrigerationAirChiller; class RefrigerationCase; @@ -1256,6 +1257,8 @@ namespace energyplus { boost::optional translatePythonPluginOutputVariable(model::PythonPluginOutputVariable& modelObject); + boost::optional translatePythonPluginSearchPaths(model::PythonPluginSearchPaths& modelObject); + boost::optional translateRefractionExtinctionGlazing(model::RefractionExtinctionGlazing& modelObject); boost::optional translateRefrigerationAirChiller(model::RefrigerationAirChiller& modelObject); diff --git a/src/energyplus/ForwardTranslator/ForwardTranslatePythonPluginSearchPaths.cpp b/src/energyplus/ForwardTranslator/ForwardTranslatePythonPluginSearchPaths.cpp new file mode 100644 index 0000000000..dce66cea4f --- /dev/null +++ b/src/energyplus/ForwardTranslator/ForwardTranslatePythonPluginSearchPaths.cpp @@ -0,0 +1,90 @@ +/*********************************************************************************************************************** +* OpenStudio(R), Copyright (c) 2008-2023, Alliance for Sustainable Energy, LLC, and other contributors. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +* following conditions are met: +* +* (1) Redistributions of source code must retain the above copyright notice, this list of conditions and the following +* disclaimer. +* +* (2) Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following +* disclaimer in the documentation and/or other materials provided with the distribution. +* +* (3) Neither the name of the copyright holder nor the names of any contributors may be used to endorse or promote products +* derived from this software without specific prior written permission from the respective party. +* +* (4) Other than as required in clauses (1) and (2), distributions in any form of modifications or other derivative works +* may not use the "OpenStudio" trademark, "OS", "os", or any other confusingly similar designation without specific prior +* written permission from Alliance for Sustainable Energy, LLC. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE UNITED STATES GOVERNMENT, OR THE UNITED +* STATES DEPARTMENT OF ENERGY, NOR ANY OF THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF +* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +***********************************************************************************************************************/ + +#include "../ForwardTranslator.hpp" +#include "../../model/Model.hpp" + +#include "../../model/PythonPluginSearchPaths.hpp" + +#include "../../utilities/idf/IdfExtensibleGroup.hpp" + +#include +#include + +constexpr static auto pythonSearchPathsName = "Python Plugin Search Paths"; + +using namespace openstudio::model; + +namespace openstudio { + +namespace energyplus { + + boost::optional ForwardTranslator::translatePythonPluginSearchPaths(model::PythonPluginSearchPaths& modelObject) { + + auto searchPaths = modelObject.searchPaths(); + if (searchPaths.empty()) { + return boost::none; + } + + IdfObject idfObject = createAndRegisterIdfObject(openstudio::IddObjectType::PythonPlugin_SearchPaths, modelObject); + + idfObject.setName(pythonSearchPathsName); + + // Add Current Working Directory to Search Path: Optional Boolean + if (modelObject.addCurrentWorkingDirectorytoSearchPath()) { + idfObject.setString(PythonPlugin_SearchPathsFields::AddCurrentWorkingDirectorytoSearchPath, "Yes"); + } else { + idfObject.setString(PythonPlugin_SearchPathsFields::AddCurrentWorkingDirectorytoSearchPath, "No"); + } + + // Add Input File Directory to Search Path: Optional Boolean + if (modelObject.addInputFileDirectorytoSearchPath()) { + idfObject.setString(PythonPlugin_SearchPathsFields::AddInputFileDirectorytoSearchPath, "Yes"); + } else { + idfObject.setString(PythonPlugin_SearchPathsFields::AddInputFileDirectorytoSearchPath, "No"); + } + + // Add epin Environment Variable to Search Path: Optional Boolean + if (modelObject.addepinEnvironmentVariabletoSearchPath()) { + idfObject.setString(PythonPlugin_SearchPathsFields::AddepinEnvironmentVariabletoSearchPath, "Yes"); + } else { + idfObject.setString(PythonPlugin_SearchPathsFields::AddepinEnvironmentVariabletoSearchPath, "No"); + } + + // Search Path + for (const openstudio::path& searchPath : modelObject.searchPaths()) { + IdfExtensibleGroup eg = idfObject.pushExtensibleGroup(); + eg.setString(PythonPlugin_SearchPathsExtensibleFields::SearchPath, searchPath.generic_string()); + } + + return idfObject; + } // End of translate function + +} // end namespace energyplus +} // end namespace openstudio diff --git a/src/energyplus/Test/PythonPluginSearchPaths_GTest.cpp b/src/energyplus/Test/PythonPluginSearchPaths_GTest.cpp new file mode 100644 index 0000000000..9cf134d64a --- /dev/null +++ b/src/energyplus/Test/PythonPluginSearchPaths_GTest.cpp @@ -0,0 +1,167 @@ +/*********************************************************************************************************************** +* OpenStudio(R), Copyright (c) 2008-2023, Alliance for Sustainable Energy, LLC, and other contributors. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +* following conditions are met: +* +* (1) Redistributions of source code must retain the above copyright notice, this list of conditions and the following +* disclaimer. +* +* (2) Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following +* disclaimer in the documentation and/or other materials provided with the distribution. +* +* (3) Neither the name of the copyright holder nor the names of any contributors may be used to endorse or promote products +* derived from this software without specific prior written permission from the respective party. +* +* (4) Other than as required in clauses (1) and (2), distributions in any form of modifications or other derivative works +* may not use the "OpenStudio" trademark, "OS", "os", or any other confusingly similar designation without specific prior +* written permission from Alliance for Sustainable Energy, LLC. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE UNITED STATES GOVERNMENT, OR THE UNITED +* STATES DEPARTMENT OF ENERGY, NOR ANY OF THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF +* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +***********************************************************************************************************************/ + +#include +#include "EnergyPlusFixture.hpp" + +#include "../ForwardTranslator.hpp" + +#include "../../model/Model.hpp" +#include "../../model/PythonPluginSearchPaths.hpp" +#include "../../model/PythonPluginSearchPaths_Impl.hpp" +#include "../../model/ExternalFile.hpp" +#include "../../model/ExternalFile_Impl.hpp" +#include "../../model/PythonPluginInstance.hpp" +#include "../../model/PythonPluginInstance_Impl.hpp" + +#include "../../utilities/idf/Workspace.hpp" +#include "../../utilities/idf/IdfObject.hpp" +#include "../../utilities/idf/WorkspaceObject.hpp" +#include "../../utilities/idf/IdfExtensibleGroup.hpp" +#include "../../utilities/idf/WorkspaceExtensibleGroup.hpp" +#include "../../utilities/core/PathHelpers.hpp" +// E+ FieldEnums +#include +#include +#include + +#include + +using namespace openstudio::energyplus; +using namespace openstudio::model; +using namespace openstudio; + +TEST_F(EnergyPlusFixture, ForwardTranslator_PythonPluginSearchPaths) { + + ForwardTranslator ft; + + Model m; + PythonPluginSearchPaths pythonPluginSearchPaths = m.getUniqueModelObject(); + + pythonPluginSearchPaths.setName("My PythonPluginSearchPaths"); + + EXPECT_TRUE(pythonPluginSearchPaths.searchPaths().empty()); + // No search paths; not translated + { + EXPECT_TRUE(pythonPluginSearchPaths.setAddCurrentWorkingDirectorytoSearchPath(false)); // Opposite from IDD default + EXPECT_TRUE(pythonPluginSearchPaths.setAddInputFileDirectorytoSearchPath(false)); // Opposite from IDD default + EXPECT_TRUE(pythonPluginSearchPaths.setAddepinEnvironmentVariabletoSearchPath(false)); // Opposite from IDD default + + const Workspace w = ft.translateModel(m); + + const auto idfObjs = w.getObjectsByType(IddObjectType::PythonPlugin_SearchPaths); + ASSERT_EQ(0u, idfObjs.size()); + } + + { + EXPECT_TRUE(pythonPluginSearchPaths.setAddCurrentWorkingDirectorytoSearchPath(true)); + EXPECT_TRUE(pythonPluginSearchPaths.setAddInputFileDirectorytoSearchPath(true)); + EXPECT_TRUE(pythonPluginSearchPaths.setAddepinEnvironmentVariabletoSearchPath(true)); + + std::vector searchPaths({"/path/to/lib1", "/path/to/lib2"}); + EXPECT_TRUE(pythonPluginSearchPaths.setSearchPaths(searchPaths)); + EXPECT_EQ(2u, pythonPluginSearchPaths.searchPaths().size()); + + const Workspace w = ft.translateModel(m); + + const auto idfObjs = w.getObjectsByType(IddObjectType::PythonPlugin_SearchPaths); + ASSERT_EQ(1u, idfObjs.size()); + + const auto& idfObject = idfObjs.front(); + EXPECT_EQ("Yes", idfObject.getString(PythonPlugin_SearchPathsFields::AddCurrentWorkingDirectorytoSearchPath).get()); + EXPECT_EQ("Yes", idfObject.getString(PythonPlugin_SearchPathsFields::AddInputFileDirectorytoSearchPath).get()); + EXPECT_EQ("Yes", idfObject.getString(PythonPlugin_SearchPathsFields::AddepinEnvironmentVariabletoSearchPath).get()); + + ASSERT_EQ(2u, idfObject.extensibleGroups().size()); + for (int i = 0; i < 2; ++i) { + EXPECT_EQ(searchPaths[i], idfObject.extensibleGroups()[i].getString(PythonPlugin_SearchPathsExtensibleFields::SearchPath).get()); + } + } + + { + EXPECT_TRUE(pythonPluginSearchPaths.setAddCurrentWorkingDirectorytoSearchPath(false)); // Opposite from IDD default + EXPECT_TRUE(pythonPluginSearchPaths.setAddInputFileDirectorytoSearchPath(false)); // Opposite from IDD default + EXPECT_TRUE(pythonPluginSearchPaths.setAddepinEnvironmentVariabletoSearchPath(false)); // Opposite from IDD default + + std::vector searchPaths({"/path/to/lib1", "/path/to/lib2"}); + EXPECT_TRUE(pythonPluginSearchPaths.setSearchPaths(searchPaths)); + EXPECT_EQ(2u, pythonPluginSearchPaths.searchPaths().size()); + + const Workspace w = ft.translateModel(m); + + const auto idfObjs = w.getObjectsByType(IddObjectType::PythonPlugin_SearchPaths); + ASSERT_EQ(1u, idfObjs.size()); + + const auto& idfObject = idfObjs.front(); + EXPECT_EQ("No", idfObject.getString(PythonPlugin_SearchPathsFields::AddCurrentWorkingDirectorytoSearchPath).get()); + EXPECT_EQ("No", idfObject.getString(PythonPlugin_SearchPathsFields::AddInputFileDirectorytoSearchPath).get()); + EXPECT_EQ("No", idfObject.getString(PythonPlugin_SearchPathsFields::AddepinEnvironmentVariabletoSearchPath).get()); + + ASSERT_EQ(2u, idfObject.extensibleGroups().size()); + for (int i = 0; i < 2; ++i) { + EXPECT_EQ(searchPaths[i], idfObject.extensibleGroups()[i].getString(PythonPlugin_SearchPathsExtensibleFields::SearchPath).get()); + } + } + + { + EXPECT_TRUE(pythonPluginSearchPaths.setAddCurrentWorkingDirectorytoSearchPath(false)); // Opposite from IDD default + EXPECT_TRUE(pythonPluginSearchPaths.setAddInputFileDirectorytoSearchPath(false)); // Opposite from IDD default + EXPECT_TRUE(pythonPluginSearchPaths.setAddepinEnvironmentVariabletoSearchPath(false)); // Opposite from IDD default + + openstudio::path p = resourcesPath() / toPath("model/PythonPluginThermochromicWindow.py"); + ASSERT_TRUE(exists(p)); + + boost::optional externalfile = ExternalFile::getExternalFile(m, openstudio::toString(p)); + ASSERT_TRUE(externalfile) << "Path doesn't exist: '" << p << "'"; + + PythonPluginInstance pythonPluginInstance(*externalfile, "ZN_1_wall_south_Window_1_Control"); + + std::vector searchPaths({"/path/to/lib1", "/path/to/lib2"}); + EXPECT_TRUE(pythonPluginSearchPaths.setSearchPaths(searchPaths)); + EXPECT_EQ(2u, pythonPluginSearchPaths.searchPaths().size()); + + const Workspace w = ft.translateModel(m); + + const auto idfObjs = w.getObjectsByType(IddObjectType::PythonPlugin_SearchPaths); + ASSERT_EQ(1u, idfObjs.size()); + + const auto& idfObject = idfObjs.front(); + EXPECT_EQ("No", idfObject.getString(PythonPlugin_SearchPathsFields::AddCurrentWorkingDirectorytoSearchPath).get()); + EXPECT_EQ("No", idfObject.getString(PythonPlugin_SearchPathsFields::AddInputFileDirectorytoSearchPath).get()); + EXPECT_EQ("No", idfObject.getString(PythonPlugin_SearchPathsFields::AddepinEnvironmentVariabletoSearchPath).get()); + + ASSERT_EQ(3u, idfObject.extensibleGroups().size()); + for (int i = 0; i < 2; ++i) { + EXPECT_EQ(searchPaths[i], idfObject.extensibleGroups()[i].getString(PythonPlugin_SearchPathsExtensibleFields::SearchPath).get()); + } + // since PythonPlugin_Instance is translated after PythonPlugin_SearchPaths, this search path is appended to the existing search paths + EXPECT_EQ(openstudio::toString(externalfile->filePath().parent_path()), + idfObject.extensibleGroups()[2].getString(PythonPlugin_SearchPathsExtensibleFields::SearchPath).get()); + } +} diff --git a/src/model/CMakeLists.txt b/src/model/CMakeLists.txt index 9a26b0c54c..8477362fea 100644 --- a/src/model/CMakeLists.txt +++ b/src/model/CMakeLists.txt @@ -1228,6 +1228,9 @@ set(${target_name}_src PythonPluginOutputVariable.hpp PythonPluginOutputVariable_Impl.hpp PythonPluginOutputVariable.cpp + PythonPluginSearchPaths.hpp + PythonPluginSearchPaths_Impl.hpp + PythonPluginSearchPaths.cpp RadianceParameters.hpp RadianceParameters_Impl.hpp RadianceParameters.cpp @@ -2221,6 +2224,7 @@ set(${target_name}_test_src test/PythonPluginVariable_GTest.cpp test/PythonPluginTrendVariable_GTest.cpp test/PythonPluginOutputVariable_GTest.cpp + test/PythonPluginSearchPaths_GTest.cpp test/RadianceParameters_GTest.cpp test/RefractionExtinctionGlazing_GTest.cpp test/RefrigerationAirChiller_GTest.cpp diff --git a/src/model/ConcreteModelObjects.hpp b/src/model/ConcreteModelObjects.hpp index c71eaadc27..9d36231578 100644 --- a/src/model/ConcreteModelObjects.hpp +++ b/src/model/ConcreteModelObjects.hpp @@ -374,6 +374,7 @@ #include "PythonPluginVariable.hpp" #include "PythonPluginTrendVariable.hpp" #include "PythonPluginOutputVariable.hpp" +#include "PythonPluginSearchPaths.hpp" #include "RadianceParameters.hpp" #include "RefractionExtinctionGlazing.hpp" #include "RefrigerationAirChiller.hpp" @@ -927,6 +928,7 @@ #include "PythonPluginVariable_Impl.hpp" #include "PythonPluginTrendVariable_Impl.hpp" #include "PythonPluginOutputVariable_Impl.hpp" +#include "PythonPluginSearchPaths_Impl.hpp" #include "RadianceParameters_Impl.hpp" #include "RefractionExtinctionGlazing_Impl.hpp" #include "RefrigerationAirChiller_Impl.hpp" diff --git a/src/model/Model.cpp b/src/model/Model.cpp index 73b8c11fb1..75d57bf3ea 100644 --- a/src/model/Model.cpp +++ b/src/model/Model.cpp @@ -936,6 +936,22 @@ namespace model { return m_cachedExternalInterface; } + boost::optional Model_Impl::pythonPluginSearchPaths() const { + if (m_cachedPythonPluginSearchPaths) { + return m_cachedPythonPluginSearchPaths; + } + + boost::optional result = this->model().getOptionalUniqueModelObject(); + if (result) { + m_cachedPythonPluginSearchPaths = result; + result->getImpl() + ->PythonPluginSearchPaths_Impl::onRemoveFromWorkspace.connect( + const_cast(this)); + } + + return m_cachedPythonPluginSearchPaths; + } + boost::optional Model_Impl::calendarYear() const { if (!m_cachedYearDescription) { m_cachedYearDescription = this->model().getUniqueModelObject(); @@ -1544,6 +1560,7 @@ namespace model { clearCachedClimateZones(dummy); clearCachedEnvironmentalImpactFactors(dummy); clearCachedExternalInterface(dummy); + clearCachedPythonPluginSearchPaths(dummy); } void Model_Impl::clearCachedBuilding(const Handle&) { @@ -1730,6 +1747,10 @@ namespace model { m_cachedExternalInterface.reset(); } + void Model_Impl::clearCachedPythonPluginSearchPaths(const Handle&) { + m_cachedPythonPluginSearchPaths.reset(); + } + void Model_Impl::autosize() { for (auto& optModelObj : objects()) { if (auto modelObj = optModelObj.optionalCast()) { // HVACComponent @@ -2064,6 +2085,10 @@ namespace model { return getImpl()->externalInterface(); } + boost::optional Model::pythonPluginSearchPaths() const { + return getImpl()->pythonPluginSearchPaths(); + } + boost::optional Model::calendarYear() const { return getImpl()->calendarYear(); } @@ -3808,6 +3833,15 @@ namespace model { } } + template <> + PythonPluginSearchPaths Model::getUniqueModelObject() { + if (boost::optional _b = pythonPluginSearchPaths()) { + return _b.get(); + } else { + return PythonPluginSearchPaths(*this); + } + } + std::shared_ptr detail::Model_Impl::ModelObjectCreator::getNew(Model_Impl* model, const IdfObject& obj, bool keepHandle) const { auto typeToCreate = obj.iddObject().type(); @@ -4204,6 +4238,7 @@ namespace model { REGISTER_CONSTRUCTOR(PythonPluginVariable); REGISTER_CONSTRUCTOR(PythonPluginTrendVariable); REGISTER_CONSTRUCTOR(PythonPluginOutputVariable); + REGISTER_CONSTRUCTOR(PythonPluginSearchPaths); REGISTER_CONSTRUCTOR(RadianceParameters); REGISTER_CONSTRUCTOR(RefractionExtinctionGlazing); REGISTER_CONSTRUCTOR(RefrigerationAirChiller); @@ -4777,6 +4812,7 @@ namespace model { REGISTER_COPYCONSTRUCTORS(PythonPluginVariable); REGISTER_COPYCONSTRUCTORS(PythonPluginTrendVariable); REGISTER_COPYCONSTRUCTORS(PythonPluginOutputVariable); + REGISTER_COPYCONSTRUCTORS(PythonPluginSearchPaths); REGISTER_COPYCONSTRUCTORS(RadianceParameters); REGISTER_COPYCONSTRUCTORS(RefractionExtinctionGlazing); REGISTER_COPYCONSTRUCTORS(RefrigerationAirChiller); diff --git a/src/model/Model.hpp b/src/model/Model.hpp index 9732ac54fb..5a69b71061 100644 --- a/src/model/Model.hpp +++ b/src/model/Model.hpp @@ -76,6 +76,7 @@ namespace model { class Schedule; class Node; class SpaceType; + class PythonPluginSearchPaths; namespace detail { class Model_Impl; @@ -316,6 +317,10 @@ namespace model { * object which can be significantly faster than calling getOptionalUniqueModelObject(). */ boost::optional externalInterface() const; + /** Get the PythonPluginSearchPaths object if there is one, this implementation uses a cached reference to the PythonPluginSearchPaths + * object which can be significantly faster than calling getOptionalUniqueModelObject(). */ + boost::optional pythonPluginSearchPaths() const; + /** Get or create the YearDescription object if there is one, then call method from YearDescription. */ // DLM: this is due to issues exporting the model::YearDescription object because of name conflict with utilities::YearDescription. boost::optional calendarYear() const; @@ -829,6 +834,9 @@ namespace model { template <> MODEL_API ExternalInterface Model::getUniqueModelObject(); + template <> + MODEL_API PythonPluginSearchPaths Model::getUniqueModelObject(); + } // namespace model } // namespace openstudio diff --git a/src/model/ModelCore.i b/src/model/ModelCore.i index 262c154dff..11e0a07787 100644 --- a/src/model/ModelCore.i +++ b/src/model/ModelCore.i @@ -74,9 +74,12 @@ // Ignore hvac objects for now, add back in with partial classes in ModelHVAC.i %ignore openstudio::model::Model::outdoorAirNode; - // Ignore hvac objects for now, add back in with partial classes in ModelAirflow.i + // Ignore airflow objects for now, add back in with partial classes in ModelAirflow.i %ignore openstudio::model::Model::airflowNetworkSimulationControl; + // Ignore resources objects for now, add back in with partial classes in ModelResources.i + %ignore openstudio::model::Model::pythonPluginSearchPaths; + // EnergyManagementSystemActuator: depends on Space (ModelGeometry.i), %ignore openstudio::model::EnergyManagementSystemActuator::EnergyManagementSystemActuator(const ModelObject& modelObject, const std::string& actuatedComponentType, diff --git a/src/model/ModelResources.i b/src/model/ModelResources.i index 475039e094..af70e0167c 100644 --- a/src/model/ModelResources.i +++ b/src/model/ModelResources.i @@ -105,6 +105,7 @@ MODELOBJECT_TEMPLATES(PythonPluginInstance); MODELOBJECT_TEMPLATES(PythonPluginVariable); MODELOBJECT_TEMPLATES(PythonPluginTrendVariable); MODELOBJECT_TEMPLATES(PythonPluginOutputVariable); +UNIQUEMODELOBJECT_TEMPLATES(PythonPluginSearchPaths); MODELOBJECT_TEMPLATES(ScheduleVariableInterval); MODELOBJECT_TEMPLATES(ScheduleCompact); MODELOBJECT_TEMPLATES(ScheduleConstant); @@ -206,6 +207,7 @@ SWIG_MODELOBJECT(PythonPluginInstance, 1); SWIG_MODELOBJECT(PythonPluginVariable, 1); SWIG_MODELOBJECT(PythonPluginTrendVariable, 1); SWIG_MODELOBJECT(PythonPluginOutputVariable, 1); +SWIG_UNIQUEMODELOBJECT(PythonPluginSearchPaths); SWIG_MODELOBJECT(ExternalFile, 1); SWIG_MODELOBJECT(ScheduleFixedInterval, 1); SWIG_MODELOBJECT(ScheduleVariableInterval, 1); @@ -311,6 +313,10 @@ SWIG_MODELOBJECT(HeatExchangerDesiccantBalancedFlowPerformanceDataType1, 1); return ems_curve.setCurveOrTableObject(curve); } + boost::optional pythonPluginSearchPaths(const openstudio::model::Model& model) { + return model.pythonPluginSearchPaths(); + } + } } } @@ -333,6 +339,10 @@ SWIG_MODELOBJECT(HeatExchangerDesiccantBalancedFlowPerformanceDataType1, 1); : this(model) { this.setCurveOrTableObject(curve); } + + public OptionalPythonPluginSearchPaths pythonPluginSearchPaths() { + return OpenStudio.OpenStudioModelResources.pythonPluginSearchPaths(this); + } } %} #endif diff --git a/src/model/ModelSimulation.i b/src/model/ModelSimulation.i index ac25115699..b67615c894 100644 --- a/src/model/ModelSimulation.i +++ b/src/model/ModelSimulation.i @@ -175,7 +175,6 @@ SWIG_UNIQUEMODELOBJECT(PerformancePrecisionTradeoffs); return model.runPeriod(); } - boost::optional climateZones(const openstudio::model::Model& model) { return model.climateZones(); } diff --git a/src/model/Model_Impl.hpp b/src/model/Model_Impl.hpp index bb0ab20adb..3e6186b133 100644 --- a/src/model/Model_Impl.hpp +++ b/src/model/Model_Impl.hpp @@ -54,6 +54,7 @@ #include "ClimateZones.hpp" #include "EnvironmentalImpactFactors.hpp" #include "ExternalInterface.hpp" +#include "PythonPluginSearchPaths.hpp" #include "../nano/nano_signal_slot.hpp" // Signal-Slot replacement @@ -335,6 +336,10 @@ namespace model { * object which can be significantly faster than calling getOptionalUniqueModelObject(). */ boost::optional externalInterface() const; + /** Get the PythonPluginSearchPaths object if there is one, this implementation uses a cached reference to the PythonPluginSearchPaths + * object which can be significantly faster than calling getOptionalUniqueModelObject(). */ + boost::optional pythonPluginSearchPaths() const; + /** Get or create the YearDescription object if there is one, then call method from YearDescription. */ // DLM: this is due to issues exporting the model::YearDescription object because of name conflict with utilities::YearDescription. boost::optional calendarYear() const; @@ -508,6 +513,7 @@ namespace model { mutable boost::optional m_cachedClimateZones; mutable boost::optional m_cachedEnvironmentalImpactFactors; mutable boost::optional m_cachedExternalInterface; + mutable boost::optional m_cachedPythonPluginSearchPaths; // private slots: void clearCachedData(); @@ -557,6 +563,7 @@ namespace model { void clearCachedClimateZones(const Handle& handle); void clearCachedEnvironmentalImpactFactors(const Handle& handle); void clearCachedExternalInterface(const Handle& handle); + void clearCachedPythonPluginSearchPaths(const Handle& handle); using CopyConstructorFunction = std::function( Model_Impl*, const std::shared_ptr&, bool)>; diff --git a/src/model/PythonPluginSearchPaths.cpp b/src/model/PythonPluginSearchPaths.cpp new file mode 100644 index 0000000000..9d3cf29a81 --- /dev/null +++ b/src/model/PythonPluginSearchPaths.cpp @@ -0,0 +1,214 @@ +/*********************************************************************************************************************** +* OpenStudio(R), Copyright (c) 2008-2023, Alliance for Sustainable Energy, LLC, and other contributors. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +* following conditions are met: +* +* (1) Redistributions of source code must retain the above copyright notice, this list of conditions and the following +* disclaimer. +* +* (2) Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following +* disclaimer in the documentation and/or other materials provided with the distribution. +* +* (3) Neither the name of the copyright holder nor the names of any contributors may be used to endorse or promote products +* derived from this software without specific prior written permission from the respective party. +* +* (4) Other than as required in clauses (1) and (2), distributions in any form of modifications or other derivative works +* may not use the "OpenStudio" trademark, "OS", "os", or any other confusingly similar designation without specific prior +* written permission from Alliance for Sustainable Energy, LLC. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE UNITED STATES GOVERNMENT, OR THE UNITED +* STATES DEPARTMENT OF ENERGY, NOR ANY OF THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF +* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +***********************************************************************************************************************/ + +#include "PythonPluginSearchPaths.hpp" +#include "PythonPluginSearchPaths_Impl.hpp" + +#include "../utilities/idf/WorkspaceExtensibleGroup.hpp" +#include "../utilities/core/Assert.hpp" +#include "../utilities/core/Path.hpp" + +#include +#include + +#include + +namespace openstudio { +namespace model { + + namespace detail { + + PythonPluginSearchPaths_Impl::PythonPluginSearchPaths_Impl(const IdfObject& idfObject, Model_Impl* model, bool keepHandle) + : ModelObject_Impl(idfObject, model, keepHandle) { + OS_ASSERT(idfObject.iddObject().type() == PythonPluginSearchPaths::iddObjectType()); + } + + PythonPluginSearchPaths_Impl::PythonPluginSearchPaths_Impl(const openstudio::detail::WorkspaceObject_Impl& other, Model_Impl* model, + bool keepHandle) + : ModelObject_Impl(other, model, keepHandle) { + OS_ASSERT(other.iddObject().type() == PythonPluginSearchPaths::iddObjectType()); + } + + PythonPluginSearchPaths_Impl::PythonPluginSearchPaths_Impl(const PythonPluginSearchPaths_Impl& other, Model_Impl* model, bool keepHandle) + : ModelObject_Impl(other, model, keepHandle) {} + + const std::vector& PythonPluginSearchPaths_Impl::outputVariableNames() const { + static std::vector result; + return result; + } + + IddObjectType PythonPluginSearchPaths_Impl::iddObjectType() const { + return PythonPluginSearchPaths::iddObjectType(); + } + + bool PythonPluginSearchPaths_Impl::addCurrentWorkingDirectorytoSearchPath() const { + return getBooleanFieldValue(OS_PythonPlugin_SearchPathsFields::AddCurrentWorkingDirectorytoSearchPath); + } + + bool PythonPluginSearchPaths_Impl::addInputFileDirectorytoSearchPath() const { + return getBooleanFieldValue(OS_PythonPlugin_SearchPathsFields::AddInputFileDirectorytoSearchPath); + } + + bool PythonPluginSearchPaths_Impl::addepinEnvironmentVariabletoSearchPath() const { + return getBooleanFieldValue(OS_PythonPlugin_SearchPathsFields::AddepinEnvironmentVariabletoSearchPath); + } + + std::vector PythonPluginSearchPaths_Impl::searchPaths() const { + std::vector result; + for (const auto& eg : extensibleGroups()) { + auto _s = eg.getString(OS_PythonPlugin_SearchPathsExtensibleFields::SearchPath); + OS_ASSERT(_s); + result.push_back(openstudio::toPath(_s.get())); + } + return result; + } + + bool PythonPluginSearchPaths_Impl::setAddCurrentWorkingDirectorytoSearchPath(bool addCurrentWorkingDirectorytoSearchPath) { + const bool result = + setBooleanFieldValue(OS_PythonPlugin_SearchPathsFields::AddCurrentWorkingDirectorytoSearchPath, addCurrentWorkingDirectorytoSearchPath); + OS_ASSERT(result); + return result; + } + + bool PythonPluginSearchPaths_Impl::setAddInputFileDirectorytoSearchPath(bool addInputFileDirectorytoSearchPath) { + const bool result = + setBooleanFieldValue(OS_PythonPlugin_SearchPathsFields::AddInputFileDirectorytoSearchPath, addInputFileDirectorytoSearchPath); + OS_ASSERT(result); + return result; + } + + bool PythonPluginSearchPaths_Impl::setAddepinEnvironmentVariabletoSearchPath(bool addepinEnvironmentVariabletoSearchPath) { + const bool result = + setBooleanFieldValue(OS_PythonPlugin_SearchPathsFields::AddepinEnvironmentVariabletoSearchPath, addepinEnvironmentVariabletoSearchPath); + OS_ASSERT(result); + return result; + } + + bool PythonPluginSearchPaths_Impl::addSearchPath(const openstudio::path& searchPath) { + std::vector existingSearchPaths = this->searchPaths(); + if (std::find(existingSearchPaths.begin(), existingSearchPaths.end(), searchPath) != existingSearchPaths.end()) { + LOG(Info, "Not adding search path '" << searchPath << "' to PythonPlugin:SearchPaths since it is already present"); + // Return true anyways, it's a success + return true; + } + + auto eg = getObject().pushExtensibleGroup().cast(); + bool result = eg.setString(OS_PythonPlugin_SearchPathsExtensibleFields::SearchPath, searchPath.generic_string()); + if (!result) { + getObject().eraseExtensibleGroup(eg.groupIndex()); + } + + return result; + } + + bool PythonPluginSearchPaths_Impl::setSearchPaths(const std::vector& searchPaths) { + bool result = true; + + clearSearchPaths(); + + for (const auto& s : searchPaths) { + bool thisResult = addSearchPath(s); + if (!thisResult) { + LOG(Warn, "Couldn't add search path " << s << " to PythonPlugin:SearchPaths, skipping and continuing."); + result = false; + } + } + + return result; + } + + void PythonPluginSearchPaths_Impl::clearSearchPaths() { + clearExtensibleGroups(); + } + + } // namespace detail + + IddObjectType PythonPluginSearchPaths::iddObjectType() { + return {IddObjectType::OS_PythonPlugin_SearchPaths}; + } + + bool PythonPluginSearchPaths::addCurrentWorkingDirectorytoSearchPath() const { + return getImpl()->addCurrentWorkingDirectorytoSearchPath(); + } + + bool PythonPluginSearchPaths::addInputFileDirectorytoSearchPath() const { + return getImpl()->addInputFileDirectorytoSearchPath(); + } + + bool PythonPluginSearchPaths::addepinEnvironmentVariabletoSearchPath() const { + return getImpl()->addepinEnvironmentVariabletoSearchPath(); + } + + std::vector PythonPluginSearchPaths::searchPaths() const { + return getImpl()->searchPaths(); + } + + bool PythonPluginSearchPaths::setAddCurrentWorkingDirectorytoSearchPath(bool addCurrentWorkingDirectorytoSearchPath) { + return getImpl()->setAddCurrentWorkingDirectorytoSearchPath(addCurrentWorkingDirectorytoSearchPath); + } + + bool PythonPluginSearchPaths::setAddInputFileDirectorytoSearchPath(bool addInputFileDirectorytoSearchPath) { + return getImpl()->setAddInputFileDirectorytoSearchPath(addInputFileDirectorytoSearchPath); + } + + bool PythonPluginSearchPaths::setAddepinEnvironmentVariabletoSearchPath(bool addepinEnvironmentVariabletoSearchPath) { + return getImpl()->setAddepinEnvironmentVariabletoSearchPath(addepinEnvironmentVariabletoSearchPath); + } + + bool PythonPluginSearchPaths::addSearchPath(const openstudio::path& searchPath) { + return getImpl()->addSearchPath(searchPath); + } + + bool PythonPluginSearchPaths::setSearchPaths(const std::vector& searchPaths) { + return getImpl()->setSearchPaths(searchPaths); + } + + bool PythonPluginSearchPaths::setSearchPaths(const std::vector& searchPaths) { + std::vector paths; + paths.reserve(searchPaths.size()); + std::transform(searchPaths.cbegin(), searchPaths.cend(), std::back_inserter(paths), [](const std::string& s) { return openstudio::toPath(s); }); + return getImpl()->setSearchPaths(paths); + } + + void PythonPluginSearchPaths::clearSearchPaths() { + getImpl()->clearSearchPaths(); + } + + /// @cond + PythonPluginSearchPaths::PythonPluginSearchPaths(std::shared_ptr impl) : ModelObject(std::move(impl)) {} + PythonPluginSearchPaths::PythonPluginSearchPaths(Model& model) : ModelObject(PythonPluginSearchPaths::iddObjectType(), model) { + setAddCurrentWorkingDirectorytoSearchPath(true); + setAddInputFileDirectorytoSearchPath(true); + setAddepinEnvironmentVariabletoSearchPath(true); + } + + /// @endcond + +} // namespace model +} // namespace openstudio diff --git a/src/model/PythonPluginSearchPaths.hpp b/src/model/PythonPluginSearchPaths.hpp new file mode 100644 index 0000000000..a4125abf12 --- /dev/null +++ b/src/model/PythonPluginSearchPaths.hpp @@ -0,0 +1,125 @@ +/*********************************************************************************************************************** +* OpenStudio(R), Copyright (c) 2008-2023, Alliance for Sustainable Energy, LLC, and other contributors. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +* following conditions are met: +* +* (1) Redistributions of source code must retain the above copyright notice, this list of conditions and the following +* disclaimer. +* +* (2) Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following +* disclaimer in the documentation and/or other materials provided with the distribution. +* +* (3) Neither the name of the copyright holder nor the names of any contributors may be used to endorse or promote products +* derived from this software without specific prior written permission from the respective party. +* +* (4) Other than as required in clauses (1) and (2), distributions in any form of modifications or other derivative works +* may not use the "OpenStudio" trademark, "OS", "os", or any other confusingly similar designation without specific prior +* written permission from Alliance for Sustainable Energy, LLC. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE UNITED STATES GOVERNMENT, OR THE UNITED +* STATES DEPARTMENT OF ENERGY, NOR ANY OF THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF +* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +***********************************************************************************************************************/ + +#ifndef MODEL_PYTHONPLUGINSEARCHPATHS_HPP +#define MODEL_PYTHONPLUGINSEARCHPATHS_HPP + +#include +#include "ModelObject.hpp" + +#include "../utilities/core/Filesystem.hpp" + +namespace openstudio { +namespace model { + + namespace detail { + + class PythonPluginSearchPaths_Impl; + + } // namespace detail + + /** PythonPluginSearchPaths is a ModelObject that wraps the OpenStudio IDD object 'OS:PythonPlugin:SearchPaths'. */ + class MODEL_API PythonPluginSearchPaths : public ModelObject + { + public: + /** @name Constructors and Destructors */ + //@{ + + virtual ~PythonPluginSearchPaths() override = default; + // Default the copy and move operators because the virtual dtor is explicit + PythonPluginSearchPaths(const PythonPluginSearchPaths& other) = default; + PythonPluginSearchPaths(PythonPluginSearchPaths&& other) = default; + PythonPluginSearchPaths& operator=(const PythonPluginSearchPaths&) = default; + PythonPluginSearchPaths& operator=(PythonPluginSearchPaths&&) = default; + + //@} + + static IddObjectType iddObjectType(); + + /** @name Getters */ + //@{ + + bool addCurrentWorkingDirectorytoSearchPath() const; + + bool addInputFileDirectorytoSearchPath() const; + + bool addepinEnvironmentVariabletoSearchPath() const; + + std::vector searchPaths() const; + + //@} + /** @name Setters */ + //@{ + + bool setAddCurrentWorkingDirectorytoSearchPath(bool addCurrentWorkingDirectorytoSearchPath); + + bool setAddInputFileDirectorytoSearchPath(bool addInputFileDirectorytoSearchPath); + + bool setAddepinEnvironmentVariabletoSearchPath(bool addepinEnvironmentVariabletoSearchPath); + + bool addSearchPath(const openstudio::path& searchPath); + bool setSearchPaths(const std::vector& searchPaths); + + // Convenience, forwards to the openstudio::path equivalent + bool setSearchPaths(const std::vector& searchPaths); + + void clearSearchPaths(); + + //@} + /** @name Other */ + //@{ + + //@} + protected: + /// @cond + using ImplType = detail::PythonPluginSearchPaths_Impl; + + explicit PythonPluginSearchPaths(std::shared_ptr impl); + + friend class detail::PythonPluginSearchPaths_Impl; + friend class Model; + friend class IdfObject; + friend class openstudio::detail::IdfObject_Impl; + explicit PythonPluginSearchPaths(Model& model); + + /// @endcond + private: + REGISTER_LOGGER("openstudio.model.PythonPluginSearchPaths"); + }; + + /** \relates PythonPluginSearchPaths*/ + using OptionalPythonPluginSearchPaths = boost::optional; + + /** \relates PythonPluginSearchPaths*/ + using PythonPluginSearchPathsVector = std::vector; + +} // namespace model +} // namespace openstudio + +#endif // MODEL_PYTHONPLUGINSEARCHPATHS_HPP diff --git a/src/model/PythonPluginSearchPaths_Impl.hpp b/src/model/PythonPluginSearchPaths_Impl.hpp new file mode 100644 index 0000000000..5afa04eedb --- /dev/null +++ b/src/model/PythonPluginSearchPaths_Impl.hpp @@ -0,0 +1,108 @@ +/*********************************************************************************************************************** +* OpenStudio(R), Copyright (c) 2008-2023, Alliance for Sustainable Energy, LLC, and other contributors. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +* following conditions are met: +* +* (1) Redistributions of source code must retain the above copyright notice, this list of conditions and the following +* disclaimer. +* +* (2) Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following +* disclaimer in the documentation and/or other materials provided with the distribution. +* +* (3) Neither the name of the copyright holder nor the names of any contributors may be used to endorse or promote products +* derived from this software without specific prior written permission from the respective party. +* +* (4) Other than as required in clauses (1) and (2), distributions in any form of modifications or other derivative works +* may not use the "OpenStudio" trademark, "OS", "os", or any other confusingly similar designation without specific prior +* written permission from Alliance for Sustainable Energy, LLC. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE UNITED STATES GOVERNMENT, OR THE UNITED +* STATES DEPARTMENT OF ENERGY, NOR ANY OF THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF +* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +***********************************************************************************************************************/ + +#ifndef MODEL_PYTHONPLUGINSEARCHPATHS_IMPL_HPP +#define MODEL_PYTHONPLUGINSEARCHPATHS_IMPL_HPP + +#include +#include "ModelObject_Impl.hpp" +#include "../utilities/core/Filesystem.hpp" + +namespace openstudio { +namespace model { + + namespace detail { + + /** PythonPluginSearchPaths_Impl is a ModelObject_Impl that is the implementation class for PythonPluginSearchPaths.*/ + class MODEL_API PythonPluginSearchPaths_Impl : public ModelObject_Impl + { + public: + /** @name Constructors and Destructors */ + //@{ + + PythonPluginSearchPaths_Impl(const IdfObject& idfObject, Model_Impl* model, bool keepHandle); + + PythonPluginSearchPaths_Impl(const openstudio::detail::WorkspaceObject_Impl& other, Model_Impl* model, bool keepHandle); + + PythonPluginSearchPaths_Impl(const PythonPluginSearchPaths_Impl& other, Model_Impl* model, bool keepHandle); + + virtual ~PythonPluginSearchPaths_Impl() override = default; + + //@} + /** @name Virtual Methods */ + //@{ + + virtual const std::vector& outputVariableNames() const override; + + virtual IddObjectType iddObjectType() const override; + + //@} + /** @name Getters */ + //@{ + + bool addCurrentWorkingDirectorytoSearchPath() const; + + bool addInputFileDirectorytoSearchPath() const; + + bool addepinEnvironmentVariabletoSearchPath() const; + + std::vector searchPaths() const; + + //@} + /** @name Setters */ + //@{ + + bool setAddCurrentWorkingDirectorytoSearchPath(bool addCurrentWorkingDirectorytoSearchPath); + + bool setAddInputFileDirectorytoSearchPath(bool addInputFileDirectorytoSearchPath); + + bool setAddepinEnvironmentVariabletoSearchPath(bool addepinEnvironmentVariabletoSearchPath); + + bool addSearchPath(const openstudio::path& searchPath); + + bool setSearchPaths(const std::vector& searchPaths); + + void clearSearchPaths(); + + //@} + /** @name Other */ + //@{ + + //@} + protected: + private: + REGISTER_LOGGER("openstudio.model.PythonPluginSearchPaths"); + }; + + } // namespace detail + +} // namespace model +} // namespace openstudio + +#endif // MODEL_PYTHONPLUGINSEARCHPATHS_IMPL_HPP diff --git a/src/model/test/Model_GTest.cpp b/src/model/test/Model_GTest.cpp index bb50bd8f0f..e468c2ff32 100644 --- a/src/model/test/Model_GTest.cpp +++ b/src/model/test/Model_GTest.cpp @@ -128,6 +128,8 @@ #include "../EnvironmentalImpactFactors_Impl.hpp" #include "../ExternalInterface.hpp" #include "../ExternalInterface_Impl.hpp" +#include "../PythonPluginSearchPaths.hpp" +#include "../PythonPluginSearchPaths_Impl.hpp" #include "../../utilities/core/PathHelpers.hpp" #include "../../utilities/data/TimeSeries.hpp" @@ -1161,6 +1163,12 @@ TEST_F(ModelFixture, UniqueModelObjectCachedGetters) { auto externalInterface = m.getUniqueModelObject(); EXPECT_TRUE(m.getOptionalUniqueModelObject()); EXPECT_EQ(++i, m.getModelObjects().size()); + + EXPECT_FALSE(m.getOptionalUniqueModelObject()); + EXPECT_EQ(i, m.getModelObjects().size()); + auto pythonPluginSearchPaths = m.getUniqueModelObject(); + EXPECT_TRUE(m.getOptionalUniqueModelObject()); + EXPECT_EQ(++i, m.getModelObjects().size()); } TEST_F(ModelFixture, Model_load) { diff --git a/src/model/test/PythonPluginSearchPaths_GTest.cpp b/src/model/test/PythonPluginSearchPaths_GTest.cpp new file mode 100644 index 0000000000..e09200a76a --- /dev/null +++ b/src/model/test/PythonPluginSearchPaths_GTest.cpp @@ -0,0 +1,85 @@ +/*********************************************************************************************************************** +* OpenStudio(R), Copyright (c) 2008-2023, Alliance for Sustainable Energy, LLC, and other contributors. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +* following conditions are met: +* +* (1) Redistributions of source code must retain the above copyright notice, this list of conditions and the following +* disclaimer. +* +* (2) Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following +* disclaimer in the documentation and/or other materials provided with the distribution. +* +* (3) Neither the name of the copyright holder nor the names of any contributors may be used to endorse or promote products +* derived from this software without specific prior written permission from the respective party. +* +* (4) Other than as required in clauses (1) and (2), distributions in any form of modifications or other derivative works +* may not use the "OpenStudio" trademark, "OS", "os", or any other confusingly similar designation without specific prior +* written permission from Alliance for Sustainable Energy, LLC. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE UNITED STATES GOVERNMENT, OR THE UNITED +* STATES DEPARTMENT OF ENERGY, NOR ANY OF THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF +* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +***********************************************************************************************************************/ + +#include + +#include "ModelFixture.hpp" + +#include "../PythonPluginSearchPaths.hpp" +#include "../PythonPluginSearchPaths_Impl.hpp" + +using namespace openstudio; +using namespace openstudio::model; + +TEST_F(ModelFixture, PythonPluginSearchPaths_GettersSetters) { + Model m; + PythonPluginSearchPaths pythonPluginSearchPaths = m.getUniqueModelObject(); + + // Add Current Working Directory to Search Path: Required Boolean + EXPECT_TRUE(pythonPluginSearchPaths.setAddCurrentWorkingDirectorytoSearchPath(true)); + EXPECT_TRUE(pythonPluginSearchPaths.addCurrentWorkingDirectorytoSearchPath()); + EXPECT_TRUE(pythonPluginSearchPaths.setAddCurrentWorkingDirectorytoSearchPath(false)); + EXPECT_FALSE(pythonPluginSearchPaths.addCurrentWorkingDirectorytoSearchPath()); + + // Add Input File Directory to Search Path: Required Boolean + EXPECT_TRUE(pythonPluginSearchPaths.setAddInputFileDirectorytoSearchPath(true)); + EXPECT_TRUE(pythonPluginSearchPaths.addInputFileDirectorytoSearchPath()); + EXPECT_TRUE(pythonPluginSearchPaths.setAddInputFileDirectorytoSearchPath(false)); + EXPECT_FALSE(pythonPluginSearchPaths.addInputFileDirectorytoSearchPath()); + + // Add epin Environment Variable to Search Path: Required Boolean + EXPECT_TRUE(pythonPluginSearchPaths.setAddepinEnvironmentVariabletoSearchPath(true)); + EXPECT_TRUE(pythonPluginSearchPaths.addepinEnvironmentVariabletoSearchPath()); + EXPECT_TRUE(pythonPluginSearchPaths.setAddepinEnvironmentVariabletoSearchPath(false)); + EXPECT_FALSE(pythonPluginSearchPaths.addepinEnvironmentVariabletoSearchPath()); + + // Search Paths + EXPECT_TRUE(pythonPluginSearchPaths.searchPaths().empty()); + + EXPECT_TRUE(pythonPluginSearchPaths.addSearchPath("/example/search/path/1")); + ASSERT_EQ(1u, pythonPluginSearchPaths.searchPaths().size()); + EXPECT_EQ("/example/search/path/1", pythonPluginSearchPaths.searchPaths()[0]); + + EXPECT_TRUE(pythonPluginSearchPaths.addSearchPath("/example/search/path/1")); + ASSERT_EQ(1u, pythonPluginSearchPaths.searchPaths().size()); + EXPECT_TRUE(pythonPluginSearchPaths.addSearchPath("/example/search/path/2")); + ASSERT_EQ(2u, pythonPluginSearchPaths.searchPaths().size()); + EXPECT_EQ("/example/search/path/1", pythonPluginSearchPaths.searchPaths()[0]); + EXPECT_EQ("/example/search/path/2", pythonPluginSearchPaths.searchPaths()[1]); + + std::vector searchPaths({"path/3", "path/4", "path/5", "path/5"}); + EXPECT_TRUE(pythonPluginSearchPaths.setSearchPaths(searchPaths)); + ASSERT_EQ(3u, pythonPluginSearchPaths.searchPaths().size()); + EXPECT_EQ("path/3", pythonPluginSearchPaths.searchPaths()[0]); + EXPECT_EQ("path/4", pythonPluginSearchPaths.searchPaths()[1]); + EXPECT_EQ("path/5", pythonPluginSearchPaths.searchPaths()[2]); + + pythonPluginSearchPaths.clearSearchPaths(); + EXPECT_EQ(0u, pythonPluginSearchPaths.searchPaths().size()); +} diff --git a/src/model/test/UniqueModelObject_GTest.cpp b/src/model/test/UniqueModelObject_GTest.cpp index c8ca196778..0c2e6c9aa7 100644 --- a/src/model/test/UniqueModelObject_GTest.cpp +++ b/src/model/test/UniqueModelObject_GTest.cpp @@ -54,6 +54,8 @@ #include "../OutputConstructions_Impl.hpp" #include "../PerformancePrecisionTradeoffs.hpp" #include "../PerformancePrecisionTradeoffs_Impl.hpp" +#include "../PythonPluginSearchPaths.hpp" +#include "../PythonPluginSearchPaths_Impl.hpp" #include "../RadianceParameters.hpp" #include "../RadianceParameters_Impl.hpp" #include "../RunPeriod.hpp" @@ -625,6 +627,31 @@ TEST_F(ModelFixture, PerformancePrecisionTradeoffs_UniqueModelObject_Clone) { EXPECT_EQ("! Custom Object", performancePrecisionTradeoffsClone2.comment()); } +TEST_F(ModelFixture, PythonPluginSearchPaths_UniqueModelObject_Clone) { + // create a model to use + Model model; + + // Get the Unique ModelObject + EXPECT_FALSE(model.getOptionalUniqueModelObject()); + auto pythonPluginSearchPaths = model.getUniqueModelObject(); + EXPECT_TRUE(model.getOptionalUniqueModelObject()); + // We use a comment to see if cloning to another model works + pythonPluginSearchPaths.setComment("Custom Object"); + + // clone it into the same model + auto pythonPluginSearchPathsClone = pythonPluginSearchPaths.clone(model).cast(); + // UniqueModelObject: should be the same as the original + EXPECT_EQ(pythonPluginSearchPaths, pythonPluginSearchPathsClone); + EXPECT_EQ("! Custom Object", pythonPluginSearchPathsClone.comment()); + + // clone it into a different model + Model model2; + EXPECT_FALSE(model2.getOptionalUniqueModelObject()); + auto pythonPluginSearchPathsClone2 = pythonPluginSearchPaths.clone(model2).cast(); + EXPECT_TRUE(model2.getOptionalUniqueModelObject()); + EXPECT_EQ("! Custom Object", pythonPluginSearchPathsClone2.comment()); +} + TEST_F(ModelFixture, RadianceParameters_UniqueModelObject_Clone) { // create a model to use Model model;