From fcbee21199f75decd25eb50bd905ddbb5fc8d2eb Mon Sep 17 00:00:00 2001 From: Guille Date: Thu, 12 Nov 2020 13:50:43 -0500 Subject: [PATCH] Bugfixing in idf_helper, changing the method exposure so now adding surfaces add materials and layers automatically, added surface getter for ThermalBoundary --- .../attributes/thermal_boundary.py | 9 ++ helpers/constants.py | 2 +- helpers/idf_helper.py | 112 ++++++++++++------ tests/test_idf.py | 25 ++-- 4 files changed, 95 insertions(+), 53 deletions(-) diff --git a/city_model_structure/attributes/thermal_boundary.py b/city_model_structure/attributes/thermal_boundary.py index 08bb0c83..110eb82f 100644 --- a/city_model_structure/attributes/thermal_boundary.py +++ b/city_model_structure/attributes/thermal_boundary.py @@ -9,6 +9,7 @@ from city_model_structure.attributes.layer import Layer from city_model_structure.attributes.thermal_opening import ThermalOpening from city_model_structure.attributes.thermal_zone import ThermalZone from helpers.configuration_helper import ConfigurationHelper +from city_model_structure.attributes.surface import Surface class ThermalBoundary: @@ -30,6 +31,14 @@ class ThermalBoundary: self._shortwave_reflectance = 1 - self._outside_solar_absorptance self._construction_name = None + @property + def surface(self) -> Surface: + """ + Get the surface that belongs to the thermal boundary + :return: Surface + """ + return self._surface + @property def delimits(self) -> List[ThermalZone]: """ diff --git a/helpers/constants.py b/helpers/constants.py index e7e50f18..dafead3c 100644 --- a/helpers/constants.py +++ b/helpers/constants.py @@ -9,4 +9,4 @@ time_scale = { 'month': 'month', 'year': 'year' } -roughness="MediumRough" +roughness = "MediumRough" diff --git a/helpers/idf_helper.py b/helpers/idf_helper.py index bbcffd62..c6698a4e 100644 --- a/helpers/idf_helper.py +++ b/helpers/idf_helper.py @@ -7,12 +7,16 @@ from geomeppy import IDF import os import esoreader from pathlib import Path -import helpers +import helpers.constants as cte + class IdfHelper: _THERMOSTAT = 'HVACTEMPLATE:THERMOSTAT' _IDEAL_LOAD_AIR_SYSTEM = 'HVACTEMPLATE:ZONE:IDEALLOADSAIRSYSTEM' _SURFACE = 'BUILDINGSURFACE:DETAILED' + _CONSTRUCTION = 'CONSTRUCTION' + _MATERIAL = 'MATERIAL' + _MATERIAL_NOMASS = 'MATERIAL:NOMASS' idf_surfaces = { 'Wall': 'wall', @@ -31,34 +35,67 @@ class IdfHelper: self._idf = IDF(self._idf_file_path, self._epw_file_path) self._idf.epw = self._epw_file_path - def add_material(self, layer): - materials = self._idf.newidfobject("MATERIAL".upper()) - materials.Name = layer.material.name - materials.Roughness = helpers.roughness - materials.Thickness = layer.thickness - materials.Conductivity = layer.material.conductivity - materials.Density = layer.material.density - materials.Specific_Heat = layer.material.specific_heat - materials.Thermal_Absorptance = layer.material.thermal_absorptance - materials.Solar_Absorptance = layer.material.solar_absorptance - materials.Visible_Absorptance = layer.material.visible_absorptance + def _add_material(self, layer): + for material in self._idf.idfobjects[self._MATERIAL]: + if material.Name == layer.material.name: + return + for material in self._idf.idfobjects[self._MATERIAL_NOMASS]: + if material.Name == layer.material.name: + return + if layer.material.no_mass: + self._idf.newidfobject(self._MATERIAL_NOMASS, + Name=layer.material.name, + Roughness=cte.roughness, + Thermal_Resistance=layer.material.thermal_resistance, + Thermal_Absorptance=layer.material.thermal_absorptance, + Solar_Absorptance=layer.material.solar_absorptance, + Visible_Absorptance=layer.material.visible_absorptance + ) + else: + self._idf.newidfobject(self._MATERIAL, + Name=layer.material.name, + Roughness=cte.roughness, + Thickness=layer.thickness, + Conductivity=layer.material.conductivity, + Density=layer.material.density, + Specific_Heat=layer.material.specific_heat, + Thermal_Absorptance=layer.material.thermal_absorptance, + Solar_Absorptance=layer.material.solar_absorptance, + Visible_Absorptance=layer.material.visible_absorptance + ) + print(f"Add material {layer.material.name}") - def add_construction(self, thermal_boundary): - for boundary in thermal_boundary: - for layer in boundary: - if len(layer) == 2: - self._idf.newidfobject("CONSTRUCTION", Name=boundary.construction_name, - Outside_Layer=layer[0].material.name, Layer_2=layer[1].material.name) - elif len(layer) == 3: - self._idf.newidfobject("CONSTRUCTION", Name=boundary.construction_name, - Outside_Layer=layer[0].material.name, Layer_2=layer[1].material.name, Layer_3=layer[2].material.name) - elif len(layer) == 4: - self._idf.newidfobject("CONSTRUCTION", Name=boundary.construction_name, - Outside_Layer=layer[0].material.name, Layer_2=layer[1].material.name, Layer_3=layer[2].material.name, Layer_4=layer[3].material.name) - else: - print("Could not find the true construction") + def _add_construction(self, thermal_boundary): + for construction in self._idf.idfobjects[self._CONSTRUCTION]: + if construction.Name == thermal_boundary.construction_name: + return + for layer in thermal_boundary.layers: + self._add_material(layer) + layers = thermal_boundary.layers + if len(layers) == 2: + self._idf.newidfobject(self._CONSTRUCTION, Name=thermal_boundary.construction_name, + Outside_Layer=layers[0].material.name, Layer_2=layers[1].material.name) + debug = f'{self._CONSTRUCTION}, Name={thermal_boundary.construction_name}, Outside_Layer={layers[0].material.name}' \ + f', Layer_2={layers[1].material.name}' + elif len(thermal_boundary.layers) == 3: + self._idf.newidfobject(self._CONSTRUCTION, Name=thermal_boundary.construction_name, + Outside_Layer=layers[0].material.name, Layer_2=layers[1].material.name, + Layer_3=layers[2].material.name) + debug = f'{self._CONSTRUCTION}, Name={thermal_boundary.construction_name}, ' \ + f'Outside_Layer={layers[0].material.name}, Layer_2={layers[1].material.name}, ' \ + f'Layer_3={layers[2].material.name}' + elif len(thermal_boundary.layers) == 4: + self._idf.newidfobject(self._CONSTRUCTION, Name=thermal_boundary.construction_name, + Outside_Layer=layers[0].material.name, Layer_2=layers[1].material.name, + Layer_3=layers[2].material.name, Layer_4=layers[3].material.name) + debug = f'{self._CONSTRUCTION}, Name={thermal_boundary.construction_name}, ' \ + f'Outside_Layer={layers[0].material.name}, Layer_2={layers[1].material.name}, ' \ + f'Layer_3={layers[2].material.name}, Layer_4={layers[3].material.name}' + else: + raise Exception("This libs version cannot handle more than 4 layers") + print(debug) - def add_heating_system(self, building): + def _add_heating_system(self, building): for usage_zone in building.usage_zones: thermostat_name = f'Thermostat {building.name}' # todo: this will fail for more than one usage zone @@ -71,7 +108,7 @@ class IdfHelper: if zone.Name.find(building.name) != -1: self._idf.newidfobject(self._IDEAL_LOAD_AIR_SYSTEM, Zone_Name=zone.Name, - Template_Thermostat_Name=static_thermostat.Name,) + Template_Thermostat_Name=static_thermostat.Name, ) @staticmethod def _matrix_to_list(points): @@ -93,7 +130,7 @@ class IdfHelper: _points = IdfHelper._matrix_to_2d_list(building.foot_print.points) self._idf.add_block(name=building.name, coordinates=_points, height=building.max_height, num_stories=int(building.storeys_above_ground)) - self.add_heating_system(building) + self._add_heating_system(building) self._idf.intersect_match() def add_surfaces(self, building): @@ -101,14 +138,16 @@ class IdfHelper: for zone in building.thermal_zones: zone_name = f'Building {building.name} usage zone {index}' self._idf.newidfobject('ZONE', Name=zone_name) - for surface in zone.surfaces: - idf_surface = self.idf_surfaces[surface.type] - wall = self._idf.newidfobject(self._SURFACE, Name=f'{building.name}-{surface.name}', Surface_Type=idf_surface, - Zone_Name=zone_name) - coordinates = IdfHelper._matrix_to_list(surface.points) + for boundary in zone.bounded: + self._add_construction(boundary) + idf_surface = self.idf_surfaces[boundary.surface.type] + wall = self._idf.newidfobject(self._SURFACE, Name=f'{building.name}-{boundary.surface.name}', + Surface_Type=idf_surface, Zone_Name=zone_name, + Construction_Name=boundary.construction_name) + coordinates = IdfHelper._matrix_to_list(boundary.surface.points) wall.setcoords(coordinates) index += 1 - self.add_heating_system(building) + self._add_heating_system(building) self._idf.intersect_match() def run(self, output_directory, window_ratio=0.35, display_render=False, output_prefix=None, keep_file=None): @@ -117,12 +156,13 @@ class IdfHelper: self._idf.translate_to_origin() if display_render: self._idf.view_model() -# Run + # Run self._idf.newidfobject("OUTPUT:METER", Key_Name="Heating:DistrictHeating", Reporting_Frequency="hourly") self._idf.newidfobject("OUTPUT:METER", Key_Name="Cooling:DistrictCooling", Reporting_Frequency="hourly") idf_path = None if keep_file is not None: idf_path = (keep_file / 'in.idf').resolve() + print(str(idf_path)) self._idf.saveas(str(idf_path)) if idf_path is None: idf_path = (Path(__file__).parent / 'in.idf').resolve() diff --git a/tests/test_idf.py b/tests/test_idf.py index cd3a041d..0a4eba77 100644 --- a/tests/test_idf.py +++ b/tests/test_idf.py @@ -39,23 +39,17 @@ class TestIdf(TestCase): idd_file_path = (self._example_path / 'energy+.idd').resolve() idf_file_path = (self._example_path / 'minimal.idf').resolve() epw_file_path = (self._example_path / 'montreal.epw').resolve() - _idf = IdfHelper(idf_file_path, idd_file_path, epw_file_path) + idf = IdfHelper(idf_file_path, idd_file_path, epw_file_path) city = self._get_city() for building in city.buildings: - _idf.add_block(building) - for thermal_zone in building.thermal_zones: - for bound in thermal_zone.bounded: - for layer in bound.layers: - print(layer.thickness) - print(layer.material.density) - + idf.add_block(building) test_prefix = 'test_idf_blocks' - _idf.run(self._output_path, output_prefix=test_prefix, keep_file=self._output_path, display_render=True) + idf.run(self._output_path, output_prefix=test_prefix, keep_file=self._output_path) eso_file_path = (self._output_path / f'{test_prefix}out.eso') - heating, cooling = _idf.read_eso(str(eso_file_path)) + heating, cooling = idf.read_eso(str(eso_file_path)) self.assertEqual(len(heating), len(cooling), "Cooling and Heating doesn't contains the same amount of values") self.assertNotEqual(len(heating), 0, "Cooling and Heating series are empty") - file_list = glob.glob(Path(self._output_path / '*').resolve()) + file_list = glob.glob(str(Path(self._output_path / '*').resolve())) for file_path in file_list: os.remove(file_path) @@ -64,15 +58,14 @@ class TestIdf(TestCase): idf_file_path = (self._example_path / 'minimal.idf').resolve() epw_file_path = (self._example_path / 'montreal.epw').resolve() - _idf = IdfHelper(idf_file_path, idd_file_path, epw_file_path) + idf = IdfHelper(idf_file_path, idd_file_path, epw_file_path) city = self._get_city() for building in city.buildings: - _idf.add_surfaces(building) - break + idf.add_surfaces(building) test_prefix = 'test_idf_blocks' - _idf.run(self._output_path, output_prefix=test_prefix, keep_file=self._output_path) + idf.run(self._output_path, output_prefix=test_prefix, keep_file=self._output_path) eso_file_path = (self._output_path / f'{test_prefix}out.eso') - heating, cooling = _idf.read_eso(str(eso_file_path)) + heating, cooling = idf.read_eso(str(eso_file_path)) self.assertEqual(len(heating), len(cooling), "Cooling and Heating doesn't contains the same amount of values") self.assertNotEqual(len(heating), 0, "Cooling and Heating series are empty") file_list = glob.glob(str(Path(self._output_path / '*').resolve()))