From e9293909e51013962ebabb0dba8130275b97f7b2 Mon Sep 17 00:00:00 2001 From: David Date: Thu, 25 May 2023 17:46:30 +0200 Subject: [PATCH 001/125] first implementations for rough idea (based on current 537 of main repo branch) --- bim2sim/plugins/PluginModelica/__init__.py | 0 .../bim2sim_modelica/__init__.py | 57 +++ .../bim2sim_modelica/models/__init__.py | 398 ++++++++++++++++++ .../bim2sim_modelica/models/buildings.py | 18 + bim2sim/plugins/PluginSpawnOfEP/__init__.py | 0 .../PluginSpawnOfEP/bim2sim_spawn/__init__.py | 26 ++ .../bim2sim_spawn/models/__init__.py | 398 ++++++++++++++++++ .../bim2sim_spawn/tasks/export_co_sim.py | 16 + bim2sim/simulation_type.py | 12 + 9 files changed, 925 insertions(+) create mode 100644 bim2sim/plugins/PluginModelica/__init__.py create mode 100644 bim2sim/plugins/PluginModelica/bim2sim_modelica/__init__.py create mode 100644 bim2sim/plugins/PluginModelica/bim2sim_modelica/models/__init__.py create mode 100644 bim2sim/plugins/PluginModelica/bim2sim_modelica/models/buildings.py create mode 100644 bim2sim/plugins/PluginSpawnOfEP/__init__.py create mode 100644 bim2sim/plugins/PluginSpawnOfEP/bim2sim_spawn/models/__init__.py create mode 100644 bim2sim/plugins/PluginSpawnOfEP/bim2sim_spawn/tasks/export_co_sim.py diff --git a/bim2sim/plugins/PluginModelica/__init__.py b/bim2sim/plugins/PluginModelica/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/bim2sim/plugins/PluginModelica/bim2sim_modelica/__init__.py b/bim2sim/plugins/PluginModelica/bim2sim_modelica/__init__.py new file mode 100644 index 0000000000..5b42d2e073 --- /dev/null +++ b/bim2sim/plugins/PluginModelica/bim2sim_modelica/__init__.py @@ -0,0 +1,57 @@ +import re +from ast import literal_eval + +from bim2sim.export.modelica import standardlibrary +from bim2sim.kernel.element import Material +from bim2sim.kernel.elements import hvac as hvac_elements +from bim2sim.plugins import Plugin +from bim2sim.plugins.PluginAixLib.bim2sim_aixlib.models import AixLib +from bim2sim.task import base, common, hvac +from bim2sim.simulation_type import PlantSimulation + + +class LoadLibrariesAixLib(base.ITask): + """Load AixLib library for export""" + touches = ('libraries', ) + + def run(self, **kwargs): + return (standardlibrary.StandardLibrary, AixLib), + + +class PluginModelica(Plugin): + name = 'Modelica' + default_workflow = PlantSimulation + export_library = 'AixLib' # todo load this + allowed_workflows = [PlantSimulation] + tasks = {LoadLibrariesAixLib} + elements = {*hvac_elements.items, Material} + default_tasks = [ + common.LoadIFC, + hvac.CheckIfcHVAC, + common.CreateElements, + hvac.ConnectElements, + hvac.MakeGraph, + hvac.ExpansionTanks, + hvac.Reduce, + hvac.DeadEnds, + LoadLibrariesAixLib, + hvac.Export, + ] + + def create_modelica_table_from_list(self, curve): + """ + + :param curve: + :return: + """ + curve = literal_eval(curve) + for key, value in curve.iteritems(): + # add first and last value to make sure there is a constant + # behaviour before and after the given heating curve + value = [value[0] - 5, value[1]] + value + [value[-2] + 5, + value[-1]] + # transform to string and replace every second comma with a + # semicolon to match_graph modelica syntax + value = str(value) + value = re.sub('(,[^,]*),', r'\1;', value) + setattr(self, key, value) diff --git a/bim2sim/plugins/PluginModelica/bim2sim_modelica/models/__init__.py b/bim2sim/plugins/PluginModelica/bim2sim_modelica/models/__init__.py new file mode 100644 index 0000000000..0898dd739a --- /dev/null +++ b/bim2sim/plugins/PluginModelica/bim2sim_modelica/models/__init__.py @@ -0,0 +1,398 @@ +"""Package for Python representations of HKESim models""" +import bim2sim.kernel.aggregation as aggregation +from bim2sim.export import modelica +from bim2sim.kernel.elements import hvac +from bim2sim.kernel.units import ureg + + +class AixLib(modelica.Instance): + library = "AixLib" + + +class Boiler(AixLib): + path = "AixLib.Fluid.BoilerCHP.BoilerNotManufacturer" + represents = hvac.Boiler + + def __init__(self, element): + super().__init__(element) + + def request_params(self): + + self.params["redeclare package Medium"] = 'AixLib.Media.Water' + self.request_param("dT_water", + self.check_numeric(min_value=0 * ureg.kelvin), + "dTWaterNom") + self.request_param("return_temperature", + self.check_numeric(min_value=0 * ureg.celsius), + "TRetNom") + self.request_param("rated_power", + self.check_numeric(min_value=0 * ureg.kilowatt), + "QNom") + self.request_param("min_PLR", + self.check_numeric(min_value=0 * ureg.dimensionless), + "PLRMin") + + def get_port_name(self, port): + try: + index = self.element.ports.index(port) + except ValueError: + # unknown port + index = -1 + if port.verbose_flow_direction == 'SINK': + return 'port_a' + if port.verbose_flow_direction == 'SOURCE': + return 'port_b' + else: + return super().get_port_name(port) # ToDo: Gas connection + + +class Radiator(AixLib): + path = "AixLib.Fluid.HeatExchangers.Radiators.RadiatorEN442_2" + represents = [hvac.SpaceHeater] + + def request_params(self): + self.request_param("rated_power", + self.check_numeric(min_value=0 * ureg.kilowatt), + "Q_flow_nominal") + # self.params["T_nominal"] = (80, 60, 20) + + +class Pump(AixLib): + path = "AixLib.Fluid.Movers.SpeedControlled_y" + represents = [hvac.Pump] + + def request_params(self): + self.params['redeclare package Medium'] = 'AixLib.Media.Water' + self.request_param( + "rated_mass_flow", + self.check_numeric(min_value=0 * ureg['kg/second'])) + self.request_param( + "rated_pressure_difference", + self.check_numeric(min_value=0 * ureg['newton/m**2'])) + # generic pump operation curve + # todo renders as "V_flow" only in Modelica + self.params["per.pressure"] =\ + f"V_flow={{0," \ + f" {self.element.rated_mass_flow}/1000," \ + f" {self.element.rated_mass_flow} /1000/0.7}}," \ + f" dp={{ {self.element.rated_pressure_difference} / 0.7," \ + f" {self.element.rated_pressure_difference}," \ + f"0}}" + + # ToDo remove decisions from tests if not asking this anymore + # self.request_param("rated_height", + # self.check_numeric(min_value=0 * ureg.meter), + # "head_set") + # self.request_param("rated_volume_flow", + # self.check_numeric(min_value=0 * ureg['m**3/hour']), + # "Vflow_set", 'm**3/hour') + # self.request_param("rated_power", + # self.check_numeric(min_value=0 * ureg.watt), + # "P_norm") + + def get_port_name(self, port): + try: + index = self.element.ports.index(port) + except ValueError: + # unknown port + index = -1 + if index == 0: + return "port_a" + elif index == 1: + return "port_b" + else: + return super().get_port_name(port) + + +class Consumer(AixLib): + path = "AixLib.Systems.HydraulicModules.SimpleConsumer" + represents = [aggregation.Consumer] + + def __init__(self, element): + self.check_volume = self.check_numeric(min_value=0 * ureg.meter ** 3) + super().__init__(element) + + def request_params(self): + self.params['redeclare package Medium'] = 'AixLib.Media.Water' + self.params["functionality"] = '\"Q_flow_fixed\"' + if self.params["functionality"] == '\"Q_flow_fixed\"': + self.params["Q_flow_fixed"] = self.element.rated_power + self.params["demand_type"] = "1" + + # self.request_param("demand_type", + # self.check_none(), + # "demandType") + + self.params["hasFeedback"] = self.element.t_control + if self.element.t_control: + self.params["TInSetValue"] = self.element.flow_temperature + self.params["TInSetSou"] = "AixLib.Systems.ModularEnergySystems." \ + "Modules.ModularConsumer.Types.InputType." \ + "Constant" + + self.params["hasPump"] = self.element.has_pump + if self.element.has_pump: + self.params["TOutSetValue"] = self.element.return_temperature + self.params["TOutSetSou"] = "AixLib.Systems.ModularEnergySystems." \ + "Modules.ModularConsumer.Types.InputType." \ + "Constant" + # self.params["dT_nom"] = self.element.dT_water + # self.params["capacity"] = self.element.heat_capacity + # self.request_param("rated_power", + # self.check_numeric(min_value=0 * ureg.kilowatt), + # "Q_flow_nom") + # self.request_param("dT_water", + # self.check_numeric(min_value=0 * ureg.kelvin), + # "dT_nom") + # self.request_param("heat_capacity", + # self.check_numeric( + # min_value=0 * ureg.joule / ureg.kelvin), + # "capacity") + + def get_port_name(self, port): + try: + index = self.element.ports.index(port) + except ValueError: + # unknown port + index = -1 + if index == 1: + return "port_a" + elif index == 0: + return "port_b" + else: + return super().get_port_name(port) + + +class ConsumerHeatingDistributorModule(AixLib): + path = "AixLib.Systems.ModularEnergySystems.Modules.ModularConsumer." \ + "ConsumerDistributorModule" + represents = [aggregation.ConsumerHeatingDistributorModule] + + def __init__(self, element): + self.check_volume = self.check_numeric(min_value=0 * ureg.meter ** 3) + super().__init__(element) + + def request_params(self): + n_consumers = len(self.element.whitelist_elements) + # Parameters + self.params["T_start"] = self.element.return_temperature + self.params['redeclare package Medium'] = 'AixLib.Media.Water' + # Consumer Design + self.params['n_consumers'] = n_consumers + self.params["functionality"] = "\"Q_flow_fixed\"" + self.request_param("demand_type", + self.check_none(), + "demandType") + self.request_param("heat_capacity", + self.check_numeric( + min_value=0 * ureg.joule / ureg.kelvin), + "capacity") + + # Nominal Conditions + # todo q_flow_fixed is just dummy value as Modelica model + # needs it for any reason, check on Modelica side + self.request_param("rated_power", + self.check_numeric(min_value=0 * ureg.kilowatt), + "Q_flow_fixed") + self.params["Q_flow_nom"] = self.element.rated_power + self.request_param("dT_water", + self.check_numeric(min_value=0 * ureg.kelvin), + "dT_nom") + + # Flow temperature control (Mixture Valve) + self.params["hasFeedback"] = self.element.t_control + self.params["TInSetSou"] = "AixLib.Systems.ModularEnergySystems." \ + "Modules.ModularConsumer.Types.InputType." \ + "Constant" + self.params["TInSet"] = self.element.flow_temperature + self.params["k_ControlConsumerValve"] = [0.1] * n_consumers + self.params["Ti_ControlConsumerValve"] = [10] * n_consumers + self.params["dp_Valve"] = [1000] * n_consumers + + # Return temperature control (Pump) + self.params["hasPump"] = self.element.has_pump + self.params["TOutSet"] = self.element.return_temperature + self.params["TOutSetSou"] = "AixLib.Systems.ModularEnergySystems." \ + "Modules.ModularConsumer.Types.InputType." \ + "Constant" + self.params["k_ControlConsumerPump"] = [0.1] * n_consumers + self.params["Ti_ControlConsumerPump"] = [10] * n_consumers + self.params["dp_nominalConPump"] = [10000] * n_consumers + + def get_port_name(self, port): + try: + index = self.element.ports.index(port) + except ValueError: + # unknown port + index = -1 + if port.verbose_flow_direction == 'SINK': + return 'port_a' + if port.verbose_flow_direction == 'SOURCE': + return 'port_b' + else: + return super().get_port_name(port) + + +class BoilerAggregation(AixLib): + """Modelica AixLib representation of the GeneratorOneFluid aggregation.""" + path = "AixLib.Systems.ModularEnergySystems.Modules.ModularBoiler." \ + "ModularBoiler" + represents = [aggregation.GeneratorOneFluid] + + def __init__(self, element): + super().__init__(element) + + def request_params(self): + + self.params["redeclare package Medium"] = 'AixLib.Media.Water' + + # System setup + self.params["Pump"] = self.element.has_pump + self.params["hasFeedback"] = self.element.has_bypass + self.request_param("rated_power", + self.check_numeric(min_value=0 * ureg.kilowatt), + "QNom") + self.request_param("min_PLR", + self.check_numeric(min_value=0 * ureg.dimensionless), + "PLRMin") + + # Nominal condition + self.request_param("return_temperature", + self.check_numeric(min_value=0 * ureg.celsius), + "TRetNom") + self.request_param("dT_water", + self.check_numeric(min_value=0 * ureg.kelvin), + "dTWaterNom") + + # Feedback + self.params["dp_Valve"] = 10000 # Todo get from hydraulic circuit + + def get_port_name(self, port): + try: + index = self.element.ports.index(port) + except ValueError: + # unknown port + index = -1 + if port.verbose_flow_direction == 'SINK': + return 'port_a' + if port.verbose_flow_direction == 'SOURCE': + return 'port_b' + else: + return super().get_port_name(port) + + +class Distributor(AixLib): + path = "AixLib.Fluid.HeatExchangers.ActiveWalls.Distributor" + represents = [hvac.Distributor] + + def __init__(self, element): + super().__init__(element) + + def request_params(self): + self.params['redeclare package Medium'] = 'AixLib.Media.Water' + self.params['n'] = self.get_n_ports() + self.request_param("rated_mass_flow", + self.check_numeric(min_value=0 * ureg.kg / ureg.s), + "m_flow_nominal") + + def get_n_ports(self): + ports = {port.guid: port for port in self.element.ports if + port.connection} + return len(ports)/2 - 1 + + def get_port_name(self, port): + try: + index = self.element.ports.index(port) + except ValueError: + # unknown port + index = -1 + if (index % 2) == 0: + return "port_a_consumer" + elif (index % 2) == 1: + return "port_b_consumer" + else: + return super().get_port_name(port) + + @staticmethod + def get_new_port_name(distributor, other_inst, distributor_port, + other_port, distributors_n, distributors_ports): + if distributor not in distributors_n: + distributors_n[distributor] = 0 + distributors_ports[distributor] = {} + distributors_n[distributor] += 1 + if type(other_inst.element) is aggregation.GeneratorOneFluid: + list_name = distributor_port.split('.')[:-1] + \ + ['mainReturn' if 'port_a' in other_port + else 'mainFlow'] + else: + port_key = other_port.split('.')[-1] + if port_key not in distributors_ports[distributor]: + distributors_ports[distributor][port_key] = 0 + distributors_ports[distributor][port_key] += 1 + n = distributors_ports[distributor][port_key] + list_name = distributor_port.split('.')[:-1] + \ + ['flowPorts[%d]' % n if 'port_a' in other_port + else 'returnPorts[%d]' % n] + return '.'.join(list_name) + + +class ThreeWayValve(AixLib): + path = "AixLib.Fluid.Actuators.Valves.ThreeWayEqualPercentageLinear" + represents = [hvac.ThreeWayValve] + + def __init__(self, element): + super().__init__(element) + + def request_params(self): + self.params['redeclare package Medium'] = 'AixLib.Media.Water' + + def get_port_name(self, port): + try: + index = self.element.ports.index(port) + except ValueError: + # unknown port + index = -1 + if index == 0: + return "port_1" + elif index == 1: + return "port_2" + elif index == 2: + return "port_3" + else: + return super().get_port_name(port) + + +class Heatpump(AixLib): + path = "AixLib.Fluid.HeatPumps.HeatPump" + represents = [hvac.HeatPump] + + def __init__(self, element): + super().__init__(element) + + def request_params(self): + self.params['redeclare package Medium_con'] = 'AixLib.Media.Water' + self.params['redeclare package Medium_eva'] = 'AixLib.Media.Water' + + def get_port_name(self, port): + # TODO heat pumps might have 4 ports (if source is modeld in BIM) + if port.verbose_flow_direction == 'SINK': + return 'port_a' + if port.verbose_flow_direction == 'SOURCE': + return 'port_b' + else: + return super().get_port_name(port) + + +class Chiller(AixLib): + represents = [hvac.Chiller] + pass + + +class CHP(AixLib): + represents = [hvac.CHP] + pass + + +# class Storage(AixLib): +# represents = [hvac.Storage] +# pass diff --git a/bim2sim/plugins/PluginModelica/bim2sim_modelica/models/buildings.py b/bim2sim/plugins/PluginModelica/bim2sim_modelica/models/buildings.py new file mode 100644 index 0000000000..656d990c46 --- /dev/null +++ b/bim2sim/plugins/PluginModelica/bim2sim_modelica/models/buildings.py @@ -0,0 +1,18 @@ +from bim2sim.export import modelica +from bim2sim.kernel.elements import bps + + +class Buildings(modelica.Instance): + library = "Buildings" + + +class BuildingThermalZone(Buildings): + path = "Buildings.ThermalZones.EnergyPlus_9_6_0.ThermalZone" + represents = bps.ThermalZone + + +class Floor(Buildings): + ... + # TODO + # Sum all zones and connect with infiltration + # connect all zones (maybe later) diff --git a/bim2sim/plugins/PluginSpawnOfEP/__init__.py b/bim2sim/plugins/PluginSpawnOfEP/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/bim2sim/plugins/PluginSpawnOfEP/bim2sim_spawn/__init__.py b/bim2sim/plugins/PluginSpawnOfEP/bim2sim_spawn/__init__.py index f1865e3d08..647e272223 100644 --- a/bim2sim/plugins/PluginSpawnOfEP/bim2sim_spawn/__init__.py +++ b/bim2sim/plugins/PluginSpawnOfEP/bim2sim_spawn/__init__.py @@ -8,6 +8,7 @@ from bim2sim.plugins.PluginModelica.bim2sim_modelica import PluginModelica from bim2sim.plugins.PluginEnergyPlus.bim2sim_energyplus import PluginEnergyPlus +<<<<<<< Updated upstream from bim2sim.task import base, common, hvac from bim2sim.simulation_type import CoSimulation @@ -60,3 +61,28 @@ def create_modelica_table_from_list(self, curve): value = str(value) value = re.sub('(,[^,]*),', r'\1;', value) setattr(self, key, value) +======= +from bim2sim.simulation_type import CoSimulation + + +class PluginSpawnOfEP(Plugin): + name = 'SpawnOfEP' + default_workflow = CoSimulation # todo: this is currently empty + + export_hvac_library = 'AixLib' # todo: this has currently no impact + allowed_workflows = [CoSimulation] + + # combine elements from both Plugins + elements = set() + elements.update(PluginModelica.elements) + elements.update(PluginEnergyPlus.elements) + + # combine tasks from both Plugins + default_tasks = [] + default_tasks.extend(PluginModelica.default_tasks) + default_tasks.extend(PluginEnergyPlus.default_tasks) + # make sure that tasks only occur once + # todo: this won't work always. We need to separate tasks that occur in + # multiple Plugins (LoadIFC, CheckIFC and CreateElements) from the rest + default_tasks = set(default_tasks) +>>>>>>> Stashed changes diff --git a/bim2sim/plugins/PluginSpawnOfEP/bim2sim_spawn/models/__init__.py b/bim2sim/plugins/PluginSpawnOfEP/bim2sim_spawn/models/__init__.py new file mode 100644 index 0000000000..701befb847 --- /dev/null +++ b/bim2sim/plugins/PluginSpawnOfEP/bim2sim_spawn/models/__init__.py @@ -0,0 +1,398 @@ +"""Package for Python representations of HKESim models""" +import bim2sim.kernel.aggregation as aggregation +from bim2sim.export import modelica +from bim2sim.kernel.elements import hvac +from bim2sim.kernel.units import ureg + + +class AixLib(modelica.Instance): + library = "AixLib" + + +class EnergyPlusFMU(AixLib): + path = "AixLib.Fluid.BoilerCHP.BoilerNotManufacturer" + represents = hvac.Boiler + + def __init__(self, element): + super().__init__(element) + + def request_params(self): + + self.params["redeclare package Medium"] = 'AixLib.Media.Water' + self.request_param("dT_water", + self.check_numeric(min_value=0 * ureg.kelvin), + "dTWaterNom") + self.request_param("return_temperature", + self.check_numeric(min_value=0 * ureg.celsius), + "TRetNom") + self.request_param("rated_power", + self.check_numeric(min_value=0 * ureg.kilowatt), + "QNom") + self.request_param("min_PLR", + self.check_numeric(min_value=0 * ureg.dimensionless), + "PLRMin") + + def get_port_name(self, port): + try: + index = self.element.ports.index(port) + except ValueError: + # unknown port + index = -1 + if port.verbose_flow_direction == 'SINK': + return 'port_a' + if port.verbose_flow_direction == 'SOURCE': + return 'port_b' + else: + return super().get_port_name(port) # ToDo: Gas connection + + +class Radiator(AixLib): + path = "AixLib.Fluid.HeatExchangers.Radiators.RadiatorEN442_2" + represents = [hvac.SpaceHeater] + + def request_params(self): + self.request_param("rated_power", + self.check_numeric(min_value=0 * ureg.kilowatt), + "Q_flow_nominal") + # self.params["T_nominal"] = (80, 60, 20) + + +class Pump(AixLib): + path = "AixLib.Fluid.Movers.SpeedControlled_y" + represents = [hvac.Pump] + + def request_params(self): + self.params['redeclare package Medium'] = 'AixLib.Media.Water' + self.request_param( + "rated_mass_flow", + self.check_numeric(min_value=0 * ureg['kg/second'])) + self.request_param( + "rated_pressure_difference", + self.check_numeric(min_value=0 * ureg['newton/m**2'])) + # generic pump operation curve + # todo renders as "V_flow" only in Modelica + self.params["per.pressure"] =\ + f"V_flow={{0," \ + f" {self.element.rated_mass_flow}/1000," \ + f" {self.element.rated_mass_flow} /1000/0.7}}," \ + f" dp={{ {self.element.rated_pressure_difference} / 0.7," \ + f" {self.element.rated_pressure_difference}," \ + f"0}}" + + # ToDo remove decisions from tests if not asking this anymore + # self.request_param("rated_height", + # self.check_numeric(min_value=0 * ureg.meter), + # "head_set") + # self.request_param("rated_volume_flow", + # self.check_numeric(min_value=0 * ureg['m**3/hour']), + # "Vflow_set", 'm**3/hour') + # self.request_param("rated_power", + # self.check_numeric(min_value=0 * ureg.watt), + # "P_norm") + + def get_port_name(self, port): + try: + index = self.element.ports.index(port) + except ValueError: + # unknown port + index = -1 + if index == 0: + return "port_a" + elif index == 1: + return "port_b" + else: + return super().get_port_name(port) + + +class Consumer(AixLib): + path = "AixLib.Systems.HydraulicModules.SimpleConsumer" + represents = [aggregation.Consumer] + + def __init__(self, element): + self.check_volume = self.check_numeric(min_value=0 * ureg.meter ** 3) + super().__init__(element) + + def request_params(self): + self.params['redeclare package Medium'] = 'AixLib.Media.Water' + self.params["functionality"] = '\"Q_flow_fixed\"' + if self.params["functionality"] == '\"Q_flow_fixed\"': + self.params["Q_flow_fixed"] = self.element.rated_power + self.params["demand_type"] = "1" + + # self.request_param("demand_type", + # self.check_none(), + # "demandType") + + self.params["hasFeedback"] = self.element.t_control + if self.element.t_control: + self.params["TInSetValue"] = self.element.flow_temperature + self.params["TInSetSou"] = "AixLib.Systems.ModularEnergySystems." \ + "Modules.ModularConsumer.Types.InputType." \ + "Constant" + + self.params["hasPump"] = self.element.has_pump + if self.element.has_pump: + self.params["TOutSetValue"] = self.element.return_temperature + self.params["TOutSetSou"] = "AixLib.Systems.ModularEnergySystems." \ + "Modules.ModularConsumer.Types.InputType." \ + "Constant" + # self.params["dT_nom"] = self.element.dT_water + # self.params["capacity"] = self.element.heat_capacity + # self.request_param("rated_power", + # self.check_numeric(min_value=0 * ureg.kilowatt), + # "Q_flow_nom") + # self.request_param("dT_water", + # self.check_numeric(min_value=0 * ureg.kelvin), + # "dT_nom") + # self.request_param("heat_capacity", + # self.check_numeric( + # min_value=0 * ureg.joule / ureg.kelvin), + # "capacity") + + def get_port_name(self, port): + try: + index = self.element.ports.index(port) + except ValueError: + # unknown port + index = -1 + if index == 1: + return "port_a" + elif index == 0: + return "port_b" + else: + return super().get_port_name(port) + + +class ConsumerHeatingDistributorModule(AixLib): + path = "AixLib.Systems.ModularEnergySystems.Modules.ModularConsumer." \ + "ConsumerDistributorModule" + represents = [aggregation.ConsumerHeatingDistributorModule] + + def __init__(self, element): + self.check_volume = self.check_numeric(min_value=0 * ureg.meter ** 3) + super().__init__(element) + + def request_params(self): + n_consumers = len(self.element.whitelist_elements) + # Parameters + self.params["T_start"] = self.element.return_temperature + self.params['redeclare package Medium'] = 'AixLib.Media.Water' + # Consumer Design + self.params['n_consumers'] = n_consumers + self.params["functionality"] = "\"Q_flow_fixed\"" + self.request_param("demand_type", + self.check_none(), + "demandType") + self.request_param("heat_capacity", + self.check_numeric( + min_value=0 * ureg.joule / ureg.kelvin), + "capacity") + + # Nominal Conditions + # todo q_flow_fixed is just dummy value as Modelica model + # needs it for any reason, check on Modelica side + self.request_param("rated_power", + self.check_numeric(min_value=0 * ureg.kilowatt), + "Q_flow_fixed") + self.params["Q_flow_nom"] = self.element.rated_power + self.request_param("dT_water", + self.check_numeric(min_value=0 * ureg.kelvin), + "dT_nom") + + # Flow temperature control (Mixture Valve) + self.params["hasFeedback"] = self.element.t_control + self.params["TInSetSou"] = "AixLib.Systems.ModularEnergySystems." \ + "Modules.ModularConsumer.Types.InputType." \ + "Constant" + self.params["TInSet"] = self.element.flow_temperature + self.params["k_ControlConsumerValve"] = [0.1] * n_consumers + self.params["Ti_ControlConsumerValve"] = [10] * n_consumers + self.params["dp_Valve"] = [1000] * n_consumers + + # Return temperature control (Pump) + self.params["hasPump"] = self.element.has_pump + self.params["TOutSet"] = self.element.return_temperature + self.params["TOutSetSou"] = "AixLib.Systems.ModularEnergySystems." \ + "Modules.ModularConsumer.Types.InputType." \ + "Constant" + self.params["k_ControlConsumerPump"] = [0.1] * n_consumers + self.params["Ti_ControlConsumerPump"] = [10] * n_consumers + self.params["dp_nominalConPump"] = [10000] * n_consumers + + def get_port_name(self, port): + try: + index = self.element.ports.index(port) + except ValueError: + # unknown port + index = -1 + if port.verbose_flow_direction == 'SINK': + return 'port_a' + if port.verbose_flow_direction == 'SOURCE': + return 'port_b' + else: + return super().get_port_name(port) + + +class BoilerAggregation(AixLib): + """Modelica AixLib representation of the GeneratorOneFluid aggregation.""" + path = "AixLib.Systems.ModularEnergySystems.Modules.ModularBoiler." \ + "ModularBoiler" + represents = [aggregation.GeneratorOneFluid] + + def __init__(self, element): + super().__init__(element) + + def request_params(self): + + self.params["redeclare package Medium"] = 'AixLib.Media.Water' + + # System setup + self.params["Pump"] = self.element.has_pump + self.params["hasFeedback"] = self.element.has_bypass + self.request_param("rated_power", + self.check_numeric(min_value=0 * ureg.kilowatt), + "QNom") + self.request_param("min_PLR", + self.check_numeric(min_value=0 * ureg.dimensionless), + "PLRMin") + + # Nominal condition + self.request_param("return_temperature", + self.check_numeric(min_value=0 * ureg.celsius), + "TRetNom") + self.request_param("dT_water", + self.check_numeric(min_value=0 * ureg.kelvin), + "dTWaterNom") + + # Feedback + self.params["dp_Valve"] = 10000 # Todo get from hydraulic circuit + + def get_port_name(self, port): + try: + index = self.element.ports.index(port) + except ValueError: + # unknown port + index = -1 + if port.verbose_flow_direction == 'SINK': + return 'port_a' + if port.verbose_flow_direction == 'SOURCE': + return 'port_b' + else: + return super().get_port_name(port) + + +class Distributor(AixLib): + path = "AixLib.Fluid.HeatExchangers.ActiveWalls.Distributor" + represents = [hvac.Distributor] + + def __init__(self, element): + super().__init__(element) + + def request_params(self): + self.params['redeclare package Medium'] = 'AixLib.Media.Water' + self.params['n'] = self.get_n_ports() + self.request_param("rated_mass_flow", + self.check_numeric(min_value=0 * ureg.kg / ureg.s), + "m_flow_nominal") + + def get_n_ports(self): + ports = {port.guid: port for port in self.element.ports if + port.connection} + return len(ports)/2 - 1 + + def get_port_name(self, port): + try: + index = self.element.ports.index(port) + except ValueError: + # unknown port + index = -1 + if (index % 2) == 0: + return "port_a_consumer" + elif (index % 2) == 1: + return "port_b_consumer" + else: + return super().get_port_name(port) + + @staticmethod + def get_new_port_name(distributor, other_inst, distributor_port, + other_port, distributors_n, distributors_ports): + if distributor not in distributors_n: + distributors_n[distributor] = 0 + distributors_ports[distributor] = {} + distributors_n[distributor] += 1 + if type(other_inst.element) is aggregation.GeneratorOneFluid: + list_name = distributor_port.split('.')[:-1] + \ + ['mainReturn' if 'port_a' in other_port + else 'mainFlow'] + else: + port_key = other_port.split('.')[-1] + if port_key not in distributors_ports[distributor]: + distributors_ports[distributor][port_key] = 0 + distributors_ports[distributor][port_key] += 1 + n = distributors_ports[distributor][port_key] + list_name = distributor_port.split('.')[:-1] + \ + ['flowPorts[%d]' % n if 'port_a' in other_port + else 'returnPorts[%d]' % n] + return '.'.join(list_name) + + +class ThreeWayValve(AixLib): + path = "AixLib.Fluid.Actuators.Valves.ThreeWayEqualPercentageLinear" + represents = [hvac.ThreeWayValve] + + def __init__(self, element): + super().__init__(element) + + def request_params(self): + self.params['redeclare package Medium'] = 'AixLib.Media.Water' + + def get_port_name(self, port): + try: + index = self.element.ports.index(port) + except ValueError: + # unknown port + index = -1 + if index == 0: + return "port_1" + elif index == 1: + return "port_2" + elif index == 2: + return "port_3" + else: + return super().get_port_name(port) + + +class Heatpump(AixLib): + path = "AixLib.Fluid.HeatPumps.HeatPump" + represents = [hvac.HeatPump] + + def __init__(self, element): + super().__init__(element) + + def request_params(self): + self.params['redeclare package Medium_con'] = 'AixLib.Media.Water' + self.params['redeclare package Medium_eva'] = 'AixLib.Media.Water' + + def get_port_name(self, port): + # TODO heat pumps might have 4 ports (if source is modeld in BIM) + if port.verbose_flow_direction == 'SINK': + return 'port_a' + if port.verbose_flow_direction == 'SOURCE': + return 'port_b' + else: + return super().get_port_name(port) + + +class Chiller(AixLib): + represents = [hvac.Chiller] + pass + + +class CHP(AixLib): + represents = [hvac.CHP] + pass + + +# class Storage(AixLib): +# represents = [hvac.Storage] +# pass diff --git a/bim2sim/plugins/PluginSpawnOfEP/bim2sim_spawn/tasks/export_co_sim.py b/bim2sim/plugins/PluginSpawnOfEP/bim2sim_spawn/tasks/export_co_sim.py new file mode 100644 index 0000000000..0810ff5103 --- /dev/null +++ b/bim2sim/plugins/PluginSpawnOfEP/bim2sim_spawn/tasks/export_co_sim.py @@ -0,0 +1,16 @@ +from bim2sim.task.base import ITask + + +class GetZoneConnections(ITask): + ... + # TODO (maybe later) + + +class CoSimExport(ITask): + ... + # TODO create static make template with + # - hydraulic: map the existing hydraulic system of Modelica export into + # one single top level component + # - floor (from Buidlings) + # - idf link to building (from Buildings) + # - connect all stuff diff --git a/bim2sim/simulation_type.py b/bim2sim/simulation_type.py index 2233421829..e364142932 100644 --- a/bim2sim/simulation_type.py +++ b/bim2sim/simulation_type.py @@ -594,7 +594,19 @@ class EnergyPlusSimulation(BuildingSimulation): ) +<<<<<<< Updated upstream:bim2sim/simulation_type.py class CFDSimulation(SimType): +======= +<<<<<<< Updated upstream:bim2sim/workflow.py +class CFDWorkflow(Workflow): +======= +class CoSimulation(BuildingSimulation, PlantSimulation): + ... + + +class CFDSimulation(SimType): +>>>>>>> Stashed changes:bim2sim/simulation_type.py +>>>>>>> Stashed changes:bim2sim/workflow.py # todo make something useful pass From 3ac7e8d7174f62d67a50bfb01ede8f47213c8cd2 Mon Sep 17 00:00:00 2001 From: David Date: Thu, 25 May 2023 17:54:08 +0200 Subject: [PATCH 002/125] further structure for co simulation --- .../bim2sim_modelica/__init__.py | 2 +- .../PluginSpawnOfEP/bim2sim_spawn/__init__.py | 61 ++----------------- .../bim2sim_spawn/tasks/__init__.py | 1 + bim2sim/simulation_type.py | 8 --- 4 files changed, 7 insertions(+), 65 deletions(-) diff --git a/bim2sim/plugins/PluginModelica/bim2sim_modelica/__init__.py b/bim2sim/plugins/PluginModelica/bim2sim_modelica/__init__.py index 5b42d2e073..bfc99a4de4 100644 --- a/bim2sim/plugins/PluginModelica/bim2sim_modelica/__init__.py +++ b/bim2sim/plugins/PluginModelica/bim2sim_modelica/__init__.py @@ -17,7 +17,7 @@ class LoadLibrariesAixLib(base.ITask): def run(self, **kwargs): return (standardlibrary.StandardLibrary, AixLib), - +# TODO remove AixLib and HKESim Plugin and use only this one class PluginModelica(Plugin): name = 'Modelica' default_workflow = PlantSimulation diff --git a/bim2sim/plugins/PluginSpawnOfEP/bim2sim_spawn/__init__.py b/bim2sim/plugins/PluginSpawnOfEP/bim2sim_spawn/__init__.py index 647e272223..d864dd9e5c 100644 --- a/bim2sim/plugins/PluginSpawnOfEP/bim2sim_spawn/__init__.py +++ b/bim2sim/plugins/PluginSpawnOfEP/bim2sim_spawn/__init__.py @@ -7,61 +7,7 @@ from bim2sim.plugins import Plugin from bim2sim.plugins.PluginModelica.bim2sim_modelica import PluginModelica from bim2sim.plugins.PluginEnergyPlus.bim2sim_energyplus import PluginEnergyPlus - -<<<<<<< Updated upstream -from bim2sim.task import base, common, hvac -from bim2sim.simulation_type import CoSimulation - - -class PluginCoSimModelica(Plugin): - name = 'Modelica' - default_workflow = CoSimulation - - export_library = 'AixLib' # todo load this - allowed_workflows = [CoSimulation] - # tasks = {LoadLibrariesAixLib} - elements = {*hvac_elements.items, Material} - - - tasks = [] - tasks.extend(PluginModelica.default_tasks) - tasks.extend(PluginEnergyPlus.default_tasks) - - tasks_loading - tasks_processing - tasks_export - - default_tasks = [ - common.LoadIFC, - hvac.CheckIfcHVAC, - common.CreateElements, - hvac.ConnectElements, - hvac.MakeGraph, - hvac.ExpansionTanks, - hvac.Reduce, - hvac.DeadEnds, - LoadLibrariesAixLib, - hvac.Export, - ] - - def create_modelica_table_from_list(self, curve): - """ - - :param curve: - :return: - """ - curve = literal_eval(curve) - for key, value in curve.iteritems(): - # add first and last value to make sure there is a constant - # behaviour before and after the given heating curve - value = [value[0] - 5, value[1]] + value + [value[-2] + 5, - value[-1]] - # transform to string and replace every second comma with a - # semicolon to match_graph modelica syntax - value = str(value) - value = re.sub('(,[^,]*),', r'\1;', value) - setattr(self, key, value) -======= +import bim2sim.plugins.PluginSpawnOfEP.bim2sim_spawn.tasks as spawn_tasks from bim2sim.simulation_type import CoSimulation @@ -81,8 +27,11 @@ class PluginSpawnOfEP(Plugin): default_tasks = [] default_tasks.extend(PluginModelica.default_tasks) default_tasks.extend(PluginEnergyPlus.default_tasks) + + default_tasks.append(spawn_tasks.GetZoneConnections) + default_tasks.append(spawn_tasks.CoSimExport) + # make sure that tasks only occur once # todo: this won't work always. We need to separate tasks that occur in # multiple Plugins (LoadIFC, CheckIFC and CreateElements) from the rest default_tasks = set(default_tasks) ->>>>>>> Stashed changes diff --git a/bim2sim/plugins/PluginSpawnOfEP/bim2sim_spawn/tasks/__init__.py b/bim2sim/plugins/PluginSpawnOfEP/bim2sim_spawn/tasks/__init__.py index e69de29bb2..13c43d7f81 100644 --- a/bim2sim/plugins/PluginSpawnOfEP/bim2sim_spawn/tasks/__init__.py +++ b/bim2sim/plugins/PluginSpawnOfEP/bim2sim_spawn/tasks/__init__.py @@ -0,0 +1 @@ +from .export_co_sim import CoSimExport, GetZoneConnections diff --git a/bim2sim/simulation_type.py b/bim2sim/simulation_type.py index e364142932..885ddcda66 100644 --- a/bim2sim/simulation_type.py +++ b/bim2sim/simulation_type.py @@ -594,19 +594,11 @@ class EnergyPlusSimulation(BuildingSimulation): ) -<<<<<<< Updated upstream:bim2sim/simulation_type.py -class CFDSimulation(SimType): -======= -<<<<<<< Updated upstream:bim2sim/workflow.py -class CFDWorkflow(Workflow): -======= class CoSimulation(BuildingSimulation, PlantSimulation): ... class CFDSimulation(SimType): ->>>>>>> Stashed changes:bim2sim/simulation_type.py ->>>>>>> Stashed changes:bim2sim/workflow.py # todo make something useful pass From e0a94a542f0772386d2c074275a67aa53962e0a3 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 18 Jul 2023 11:41:36 +0200 Subject: [PATCH 003/125] minor fixes after merge --- .../PluginSpawnOfEP/bim2sim_spawn/__init__.py | 16 +++++----------- bim2sim/sim_settings.py | 4 ++++ 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/bim2sim/plugins/PluginSpawnOfEP/bim2sim_spawn/__init__.py b/bim2sim/plugins/PluginSpawnOfEP/bim2sim_spawn/__init__.py index d864dd9e5c..374dbb431e 100644 --- a/bim2sim/plugins/PluginSpawnOfEP/bim2sim_spawn/__init__.py +++ b/bim2sim/plugins/PluginSpawnOfEP/bim2sim_spawn/__init__.py @@ -1,22 +1,16 @@ -import re -from ast import literal_eval - -from bim2sim.export.modelica import standardlibrary -from bim2sim.kernel.element import Material -from bim2sim.kernel.elements import hvac as hvac_elements -from bim2sim.plugins import Plugin +from bim2sim.plugins import Plugin from bim2sim.plugins.PluginModelica.bim2sim_modelica import PluginModelica from bim2sim.plugins.PluginEnergyPlus.bim2sim_energyplus import PluginEnergyPlus -import bim2sim.plugins.PluginSpawnOfEP.bim2sim_spawn.tasks as spawn_tasks -from bim2sim.simulation_type import CoSimulation +import bim2sim.plugins.PluginSpawnOfEP.bim2sim_spawn.tasks as spawn_tasks +from bim2sim.sim_settings import CoSimulation +# TODO: this is just a concept and not working already class PluginSpawnOfEP(Plugin): name = 'SpawnOfEP' - default_workflow = CoSimulation # todo: this is currently empty + sim_settings = CoSimulation # todo: this is currently empty export_hvac_library = 'AixLib' # todo: this has currently no impact - allowed_workflows = [CoSimulation] # combine elements from both Plugins elements = set() diff --git a/bim2sim/sim_settings.py b/bim2sim/sim_settings.py index ba94ef6bc1..11c3930341 100644 --- a/bim2sim/sim_settings.py +++ b/bim2sim/sim_settings.py @@ -668,3 +668,7 @@ class EnergyPlusSimSettings(BuildingSimSettings): multiple_choice=True, for_frontend=True ) + + +class CoSimulation(BuildingSimSettings, PlantSimSettings): + ... From 90137b3f9c8ef676f9fa39aebe601145ee953e3e Mon Sep 17 00:00:00 2001 From: David Date: Mon, 14 Aug 2023 12:14:29 +0200 Subject: [PATCH 004/125] basic export for EPBuilding and freshAirSource working --- bim2sim/elements/bps_elements.py | 24 +- bim2sim/export/modelica/__init__.py | 3 + .../bim2sim_aixlib/models/__init__.py | 41 +- .../__init__.py | 0 .../bim2sim_buildings/__init__.py | 33 ++ .../bim2sim_buildings/data/modelica_language | 36 ++ .../bim2sim_buildings/examples}/__init__.py | 0 .../e1_simple_project_bps_buildings.py | 50 +++ .../bim2sim_buildings/models/__init__.py | 79 ++++ .../bim2sim_modelica/__init__.py | 57 --- .../bim2sim_modelica/models/__init__.py | 398 ------------------ .../bim2sim_modelica/models/buildings.py | 18 - bim2sim/plugins/PluginSpawn/__init__.py | 0 .../bim2sim_spawn/__init__.py | 14 +- .../bim2sim_spawn/examples/__init__.py | 0 .../bim2sim_spawn/models/__init__.py | 13 +- .../bim2sim_spawn/tasks/__init__.py | 2 + .../tasks/create_spawn_elements.py | 26 ++ .../bim2sim_spawn/tasks/export_co_sim.py | 61 +++ .../bim2sim_spawn/tasks/export_co_sim.py | 16 - bim2sim/tasks/common/__init__.py | 1 + bim2sim/tasks/common/export_modelica.py | 39 ++ 22 files changed, 406 insertions(+), 505 deletions(-) rename bim2sim/plugins/{PluginModelica => PluginBuildings}/__init__.py (100%) create mode 100644 bim2sim/plugins/PluginBuildings/bim2sim_buildings/__init__.py create mode 100644 bim2sim/plugins/PluginBuildings/bim2sim_buildings/data/modelica_language rename bim2sim/plugins/{PluginSpawnOfEP => PluginBuildings/bim2sim_buildings/examples}/__init__.py (100%) create mode 100644 bim2sim/plugins/PluginBuildings/bim2sim_buildings/examples/e1_simple_project_bps_buildings.py create mode 100644 bim2sim/plugins/PluginBuildings/bim2sim_buildings/models/__init__.py delete mode 100644 bim2sim/plugins/PluginModelica/bim2sim_modelica/__init__.py delete mode 100644 bim2sim/plugins/PluginModelica/bim2sim_modelica/models/__init__.py delete mode 100644 bim2sim/plugins/PluginModelica/bim2sim_modelica/models/buildings.py create mode 100644 bim2sim/plugins/PluginSpawn/__init__.py rename bim2sim/plugins/{PluginSpawnOfEP => PluginSpawn}/bim2sim_spawn/__init__.py (66%) create mode 100644 bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/__init__.py rename bim2sim/plugins/{PluginSpawnOfEP => PluginSpawn}/bim2sim_spawn/models/__init__.py (98%) create mode 100644 bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/__init__.py create mode 100644 bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/create_spawn_elements.py create mode 100644 bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_co_sim.py delete mode 100644 bim2sim/plugins/PluginSpawnOfEP/bim2sim_spawn/tasks/export_co_sim.py create mode 100644 bim2sim/tasks/common/export_modelica.py diff --git a/bim2sim/elements/bps_elements.py b/bim2sim/elements/bps_elements.py index 69c1d3dc31..61b38a39bf 100644 --- a/bim2sim/elements/bps_elements.py +++ b/bim2sim/elements/bps_elements.py @@ -5,10 +5,12 @@ import re import sys from datetime import date +from pathlib import Path from typing import Set, List import ifcopenshell import ifcopenshell.geom +import numpy as np from OCC.Core.BRep import BRep_Tool from OCC.Core.BRepBndLib import brepbndlib_Add from OCC.Core.BRepBuilderAPI import BRepBuilderAPI_Transform @@ -28,7 +30,7 @@ from bim2sim.kernel.decorators import cached_property from bim2sim.elements.mapping import condition, attribute -from bim2sim.elements.base_elements import ProductBased, RelationBased +from bim2sim.elements.base_elements import ProductBased, RelationBased, Element from bim2sim.elements.mapping.units import ureg from bim2sim.tasks.common.inner_loop_remover import remove_inner_loops from bim2sim.utilities.common_functions import vector_angle, angle_equivalent @@ -1930,6 +1932,26 @@ def calc_cost_group(self) -> int: return 300 +class SpawnBuilding(Element): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.idfName: Path = Path("") + self.epwName: Path = Path("") + self.weaName: Path = Path("") + self.printUnits: bool = True + + def calc_position(self) -> np.array: + return np.array([-80, 10, 5]) + + +class FreshAirSource(Element): + def calc_position(self) -> np.array: + return np.array([-40, 20, 8]) + + +class SpawnMultiZone(Element): + pass + # collect all domain classes items: Set[BPSProduct] = set() for name, cls in inspect.getmembers( diff --git a/bim2sim/export/modelica/__init__.py b/bim2sim/export/modelica/__init__.py index a92b708112..07aa512fef 100644 --- a/bim2sim/export/modelica/__init__.py +++ b/bim2sim/export/modelica/__init__.py @@ -303,6 +303,9 @@ def to_modelica(parameter): if isinstance(parameter, (list, tuple, set)): return "{%s}" % ( ",".join((Instance.to_modelica(par) for par in parameter))) + if isinstance(parameter, Path): + return \ + f"Modelica.Utilities.Files.loadResource(\"{str(parameter)}\")" logger.warning("Unknown class (%s) for conversion", parameter.__class__) return str(parameter) diff --git a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py index 1c60a47092..0d54b12e4b 100644 --- a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py +++ b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py @@ -2,6 +2,7 @@ from bim2sim.elements.aggregation import hvac_aggregations from bim2sim.export import modelica from bim2sim.elements import hvac_elements as hvac +from bim2sim.elements import bps_elements as bps from bim2sim.elements.mapping.units import ureg @@ -420,4 +421,42 @@ def request_params(self): # triggered for them if they don't exist. Problem is documented in #542 self.params["data"] = f"AixLib.DataBase.Storage.Generic_New_2000l(" \ f"hTank={self.element.height}," \ - f" dTank={self.element.diameter})" \ No newline at end of file + f" dTank={self.element.diameter})" + + +class SpawnMultizone(AixLib): + # TODO #1 + path = "AixLib.ThermalZone..." + represents = [bps.SpawnMultiZone] + + def __init__(self, element): + super().__init__(element) + + def request_params(self): + + self.params["redeclare package Medium"] = 'AixLib.Media.Water' + self.request_param("dT_water", + self.check_numeric(min_value=0 * ureg.kelvin), + "dTWaterNom") + self.request_param("return_temperature", + self.check_numeric(min_value=0 * ureg.celsius), + "TRetNom") + self.request_param("rated_power", + self.check_numeric(min_value=0 * ureg.kilowatt), + "QNom") + self.request_param("min_PLR", + self.check_numeric(min_value=0 * ureg.dimensionless), + "PLRMin") + + def get_port_name(self, port): + try: + index = self.element.ports.index(port) + except ValueError: + # unknown port + index = -1 + if port.verbose_flow_direction == 'SINK': + return 'port_a' + if port.verbose_flow_direction == 'SOURCE': + return 'port_b' + else: + return super().get_port_name(port) # ToDo: Gas connection diff --git a/bim2sim/plugins/PluginModelica/__init__.py b/bim2sim/plugins/PluginBuildings/__init__.py similarity index 100% rename from bim2sim/plugins/PluginModelica/__init__.py rename to bim2sim/plugins/PluginBuildings/__init__.py diff --git a/bim2sim/plugins/PluginBuildings/bim2sim_buildings/__init__.py b/bim2sim/plugins/PluginBuildings/bim2sim_buildings/__init__.py new file mode 100644 index 0000000000..4e9b42f108 --- /dev/null +++ b/bim2sim/plugins/PluginBuildings/bim2sim_buildings/__init__.py @@ -0,0 +1,33 @@ +import re +from ast import literal_eval + +from bim2sim.export.modelica import standardlibrary +from bim2sim.plugins import Plugin +from bim2sim.plugins.PluginBuildings.bim2sim_buildings.models import Buildings +from bim2sim.tasks import base, common, hvac +from bim2sim.sim_settings import BuildingSimSettings +import bim2sim.plugins.PluginSpawn.bim2sim_spawn.tasks as spawn_tasks + + +class LoadLibrariesBuildings(base.ITask): + """Load AixLib library for export""" + touches = ('libraries', ) + + def run(self, **kwargs): + return (standardlibrary.StandardLibrary, Buildings), + + def overwrite_standarlib_models(self): + pass + + +class PluginBuildings(Plugin): + name = 'Buildings' + sim_settings = BuildingSimSettings + tasks = {LoadLibrariesBuildings} + default_tasks = [ + # common.LoadIFC, + # common.CreateElements, + spawn_tasks.CreateSpawnElements, + LoadLibrariesBuildings, + spawn_tasks.ExportModelicaSpawn, + ] diff --git a/bim2sim/plugins/PluginBuildings/bim2sim_buildings/data/modelica_language b/bim2sim/plugins/PluginBuildings/bim2sim_buildings/data/modelica_language new file mode 100644 index 0000000000..56a26c2bd4 --- /dev/null +++ b/bim2sim/plugins/PluginBuildings/bim2sim_buildings/data/modelica_language @@ -0,0 +1,36 @@ +##get false +<%def name="get_true_false(value)", filter="trim"> + <% + if value is False: + return "false" + elif value is True: + return "true" + else: + return value + %> + + +##list to modelica list +##Modelica does not allow empty lists, sets one zero entry as default for +##such cases +<%def name="get_list(list)", filter="trim"> + <% + if list: + return str(list).replace('[', '{').replace(']', '}') + else: + ret="{0}" + return ret + %> + + +##catch zero orientations +##The value nOrientations defines length of lists in Modelica, thus must be +##greater than zero +<%def name="min_orientations(orientation)", filter="trim"> + <% + if orientation < 1: + return 1 + else: + return orientation + %> + diff --git a/bim2sim/plugins/PluginSpawnOfEP/__init__.py b/bim2sim/plugins/PluginBuildings/bim2sim_buildings/examples/__init__.py similarity index 100% rename from bim2sim/plugins/PluginSpawnOfEP/__init__.py rename to bim2sim/plugins/PluginBuildings/bim2sim_buildings/examples/__init__.py diff --git a/bim2sim/plugins/PluginBuildings/bim2sim_buildings/examples/e1_simple_project_bps_buildings.py b/bim2sim/plugins/PluginBuildings/bim2sim_buildings/examples/e1_simple_project_bps_buildings.py new file mode 100644 index 0000000000..a34c22929c --- /dev/null +++ b/bim2sim/plugins/PluginBuildings/bim2sim_buildings/examples/e1_simple_project_bps_buildings.py @@ -0,0 +1,50 @@ +import tempfile +from pathlib import Path + +import bim2sim +from bim2sim import Project, run_project, ConsoleDecisionHandler +from bim2sim.kernel.log import default_logging_setup +from bim2sim.utilities.types import IFCDomain + + +def run_example_1(): + """Run a building performance simulation with the EnergyPlus backend. + + This example runs a BPS with the EnergyPlus backend. Specifies project + directory and location of the IFC file. Then, it creates a bim2sim + project with the EnergyPlus backend. Simulation settings are specified + (EnergyPlus location needs to be specified according to your system, + other settings are set to default if not specified otherwise), + before the project is executed with the previously specified settings. + """ + # Create the default logging to for quality log and bim2sim main log ( + # see logging documentation for more information + default_logging_setup() + + # Create a temp directory for the project, feel free to use a "normal" + # directory + project_path = Path( + tempfile.TemporaryDirectory(prefix='bim2sim_example1').name) + + # Get path of the IFC Building model that is used for this example + ifc_paths = {IFCDomain.arch: Path( + bim2sim.__file__).parent / + 'assets/ifc_example_files/AC20-FZK-Haus.ifc', + } + # Create a project including the folder structure for the project with + # energyplus as backend + project = Project.create(project_path, ifc_paths, 'buildings') + + # Set the install path to your EnergyPlus installation according to your + # system requirements + # project.sim_settings.ep_install_path = 'C://EnergyPlusV9-4-0/' + + # Set other simulation settings, otherwise all settings are set to default + + # Run the project with the ConsoleDecisionHandler. This allows interactive + # input to answer upcoming questions regarding the imported IFC. + run_project(project, ConsoleDecisionHandler()) + + +if __name__ == '__main__': + run_example_1() diff --git a/bim2sim/plugins/PluginBuildings/bim2sim_buildings/models/__init__.py b/bim2sim/plugins/PluginBuildings/bim2sim_buildings/models/__init__.py new file mode 100644 index 0000000000..f9da082d08 --- /dev/null +++ b/bim2sim/plugins/PluginBuildings/bim2sim_buildings/models/__init__.py @@ -0,0 +1,79 @@ +"""Package for Python representations of AixLib models""" +from bim2sim.export import modelica +from bim2sim.elements import hvac_elements as hvac +from bim2sim.elements import bps_elements as bps +from bim2sim.elements.mapping.units import ureg + + +class Buildings(modelica.Instance): + library = "Buildings" + + +class EPThermalZone(Buildings): + pass + + +class SpawnBuilding(Buildings): + path = "Buildings.ThermalZones.EnergyPlus_9_6_0.Building" + represents = [bps.SpawnBuilding] + + def request_params(self): + self.params["idfName"] = self.element.idfName + self.params["epwName"] = self.element.epwName + self.params["weaName"] = self.element.weaName + self.params["printUnits"] = self.element.printUnits + # self.request_param("idfName", None) + # self.request_param("epwName", None) + # self.request_param("weaName", None) + # self.request_param("printUnits", None) + # self.params["epwName"] = "D:/Test" + # self.params["weaName"] = "D:/Test" + # self.params["printUnits"] = True + + def get_port_name(self, port): + return "weaBus" + + +class FreshAirSource(Buildings): + path = "Buildings.Fluid.Sources.MassFlowSource_WeatherData" + represents = [bps.FreshAirSource] + + def get_port_name(self, port): + return "weaBus" + + +# class EPMultizone(Buildings): +# path = "AixLib.Fluid.BoilerCHP.BoilerGeneric" +# represents = [hvac.Boiler] +# +# def __init__(self, element): +# super().__init__(element) +# +# def request_params(self): +# +# self.params["redeclare package Medium"] = 'AixLib.Media.Water' +# self.request_param("dT_water", +# self.check_numeric(min_value=0 * ureg.kelvin), +# "dTWaterNom") +# self.request_param("return_temperature", +# self.check_numeric(min_value=0 * ureg.celsius), +# "TRetNom") +# self.request_param("rated_power", +# self.check_numeric(min_value=0 * ureg.kilowatt), +# "QNom") +# self.request_param("min_PLR", +# self.check_numeric(min_value=0 * ureg.dimensionless), +# "PLRMin") +# +# def get_port_name(self, port): +# try: +# index = self.element.ports.index(port) +# except ValueError: +# # unknown port +# index = -1 +# if port.verbose_flow_direction == 'SINK': +# return 'port_a' +# if port.verbose_flow_direction == 'SOURCE': +# return 'port_b' +# else: +# return super().get_port_name(port) # ToDo: Gas connection diff --git a/bim2sim/plugins/PluginModelica/bim2sim_modelica/__init__.py b/bim2sim/plugins/PluginModelica/bim2sim_modelica/__init__.py deleted file mode 100644 index bfc99a4de4..0000000000 --- a/bim2sim/plugins/PluginModelica/bim2sim_modelica/__init__.py +++ /dev/null @@ -1,57 +0,0 @@ -import re -from ast import literal_eval - -from bim2sim.export.modelica import standardlibrary -from bim2sim.kernel.element import Material -from bim2sim.kernel.elements import hvac as hvac_elements -from bim2sim.plugins import Plugin -from bim2sim.plugins.PluginAixLib.bim2sim_aixlib.models import AixLib -from bim2sim.task import base, common, hvac -from bim2sim.simulation_type import PlantSimulation - - -class LoadLibrariesAixLib(base.ITask): - """Load AixLib library for export""" - touches = ('libraries', ) - - def run(self, **kwargs): - return (standardlibrary.StandardLibrary, AixLib), - -# TODO remove AixLib and HKESim Plugin and use only this one -class PluginModelica(Plugin): - name = 'Modelica' - default_workflow = PlantSimulation - export_library = 'AixLib' # todo load this - allowed_workflows = [PlantSimulation] - tasks = {LoadLibrariesAixLib} - elements = {*hvac_elements.items, Material} - default_tasks = [ - common.LoadIFC, - hvac.CheckIfcHVAC, - common.CreateElements, - hvac.ConnectElements, - hvac.MakeGraph, - hvac.ExpansionTanks, - hvac.Reduce, - hvac.DeadEnds, - LoadLibrariesAixLib, - hvac.Export, - ] - - def create_modelica_table_from_list(self, curve): - """ - - :param curve: - :return: - """ - curve = literal_eval(curve) - for key, value in curve.iteritems(): - # add first and last value to make sure there is a constant - # behaviour before and after the given heating curve - value = [value[0] - 5, value[1]] + value + [value[-2] + 5, - value[-1]] - # transform to string and replace every second comma with a - # semicolon to match_graph modelica syntax - value = str(value) - value = re.sub('(,[^,]*),', r'\1;', value) - setattr(self, key, value) diff --git a/bim2sim/plugins/PluginModelica/bim2sim_modelica/models/__init__.py b/bim2sim/plugins/PluginModelica/bim2sim_modelica/models/__init__.py deleted file mode 100644 index 0898dd739a..0000000000 --- a/bim2sim/plugins/PluginModelica/bim2sim_modelica/models/__init__.py +++ /dev/null @@ -1,398 +0,0 @@ -"""Package for Python representations of HKESim models""" -import bim2sim.kernel.aggregation as aggregation -from bim2sim.export import modelica -from bim2sim.kernel.elements import hvac -from bim2sim.kernel.units import ureg - - -class AixLib(modelica.Instance): - library = "AixLib" - - -class Boiler(AixLib): - path = "AixLib.Fluid.BoilerCHP.BoilerNotManufacturer" - represents = hvac.Boiler - - def __init__(self, element): - super().__init__(element) - - def request_params(self): - - self.params["redeclare package Medium"] = 'AixLib.Media.Water' - self.request_param("dT_water", - self.check_numeric(min_value=0 * ureg.kelvin), - "dTWaterNom") - self.request_param("return_temperature", - self.check_numeric(min_value=0 * ureg.celsius), - "TRetNom") - self.request_param("rated_power", - self.check_numeric(min_value=0 * ureg.kilowatt), - "QNom") - self.request_param("min_PLR", - self.check_numeric(min_value=0 * ureg.dimensionless), - "PLRMin") - - def get_port_name(self, port): - try: - index = self.element.ports.index(port) - except ValueError: - # unknown port - index = -1 - if port.verbose_flow_direction == 'SINK': - return 'port_a' - if port.verbose_flow_direction == 'SOURCE': - return 'port_b' - else: - return super().get_port_name(port) # ToDo: Gas connection - - -class Radiator(AixLib): - path = "AixLib.Fluid.HeatExchangers.Radiators.RadiatorEN442_2" - represents = [hvac.SpaceHeater] - - def request_params(self): - self.request_param("rated_power", - self.check_numeric(min_value=0 * ureg.kilowatt), - "Q_flow_nominal") - # self.params["T_nominal"] = (80, 60, 20) - - -class Pump(AixLib): - path = "AixLib.Fluid.Movers.SpeedControlled_y" - represents = [hvac.Pump] - - def request_params(self): - self.params['redeclare package Medium'] = 'AixLib.Media.Water' - self.request_param( - "rated_mass_flow", - self.check_numeric(min_value=0 * ureg['kg/second'])) - self.request_param( - "rated_pressure_difference", - self.check_numeric(min_value=0 * ureg['newton/m**2'])) - # generic pump operation curve - # todo renders as "V_flow" only in Modelica - self.params["per.pressure"] =\ - f"V_flow={{0," \ - f" {self.element.rated_mass_flow}/1000," \ - f" {self.element.rated_mass_flow} /1000/0.7}}," \ - f" dp={{ {self.element.rated_pressure_difference} / 0.7," \ - f" {self.element.rated_pressure_difference}," \ - f"0}}" - - # ToDo remove decisions from tests if not asking this anymore - # self.request_param("rated_height", - # self.check_numeric(min_value=0 * ureg.meter), - # "head_set") - # self.request_param("rated_volume_flow", - # self.check_numeric(min_value=0 * ureg['m**3/hour']), - # "Vflow_set", 'm**3/hour') - # self.request_param("rated_power", - # self.check_numeric(min_value=0 * ureg.watt), - # "P_norm") - - def get_port_name(self, port): - try: - index = self.element.ports.index(port) - except ValueError: - # unknown port - index = -1 - if index == 0: - return "port_a" - elif index == 1: - return "port_b" - else: - return super().get_port_name(port) - - -class Consumer(AixLib): - path = "AixLib.Systems.HydraulicModules.SimpleConsumer" - represents = [aggregation.Consumer] - - def __init__(self, element): - self.check_volume = self.check_numeric(min_value=0 * ureg.meter ** 3) - super().__init__(element) - - def request_params(self): - self.params['redeclare package Medium'] = 'AixLib.Media.Water' - self.params["functionality"] = '\"Q_flow_fixed\"' - if self.params["functionality"] == '\"Q_flow_fixed\"': - self.params["Q_flow_fixed"] = self.element.rated_power - self.params["demand_type"] = "1" - - # self.request_param("demand_type", - # self.check_none(), - # "demandType") - - self.params["hasFeedback"] = self.element.t_control - if self.element.t_control: - self.params["TInSetValue"] = self.element.flow_temperature - self.params["TInSetSou"] = "AixLib.Systems.ModularEnergySystems." \ - "Modules.ModularConsumer.Types.InputType." \ - "Constant" - - self.params["hasPump"] = self.element.has_pump - if self.element.has_pump: - self.params["TOutSetValue"] = self.element.return_temperature - self.params["TOutSetSou"] = "AixLib.Systems.ModularEnergySystems." \ - "Modules.ModularConsumer.Types.InputType." \ - "Constant" - # self.params["dT_nom"] = self.element.dT_water - # self.params["capacity"] = self.element.heat_capacity - # self.request_param("rated_power", - # self.check_numeric(min_value=0 * ureg.kilowatt), - # "Q_flow_nom") - # self.request_param("dT_water", - # self.check_numeric(min_value=0 * ureg.kelvin), - # "dT_nom") - # self.request_param("heat_capacity", - # self.check_numeric( - # min_value=0 * ureg.joule / ureg.kelvin), - # "capacity") - - def get_port_name(self, port): - try: - index = self.element.ports.index(port) - except ValueError: - # unknown port - index = -1 - if index == 1: - return "port_a" - elif index == 0: - return "port_b" - else: - return super().get_port_name(port) - - -class ConsumerHeatingDistributorModule(AixLib): - path = "AixLib.Systems.ModularEnergySystems.Modules.ModularConsumer." \ - "ConsumerDistributorModule" - represents = [aggregation.ConsumerHeatingDistributorModule] - - def __init__(self, element): - self.check_volume = self.check_numeric(min_value=0 * ureg.meter ** 3) - super().__init__(element) - - def request_params(self): - n_consumers = len(self.element.whitelist_elements) - # Parameters - self.params["T_start"] = self.element.return_temperature - self.params['redeclare package Medium'] = 'AixLib.Media.Water' - # Consumer Design - self.params['n_consumers'] = n_consumers - self.params["functionality"] = "\"Q_flow_fixed\"" - self.request_param("demand_type", - self.check_none(), - "demandType") - self.request_param("heat_capacity", - self.check_numeric( - min_value=0 * ureg.joule / ureg.kelvin), - "capacity") - - # Nominal Conditions - # todo q_flow_fixed is just dummy value as Modelica model - # needs it for any reason, check on Modelica side - self.request_param("rated_power", - self.check_numeric(min_value=0 * ureg.kilowatt), - "Q_flow_fixed") - self.params["Q_flow_nom"] = self.element.rated_power - self.request_param("dT_water", - self.check_numeric(min_value=0 * ureg.kelvin), - "dT_nom") - - # Flow temperature control (Mixture Valve) - self.params["hasFeedback"] = self.element.t_control - self.params["TInSetSou"] = "AixLib.Systems.ModularEnergySystems." \ - "Modules.ModularConsumer.Types.InputType." \ - "Constant" - self.params["TInSet"] = self.element.flow_temperature - self.params["k_ControlConsumerValve"] = [0.1] * n_consumers - self.params["Ti_ControlConsumerValve"] = [10] * n_consumers - self.params["dp_Valve"] = [1000] * n_consumers - - # Return temperature control (Pump) - self.params["hasPump"] = self.element.has_pump - self.params["TOutSet"] = self.element.return_temperature - self.params["TOutSetSou"] = "AixLib.Systems.ModularEnergySystems." \ - "Modules.ModularConsumer.Types.InputType." \ - "Constant" - self.params["k_ControlConsumerPump"] = [0.1] * n_consumers - self.params["Ti_ControlConsumerPump"] = [10] * n_consumers - self.params["dp_nominalConPump"] = [10000] * n_consumers - - def get_port_name(self, port): - try: - index = self.element.ports.index(port) - except ValueError: - # unknown port - index = -1 - if port.verbose_flow_direction == 'SINK': - return 'port_a' - if port.verbose_flow_direction == 'SOURCE': - return 'port_b' - else: - return super().get_port_name(port) - - -class BoilerAggregation(AixLib): - """Modelica AixLib representation of the GeneratorOneFluid aggregation.""" - path = "AixLib.Systems.ModularEnergySystems.Modules.ModularBoiler." \ - "ModularBoiler" - represents = [aggregation.GeneratorOneFluid] - - def __init__(self, element): - super().__init__(element) - - def request_params(self): - - self.params["redeclare package Medium"] = 'AixLib.Media.Water' - - # System setup - self.params["Pump"] = self.element.has_pump - self.params["hasFeedback"] = self.element.has_bypass - self.request_param("rated_power", - self.check_numeric(min_value=0 * ureg.kilowatt), - "QNom") - self.request_param("min_PLR", - self.check_numeric(min_value=0 * ureg.dimensionless), - "PLRMin") - - # Nominal condition - self.request_param("return_temperature", - self.check_numeric(min_value=0 * ureg.celsius), - "TRetNom") - self.request_param("dT_water", - self.check_numeric(min_value=0 * ureg.kelvin), - "dTWaterNom") - - # Feedback - self.params["dp_Valve"] = 10000 # Todo get from hydraulic circuit - - def get_port_name(self, port): - try: - index = self.element.ports.index(port) - except ValueError: - # unknown port - index = -1 - if port.verbose_flow_direction == 'SINK': - return 'port_a' - if port.verbose_flow_direction == 'SOURCE': - return 'port_b' - else: - return super().get_port_name(port) - - -class Distributor(AixLib): - path = "AixLib.Fluid.HeatExchangers.ActiveWalls.Distributor" - represents = [hvac.Distributor] - - def __init__(self, element): - super().__init__(element) - - def request_params(self): - self.params['redeclare package Medium'] = 'AixLib.Media.Water' - self.params['n'] = self.get_n_ports() - self.request_param("rated_mass_flow", - self.check_numeric(min_value=0 * ureg.kg / ureg.s), - "m_flow_nominal") - - def get_n_ports(self): - ports = {port.guid: port for port in self.element.ports if - port.connection} - return len(ports)/2 - 1 - - def get_port_name(self, port): - try: - index = self.element.ports.index(port) - except ValueError: - # unknown port - index = -1 - if (index % 2) == 0: - return "port_a_consumer" - elif (index % 2) == 1: - return "port_b_consumer" - else: - return super().get_port_name(port) - - @staticmethod - def get_new_port_name(distributor, other_inst, distributor_port, - other_port, distributors_n, distributors_ports): - if distributor not in distributors_n: - distributors_n[distributor] = 0 - distributors_ports[distributor] = {} - distributors_n[distributor] += 1 - if type(other_inst.element) is aggregation.GeneratorOneFluid: - list_name = distributor_port.split('.')[:-1] + \ - ['mainReturn' if 'port_a' in other_port - else 'mainFlow'] - else: - port_key = other_port.split('.')[-1] - if port_key not in distributors_ports[distributor]: - distributors_ports[distributor][port_key] = 0 - distributors_ports[distributor][port_key] += 1 - n = distributors_ports[distributor][port_key] - list_name = distributor_port.split('.')[:-1] + \ - ['flowPorts[%d]' % n if 'port_a' in other_port - else 'returnPorts[%d]' % n] - return '.'.join(list_name) - - -class ThreeWayValve(AixLib): - path = "AixLib.Fluid.Actuators.Valves.ThreeWayEqualPercentageLinear" - represents = [hvac.ThreeWayValve] - - def __init__(self, element): - super().__init__(element) - - def request_params(self): - self.params['redeclare package Medium'] = 'AixLib.Media.Water' - - def get_port_name(self, port): - try: - index = self.element.ports.index(port) - except ValueError: - # unknown port - index = -1 - if index == 0: - return "port_1" - elif index == 1: - return "port_2" - elif index == 2: - return "port_3" - else: - return super().get_port_name(port) - - -class Heatpump(AixLib): - path = "AixLib.Fluid.HeatPumps.HeatPump" - represents = [hvac.HeatPump] - - def __init__(self, element): - super().__init__(element) - - def request_params(self): - self.params['redeclare package Medium_con'] = 'AixLib.Media.Water' - self.params['redeclare package Medium_eva'] = 'AixLib.Media.Water' - - def get_port_name(self, port): - # TODO heat pumps might have 4 ports (if source is modeld in BIM) - if port.verbose_flow_direction == 'SINK': - return 'port_a' - if port.verbose_flow_direction == 'SOURCE': - return 'port_b' - else: - return super().get_port_name(port) - - -class Chiller(AixLib): - represents = [hvac.Chiller] - pass - - -class CHP(AixLib): - represents = [hvac.CHP] - pass - - -# class Storage(AixLib): -# represents = [hvac.Storage] -# pass diff --git a/bim2sim/plugins/PluginModelica/bim2sim_modelica/models/buildings.py b/bim2sim/plugins/PluginModelica/bim2sim_modelica/models/buildings.py deleted file mode 100644 index 656d990c46..0000000000 --- a/bim2sim/plugins/PluginModelica/bim2sim_modelica/models/buildings.py +++ /dev/null @@ -1,18 +0,0 @@ -from bim2sim.export import modelica -from bim2sim.kernel.elements import bps - - -class Buildings(modelica.Instance): - library = "Buildings" - - -class BuildingThermalZone(Buildings): - path = "Buildings.ThermalZones.EnergyPlus_9_6_0.ThermalZone" - represents = bps.ThermalZone - - -class Floor(Buildings): - ... - # TODO - # Sum all zones and connect with infiltration - # connect all zones (maybe later) diff --git a/bim2sim/plugins/PluginSpawn/__init__.py b/bim2sim/plugins/PluginSpawn/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/bim2sim/plugins/PluginSpawnOfEP/bim2sim_spawn/__init__.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/__init__.py similarity index 66% rename from bim2sim/plugins/PluginSpawnOfEP/bim2sim_spawn/__init__.py rename to bim2sim/plugins/PluginSpawn/bim2sim_spawn/__init__.py index 374dbb431e..c4d128bcee 100644 --- a/bim2sim/plugins/PluginSpawnOfEP/bim2sim_spawn/__init__.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/__init__.py @@ -1,11 +1,11 @@ from bim2sim.plugins import Plugin -from bim2sim.plugins.PluginModelica.bim2sim_modelica import PluginModelica +# from bim2sim.plugins.PluginModelica.bim2sim_modelica import PluginModelica from bim2sim.plugins.PluginEnergyPlus.bim2sim_energyplus import PluginEnergyPlus -import bim2sim.plugins.PluginSpawnOfEP.bim2sim_spawn.tasks as spawn_tasks +# import bim2sim.plugins.PluginSpawnOfEP.bim2sim_spawn.tasks as spawn_tasks from bim2sim.sim_settings import CoSimulation -# TODO: this is just a concept and not working already +# # TODO: this is just a concept and not working already class PluginSpawnOfEP(Plugin): name = 'SpawnOfEP' sim_settings = CoSimulation # todo: this is currently empty @@ -14,16 +14,16 @@ class PluginSpawnOfEP(Plugin): # combine elements from both Plugins elements = set() - elements.update(PluginModelica.elements) + # elements.update(PluginModelica.elements) elements.update(PluginEnergyPlus.elements) # combine tasks from both Plugins default_tasks = [] - default_tasks.extend(PluginModelica.default_tasks) + # default_tasks.extend(PluginModelica.default_tasks) default_tasks.extend(PluginEnergyPlus.default_tasks) - default_tasks.append(spawn_tasks.GetZoneConnections) - default_tasks.append(spawn_tasks.CoSimExport) + # default_tasks.append(spawn_tasks.GetZoneConnections) + # default_tasks.append(spawn_tasks.CoSimExport) # make sure that tasks only occur once # todo: this won't work always. We need to separate tasks that occur in diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/__init__.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/bim2sim/plugins/PluginSpawnOfEP/bim2sim_spawn/models/__init__.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/models/__init__.py similarity index 98% rename from bim2sim/plugins/PluginSpawnOfEP/bim2sim_spawn/models/__init__.py rename to bim2sim/plugins/PluginSpawn/bim2sim_spawn/models/__init__.py index 701befb847..39e216fee9 100644 --- a/bim2sim/plugins/PluginSpawnOfEP/bim2sim_spawn/models/__init__.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/models/__init__.py @@ -1,13 +1,12 @@ """Package for Python representations of HKESim models""" -import bim2sim.kernel.aggregation as aggregation -from bim2sim.export import modelica -from bim2sim.kernel.elements import hvac -from bim2sim.kernel.units import ureg +import bim2sim.elements.aggregation.bps_aggregations as aggregation +import bim2sim.elements.hvac_elements as hvac +from bim2sim.elements.mapping.units import ureg -class AixLib(modelica.Instance): - library = "AixLib" - +# class AixLib(modelica.Instance): +# library = "AixLib" +# class EnergyPlusFMU(AixLib): path = "AixLib.Fluid.BoilerCHP.BoilerNotManufacturer" diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/__init__.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/__init__.py new file mode 100644 index 0000000000..749ac18286 --- /dev/null +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/__init__.py @@ -0,0 +1,2 @@ +from .create_spawn_elements import CreateSpawnElements +from .export_co_sim import ExportModelicaSpawn diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/create_spawn_elements.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/create_spawn_elements.py new file mode 100644 index 0000000000..80499c1728 --- /dev/null +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/create_spawn_elements.py @@ -0,0 +1,26 @@ +from pathlib import Path + +from bim2sim.tasks.base import ITask +from bim2sim.elements.bps_elements import SpawnBuilding, SpawnMultiZone, FreshAirSource + + +class CreateSpawnElements(ITask): + # reads = ('instances',) + touches = ('instances',) + + def run(self): + # def run(self, instances): + # TODO maybe add an option to manual create elements for export without + # represents. Because the represents attribute currently makes it + # necessary to create those temp objects here and add them to elements + spawn_building = SpawnBuilding() + spawn_building.idfName = Path("D:/Test") + spawn_building.epwName = Path("D:/Test") + spawn_building.weaName = Path("D:/Test") + spawn_building.printUnits = True + fresh_air_source = FreshAirSource() + + instances = { + spawn_building.guid: spawn_building, + fresh_air_source.guid: fresh_air_source} + return instances, diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_co_sim.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_co_sim.py new file mode 100644 index 0000000000..0883b29ee8 --- /dev/null +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_co_sim.py @@ -0,0 +1,61 @@ +from datetime import datetime + +from bim2sim.elements.base_elements import ProductBased +from bim2sim.export import modelica +from bim2sim.tasks.base import ITask +from bim2sim.plugins.PluginBuildings.bim2sim_buildings.models import SpawnBuilding,\ + EPThermalZone, FreshAirSource +from bim2sim.plugins.PluginAixLib.bim2sim_aixlib.models import SpawnMultizone + + +class ExportModelicaSpawn(ITask): + """Export to Dymola/Modelica""" + + reads = ('libraries', 'instances',) + final = True + + def run(self, libraries: tuple, instances: dict): + self.logger.info("Export to Modelica code") + modelica.Instance.init_factory(libraries) + export_instances = {inst: modelica.Instance.factory(inst) + for inst in instances.values()} + + yield from ProductBased.get_pending_attribute_decisions( + instances.values()) + + for instance in export_instances.values(): + instance.collect_params() + static_connections = self.get_static_connections(export_instances) + # TODO dynamic connections between multizone and hvac + # dyn_connections = get_dynamic_connections(export_instances) + # connections = static_connections + dyn_connections + connections = static_connections + + self.logger.info( + "Creating Modelica model with %d model instances.", + len(export_instances),) + + prj_name = self.prj_name.replace('-','_') + + modelica_model = modelica.Model( + name="bim2sim_"+prj_name, + comment=f"Autogenerated by BIM2SIM on {datetime.now():%Y-%m-%d %H:%M:%S%z}", + instances=list(export_instances.values()), + connections=connections, + ) + modelica_model.save(self.paths.export) + + def get_static_connections(self, instances): + connections = [] + for inst in instances.values(): + # Todo why does isinstance not work? + if isinstance(inst, SpawnBuilding): + spawn_building = inst + if isinstance(inst, FreshAirSource): + fresh_air = inst + if isinstance(inst, SpawnMultizone): + multi = inst + if spawn_building and fresh_air: + connections.append((str(spawn_building.name)+'.weaBus', + str(fresh_air.name) +'.weaBus')) + return connections diff --git a/bim2sim/plugins/PluginSpawnOfEP/bim2sim_spawn/tasks/export_co_sim.py b/bim2sim/plugins/PluginSpawnOfEP/bim2sim_spawn/tasks/export_co_sim.py deleted file mode 100644 index 0810ff5103..0000000000 --- a/bim2sim/plugins/PluginSpawnOfEP/bim2sim_spawn/tasks/export_co_sim.py +++ /dev/null @@ -1,16 +0,0 @@ -from bim2sim.task.base import ITask - - -class GetZoneConnections(ITask): - ... - # TODO (maybe later) - - -class CoSimExport(ITask): - ... - # TODO create static make template with - # - hydraulic: map the existing hydraulic system of Modelica export into - # one single top level component - # - floor (from Buidlings) - # - idf link to building (from Buildings) - # - connect all stuff diff --git a/bim2sim/tasks/common/__init__.py b/bim2sim/tasks/common/__init__.py index 293c154371..f5f8ada6c4 100644 --- a/bim2sim/tasks/common/__init__.py +++ b/bim2sim/tasks/common/__init__.py @@ -4,4 +4,5 @@ from .check_ifc import CheckIfc, CheckIfcBPS, CheckIfcHVAC from .create_elements import CreateElements from .weather import Weather +from .export_modelica import ExportModelica diff --git a/bim2sim/tasks/common/export_modelica.py b/bim2sim/tasks/common/export_modelica.py new file mode 100644 index 0000000000..606775fbb3 --- /dev/null +++ b/bim2sim/tasks/common/export_modelica.py @@ -0,0 +1,39 @@ +from datetime import datetime + +from bim2sim.elements.base_elements import ProductBased +from bim2sim.export import modelica +from bim2sim.tasks.base import ITask + + +class ExportModelica(ITask): + """Export to Dymola/Modelica""" + + reads = ('libraries', 'instances',) + final = True + + def run(self, libraries: tuple, instances: dict): + self.logger.info("Export to Modelica code") + modelica.Instance.init_factory(libraries) + export_instances = {inst: modelica.Instance.factory(inst) + for inst in instances.values()} + + yield from ProductBased.get_pending_attribute_decisions( + instances.values()) + + for instance in export_instances.values(): + instance.collect_params() + static_connections = get_static_connections() + dyn_connections = get_dynamic_connections(export_instances) + connections = static_connections + dyn_connections + + self.logger.info( + "Creating Modelica model with %d model instances.", + len(export_instances),) + + modelica_model = modelica.Model( + name="bim2sim_"+self.prj_name, + comment=f"Autogenerated by BIM2SIM on {datetime.now():%Y-%m-%d %H:%M:%S%z}", + instances=list(export_instances.values()), + connections=connections, + ) + modelica_model.save(self.paths.export) \ No newline at end of file From 04754ced6b864c4f02e35d271e3a1491956c42c4 Mon Sep 17 00:00:00 2001 From: David Date: Thu, 17 Aug 2023 15:43:40 +0200 Subject: [PATCH 005/125] first export of multizone implemented --- bim2sim/elements/bps_elements.py | 15 +++++++- .../bim2sim_aixlib/models/__init__.py | 36 ------------------- .../bim2sim_buildings/models/__init__.py | 26 ++++++++++++-- .../tasks/create_spawn_elements.py | 4 ++- .../bim2sim_spawn/tasks/export_co_sim.py | 11 +++--- 5 files changed, 48 insertions(+), 44 deletions(-) diff --git a/bim2sim/elements/bps_elements.py b/bim2sim/elements/bps_elements.py index 7b1e3ce98a..fd978b850a 100644 --- a/bim2sim/elements/bps_elements.py +++ b/bim2sim/elements/bps_elements.py @@ -1953,7 +1953,20 @@ def calc_position(self) -> np.array: class SpawnMultiZone(Element): - pass + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.Medium: str = "" + self.zone_names: list = self.get_zone_names() + self.use_c_flow: bool = False + + @staticmethod + def get_zone_names(): + # TODO #1: get names from IDF or EP process for ep zones in + # correct order + return ['"Living"'] + + def calc_position(self) -> np.array: + return np.array([+10, 20, 12]) # collect all domain classes items: Set[BPSProduct] = set() diff --git a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py index 0d54b12e4b..f9c933fdd0 100644 --- a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py +++ b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py @@ -424,39 +424,3 @@ def request_params(self): f" dTank={self.element.diameter})" -class SpawnMultizone(AixLib): - # TODO #1 - path = "AixLib.ThermalZone..." - represents = [bps.SpawnMultiZone] - - def __init__(self, element): - super().__init__(element) - - def request_params(self): - - self.params["redeclare package Medium"] = 'AixLib.Media.Water' - self.request_param("dT_water", - self.check_numeric(min_value=0 * ureg.kelvin), - "dTWaterNom") - self.request_param("return_temperature", - self.check_numeric(min_value=0 * ureg.celsius), - "TRetNom") - self.request_param("rated_power", - self.check_numeric(min_value=0 * ureg.kilowatt), - "QNom") - self.request_param("min_PLR", - self.check_numeric(min_value=0 * ureg.dimensionless), - "PLRMin") - - def get_port_name(self, port): - try: - index = self.element.ports.index(port) - except ValueError: - # unknown port - index = -1 - if port.verbose_flow_direction == 'SINK': - return 'port_a' - if port.verbose_flow_direction == 'SOURCE': - return 'port_b' - else: - return super().get_port_name(port) # ToDo: Gas connection diff --git a/bim2sim/plugins/PluginBuildings/bim2sim_buildings/models/__init__.py b/bim2sim/plugins/PluginBuildings/bim2sim_buildings/models/__init__.py index f9da082d08..fc1149db76 100644 --- a/bim2sim/plugins/PluginBuildings/bim2sim_buildings/models/__init__.py +++ b/bim2sim/plugins/PluginBuildings/bim2sim_buildings/models/__init__.py @@ -38,8 +38,30 @@ class FreshAirSource(Buildings): path = "Buildings.Fluid.Sources.MassFlowSource_WeatherData" represents = [bps.FreshAirSource] - def get_port_name(self, port): - return "weaBus" + +# TODO this should be placed in AixLib but currently bim2sim only supports one +# modelica library for export +class SpawnMultizone(Buildings): + path = "AixLib.ThermalZones.HighOrder.SpawnOfEP.Multizone" + represents = [bps.SpawnMultiZone] + + def _get_name(self): + # TODO #1 maybe find a more generic way via mixins? then lookup needs to + # be adjusted + """For static export elements + + This removes the dynamic name creation for elements which will always + occur static in later export. + """ + name = self.element.__class__.__name__.lower() + return name + + def request_params(self): + # TODO #1: get names of ep zones in correct order + self.params["nZones"] = len(self.element.zone_names) + # TODO: #542 How to export an array of values + self.params["zoneNames"] = self.element.zone_names + # class EPMultizone(Buildings): diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/create_spawn_elements.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/create_spawn_elements.py index 80499c1728..72d6355ad9 100644 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/create_spawn_elements.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/create_spawn_elements.py @@ -19,8 +19,10 @@ def run(self): spawn_building.weaName = Path("D:/Test") spawn_building.printUnits = True fresh_air_source = FreshAirSource() + spawn_multi = SpawnMultiZone() instances = { spawn_building.guid: spawn_building, - fresh_air_source.guid: fresh_air_source} + fresh_air_source.guid: fresh_air_source, + spawn_multi.guid: spawn_multi} return instances, diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_co_sim.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_co_sim.py index 0883b29ee8..f610f3a228 100644 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_co_sim.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_co_sim.py @@ -4,8 +4,7 @@ from bim2sim.export import modelica from bim2sim.tasks.base import ITask from bim2sim.plugins.PluginBuildings.bim2sim_buildings.models import SpawnBuilding,\ - EPThermalZone, FreshAirSource -from bim2sim.plugins.PluginAixLib.bim2sim_aixlib.models import SpawnMultizone + EPThermalZone, SpawnMultizone, FreshAirSource class ExportModelicaSpawn(ITask): @@ -48,14 +47,18 @@ def run(self, libraries: tuple, instances: dict): def get_static_connections(self, instances): connections = [] for inst in instances.values(): - # Todo why does isinstance not work? if isinstance(inst, SpawnBuilding): spawn_building = inst if isinstance(inst, FreshAirSource): fresh_air = inst if isinstance(inst, SpawnMultizone): multi = inst - if spawn_building and fresh_air: + # TODO remove if as this is only temporary for development + if spawn_building and fresh_air and multi: connections.append((str(spawn_building.name)+'.weaBus', str(fresh_air.name) +'.weaBus')) + # TODO clarify export and arrays in modelica + connections.append(( + str(multi.name)+".portsExt[nZones]", + str(fresh_air.name)+".ports[nPorts]")) return connections From 8c40a964286f5c60f050a1d14e1badcf9d0ca283 Mon Sep 17 00:00:00 2001 From: David Date: Fri, 18 Aug 2023 12:01:42 +0200 Subject: [PATCH 006/125] commit to get zone_lists from energyplus task for later spawn model --- .../bim2sim_energyplus/task/ep_create_idf.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/bim2sim/plugins/PluginEnergyPlus/bim2sim_energyplus/task/ep_create_idf.py b/bim2sim/plugins/PluginEnergyPlus/bim2sim_energyplus/task/ep_create_idf.py index 8f37dbbfb5..1a3ba84b8c 100644 --- a/bim2sim/plugins/PluginEnergyPlus/bim2sim_energyplus/task/ep_create_idf.py +++ b/bim2sim/plugins/PluginEnergyPlus/bim2sim_energyplus/task/ep_create_idf.py @@ -47,7 +47,7 @@ class CreateIdf(ITask): """ reads = ('instances', 'weather_file',) - touches = ('idf',) + touches = ('idf','zone_lists') def __init__(self, playground): super().__init__(playground) @@ -60,7 +60,7 @@ def run(self, instances, weather_file): weather_file, self.prj_name) self.init_zone(self.playground.sim_settings, instances, idf) self.init_zonelist(idf) - self.init_zonegroups(instances, idf) + zone_lists = self.init_zonegroups(instances, idf) self.get_preprocessed_materials_and_constructions( self.playground.sim_settings, instances, idf) if self.playground.sim_settings.add_shadings: @@ -80,7 +80,7 @@ def run(self, instances, weather_file): idf.save(idf.idfname) logger.info("Idf file successfully saved.") - return idf, + return idf, zone_lists @staticmethod def init_idf(sim_settings: EnergyPlusSimSettings, paths: FolderStructure, @@ -199,6 +199,9 @@ def init_zonegroups(self, instances: dict, idf: IDF): Args: instances: dict[guid: element] idf: idf file object + Returns + zone_lists: list of all zones with their name in EP + @Veronika Richter, correct? """ spaces = get_spaces_with_bounds(instances) # assign storeys to spaces (ThermalZone) @@ -228,6 +231,8 @@ def init_zonegroups(self, instances: dict, idf: IDF): Zone_List_Name=zlist.Name, Zone_List_Multiplier=1 ) + return zone_lists + @staticmethod def check_preprocessed_materials_and_constructions(rel_elem, layers): """Check if preprocessed materials and constructions are valid.""" From bb39304a1347f0f40940d1e2a28f7ac1085b0619 Mon Sep 17 00:00:00 2001 From: David Date: Fri, 18 Aug 2023 12:13:43 +0200 Subject: [PATCH 007/125] ep_zone_list is now taken from previous energyplus run --- bim2sim/elements/bps_elements.py | 7 +------ .../bim2sim_energyplus/task/ep_create_idf.py | 6 +++--- .../bim2sim_spawn/tasks/create_spawn_elements.py | 12 ++++++++++++ 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/bim2sim/elements/bps_elements.py b/bim2sim/elements/bps_elements.py index fd978b850a..0a245036f5 100644 --- a/bim2sim/elements/bps_elements.py +++ b/bim2sim/elements/bps_elements.py @@ -1956,14 +1956,9 @@ class SpawnMultiZone(Element): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.Medium: str = "" - self.zone_names: list = self.get_zone_names() + self.zone_names: list = [] self.use_c_flow: bool = False - @staticmethod - def get_zone_names(): - # TODO #1: get names from IDF or EP process for ep zones in - # correct order - return ['"Living"'] def calc_position(self) -> np.array: return np.array([+10, 20, 12]) diff --git a/bim2sim/plugins/PluginEnergyPlus/bim2sim_energyplus/task/ep_create_idf.py b/bim2sim/plugins/PluginEnergyPlus/bim2sim_energyplus/task/ep_create_idf.py index 1a3ba84b8c..0e8da7b0f1 100644 --- a/bim2sim/plugins/PluginEnergyPlus/bim2sim_energyplus/task/ep_create_idf.py +++ b/bim2sim/plugins/PluginEnergyPlus/bim2sim_energyplus/task/ep_create_idf.py @@ -47,7 +47,7 @@ class CreateIdf(ITask): """ reads = ('instances', 'weather_file',) - touches = ('idf','zone_lists') + touches = ('idf', 'ep_zone_lists') def __init__(self, playground): super().__init__(playground) @@ -60,7 +60,7 @@ def run(self, instances, weather_file): weather_file, self.prj_name) self.init_zone(self.playground.sim_settings, instances, idf) self.init_zonelist(idf) - zone_lists = self.init_zonegroups(instances, idf) + ep_zone_lists = self.init_zonegroups(instances, idf) self.get_preprocessed_materials_and_constructions( self.playground.sim_settings, instances, idf) if self.playground.sim_settings.add_shadings: @@ -80,7 +80,7 @@ def run(self, instances, weather_file): idf.save(idf.idfname) logger.info("Idf file successfully saved.") - return idf, zone_lists + return idf, ep_zone_lists @staticmethod def init_idf(sim_settings: EnergyPlusSimSettings, paths: FolderStructure, diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/create_spawn_elements.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/create_spawn_elements.py index 72d6355ad9..81edec80fd 100644 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/create_spawn_elements.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/create_spawn_elements.py @@ -20,9 +20,21 @@ def run(self): spawn_building.printUnits = True fresh_air_source = FreshAirSource() spawn_multi = SpawnMultiZone() + spawn_multi.zone_names = self.get_zone_names() instances = { spawn_building.guid: spawn_building, fresh_air_source.guid: fresh_air_source, spawn_multi.guid: spawn_multi} return instances, + + def get_zone_names(self): + # TODO #1: get names from IDF or EP process for ep zones in + # correct order + if "ep_zone_lists" in self.playground.state: + zone_list = self.playground.state["ep_zone_lists"] + else: + raise ValueError("'ep_zone_list' not found in playground state, " + "please make sure that EnergyPlus model creation " + "was successful.") + return zone_list From 1d6f30f4b83f5988c996aaa9a3df59b9738f465a Mon Sep 17 00:00:00 2001 From: veronika_richter Date: Tue, 22 Aug 2023 10:10:08 +0200 Subject: [PATCH 008/125] export list of thermal zone names (i.e., list of guids) --- .../PluginEnergyPlus/bim2sim_energyplus/task/ep_create_idf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bim2sim/plugins/PluginEnergyPlus/bim2sim_energyplus/task/ep_create_idf.py b/bim2sim/plugins/PluginEnergyPlus/bim2sim_energyplus/task/ep_create_idf.py index 0e8da7b0f1..1aba293ddd 100644 --- a/bim2sim/plugins/PluginEnergyPlus/bim2sim_energyplus/task/ep_create_idf.py +++ b/bim2sim/plugins/PluginEnergyPlus/bim2sim_energyplus/task/ep_create_idf.py @@ -60,7 +60,8 @@ def run(self, instances, weather_file): weather_file, self.prj_name) self.init_zone(self.playground.sim_settings, instances, idf) self.init_zonelist(idf) - ep_zone_lists = self.init_zonegroups(instances, idf) + self.init_zonegroups(instances, idf) + ep_zone_lists = [z.Name for z in idf.idfobjects['ZONE']] self.get_preprocessed_materials_and_constructions( self.playground.sim_settings, instances, idf) if self.playground.sim_settings.add_shadings: @@ -231,7 +232,6 @@ def init_zonegroups(self, instances: dict, idf: IDF): Zone_List_Name=zlist.Name, Zone_List_Multiplier=1 ) - return zone_lists @staticmethod def check_preprocessed_materials_and_constructions(rel_elem, layers): From 4f2ea8550eca9c782bedc43f41e58a78771cab52 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 29 Aug 2023 09:19:01 +0200 Subject: [PATCH 009/125] adapt example for buildings to new example/test files structure --- .../examples/e1_simple_project_bps_buildings.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/bim2sim/plugins/PluginBuildings/bim2sim_buildings/examples/e1_simple_project_bps_buildings.py b/bim2sim/plugins/PluginBuildings/bim2sim_buildings/examples/e1_simple_project_bps_buildings.py index a34c22929c..c5b4ff1090 100644 --- a/bim2sim/plugins/PluginBuildings/bim2sim_buildings/examples/e1_simple_project_bps_buildings.py +++ b/bim2sim/plugins/PluginBuildings/bim2sim_buildings/examples/e1_simple_project_bps_buildings.py @@ -4,6 +4,7 @@ import bim2sim from bim2sim import Project, run_project, ConsoleDecisionHandler from bim2sim.kernel.log import default_logging_setup +from bim2sim.utilities.common_functions import download_test_resources from bim2sim.utilities.types import IFCDomain @@ -26,18 +27,21 @@ def run_example_1(): project_path = Path( tempfile.TemporaryDirectory(prefix='bim2sim_example1').name) - # Get path of the IFC Building model that is used for this example - ifc_paths = {IFCDomain.arch: Path( - bim2sim.__file__).parent / - 'assets/ifc_example_files/AC20-FZK-Haus.ifc', - } + download_test_resources(IFCDomain.arch, force_new=False) + # Set the ifc path to use and define which domain the IFC belongs to + ifc_paths = { + IFCDomain.arch: + Path(bim2sim.__file__).parent.parent / + 'test/resources/arch/ifc/AC20-FZK-Haus.ifc', + } + # Create a project including the folder structure for the project with # energyplus as backend project = Project.create(project_path, ifc_paths, 'buildings') # Set the install path to your EnergyPlus installation according to your # system requirements - # project.sim_settings.ep_install_path = 'C://EnergyPlusV9-4-0/' + project.sim_settings.ep_install_path = 'D://04_Programme/EnergyPlus-9-4-0/' # Set other simulation settings, otherwise all settings are set to default From 3d0ac8cb0053d46d96d773e402a65a24bdca027f Mon Sep 17 00:00:00 2001 From: David Date: Tue, 29 Aug 2023 09:24:44 +0200 Subject: [PATCH 010/125] temporary use EPSimSettings as their seems to be an issue with inherting --- bim2sim/plugins/PluginBuildings/bim2sim_buildings/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bim2sim/plugins/PluginBuildings/bim2sim_buildings/__init__.py b/bim2sim/plugins/PluginBuildings/bim2sim_buildings/__init__.py index 4e9b42f108..42c68da5e9 100644 --- a/bim2sim/plugins/PluginBuildings/bim2sim_buildings/__init__.py +++ b/bim2sim/plugins/PluginBuildings/bim2sim_buildings/__init__.py @@ -5,7 +5,7 @@ from bim2sim.plugins import Plugin from bim2sim.plugins.PluginBuildings.bim2sim_buildings.models import Buildings from bim2sim.tasks import base, common, hvac -from bim2sim.sim_settings import BuildingSimSettings +from bim2sim.sim_settings import BuildingSimSettings, EnergyPlusSimSettings import bim2sim.plugins.PluginSpawn.bim2sim_spawn.tasks as spawn_tasks @@ -22,7 +22,7 @@ def overwrite_standarlib_models(self): class PluginBuildings(Plugin): name = 'Buildings' - sim_settings = BuildingSimSettings + sim_settings = EnergyPlusSimSettings tasks = {LoadLibrariesBuildings} default_tasks = [ # common.LoadIFC, From 0401fdb6e56a61f253480c3b24821e7f5e5ec1c5 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 29 Aug 2023 13:28:26 +0200 Subject: [PATCH 011/125] start implementation for spawnEP base template --- .../assets/templates/modelica/tmplSpawn.txt | 0 .../bim2sim_buildings/data/modelica_language | 36 ------------------- 2 files changed, 36 deletions(-) create mode 100644 bim2sim/assets/templates/modelica/tmplSpawn.txt delete mode 100644 bim2sim/plugins/PluginBuildings/bim2sim_buildings/data/modelica_language diff --git a/bim2sim/assets/templates/modelica/tmplSpawn.txt b/bim2sim/assets/templates/modelica/tmplSpawn.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/bim2sim/plugins/PluginBuildings/bim2sim_buildings/data/modelica_language b/bim2sim/plugins/PluginBuildings/bim2sim_buildings/data/modelica_language deleted file mode 100644 index 56a26c2bd4..0000000000 --- a/bim2sim/plugins/PluginBuildings/bim2sim_buildings/data/modelica_language +++ /dev/null @@ -1,36 +0,0 @@ -##get false -<%def name="get_true_false(value)", filter="trim"> - <% - if value is False: - return "false" - elif value is True: - return "true" - else: - return value - %> - - -##list to modelica list -##Modelica does not allow empty lists, sets one zero entry as default for -##such cases -<%def name="get_list(list)", filter="trim"> - <% - if list: - return str(list).replace('[', '{').replace(']', '}') - else: - ret="{0}" - return ret - %> - - -##catch zero orientations -##The value nOrientations defines length of lists in Modelica, thus must be -##greater than zero -<%def name="min_orientations(orientation)", filter="trim"> - <% - if orientation < 1: - return 1 - else: - return orientation - %> - From ebd966cef1fb570874636709978d0d12eacbecf7 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 29 Aug 2023 13:28:49 +0200 Subject: [PATCH 012/125] start implementation for spawnEP base template [2] --- .../assets/templates/modelica/tmplSpawn.txt | 37 +++++++++++++++++++ bim2sim/export/modelica/__init__.py | 3 +- .../bim2sim_buildings/__init__.py | 21 +++++++++-- .../e1_simple_project_bps_buildings.py | 3 +- .../bim2sim_buildings/models/__init__.py | 18 ++++++++- .../tasks/create_spawn_elements.py | 10 +++-- 6 files changed, 83 insertions(+), 9 deletions(-) diff --git a/bim2sim/assets/templates/modelica/tmplSpawn.txt b/bim2sim/assets/templates/modelica/tmplSpawn.txt index e69de29bb2..8c905df584 100644 --- a/bim2sim/assets/templates/modelica/tmplSpawn.txt +++ b/bim2sim/assets/templates/modelica/tmplSpawn.txt @@ -0,0 +1,37 @@ +model ${model.name} "${model.comment}" + extends Modelica.Icons.Example; + import SI = Modelica.Units.SI; + + Buildings.ThermalZones.EnergyPlus_9_6_0.Building Building ( + idfName = "${model.building_idf}" # Modelica.Utilities.Files.loadResource("C:\Users\dja\AppData\Local\Temp\bim2sim_example1hb0js61u\export\AC20-FZK-Haus.idf"), + epwName = "${model.building_wea_epw}" # Modelica.Utilities.Files.loadResource("C:\Users\dja\AppData\Local\Temp\bim2sim_example1hb0js61u\weatherfiles\DEU_NW_Aachen.105010_TMYx.epw"), + weaName = "${model.building_wea_mos}" # Modelica.Utilities.Files.loadResource("C:\Users\dja\AppData\Local\Temp\bim2sim_example1hb0js61u\weatherfiles\DEU_NW_Aachen.105010_TMYx.mos"), + printUnits = true) + "" annotation (Placement(transformation(extent={{-178,-30},{-158,-10}}))); + + Buildings.Fluid.Sources.MassFlowSource_WeatherData freshairsource["${model.n_zones}"](redeclare package Medium = Buildings.Media.Air) "" + annotation (Placement(transformation(extent={{-19.1111,54},{0.88889,74}}))); +# TODO from here on + AixLib.ThermalZones.HighOrder.SpawnOfEP.Multizone spawnmultizone( + redeclare package Medium = Buildings.Media.Air, + nZones = "${model.n_zones}", + zoneNames = {"0Lt8gR_E9ESeGH5uY_g9e9","0e_hbkIQ5DMQlIJ$2V3j_m","17JZcMFrf5tOftUTidA0d3","2RSCzLOBz4FAK$_wE8VckM","2dQFggKBb1fOc1CqZDIDlx","3$f2p7VyLB7eox67SA_zKE","347jFE2yX7IhCEIALmupEH"}) + "" annotation (Placement(transformation(extent={{114,-12},{134,8}}))); + +equation + for i in 1:spawnmultizone.nZones loop + connect(freshairsource_0000000000000000000054[i].ports[:], spawnmultizone.portsExt[i,:]) + annotation (Line(points={{0.88889,64},{118,64},{118,-18},{6,-18},{6,-11.9},{ + 124.2,-11.9}}, color={0,0,127})); + connect(spawnbuilding_0000000000000000000053.weaBus, + freshairsource_0000000000000000000054[i].weaBus) annotation (Line( + points={{-158,-20},{-114,-20},{-114,-22},{-62,-22},{-62,64.2},{-19.1111,64.2}}, + color={255,204,51}, + thickness=0.5)); + end for; + annotation ( + experiment(StopTime=36000), uses( + Modelica(version="4.0.0"), + Buildings(version="10.0.0"), + AixLib(version="1.3.2"))); +end bim2sim_AC20_FZK_Haus_test1; diff --git a/bim2sim/export/modelica/__init__.py b/bim2sim/export/modelica/__init__.py index 07aa512fef..c2ebbae3dd 100644 --- a/bim2sim/export/modelica/__init__.py +++ b/bim2sim/export/modelica/__init__.py @@ -305,7 +305,8 @@ def to_modelica(parameter): ",".join((Instance.to_modelica(par) for par in parameter))) if isinstance(parameter, Path): return \ - f"Modelica.Utilities.Files.loadResource(\"{str(parameter)}\")" + f"Modelica.Utilities.Files.loadResource(\"{str(parameter)}\")"\ + .replace("\\", "\\\\") logger.warning("Unknown class (%s) for conversion", parameter.__class__) return str(parameter) diff --git a/bim2sim/plugins/PluginBuildings/bim2sim_buildings/__init__.py b/bim2sim/plugins/PluginBuildings/bim2sim_buildings/__init__.py index 42c68da5e9..1797286794 100644 --- a/bim2sim/plugins/PluginBuildings/bim2sim_buildings/__init__.py +++ b/bim2sim/plugins/PluginBuildings/bim2sim_buildings/__init__.py @@ -4,9 +4,10 @@ from bim2sim.export.modelica import standardlibrary from bim2sim.plugins import Plugin from bim2sim.plugins.PluginBuildings.bim2sim_buildings.models import Buildings -from bim2sim.tasks import base, common, hvac +from bim2sim.tasks import base, common, hvac, bps from bim2sim.sim_settings import BuildingSimSettings, EnergyPlusSimSettings import bim2sim.plugins.PluginSpawn.bim2sim_spawn.tasks as spawn_tasks +from bim2sim.plugins.PluginEnergyPlus.bim2sim_energyplus import task as ep_tasks class LoadLibrariesBuildings(base.ITask): @@ -25,8 +26,22 @@ class PluginBuildings(Plugin): sim_settings = EnergyPlusSimSettings tasks = {LoadLibrariesBuildings} default_tasks = [ - # common.LoadIFC, - # common.CreateElements, + common.LoadIFC, + # common.CheckIfc, + common.CreateElements, + bps.CreateSpaceBoundaries, + bps.Prepare, + common.BindStoreys, + bps.EnrichUseConditions, + bps.VerifyLayersMaterials, # LOD.full + bps.EnrichMaterial, # LOD.full + ep_tasks.EPGeomPreprocessing, + ep_tasks.AddSpaceBoundaries2B, + ep_tasks.WeatherEnergyPlus, + ep_tasks.CreateIdf, + # ep_tasks.IdfPostprocessing, + # ep_tasks.ExportIdfForCfd, + # ep_tasks.RunEnergyPlusSimulation, spawn_tasks.CreateSpawnElements, LoadLibrariesBuildings, spawn_tasks.ExportModelicaSpawn, diff --git a/bim2sim/plugins/PluginBuildings/bim2sim_buildings/examples/e1_simple_project_bps_buildings.py b/bim2sim/plugins/PluginBuildings/bim2sim_buildings/examples/e1_simple_project_bps_buildings.py index c5b4ff1090..78afa71b17 100644 --- a/bim2sim/plugins/PluginBuildings/bim2sim_buildings/examples/e1_simple_project_bps_buildings.py +++ b/bim2sim/plugins/PluginBuildings/bim2sim_buildings/examples/e1_simple_project_bps_buildings.py @@ -41,7 +41,8 @@ def run_example_1(): # Set the install path to your EnergyPlus installation according to your # system requirements - project.sim_settings.ep_install_path = 'D://04_Programme/EnergyPlus-9-4-0/' + project.sim_settings.ep_install_path = Path( + 'D://04_Programme/EnergyPlus-9-4-0/') # Set other simulation settings, otherwise all settings are set to default diff --git a/bim2sim/plugins/PluginBuildings/bim2sim_buildings/models/__init__.py b/bim2sim/plugins/PluginBuildings/bim2sim_buildings/models/__init__.py index fc1149db76..9187fb9c12 100644 --- a/bim2sim/plugins/PluginBuildings/bim2sim_buildings/models/__init__.py +++ b/bim2sim/plugins/PluginBuildings/bim2sim_buildings/models/__init__.py @@ -3,6 +3,7 @@ from bim2sim.elements import hvac_elements as hvac from bim2sim.elements import bps_elements as bps from bim2sim.elements.mapping.units import ureg +from bim2sim.export.modelica import Model class Buildings(modelica.Instance): @@ -38,6 +39,8 @@ class FreshAirSource(Buildings): path = "Buildings.Fluid.Sources.MassFlowSource_WeatherData" represents = [bps.FreshAirSource] + def request_params(self): + self.params["redeclare package Medium"] = 'Buildings.Media.Air' # TODO this should be placed in AixLib but currently bim2sim only supports one # modelica library for export @@ -57,10 +60,23 @@ def _get_name(self): return name def request_params(self): + self.params["redeclare package Medium"] = 'Buildings.Media.Air' # TODO #1: get names of ep zones in correct order self.params["nZones"] = len(self.element.zone_names) # TODO: #542 How to export an array of values - self.params["zoneNames"] = self.element.zone_names + self.params["zoneNames"] = [f'"{ele}"' + for ele in self.element.zone_names] + + +class SpawnModel(Model): + def __init__(self, name, comment, instances: list, connections: list): + super().__init__(name, comment, instances, connections) + self.building_idf = None + self.building_wea_epw = None + self.building_wea_mos = None + + self.n_zones = None + self.zone_names = [] diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/create_spawn_elements.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/create_spawn_elements.py index 81edec80fd..d11b694f7d 100644 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/create_spawn_elements.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/create_spawn_elements.py @@ -14,9 +14,13 @@ def run(self): # represents. Because the represents attribute currently makes it # necessary to create those temp objects here and add them to elements spawn_building = SpawnBuilding() - spawn_building.idfName = Path("D:/Test") - spawn_building.epwName = Path("D:/Test") - spawn_building.weaName = Path("D:/Test") + spawn_building.idfName = self.paths.export / str( + self.prj_name + ".idf") + # todo use loadresource maybe after prototype ready + spawn_building.epwName = self.paths.root / 'weatherfiles' / \ + str(self.playground.state["weather_file"].stem + '.epw') + spawn_building.weaName = self.paths.root / 'weatherfiles' / \ + str(self.playground.state["weather_file"].stem + '.mos') spawn_building.printUnits = True fresh_air_source = FreshAirSource() spawn_multi = SpawnMultiZone() From cd4b5b2a188abef290cdea2d0d44122d7ce6075b Mon Sep 17 00:00:00 2001 From: David Jansen Date: Tue, 19 Dec 2023 12:00:28 +0100 Subject: [PATCH 013/125] move from dynamic to static template for spawn building sim --- .../assets/templates/modelica/tmplSpawn.txt | 38 +++--- bim2sim/plugins/PluginBuildings/__init__.py | 0 .../bim2sim_buildings/__init__.py | 49 -------- .../bim2sim_buildings/examples/__init__.py | 0 .../bim2sim_buildings/models/__init__.py | 117 ------------------ .../PluginSpawn/bim2sim_spawn/__init__.py | 54 ++++---- .../examples/e1_simple_project_bps_spawn.py} | 4 +- .../bim2sim_spawn/tasks/__init__.py | 3 +- .../tasks/create_spawn_elements.py | 44 ------- .../bim2sim_spawn/tasks/export_co_sim.py | 64 ---------- .../tasks/export_co_sim_static.py | 110 ++++++++++++++++ bim2sim/tasks/common/weather.py | 3 +- 12 files changed, 160 insertions(+), 326 deletions(-) delete mode 100644 bim2sim/plugins/PluginBuildings/__init__.py delete mode 100644 bim2sim/plugins/PluginBuildings/bim2sim_buildings/__init__.py delete mode 100644 bim2sim/plugins/PluginBuildings/bim2sim_buildings/examples/__init__.py delete mode 100644 bim2sim/plugins/PluginBuildings/bim2sim_buildings/models/__init__.py rename bim2sim/plugins/{PluginBuildings/bim2sim_buildings/examples/e1_simple_project_bps_buildings.py => PluginSpawn/bim2sim_spawn/examples/e1_simple_project_bps_spawn.py} (94%) delete mode 100644 bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/create_spawn_elements.py delete mode 100644 bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_co_sim.py create mode 100644 bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_co_sim_static.py diff --git a/bim2sim/assets/templates/modelica/tmplSpawn.txt b/bim2sim/assets/templates/modelica/tmplSpawn.txt index 8c905df584..0e494e9859 100644 --- a/bim2sim/assets/templates/modelica/tmplSpawn.txt +++ b/bim2sim/assets/templates/modelica/tmplSpawn.txt @@ -1,34 +1,32 @@ -model ${model.name} "${model.comment}" - extends Modelica.Icons.Example; +model ${model_name} "${model_comment}" import SI = Modelica.Units.SI; + parameter String zoneNames[:] + "Name of the thermal zone as specified in the EnergyPlus input"; + Buildings.ThermalZones.EnergyPlus_9_6_0.Building Building ( - idfName = "${model.building_idf}" # Modelica.Utilities.Files.loadResource("C:\Users\dja\AppData\Local\Temp\bim2sim_example1hb0js61u\export\AC20-FZK-Haus.idf"), - epwName = "${model.building_wea_epw}" # Modelica.Utilities.Files.loadResource("C:\Users\dja\AppData\Local\Temp\bim2sim_example1hb0js61u\weatherfiles\DEU_NW_Aachen.105010_TMYx.epw"), - weaName = "${model.building_wea_mos}" # Modelica.Utilities.Files.loadResource("C:\Users\dja\AppData\Local\Temp\bim2sim_example1hb0js61u\weatherfiles\DEU_NW_Aachen.105010_TMYx.mos"), + idfName = ${idf_path}, + epwName = ${weather_path_ep}, + weaName = ${weather_path_mos}, printUnits = true) "" annotation (Placement(transformation(extent={{-178,-30},{-158,-10}}))); - Buildings.Fluid.Sources.MassFlowSource_WeatherData freshairsource["${model.n_zones}"](redeclare package Medium = Buildings.Media.Air) "" + Buildings.Fluid.Sources.MassFlowSource_WeatherData freshairsource (redeclare package Medium = Buildings.Media.Air) "" annotation (Placement(transformation(extent={{-19.1111,54},{0.88889,74}}))); -# TODO from here on - AixLib.ThermalZones.HighOrder.SpawnOfEP.Multizone spawnmultizone( - redeclare package Medium = Buildings.Media.Air, - nZones = "${model.n_zones}", - zoneNames = {"0Lt8gR_E9ESeGH5uY_g9e9","0e_hbkIQ5DMQlIJ$2V3j_m","17JZcMFrf5tOftUTidA0d3","2RSCzLOBz4FAK$_wE8VckM","2dQFggKBb1fOc1CqZDIDlx","3$f2p7VyLB7eox67SA_zKE","347jFE2yX7IhCEIALmupEH"}) - "" annotation (Placement(transformation(extent={{114,-12},{134,8}}))); + + Buildings.ThermalZones.EnergyPlus_9_6_0.ThermalZone zon[nZones]( + zoneName=zoneNames, + redeclare package Medium = Medium, + use_C_flow=use_C_flow, + each nPorts=nPorts) + annotation (Placement(transformation(extent={{-20,-20},{20,20}}))); equation - for i in 1:spawnmultizone.nZones loop - connect(freshairsource_0000000000000000000054[i].ports[:], spawnmultizone.portsExt[i,:]) - annotation (Line(points={{0.88889,64},{118,64},{118,-18},{6,-18},{6,-11.9},{ - 124.2,-11.9}}, color={0,0,127})); - connect(spawnbuilding_0000000000000000000053.weaBus, - freshairsource_0000000000000000000054[i].weaBus) annotation (Line( - points={{-158,-20},{-114,-20},{-114,-22},{-62,-22},{-62,64.2},{-19.1111,64.2}}, + connect(Building.weaBus, freshairsource.weaBus) annotation (Line( + points={{-74,50},{-26,50},{-26,64.2},{-19.1111,64.2}}, color={255,204,51}, thickness=0.5)); - end for; + annotation ( experiment(StopTime=36000), uses( Modelica(version="4.0.0"), diff --git a/bim2sim/plugins/PluginBuildings/__init__.py b/bim2sim/plugins/PluginBuildings/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/bim2sim/plugins/PluginBuildings/bim2sim_buildings/__init__.py b/bim2sim/plugins/PluginBuildings/bim2sim_buildings/__init__.py deleted file mode 100644 index 1b5671794b..0000000000 --- a/bim2sim/plugins/PluginBuildings/bim2sim_buildings/__init__.py +++ /dev/null @@ -1,49 +0,0 @@ -import re -from ast import literal_eval - -from bim2sim.export.modelica import standardlibrary -from bim2sim.plugins import Plugin -from bim2sim.plugins.PluginBuildings.bim2sim_buildings.models import Buildings -from bim2sim.tasks import base, common, hvac, bps -from bim2sim.sim_settings import BuildingSimSettings, EnergyPlusSimSettings -import bim2sim.plugins.PluginSpawn.bim2sim_spawn.tasks as spawn_tasks -from bim2sim.plugins.PluginEnergyPlus.bim2sim_energyplus import task as ep_tasks - - -class LoadLibrariesBuildings(base.ITask): - """Load AixLib library for export""" - touches = ('libraries', ) - - def run(self, **kwargs): - return (standardlibrary.StandardLibrary, Buildings), - - def overwrite_standarlib_models(self): - pass - - -class PluginBuildings(Plugin): - name = 'Buildings' - sim_settings = EnergyPlusSimSettings - tasks = {LoadLibrariesBuildings} - default_tasks = [ - common.LoadIFC, - # common.CheckIfc, - common.CreateElements, - bps.CreateSpaceBoundaries, - bps.FilterTZ, - bps.ProcessSlabsRoofs, - common.BindStoreys, - bps.EnrichUseConditions, - bps.VerifyLayersMaterials, # LOD.full - bps.EnrichMaterial, # LOD.full - ep_tasks.EPGeomPreprocessing, - ep_tasks.AddSpaceBoundaries2B, - common.Weather, - ep_tasks.CreateIdf, - # ep_tasks.IdfPostprocessing, - # ep_tasks.ExportIdfForCfd, - # ep_tasks.RunEnergyPlusSimulation, - spawn_tasks.CreateSpawnElements, - LoadLibrariesBuildings, - spawn_tasks.ExportModelicaSpawn, - ] diff --git a/bim2sim/plugins/PluginBuildings/bim2sim_buildings/examples/__init__.py b/bim2sim/plugins/PluginBuildings/bim2sim_buildings/examples/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/bim2sim/plugins/PluginBuildings/bim2sim_buildings/models/__init__.py b/bim2sim/plugins/PluginBuildings/bim2sim_buildings/models/__init__.py deleted file mode 100644 index e8d423f21a..0000000000 --- a/bim2sim/plugins/PluginBuildings/bim2sim_buildings/models/__init__.py +++ /dev/null @@ -1,117 +0,0 @@ -"""Package for Python representations of AixLib models""" -from bim2sim.export import modelica -from bim2sim.elements import hvac_elements as hvac -from bim2sim.elements import bps_elements as bps -from bim2sim.elements.mapping.units import ureg -from bim2sim.export.modelica import Model - - -class Buildings(modelica.Instance): - library = "Buildings" - - -class EPThermalZone(Buildings): - pass - - -class SpawnBuilding(Buildings): - path = "Buildings.ThermalZones.EnergyPlus_9_6_0.Building" - represents = [bps.SpawnBuilding] - - def request_params(self): - self.params["idfName"] = self.element.idfName - self.params["epwName"] = self.element.epwName - self.params["weaName"] = self.element.weaName - self.params["printUnits"] = self.element.printUnits - # self.request_param("idfName", None) - # self.request_param("epwName", None) - # self.request_param("weaName", None) - # self.request_param("printUnits", None) - # self.params["epwName"] = "D:/Test" - # self.params["weaName"] = "D:/Test" - # self.params["printUnits"] = True - - def get_port_name(self, port): - return "weaBus" - - -class FreshAirSource(Buildings): - path = "Buildings.Fluid.Sources.MassFlowSource_WeatherData" - represents = [bps.FreshAirSource] - - def request_params(self): - self.params["redeclare package Medium"] = 'Buildings.Media.Air' - -# TODO this should be placed in AixLib but currently bim2sim only supports one -# modelica library for export -class SpawnMultizone(Buildings): - path = "AixLib.ThermalZones.HighOrder.SpawnOfEP.Multizone" - represents = [bps.SpawnMultiZone] - - def _get_name(self): - # TODO #1 maybe find a more generic way via mixins? then lookup needs to - # be adjusted - """For static export elements - - This removes the dynamic name creation for elements which will always - occur static in later export. - """ - name = self.element.__class__.__name__.lower() - return name - - def request_params(self): - self.params["redeclare package Medium"] = 'Buildings.Media.Air' - # TODO #1: get names of ep zones in correct order - self.params["nZones"] = len(self.element.zone_names) - # TODO: #542 How to export an array of values - self.params["zoneNames"] = [f'"{ele}"' - for ele in self.element.zone_names] - - -class SpawnModel(Model): - def __init__(self, name, comment, elements: list, connections: list): - super().__init__(name, comment, elements, connections) - self.building_idf = None - self.building_wea_epw = None - self.building_wea_mos = None - - self.n_zones = None - self.zone_names = [] - - - -# class EPMultizone(Buildings): -# path = "AixLib.Fluid.BoilerCHP.BoilerGeneric" -# represents = [hvac.Boiler] -# -# def __init__(self, element): -# super().__init__(element) -# -# def request_params(self): -# -# self.params["redeclare package Medium"] = 'AixLib.Media.Water' -# self.request_param("dT_water", -# self.check_numeric(min_value=0 * ureg.kelvin), -# "dTWaterNom") -# self.request_param("return_temperature", -# self.check_numeric(min_value=0 * ureg.celsius), -# "TRetNom") -# self.request_param("rated_power", -# self.check_numeric(min_value=0 * ureg.kilowatt), -# "QNom") -# self.request_param("min_PLR", -# self.check_numeric(min_value=0 * ureg.dimensionless), -# "PLRMin") -# -# def get_port_name(self, port): -# try: -# index = self.element.ports.index(port) -# except ValueError: -# # unknown port -# index = -1 -# if port.verbose_flow_direction == 'SINK': -# return 'port_a' -# if port.verbose_flow_direction == 'SOURCE': -# return 'port_b' -# else: -# return super().get_port_name(port) # ToDo: Gas connection diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/__init__.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/__init__.py index c4d128bcee..fc8cbfe545 100644 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/__init__.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/__init__.py @@ -1,31 +1,33 @@ from bim2sim.plugins import Plugin -# from bim2sim.plugins.PluginModelica.bim2sim_modelica import PluginModelica -from bim2sim.plugins.PluginEnergyPlus.bim2sim_energyplus import PluginEnergyPlus -# import bim2sim.plugins.PluginSpawnOfEP.bim2sim_spawn.tasks as spawn_tasks -from bim2sim.sim_settings import CoSimulation +from bim2sim.tasks import base, common, hvac, bps +from bim2sim.sim_settings import BuildingSimSettings, EnergyPlusSimSettings +import bim2sim.plugins.PluginSpawn.bim2sim_spawn.tasks as spawn_tasks +from bim2sim.plugins.PluginEnergyPlus.bim2sim_energyplus import task as ep_tasks # # TODO: this is just a concept and not working already class PluginSpawnOfEP(Plugin): - name = 'SpawnOfEP' - sim_settings = CoSimulation # todo: this is currently empty - - export_hvac_library = 'AixLib' # todo: this has currently no impact - - # combine elements from both Plugins - elements = set() - # elements.update(PluginModelica.elements) - elements.update(PluginEnergyPlus.elements) - - # combine tasks from both Plugins - default_tasks = [] - # default_tasks.extend(PluginModelica.default_tasks) - default_tasks.extend(PluginEnergyPlus.default_tasks) - - # default_tasks.append(spawn_tasks.GetZoneConnections) - # default_tasks.append(spawn_tasks.CoSimExport) - - # make sure that tasks only occur once - # todo: this won't work always. We need to separate tasks that occur in - # multiple Plugins (LoadIFC, CheckIFC and CreateElements) from the rest - default_tasks = set(default_tasks) + name = 'spawn' + sim_settings = EnergyPlusSimSettings + default_tasks = [ + common.LoadIFC, + # common.CheckIfc, + common.CreateElements, + bps.CreateSpaceBoundaries, + bps.FilterTZ, + bps.ProcessSlabsRoofs, + common.BindStoreys, + bps.EnrichUseConditions, + bps.VerifyLayersMaterials, # LOD.full + bps.EnrichMaterial, # LOD.full + ep_tasks.EPGeomPreprocessing, + ep_tasks.AddSpaceBoundaries2B, + common.Weather, + ep_tasks.CreateIdf, + # ep_tasks.IdfPostprocessing, + # ep_tasks.ExportIdfForCfd, + # ep_tasks.RunEnergyPlusSimulation, + # spawn_tasks.CreateSpawnElements, + # TODO remove this? + spawn_tasks.ExportModelicaSpawnStatic, + ] diff --git a/bim2sim/plugins/PluginBuildings/bim2sim_buildings/examples/e1_simple_project_bps_buildings.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e1_simple_project_bps_spawn.py similarity index 94% rename from bim2sim/plugins/PluginBuildings/bim2sim_buildings/examples/e1_simple_project_bps_buildings.py rename to bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e1_simple_project_bps_spawn.py index 3221b3b899..273139cf56 100644 --- a/bim2sim/plugins/PluginBuildings/bim2sim_buildings/examples/e1_simple_project_bps_buildings.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e1_simple_project_bps_spawn.py @@ -25,7 +25,7 @@ def run_example_1(): # Create a temp directory for the project, feel free to use a "normal" # directory project_path = Path( - tempfile.TemporaryDirectory(prefix='bim2sim_example1').name) + tempfile.TemporaryDirectory(prefix='bim2sim_example_spawn').name) download_test_resources(IFCDomain.arch, force_new=False) # Set the ifc path to use and define which domain the IFC belongs to @@ -37,7 +37,7 @@ def run_example_1(): # Create a project including the folder structure for the project with # energyplus as backend - project = Project.create(project_path, ifc_paths, 'buildings') + project = Project.create(project_path, ifc_paths, 'spawn') # Set the install path to your EnergyPlus installation according to your # system requirements diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/__init__.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/__init__.py index 749ac18286..ea46f84741 100644 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/__init__.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/__init__.py @@ -1,2 +1 @@ -from .create_spawn_elements import CreateSpawnElements -from .export_co_sim import ExportModelicaSpawn +from .export_co_sim_static import ExportModelicaSpawnStatic diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/create_spawn_elements.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/create_spawn_elements.py deleted file mode 100644 index 332d83b5ea..0000000000 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/create_spawn_elements.py +++ /dev/null @@ -1,44 +0,0 @@ -from pathlib import Path - -from bim2sim.tasks.base import ITask -from bim2sim.elements.bps_elements import SpawnBuilding, SpawnMultiZone, FreshAirSource - - -class CreateSpawnElements(ITask): - # reads = ('instances',) - touches = ('elements',) - - def run(self): - # def run(self, elements): - # TODO maybe add an option to manual create elements for export without - # represents. Because the represents attribute currently makes it - # necessary to create those temp objects here and add them to elements - spawn_building = SpawnBuilding() - spawn_building.idfName = self.paths.export / str( - self.prj_name + ".idf") - # todo use loadresource maybe after prototype ready - spawn_building.epwName = self.paths.root / 'weatherfiles' / \ - str(self.playground.state["weather_file"].stem + '.epw') - spawn_building.weaName = self.paths.root / 'weatherfiles' / \ - str(self.playground.state["weather_file"].stem + '.mos') - spawn_building.printUnits = True - fresh_air_source = FreshAirSource() - spawn_multi = SpawnMultiZone() - spawn_multi.zone_names = self.get_zone_names() - - elements = { - spawn_building.guid: spawn_building, - fresh_air_source.guid: fresh_air_source, - spawn_multi.guid: spawn_multi} - return elements, - - def get_zone_names(self): - # TODO #1: get names from IDF or EP process for ep zones in - # correct order - if "ep_zone_lists" in self.playground.state: - zone_list = self.playground.state["ep_zone_lists"] - else: - raise ValueError("'ep_zone_list' not found in playground state, " - "please make sure that EnergyPlus model creation " - "was successful.") - return zone_list diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_co_sim.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_co_sim.py deleted file mode 100644 index 299cbba84c..0000000000 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_co_sim.py +++ /dev/null @@ -1,64 +0,0 @@ -from datetime import datetime - -from bim2sim.elements.base_elements import ProductBased -from bim2sim.export import modelica -from bim2sim.tasks.base import ITask -from bim2sim.plugins.PluginBuildings.bim2sim_buildings.models import SpawnBuilding,\ - EPThermalZone, SpawnMultizone, FreshAirSource - - -class ExportModelicaSpawn(ITask): - """Export to Dymola/Modelica""" - - reads = ('libraries', 'elements',) - final = True - - def run(self, libraries: tuple, elements: dict): - self.logger.info("Export to Modelica code") - modelica.Instance.init_factory(libraries) - export_elements = {inst: modelica.Instance.factory(inst) - for inst in elements.values()} - - yield from ProductBased.get_pending_attribute_decisions( - elements.values()) - - for instance in export_elements.values(): - instance.collect_params() - static_connections = self.get_static_connections(export_elements) - # TODO dynamic connections between multizone and hvac - # dyn_connections = get_dynamic_connections(export_elements) - # connections = static_connections + dyn_connections - connections = static_connections - - self.logger.info( - "Creating Modelica model with %d model elements.", - len(export_elements),) - - prj_name = self.prj_name.replace('-','_') - - modelica_model = modelica.Model( - name="bim2sim_"+prj_name, - comment=f"Autogenerated by BIM2SIM on {datetime.now():%Y-%m-%d %H:%M:%S%z}", - elements=list(export_elements.values()), - connections=connections, - ) - modelica_model.save(self.paths.export) - - def get_static_connections(self, elements): - connections = [] - for inst in elements.values(): - if isinstance(inst, SpawnBuilding): - spawn_building = inst - if isinstance(inst, FreshAirSource): - fresh_air = inst - if isinstance(inst, SpawnMultizone): - multi = inst - # TODO remove if as this is only temporary for development - if spawn_building and fresh_air and multi: - connections.append((str(spawn_building.name)+'.weaBus', - str(fresh_air.name) +'.weaBus')) - # TODO clarify export and arrays in modelica - connections.append(( - str(multi.name)+".portsExt[nZones]", - str(fresh_air.name)+".ports[nPorts]")) - return connections diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_co_sim_static.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_co_sim_static.py new file mode 100644 index 0000000000..271afe0640 --- /dev/null +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_co_sim_static.py @@ -0,0 +1,110 @@ +from datetime import datetime +from pathlib import Path +import codecs +from mako.template import Template + +import bim2sim +from bim2sim.elements.base_elements import ProductBased +from bim2sim.export import modelica +from bim2sim.tasks.base import ITask + + +class ExportModelicaSpawnStatic(ITask): + """Export to Dymola/Modelica""" + + reads = ('elements',) + # reads = ('libraries', 'elements',) + final = True + + def run(self, elements: dict): + self.logger.info("Export to Modelica code") + # EXPORT MULTIZONE MODEL + ## This is a "static" model for now, means no elements are created + # dynamically but only the parameters are changed based on render function + templ_path = Path(bim2sim.__file__).parent / \ + 'assets/templates/modelica/tmplSpawn.txt' + + with open(templ_path) as f: + templateStr = f.read() + template = Template(templateStr) + weather_path_ep = self.paths.root / 'weatherfiles' / \ + str(self.playground.state["weather_file"].stem + '.epw') + weather_path_mos = self.paths.root / 'weatherfiles' / \ + str(self.playground.state["weather_file"].stem + '.mos') + zone_names = self.get_zone_names() + idf_path = self.paths.export / str( + self.prj_name + ".idf") + # TODO multithreading lock needed? see modelica/__init__.py for example + # with lock: + data = template.render( + model_name=self.to_modelica('building_simulation'), + model_comment=self.to_modelica('test2'), + weather_path_ep=self.to_modelica(weather_path_ep), + weather_path_mos=self.to_modelica(weather_path_mos), + zone_names=self.to_modelica(zone_names), + idf_path=self.to_modelica(idf_path) + ) + + + + export_path = self.paths.export / 'testmodel.mo' + # user_logger.info("Saving '%s' to '%s'", self.name, _path) + with codecs.open(export_path, "w", "utf-8") as file: + file.write(data) + + # TODO + # EXPORT MAIN MODEL + # This is the main model that should holds building_simulation and + # hvac_simulation + + + def get_zone_names(self): + # TODO #1: get names from IDF or EP process for ep zones in + # correct order + if "ep_zone_lists" in self.playground.state: + zone_list = self.playground.state["ep_zone_lists"] + else: + raise ValueError("'ep_zone_list' not found in playground state, " + "please make sure that EnergyPlus model creation " + "was successful.") + return zone_list + + @staticmethod + def to_modelica(parameter): + """converts parameter to modelica readable string""" + if parameter is None: + return parameter + if isinstance(parameter, bool): + return 'true' if parameter else 'false' + if isinstance(parameter, (int, float)): + return str(parameter) + if isinstance(parameter, str): + return '%s' % parameter + if isinstance(parameter, (list, tuple, set)): + return "{%s}" % ( + ",".join((ExportModelicaSpawnStatic.to_modelica(par) for par in parameter))) + if isinstance(parameter, Path): + return \ + f"Modelica.Utilities.Files.loadResource(\"{str(parameter)}\")"\ + .replace("\\", "\\\\") + return str(parameter) + + # + # def get_static_connections(self, elements): + # connections = [] + # for inst in elements.values(): + # if isinstance(inst, SpawnBuilding): + # spawn_building = inst + # if isinstance(inst, FreshAirSource): + # fresh_air = inst + # if isinstance(inst, SpawnMultizone): + # multi = inst + # # TODO remove if as this is only temporary for development + # if spawn_building and fresh_air and multi: + # connections.append((str(spawn_building.name)+'.weaBus', + # str(fresh_air.name) +'.weaBus')) + # # TODO clarify export and arrays in modelica + # connections.append(( + # str(multi.name)+".portsExt[nZones]", + # str(fresh_air.name)+".ports[nPorts]")) + # return connections diff --git a/bim2sim/tasks/common/weather.py b/bim2sim/tasks/common/weather.py index 732ff1e56a..4e515239f7 100644 --- a/bim2sim/tasks/common/weather.py +++ b/bim2sim/tasks/common/weather.py @@ -28,8 +28,7 @@ def run(self, elements): def check_file_ending(self, weather_file): """Check if the file ending fits the simulation model type.""" plugin_name = self.playground.project.plugin_cls.name - # TODO check if spawn makes sense - if plugin_name == 'EnergyPlus' or plugin_name == 'Buildings' or plugin_name == 'Spawn': + if plugin_name == 'EnergyPlus' or plugin_name == 'spawn': if not weather_file.suffix == '.epw': raise ValueError( f"EnergyPlus simulation model should be created, but " From 9921df3791065dd353df10be4de8d97d605c7ff2 Mon Sep 17 00:00:00 2001 From: David Jansen Date: Tue, 2 Jan 2024 11:46:28 +0100 Subject: [PATCH 014/125] adjust static template for spawn --- .../assets/templates/modelica/tmplSpawn.txt | 87 ++++++++++++++++--- .../tasks/export_co_sim_static.py | 19 ++-- 2 files changed, 87 insertions(+), 19 deletions(-) diff --git a/bim2sim/assets/templates/modelica/tmplSpawn.txt b/bim2sim/assets/templates/modelica/tmplSpawn.txt index 0e494e9859..07197643e7 100644 --- a/bim2sim/assets/templates/modelica/tmplSpawn.txt +++ b/bim2sim/assets/templates/modelica/tmplSpawn.txt @@ -1,7 +1,17 @@ model ${model_name} "${model_comment}" import SI = Modelica.Units.SI; - parameter String zoneNames[:] + // TODO + parameter Integer nPorts=2 + "Number of fluid ports (equals to 2 for one inlet and one outlet)" + annotation (Evaluate=true,Dialog(connectorSizing=true,tab="General",group="Ports")); + + parameter Integer nZones = ${n_zones}; + + final parameter Modelica.Units.SI.MassFlowRate mOut_flow[nZones]=0.3/3600 * fill(1, nZones) * 1.2 + "Outside air infiltration for each exterior room"; + + parameter String zoneNames[nZones] = ${zone_names} "Name of the thermal zone as specified in the EnergyPlus input"; Buildings.ThermalZones.EnergyPlus_9_6_0.Building Building ( @@ -9,27 +19,84 @@ model ${model_name} "${model_comment}" epwName = ${weather_path_ep}, weaName = ${weather_path_mos}, printUnits = true) - "" annotation (Placement(transformation(extent={{-178,-30},{-158,-10}}))); + "" annotation (Placement(transformation(extent={{-102,64},{-82,84}}))); - Buildings.Fluid.Sources.MassFlowSource_WeatherData freshairsource (redeclare package Medium = Buildings.Media.Air) "" - annotation (Placement(transformation(extent={{-19.1111,54},{0.88889,74}}))); + + Buildings.Fluid.Sources.MassFlowSource_WeatherData freshairsource[nZones]( + redeclare package Medium = Buildings.Media.Air, + m_flow=mOut_flow, + each nPorts=1) "" + annotation (Placement(transformation(extent={{-43.1111,58},{-23.1111,78}}))); Buildings.ThermalZones.EnergyPlus_9_6_0.ThermalZone zon[nZones]( zoneName=zoneNames, - redeclare package Medium = Medium, - use_C_flow=use_C_flow, - each nPorts=nPorts) + redeclare package Medium = Buildings.Media.Air, + use_C_flow=false, + nPorts=nPorts) annotation (Placement(transformation(extent={{-20,-20},{20,20}}))); +// Infiltration + Buildings.Fluid.Sources.Outside out[nZones]( + redeclare package Medium = Buildings.Media.Air, + each nPorts=1) "Outside condition" + annotation (Placement(transformation(extent={{-44,32},{-24,52}}))); + + Buildings.Fluid.FixedResistances.PressureDrop resOut[nZones]( + redeclare each package Medium = Buildings.Media.Air, + each m_flow_nominal=sum(mOut_flow), + each dp_nominal=10, + each linearized=true) "Small flow resistance for inlet" + annotation (Placement(transformation(extent={{-6,58},{14,78}}))); + + Buildings.Fluid.FixedResistances.PressureDrop resIn[nZones]( + redeclare package Medium = Buildings.Media.Air, + m_flow_nominal=sum(mOut_flow), + dp_nominal=10, + linearized=true) "Small flow resistance for outlet" + annotation (Placement(transformation(extent={{-6,32},{14,52}}))); + +// Interfaces + Modelica.Thermal.HeatTransfer.Interfaces.HeatPort_a heaPorAir[nZones] + "Heat port to air volume for each zone" + annotation (Placement(transformation(extent={{-110,-10},{-90,10}}))); + + + + Modelica.Blocks.Sources.Constant const[nZones,3](k=0) "TODO" + annotation (Placement(transformation(extent={{-100,16},{-80,36}}))); equation - connect(Building.weaBus, freshairsource.weaBus) annotation (Line( - points={{-74,50},{-26,50},{-26,64.2},{-19.1111,64.2}}, + + for i in 1:nZones loop + connect(Building.weaBus, freshairsource[i].weaBus) annotation (Line( + points={{-80,68},{-52,68},{-52,68.2},{-43.1111,68.2}}, + color={255,204,51}, + thickness=0.5)); + connect(Building.weaBus, out[i].weaBus) annotation (Line( + points={{-80,68},{-52,68},{-52,42.2},{-44,42.2}}, color={255,204,51}, thickness=0.5)); + connect(resOut[i].port_b, zon[i].ports[1]) annotation (Line(points={{14,68}, + {98,68},{98,-28},{-1,-28},{-1,-19.1}}, color={0,127,255})); + connect(out[i].ports[1], resIn[i].port_a) annotation (Line(points={{-24,42}, + {-6,42}}, color={0,127,255})); + connect(resIn[i].port_b, zon[i].ports[2]) annotation (Line(points={{14,42},{ + 98,42},{98,-28},{1,-28},{1,-19.1}}, color={0,127,255})); + end for; + + connect(freshairsource[:].ports[1], resOut[:].port_a) + annotation (Line(points={{-23.1111,68},{-14,68},{-14,68},{-6,68}}, + color={0,127,255})); + + connect(heaPorAir, zon.heaPorAir) annotation (Line(points={{-100,0},{0,0}}, + color={191,0,0})); + + connect(const.y, zon.qGai_flow) annotation (Line(points={{-79,26},{-32,26},{-32, + 10},{-22,10}}, color={0,0,127})); annotation ( experiment(StopTime=36000), uses( Modelica(version="4.0.0"), Buildings(version="10.0.0"), AixLib(version="1.3.2"))); -end bim2sim_AC20_FZK_Haus_test1; + +end ${model_name}; diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_co_sim_static.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_co_sim_static.py index 271afe0640..d2fa4f2f5d 100644 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_co_sim_static.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_co_sim_static.py @@ -37,12 +37,13 @@ def run(self, elements: dict): # TODO multithreading lock needed? see modelica/__init__.py for example # with lock: data = template.render( - model_name=self.to_modelica('building_simulation'), - model_comment=self.to_modelica('test2'), - weather_path_ep=self.to_modelica(weather_path_ep), - weather_path_mos=self.to_modelica(weather_path_mos), - zone_names=self.to_modelica(zone_names), - idf_path=self.to_modelica(idf_path) + model_name='building_simulation', + model_comment='test2', + weather_path_ep=self.to_modelica_spawn(weather_path_ep), + weather_path_mos=self.to_modelica_spawn(weather_path_mos), + zone_names=self.to_modelica_spawn(zone_names), + idf_path=self.to_modelica_spawn(idf_path), + n_zones=len(zone_names) ) @@ -70,7 +71,7 @@ def get_zone_names(self): return zone_list @staticmethod - def to_modelica(parameter): + def to_modelica_spawn(parameter): """converts parameter to modelica readable string""" if parameter is None: return parameter @@ -79,10 +80,10 @@ def to_modelica(parameter): if isinstance(parameter, (int, float)): return str(parameter) if isinstance(parameter, str): - return '%s' % parameter + return '"%s"' % parameter if isinstance(parameter, (list, tuple, set)): return "{%s}" % ( - ",".join((ExportModelicaSpawnStatic.to_modelica(par) for par in parameter))) + ",".join((ExportModelicaSpawnStatic.to_modelica_spawn(par) for par in parameter))) if isinstance(parameter, Path): return \ f"Modelica.Utilities.Files.loadResource(\"{str(parameter)}\")"\ From 29a50681e49b9401071524a127398feca88e521c Mon Sep 17 00:00:00 2001 From: David Jansen Date: Thu, 18 Jan 2024 15:02:26 +0100 Subject: [PATCH 015/125] update spawn export --- bim2sim/assets/templates/modelica/tmplSpawn.txt | 2 +- .../PluginSpawn/bim2sim_spawn/tasks/export_co_sim_static.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bim2sim/assets/templates/modelica/tmplSpawn.txt b/bim2sim/assets/templates/modelica/tmplSpawn.txt index 07197643e7..2f016bd818 100644 --- a/bim2sim/assets/templates/modelica/tmplSpawn.txt +++ b/bim2sim/assets/templates/modelica/tmplSpawn.txt @@ -14,7 +14,7 @@ model ${model_name} "${model_comment}" parameter String zoneNames[nZones] = ${zone_names} "Name of the thermal zone as specified in the EnergyPlus input"; - Buildings.ThermalZones.EnergyPlus_9_6_0.Building Building ( + inner Buildings.ThermalZones.EnergyPlus_9_6_0.Building building ( idfName = ${idf_path}, epwName = ${weather_path_ep}, weaName = ${weather_path_mos}, diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_co_sim_static.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_co_sim_static.py index d2fa4f2f5d..1bc4c30a29 100644 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_co_sim_static.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_co_sim_static.py @@ -32,8 +32,8 @@ def run(self, elements: dict): weather_path_mos = self.paths.root / 'weatherfiles' / \ str(self.playground.state["weather_file"].stem + '.mos') zone_names = self.get_zone_names() - idf_path = self.paths.export / str( - self.prj_name + ".idf") + idf_path = (self.paths.export / "EnergyPlus/SimResults" / + self.prj_name / str(self.prj_name + ".idf")) # TODO multithreading lock needed? see modelica/__init__.py for example # with lock: data = template.render( From b7b983f0d786f19ac6ad615534701d70aaf9230b Mon Sep 17 00:00:00 2001 From: David Jansen Date: Thu, 18 Jan 2024 15:30:49 +0100 Subject: [PATCH 016/125] update spawn export template --- bim2sim/assets/templates/modelica/tmplSpawn.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bim2sim/assets/templates/modelica/tmplSpawn.txt b/bim2sim/assets/templates/modelica/tmplSpawn.txt index 2f016bd818..48a8cf9ac7 100644 --- a/bim2sim/assets/templates/modelica/tmplSpawn.txt +++ b/bim2sim/assets/templates/modelica/tmplSpawn.txt @@ -67,11 +67,11 @@ model ${model_name} "${model_comment}" equation for i in 1:nZones loop - connect(Building.weaBus, freshairsource[i].weaBus) annotation (Line( + connect(building.weaBus, freshairsource[i].weaBus) annotation (Line( points={{-80,68},{-52,68},{-52,68.2},{-43.1111,68.2}}, color={255,204,51}, thickness=0.5)); - connect(Building.weaBus, out[i].weaBus) annotation (Line( + connect(building.weaBus, out[i].weaBus) annotation (Line( points={{-80,68},{-52,68},{-52,42.2},{-44,42.2}}, color={255,204,51}, thickness=0.5)); From 1a641e9b719471c266697068af144dde696fdd5b Mon Sep 17 00:00:00 2001 From: David Jansen Date: Thu, 18 Jan 2024 15:31:04 +0100 Subject: [PATCH 017/125] update EP export to 9-6 for spawn --- .../bim2sim_spawn/examples/e1_simple_project_bps_spawn.py | 4 ++-- bim2sim/sim_settings.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e1_simple_project_bps_spawn.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e1_simple_project_bps_spawn.py index 273139cf56..b490fb0edd 100644 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e1_simple_project_bps_spawn.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e1_simple_project_bps_spawn.py @@ -42,8 +42,8 @@ def run_example_1(): # Set the install path to your EnergyPlus installation according to your # system requirements project.sim_settings.ep_install_path = Path( - 'C:/EnergyPlusV9-4-0/') - + 'C:/EnergyPlusV9-6-0/') + project.sim_settings.ep_version = "9-6-0" project.sim_settings.weather_file_path = ( Path(bim2sim.__file__).parent.parent / 'test/resources/weather_files/DEU_NW_Aachen.105010_TMYx.epw') diff --git a/bim2sim/sim_settings.py b/bim2sim/sim_settings.py index 68fb96f9a7..b148972cae 100644 --- a/bim2sim/sim_settings.py +++ b/bim2sim/sim_settings.py @@ -782,6 +782,7 @@ class EnergyPlusSimSettings(BuildingSimSettings): choices={ '9-2-0': 'EnergyPlus Version 9-2-0', '9-4-0': 'EnergyPlus Version 9-4-0', + '9-6-0': 'EnergyPlus Version 9-6-0', '22-2-0': 'EnergyPlus Version 22-2-0' # todo: Test latest version }, description='Choose EnergyPlus Version', From fa599c404dbf3914df421a1fd357f2b4f552cd98 Mon Sep 17 00:00:00 2001 From: David Jansen Date: Thu, 18 Jan 2024 15:45:19 +0100 Subject: [PATCH 018/125] renaming and preparation for package export --- .../tasks/export_co_sim_static.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_co_sim_static.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_co_sim_static.py index 1bc4c30a29..d018ef971b 100644 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_co_sim_static.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_co_sim_static.py @@ -21,12 +21,12 @@ def run(self, elements: dict): # EXPORT MULTIZONE MODEL ## This is a "static" model for now, means no elements are created # dynamically but only the parameters are changed based on render function - templ_path = Path(bim2sim.__file__).parent / \ + templ_path_building = Path(bim2sim.__file__).parent / \ 'assets/templates/modelica/tmplSpawn.txt' - with open(templ_path) as f: - templateStr = f.read() - template = Template(templateStr) + with open(templ_path_building) as f: + template_bldg_str = f.read() + template_bldg = Template(template_bldg_str) weather_path_ep = self.paths.root / 'weatherfiles' / \ str(self.playground.state["weather_file"].stem + '.epw') weather_path_mos = self.paths.root / 'weatherfiles' / \ @@ -36,7 +36,7 @@ def run(self, elements: dict): self.prj_name / str(self.prj_name + ".idf")) # TODO multithreading lock needed? see modelica/__init__.py for example # with lock: - data = template.render( + building_template_data = template_bldg.render( model_name='building_simulation', model_comment='test2', weather_path_ep=self.to_modelica_spawn(weather_path_ep), @@ -46,12 +46,18 @@ def run(self, elements: dict): n_zones=len(zone_names) ) + # TODO + template_total = None + total_template_data = template_total.render( + ... + ) + export_path = self.paths.export / 'testmodel.mo' # user_logger.info("Saving '%s' to '%s'", self.name, _path) with codecs.open(export_path, "w", "utf-8") as file: - file.write(data) + file.write(building_template_data) # TODO # EXPORT MAIN MODEL From 20ddce8e17e1376fc62358fe82028dd0ca6acd18 Mon Sep 17 00:00:00 2001 From: David Jansen Date: Thu, 18 Jan 2024 17:04:08 +0100 Subject: [PATCH 019/125] split up weather file handling for EP and Modelica --- bim2sim/examples/e1_template_plugin.py | 2 +- .../examples/e2_compare_two_simulations.py | 4 +- bim2sim/examples/e2_interactive_project.py | 2 +- .../examples/e1_simple_project_hvac_aixlib.py | 2 +- .../e2_complex_project_hvac_aixlib.py | 2 +- .../test/integration/test_aixlib.py | 6 ++ .../e1_simple_project_bps_energyplus.py | 2 +- .../bim2sim_energyplus/task/ep_create_idf.py | 12 ++-- .../test/integration/test_energyplus.py | 7 ++- .../examples/e1_simple_project_hvac_hkesim.py | 2 +- .../test/integration/test_hkesim.py | 6 ++ .../examples/e1_export_quantities_for_lca.py | 2 +- .../examples/e1_simple_project_bps_spawn.py | 6 +- .../tasks/export_co_sim_static.py | 19 +++--- .../examples/e1_simple_project_bps_teaser.py | 2 +- .../bim2sim_teaser/task/create_teaser_prj.py | 6 +- .../test/integration/test_teaser.py | 5 ++ bim2sim/sim_settings.py | 23 ++++++-- bim2sim/tasks/common/weather.py | 59 ++++++++++++------- bim2sim/utilities/test.py | 8 +-- test/unit/tasks/common/test_inspect.py | 8 +-- test/unit/tasks/common/test_weather.py | 8 +-- 22 files changed, 125 insertions(+), 68 deletions(-) diff --git a/bim2sim/examples/e1_template_plugin.py b/bim2sim/examples/e1_template_plugin.py index 25e2791aa9..c2d7ca4329 100644 --- a/bim2sim/examples/e1_template_plugin.py +++ b/bim2sim/examples/e1_template_plugin.py @@ -73,7 +73,7 @@ def run_simple_project(): # Let's assign a weather file first. This is currently needed, even if no # simulation is performed - project.sim_settings.weather_file_path = ( + project.sim_settings.weather_file_path_modelica = ( Path(bim2sim.__file__).parent.parent / 'test/resources/weather_files/DEU_NW_Aachen.105010_TMYx.mos') diff --git a/bim2sim/examples/e2_compare_two_simulations.py b/bim2sim/examples/e2_compare_two_simulations.py index 13518c4e65..f2c6e72255 100644 --- a/bim2sim/examples/e2_compare_two_simulations.py +++ b/bim2sim/examples/e2_compare_two_simulations.py @@ -41,7 +41,7 @@ def run_ep_simulation(): # Create a project including the folder structure for the project with # energyplus as backend project = Project.create(project_path, ifc_paths, 'energyplus') - project.sim_settings.weather_file_path = ( + project.sim_settings.weather_file_path_ep = ( Path(bim2sim.__file__).parent.parent / 'test/resources/weather_files/DEU_NW_Aachen.105010_TMYx.epw') # Set the install path to your EnergyPlus installation according to your @@ -136,7 +136,7 @@ def run_teaser_simulation(): 'Alu- oder Stahlfenster, Waermeschutzverglasung, zweifach' # set weather file data - project.sim_settings.weather_file_path = ( + project.sim_settings.weather_file_path_modelica = ( Path(bim2sim.__file__).parent.parent / 'test/resources/weather_files/DEU_NW_Aachen.105010_TMYx.mos') # Run a simulation directly with dymola after model creation diff --git a/bim2sim/examples/e2_interactive_project.py b/bim2sim/examples/e2_interactive_project.py index 0346e6ad66..0fb3082746 100644 --- a/bim2sim/examples/e2_interactive_project.py +++ b/bim2sim/examples/e2_interactive_project.py @@ -46,7 +46,7 @@ def run_interactive_example(): project_path, ifc_paths, 'template', open_conf=True) # set weather file data - project.sim_settings.weather_file_path = ( + project.sim_settings.weather_file_path_modelica = ( Path(bim2sim.__file__).parent.parent / 'test/resources/weather_files/DEU_NW_Aachen.105010_TMYx.mos') diff --git a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/examples/e1_simple_project_hvac_aixlib.py b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/examples/e1_simple_project_hvac_aixlib.py index fad917b82a..7cc7e83031 100644 --- a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/examples/e1_simple_project_hvac_aixlib.py +++ b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/examples/e1_simple_project_hvac_aixlib.py @@ -43,7 +43,7 @@ def run_example_simple_hvac_aixlib(): project = Project.create(project_path, ifc_paths, 'aixlib') # set weather file data - project.sim_settings.weather_file_path = ( + project.sim_settings.weather_file_path_modelica = ( Path(bim2sim.__file__).parent.parent / 'test/resources/weather_files/DEU_NW_Aachen.105010_TMYx.mos') diff --git a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/examples/e2_complex_project_hvac_aixlib.py b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/examples/e2_complex_project_hvac_aixlib.py index 5fdb12d19b..2d528c182a 100644 --- a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/examples/e2_complex_project_hvac_aixlib.py +++ b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/examples/e2_complex_project_hvac_aixlib.py @@ -44,7 +44,7 @@ def run_example_complex_hvac_aixlib(): project = Project.create(project_path, ifc_paths, 'aixlib') # set weather file data - project.sim_settings.weather_file_path = ( + project.sim_settings.weather_file_path_modelica = ( Path(bim2sim.__file__).parent.parent / 'test/resources/weather_files/DEU_NW_Aachen.105010_TMYx.mos') diff --git a/bim2sim/plugins/PluginAixLib/test/integration/test_aixlib.py b/bim2sim/plugins/PluginAixLib/test/integration/test_aixlib.py index c6063f4e25..d964d397d9 100644 --- a/bim2sim/plugins/PluginAixLib/test/integration/test_aixlib.py +++ b/bim2sim/plugins/PluginAixLib/test/integration/test_aixlib.py @@ -17,6 +17,12 @@ def tearDown(self): def model_domain_path(self) -> str: return 'hydraulic' + def set_test_weather_file(self): + """Set the weather file path.""" + self.project.sim_settings.weather_file_path_ep = ( + self.test_resources_path() / + 'weather_files/DEU_NW_Aachen.105010_TMYx.epw') + class TestIntegrationAixLib(IntegrationBaseAixLib, unittest.TestCase): diff --git a/bim2sim/plugins/PluginEnergyPlus/bim2sim_energyplus/examples/e1_simple_project_bps_energyplus.py b/bim2sim/plugins/PluginEnergyPlus/bim2sim_energyplus/examples/e1_simple_project_bps_energyplus.py index 0090c9e9a7..55f9447c90 100644 --- a/bim2sim/plugins/PluginEnergyPlus/bim2sim_energyplus/examples/e1_simple_project_bps_energyplus.py +++ b/bim2sim/plugins/PluginEnergyPlus/bim2sim_energyplus/examples/e1_simple_project_bps_energyplus.py @@ -42,7 +42,7 @@ def run_example_1(): project = Project.create(project_path, ifc_paths, 'energyplus') # set weather file data - project.sim_settings.weather_file_path = ( + project.sim_settings.weather_file_path_ep = ( Path(bim2sim.__file__).parent.parent / 'test/resources/weather_files/DEU_NW_Aachen.105010_TMYx.epw') # Set the install path to your EnergyPlus installation according to your diff --git a/bim2sim/plugins/PluginEnergyPlus/bim2sim_energyplus/task/ep_create_idf.py b/bim2sim/plugins/PluginEnergyPlus/bim2sim_energyplus/task/ep_create_idf.py index 65a8cbb6da..4c673d4307 100644 --- a/bim2sim/plugins/PluginEnergyPlus/bim2sim_energyplus/task/ep_create_idf.py +++ b/bim2sim/plugins/PluginEnergyPlus/bim2sim_energyplus/task/ep_create_idf.py @@ -47,19 +47,21 @@ class CreateIdf(ITask): preprocessed space boundary geometries. """ - reads = ('elements', 'weather_file',) + reads = ('elements', 'weather_file_ep',) touches = ('idf', 'sim_results_path', 'ep_zone_lists') def __init__(self, playground): super().__init__(playground) self.idf = None - def run(self, elements, weather_file): + def run(self, elements, weather_file_ep): """Execute all methods to export an IDF from BIM2SIM.""" logger.info("IDF generation started ...") - idf, sim_results_path = self.init_idf(self.playground.sim_settings, - self.paths, - weather_file, self.prj_name) + idf, sim_results_path = self.init_idf( + self.playground.sim_settings, + self.paths, + weather_file_ep, + self.prj_name) self.init_zone(self.playground.sim_settings, elements, idf) self.init_zonelist(idf) self.init_zonegroups(elements, idf) diff --git a/bim2sim/plugins/PluginEnergyPlus/test/integration/test_energyplus.py b/bim2sim/plugins/PluginEnergyPlus/test/integration/test_energyplus.py index 120681884d..9e64a7dd04 100644 --- a/bim2sim/plugins/PluginEnergyPlus/test/integration/test_energyplus.py +++ b/bim2sim/plugins/PluginEnergyPlus/test/integration/test_energyplus.py @@ -51,12 +51,13 @@ def tearDown(self): sys.stderr = self.old_stderr super().tearDown() - def model_domain_path(self) -> str: return 'arch' - def weather_file_path(self) -> Path: - return (self.test_resources_path() / + def set_test_weather_file(self): + """Set the weather file path.""" + self.project.sim_settings.weather_file_path_ep = ( + self.test_resources_path() / 'weather_files/DEU_NW_Aachen.105010_TMYx.epw') diff --git a/bim2sim/plugins/PluginHKESim/bim2sim_hkesim/examples/e1_simple_project_hvac_hkesim.py b/bim2sim/plugins/PluginHKESim/bim2sim_hkesim/examples/e1_simple_project_hvac_hkesim.py index 3dd483c6b1..868297f922 100644 --- a/bim2sim/plugins/PluginHKESim/bim2sim_hkesim/examples/e1_simple_project_hvac_hkesim.py +++ b/bim2sim/plugins/PluginHKESim/bim2sim_hkesim/examples/e1_simple_project_hvac_hkesim.py @@ -42,7 +42,7 @@ def run_example_simple_hvac_hkesim(): project = Project.create(project_path, ifc_paths, 'HKESim') # set weather file data - project.sim_settings.weather_file_path = ( + project.sim_settings.weather_file_path_modelica = ( Path(bim2sim.__file__).parent.parent / 'test/resources/weather_files/DEU_NW_Aachen.105010_TMYx.mos') diff --git a/bim2sim/plugins/PluginHKESim/test/integration/test_hkesim.py b/bim2sim/plugins/PluginHKESim/test/integration/test_hkesim.py index c0e97b2537..e8bb4d1ca9 100644 --- a/bim2sim/plugins/PluginHKESim/test/integration/test_hkesim.py +++ b/bim2sim/plugins/PluginHKESim/test/integration/test_hkesim.py @@ -18,6 +18,12 @@ def tearDown(self): def model_domain_path(self) -> str: return 'hydraulic' + def set_test_weather_file(self): + """Set the weather file path.""" + self.project.sim_settings.weather_file_path_ep = ( + self.test_resources_path() / + 'weather_files/DEU_NW_Aachen.105010_TMYx.epw') + class TestIntegrationHKESIM(IntegrationBaseHKESIM, unittest.TestCase): diff --git a/bim2sim/plugins/PluginLCA/bim2sim_lca/examples/e1_export_quantities_for_lca.py b/bim2sim/plugins/PluginLCA/bim2sim_lca/examples/e1_export_quantities_for_lca.py index 1ccab0e978..dfc2a217de 100644 --- a/bim2sim/plugins/PluginLCA/bim2sim_lca/examples/e1_export_quantities_for_lca.py +++ b/bim2sim/plugins/PluginLCA/bim2sim_lca/examples/e1_export_quantities_for_lca.py @@ -49,7 +49,7 @@ def run_example_complex_building_lca(): project = Project.create(project_path, ifc_paths, 'lca') # set weather file data - project.sim_settings.weather_file_path = ( + project.sim_settings.weather_file_path_modelica = ( Path(bim2sim.__file__).parent.parent / 'test/resources/weather_files/DEU_NW_Aachen.105010_TMYx.mos') diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e1_simple_project_bps_spawn.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e1_simple_project_bps_spawn.py index b490fb0edd..7cb6e79d58 100644 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e1_simple_project_bps_spawn.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e1_simple_project_bps_spawn.py @@ -44,9 +44,13 @@ def run_example_1(): project.sim_settings.ep_install_path = Path( 'C:/EnergyPlusV9-6-0/') project.sim_settings.ep_version = "9-6-0" - project.sim_settings.weather_file_path = ( + project.sim_settings.weather_file_path_ep = ( Path(bim2sim.__file__).parent.parent / 'test/resources/weather_files/DEU_NW_Aachen.105010_TMYx.epw') + # TODO make sure that a non existing sim_setting assignment raises an error + project.sim_settings.weather_file_path_modelica = ( + Path(bim2sim.__file__).parent.parent / + 'test/resources/weather_files/DEU_NW_Aachen.105010_TMYx.mos') # Set other simulation settings, otherwise all settings are set to default diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_co_sim_static.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_co_sim_static.py index d018ef971b..f6cd7e96ec 100644 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_co_sim_static.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_co_sim_static.py @@ -12,11 +12,12 @@ class ExportModelicaSpawnStatic(ITask): """Export to Dymola/Modelica""" - reads = ('elements',) + reads = ('elements', 'weather_file_modelica', 'weather_file_ep') # reads = ('libraries', 'elements',) final = True - def run(self, elements: dict): + def run(self, elements: dict, weather_file_modelica: Path, + weather_file_ep: Path): self.logger.info("Export to Modelica code") # EXPORT MULTIZONE MODEL ## This is a "static" model for now, means no elements are created @@ -27,10 +28,8 @@ def run(self, elements: dict): with open(templ_path_building) as f: template_bldg_str = f.read() template_bldg = Template(template_bldg_str) - weather_path_ep = self.paths.root / 'weatherfiles' / \ - str(self.playground.state["weather_file"].stem + '.epw') - weather_path_mos = self.paths.root / 'weatherfiles' / \ - str(self.playground.state["weather_file"].stem + '.mos') + weather_path_ep = weather_file_ep + weather_path_mos = weather_file_modelica zone_names = self.get_zone_names() idf_path = (self.paths.export / "EnergyPlus/SimResults" / self.prj_name / str(self.prj_name + ".idf")) @@ -47,10 +46,10 @@ def run(self, elements: dict): ) # TODO - template_total = None - total_template_data = template_total.render( - ... - ) + # template_total = None + # total_template_data = template_total.render( + # ... + # ) diff --git a/bim2sim/plugins/PluginTEASER/bim2sim_teaser/examples/e1_simple_project_bps_teaser.py b/bim2sim/plugins/PluginTEASER/bim2sim_teaser/examples/e1_simple_project_bps_teaser.py index 671d74430d..842502045b 100644 --- a/bim2sim/plugins/PluginTEASER/bim2sim_teaser/examples/e1_simple_project_bps_teaser.py +++ b/bim2sim/plugins/PluginTEASER/bim2sim_teaser/examples/e1_simple_project_bps_teaser.py @@ -58,7 +58,7 @@ def run_example_simple_building_teaser(): 'Alu- oder Stahlfenster, Waermeschutzverglasung, zweifach' # set weather file data - project.sim_settings.weather_file_path = ( + project.sim_settings.weather_file_path_modelica = ( Path(bim2sim.__file__).parent.parent / 'test/resources/weather_files/DEU_NW_Aachen.105010_TMYx.mos') # Run a simulation directly with dymola after model creation diff --git a/bim2sim/plugins/PluginTEASER/bim2sim_teaser/task/create_teaser_prj.py b/bim2sim/plugins/PluginTEASER/bim2sim_teaser/task/create_teaser_prj.py index 4f47bd3651..90785f6908 100644 --- a/bim2sim/plugins/PluginTEASER/bim2sim_teaser/task/create_teaser_prj.py +++ b/bim2sim/plugins/PluginTEASER/bim2sim_teaser/task/create_teaser_prj.py @@ -20,7 +20,7 @@ class CreateTEASER(ITask): """Creates a TEASER project by using the found information from IFC""" - reads = ('libraries', 'elements', 'weather_file') + reads = ('libraries', 'elements', 'weather_file_modelica') touches = ('teaser_prj', 'bldg_names', 'orig_heat_loads', 'orig_cool_loads', 'tz_mapping') instance_switcher = {'OuterWall': OuterWall, @@ -33,7 +33,7 @@ class CreateTEASER(ITask): 'InnerDoor': InnerWall } - def run(self, libraries, elements, weather_file): + def run(self, libraries, elements, weather_file_modelica): self.logger.info("Start creating the TEASER project from the derived " "building") @@ -57,7 +57,7 @@ def run(self, libraries, elements, weather_file): self.overwrite_heatloads(exported_buildings) tz_mapping = self.create_tz_mapping(exported_buildings) self.save_tz_mapping_to_json(tz_mapping) - teaser_prj.weather_file_path = weather_file + teaser_prj.weather_file_path = weather_file_modelica bldg_names = [] for bldg in exported_buildings: diff --git a/bim2sim/plugins/PluginTEASER/test/integration/test_teaser.py b/bim2sim/plugins/PluginTEASER/test/integration/test_teaser.py index bfe70251a3..46940d9397 100644 --- a/bim2sim/plugins/PluginTEASER/test/integration/test_teaser.py +++ b/bim2sim/plugins/PluginTEASER/test/integration/test_teaser.py @@ -12,6 +12,11 @@ class IntegrationBaseTEASER(IntegrationBase): def model_domain_path(self) -> str: return 'arch' + def set_test_weather_file(self): + """Set the weather file path.""" + self.project.sim_settings.weather_file_path_ep = ( + self.test_resources_path() / + 'weather_files/DEU_NW_Aachen.105010_TMYx.epw') class TestIntegrationTEASER(IntegrationBaseTEASER, unittest.TestCase): def test_run_kitoffice_spaces_medium_layers_low(self): diff --git a/bim2sim/sim_settings.py b/bim2sim/sim_settings.py index b148972cae..c1fadbdcc4 100644 --- a/bim2sim/sim_settings.py +++ b/bim2sim/sim_settings.py @@ -405,6 +405,7 @@ def update_from_config(self, config): # handle all other strings val = set_from_cfg else: + # handle all other data types val = set_from_cfg setattr(self, setting, val) @@ -480,19 +481,33 @@ def check_mandatory(self): for_frontend=True ) - weather_file_path = PathSetting( + # TODO fix mandatory for EP and MODELICA seperated weather files + weather_file_path_modelica = PathSetting( + default=None, + description='Path to the weather file that should be used for the ' + 'simulation. If no path is provided, we will try to get ' + 'the location from the IFC and download a fitting weather' + ' file. For Modelica provide .mos files, for EnergyPlus ' + '.epw files. If the format does not fit, we will try to ' + 'convert.', + for_frontend=True, + mandatory=False + ) + + weather_file_path_ep = PathSetting( default=None, description='Path to the weather file that should be used for the ' - 'simulation. If no path is provided, we will try to get the' - 'location from the IFC and download a fitting weather' + 'simulation. If no path is provided, we will try to get ' + 'the location from the IFC and download a fitting weather' ' file. For Modelica provide .mos files, for EnergyPlus ' '.epw files. If the format does not fit, we will try to ' 'convert.', for_frontend=True, - mandatory=True + mandatory=False ) + class PlantSimSettings(BaseSimSettings): def __init__(self): super().__init__( diff --git a/bim2sim/tasks/common/weather.py b/bim2sim/tasks/common/weather.py index 4e515239f7..ed95e0a9e3 100644 --- a/bim2sim/tasks/common/weather.py +++ b/bim2sim/tasks/common/weather.py @@ -5,40 +5,59 @@ class Weather(ITask): """Task to get the weather file for later simulation""" reads = ('elements',) - touches = ('weather_file',) + touches = ('weather_file_modelica', 'weather_file_ep') def run(self, elements): self.logger.info("Setting weather file.") - weather_file = None - # try to get weather file from settings - if self.playground.sim_settings.weather_file_path: - weather_file = self.playground.sim_settings.weather_file_path + weather_file_modelica = None + weather_file_ep = None + # try to get weather file from settings for modelica and energyplus + if self.playground.sim_settings.weather_file_path_modelica: + weather_file_modelica = ( + self.playground.sim_settings.weather_file_path_modelica) + if self.playground.sim_settings.weather_file_path_ep: + weather_file_ep = self.playground.sim_settings.weather_file_path_ep + # try to get TRY weather file for location of IFC - if not weather_file: + if not weather_file_ep and not weather_file_modelica: raise NotImplementedError("Waiting for response from DWD if we can" "implement this") # lat, long = self.get_location_lat_long_from_ifc(elements) # weather_file = self.get_weatherfile_from_dwd(lat, long) - self.check_file_ending(weather_file) - if not weather_file: + self.check_weather_file(weather_file_modelica, weather_file_ep) + if not weather_file_ep and not weather_file_modelica: raise ValueError("No weather file provided for the simulation, " "can't continue model generation.") - return weather_file, + return weather_file_modelica, weather_file_ep - def check_file_ending(self, weather_file): - """Check if the file ending fits the simulation model type.""" + def check_weather_file(self, weather_file_modelica, weather_file_ep): + """Check if the file exists and has the correct ending.""" plugin_name = self.playground.project.plugin_cls.name - if plugin_name == 'EnergyPlus' or plugin_name == 'spawn': - if not weather_file.suffix == '.epw': + + expected_endings = { + 'EnergyPlus': ['.epw'], + 'spawn': ['.epw', '.mos'], + } + + # all other plugins need .mos file + if plugin_name not in expected_endings: + expected_endings[plugin_name] = ['.mos'] + + for file, expected_suffix in zip( + [weather_file_ep, weather_file_modelica], + expected_endings[plugin_name] + ): + if not file: raise ValueError( - f"EnergyPlus simulation model should be created, but " - f"instead .epw a {weather_file.suffix} file was provided.") - # all other plugins currently use .mos files - else: - if not weather_file.suffix == '.mos': + f"For Plugin {plugin_name} no weather file" + f" with ending {expected_suffix}" + f" has been assigned, check your sim_settings.") + if not file.suffix == expected_suffix: raise ValueError( - f"Modelica simulation model should be created, but " - f"instead .mos a {weather_file.suffix} file was provided.") + f"{plugin_name} weather file should have ending " + f"'{expected_suffix}', but a {file.suffix} file was" + f" provided." + ) def get_location_lat_long_from_ifc(self, elements: dict) -> [float]: """ diff --git a/bim2sim/utilities/test.py b/bim2sim/utilities/test.py index 52e602a0a7..cfa958f558 100644 --- a/bim2sim/utilities/test.py +++ b/bim2sim/utilities/test.py @@ -43,7 +43,7 @@ def create_project( ifc_paths=ifc_paths, plugin=plugin) # set weather file data - self.project.sim_settings.weather_file_path = self.weather_file_path() + self.set_test_weather_file() return self.project @staticmethod @@ -53,9 +53,9 @@ def test_resources_path() -> Path: def model_domain_path(self) -> Union[str, None]: return None - def weather_file_path(self) -> Path: - return (self.test_resources_path() / - 'weather_files/DEU_NW_Aachen.105010_TMYx.mos') + def set_test_weather_file(self): + """Set the weather file path.""" + raise NotImplementedError("") class RegressionTestBase(IntegrationBase): diff --git a/test/unit/tasks/common/test_inspect.py b/test/unit/tasks/common/test_inspect.py index f4e8186735..fdc231debc 100644 --- a/test/unit/tasks/common/test_inspect.py +++ b/test/unit/tasks/common/test_inspect.py @@ -48,7 +48,7 @@ def test_case_1(self): 'hydraulic/ifc/B01_2_HeatExchanger_Pipes.ifc'} self.project = Project.create(self.test_dir.name, ifc_paths, plugin=PluginDummy, ) - self.project.sim_settings.weather_file_path = ( + self.project.sim_settings.weather_file_path_modelica = ( test_rsrc_path / 'weather_files/DEU_NW_Aachen.105010_TMYx.mos') handler = DebugDecisionHandler([HeatExchanger.key]) handler.handle(self.project.run(cleanup=False)) @@ -67,7 +67,7 @@ def test_case_2(self): 'B01_3_HeatExchanger_noPorts.ifc'} self.project = Project.create(self.test_dir.name, ifc_paths, plugin=PluginDummy, ) - self.project.sim_settings.weather_file_path = ( + self.project.sim_settings.weather_file_path_modelica = ( test_rsrc_path / 'weather_files/DEU_NW_Aachen.105010_TMYx.mos') handler = DebugDecisionHandler([HeatExchanger.key, *(Pipe.key,) * 4]) @@ -88,7 +88,7 @@ def test_case_3(self): 'B01_4_HeatExchanger_noConnection.ifc'} self.project = Project.create(self.test_dir.name, ifc_paths, plugin=PluginDummy, ) - self.project.sim_settings.weather_file_path = ( + self.project.sim_settings.weather_file_path_modelica = ( test_rsrc_path / 'weather_files/DEU_NW_Aachen.105010_TMYx.epw') handler = DebugDecisionHandler([HeatExchanger.key]) handler.handle(self.project.run(cleanup=False)) @@ -107,7 +107,7 @@ def test_case_4(self): 'B01_5_HeatExchanger_mixConnection.ifc'} self.project = Project.create(self.test_dir.name, ifc_paths, plugin=PluginDummy, ) - self.project.sim_settings.weather_file_path = ( + self.project.sim_settings.weather_file_path_modelica = ( test_rsrc_path / 'weather_files/DEU_NW_Aachen.105010_TMYx.mos') handler = DebugDecisionHandler([HeatExchanger.key]) handler.handle(self.project.run(cleanup=False)) diff --git a/test/unit/tasks/common/test_weather.py b/test/unit/tasks/common/test_weather.py index 55247a8379..927080f640 100644 --- a/test/unit/tasks/common/test_weather.py +++ b/test/unit/tasks/common/test_weather.py @@ -48,7 +48,7 @@ def test_weather_modelica(self): IFCDomain.arch: test_rsrc_path / 'arch/ifc/AC20-FZK-Haus.ifc'} self.project = Project.create(self.test_dir.name, ifc_paths, plugin=PluginWeatherDummyTEASER) - self.project.sim_settings.weather_file_path = ( + self.project.sim_settings.weather_file_path_modelica = ( test_rsrc_path / 'weather_files/DEU_NW_Aachen.105010_TMYx.mos') handler = DebugDecisionHandler([]) handler.handle(self.project.run(cleanup=False)) @@ -58,7 +58,7 @@ def test_weather_modelica(self): raise ValueError(f"No weather file set through Weather task. An" f"error occurred.") self.assertEquals(weather_file, - self.project.sim_settings.weather_file_path) + self.project.sim_settings.weather_file_path_modelica) def test_weather_energyplus(self): """Test if the weather file is correctly set for energyplus.""" @@ -67,7 +67,7 @@ def test_weather_energyplus(self): IFCDomain.arch: test_rsrc_path / 'arch/ifc/AC20-FZK-Haus.ifc'} self.project = Project.create(self.test_dir.name, ifc_paths, plugin=PluginWeatherDummyEP) - self.project.sim_settings.weather_file_path = ( + self.project.sim_settings.weather_file_path_ep = ( test_rsrc_path / 'weather_files/DEU_NW_Aachen.105010_TMYx.epw') handler = DebugDecisionHandler([]) handler.handle(self.project.run(cleanup=False)) @@ -77,4 +77,4 @@ def test_weather_energyplus(self): raise ValueError(f"No weather file set through Weather task. An" f"error occurred.") self.assertEquals(weather_file, - self.project.sim_settings.weather_file_path) + self.project.sim_settings.weather_file_path_ep) From 430101f18e0e57fc83510034823d779d6dfeef90 Mon Sep 17 00:00:00 2001 From: David Jansen Date: Thu, 18 Jan 2024 17:04:33 +0100 Subject: [PATCH 020/125] introducing each in some parameters to eliminate warnings --- bim2sim/assets/templates/modelica/tmplSpawn.txt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/bim2sim/assets/templates/modelica/tmplSpawn.txt b/bim2sim/assets/templates/modelica/tmplSpawn.txt index 48a8cf9ac7..bf7287ee81 100644 --- a/bim2sim/assets/templates/modelica/tmplSpawn.txt +++ b/bim2sim/assets/templates/modelica/tmplSpawn.txt @@ -31,8 +31,8 @@ model ${model_name} "${model_comment}" Buildings.ThermalZones.EnergyPlus_9_6_0.ThermalZone zon[nZones]( zoneName=zoneNames, redeclare package Medium = Buildings.Media.Air, - use_C_flow=false, - nPorts=nPorts) + each use_C_flow=false, + each nPorts=nPorts) annotation (Placement(transformation(extent={{-20,-20},{20,20}}))); // Infiltration @@ -50,9 +50,9 @@ model ${model_name} "${model_comment}" Buildings.Fluid.FixedResistances.PressureDrop resIn[nZones]( redeclare package Medium = Buildings.Media.Air, - m_flow_nominal=sum(mOut_flow), - dp_nominal=10, - linearized=true) "Small flow resistance for outlet" + each m_flow_nominal=sum(mOut_flow), + each dp_nominal=10, + each linearized=true) "Small flow resistance for outlet" annotation (Placement(transformation(extent={{-6,32},{14,52}}))); // Interfaces @@ -62,7 +62,7 @@ model ${model_name} "${model_comment}" - Modelica.Blocks.Sources.Constant const[nZones,3](k=0) "TODO" + Modelica.Blocks.Sources.Constant const[nZones,3](each k=0) "TODO" annotation (Placement(transformation(extent={{-100,16},{-80,36}}))); equation From ab97d1b8431dc4d321e1ad1487262a655d8e1f91 Mon Sep 17 00:00:00 2001 From: sfreund1 Date: Thu, 25 Jan 2024 14:12:30 +0100 Subject: [PATCH 021/125] implement modelica package export in spwan plugin --- bim2sim/assets/templates/modelica/package | 14 +++ .../assets/templates/modelica/package_order | 10 ++ .../assets/templates/modelica/tmplSpawn.txt | 1 + bim2sim/export/modelica/__init__.py | 63 ++++++++++++- .../tasks/export_co_sim_static.py | 93 ++++++++++++++++--- 5 files changed, 169 insertions(+), 12 deletions(-) create mode 100644 bim2sim/assets/templates/modelica/package create mode 100644 bim2sim/assets/templates/modelica/package_order diff --git a/bim2sim/assets/templates/modelica/package b/bim2sim/assets/templates/modelica/package new file mode 100644 index 0000000000..afbf594abb --- /dev/null +++ b/bim2sim/assets/templates/modelica/package @@ -0,0 +1,14 @@ +%if within is not None: +within ${within}; +%endif +package ${name} + extends Modelica.Icons.Package; + + %if uses is not None: + annotation (uses( + %for use in uses: + ${use}${',' if not loop.last else '),'}\ + %endfor + version="1"); + %endif +end ${name}; \ No newline at end of file diff --git a/bim2sim/assets/templates/modelica/package_order b/bim2sim/assets/templates/modelica/package_order new file mode 100644 index 0000000000..3536bb05ac --- /dev/null +++ b/bim2sim/assets/templates/modelica/package_order @@ -0,0 +1,10 @@ +%if extra != None: +${extra} +%endif +%for i in list: +%if addition != None: +${addition}${i.replace(" ", "")} +%else: +${i.replace(" ", "")} +%endif +%endfor \ No newline at end of file diff --git a/bim2sim/assets/templates/modelica/tmplSpawn.txt b/bim2sim/assets/templates/modelica/tmplSpawn.txt index bf7287ee81..802267c0d4 100644 --- a/bim2sim/assets/templates/modelica/tmplSpawn.txt +++ b/bim2sim/assets/templates/modelica/tmplSpawn.txt @@ -1,3 +1,4 @@ +within ${within}; model ${model_name} "${model_comment}" import SI = Modelica.Units.SI; diff --git a/bim2sim/export/modelica/__init__.py b/bim2sim/export/modelica/__init__.py index 7fc4b55d02..79d679135a 100644 --- a/bim2sim/export/modelica/__init__.py +++ b/bim2sim/export/modelica/__init__.py @@ -5,7 +5,7 @@ import os from pathlib import Path from threading import Lock -from typing import Union, Type, Dict, Container, Tuple, Callable +from typing import Union, Type, Dict, Container, Tuple, Callable, List import numpy as np import pint @@ -41,6 +41,67 @@ def clean_string(string: str) -> str: return string.replace('$', '_') +def help_package(path: Path, name: str, uses: str = None, + within: str = None): + """creates a package.mo file + + private function, do not call + + Parameters + ---------- + + path : string + path of where the package.mo should be placed + name : string + name of the Modelica package + within : string + path of Modelica package containing this package + + """ + + template_path_package = Path(bim2sim.__file__).parent / \ + "assets/templates/modelica/package" + package_template = Template(filename=str(template_path_package)) + with open(path / 'package.mo', 'w') as out_file: + out_file.write(package_template.render_unicode( + name=name, + within=within, + uses=uses)) + out_file.close() + + +def help_package_order(path: Path, package_list: List[str], addition=None, + extra=None): + """creates a package.order file + + private function, do not call + + Parameters + ---------- + + package_list : [string] + name of all models or packages contained in the package + addition : string + if there should be a suffix in front of package_list.string it can + be specified + extra : string + an extra package or model not contained in package_list can be + specified + + """ + + template_package_order_path = Path(bim2sim.__file__).parent / \ + "assets/templates/modelica/package_order" + package_order_template = Template(filename=str( + template_package_order_path)) + with open(path / 'package.order', 'w') as out_file: + out_file.write(package_order_template.render_unicode( + list=package_list, + addition=addition, + extra=extra)) + out_file.close() + + class Model: """Modelica model""" diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_co_sim_static.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_co_sim_static.py index f6cd7e96ec..1106eb8f36 100644 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_co_sim_static.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_co_sim_static.py @@ -1,3 +1,4 @@ +import os from datetime import datetime from pathlib import Path import codecs @@ -6,6 +7,7 @@ import bim2sim from bim2sim.elements.base_elements import ProductBased from bim2sim.export import modelica +from bim2sim.export.modelica import help_package, help_package_order from bim2sim.tasks.base import ITask @@ -19,11 +21,23 @@ class ExportModelicaSpawnStatic(ITask): def run(self, elements: dict, weather_file_modelica: Path, weather_file_ep: Path): self.logger.info("Export to Modelica code") + + package_path = self.paths.export / 'bim2sim_spawn' + os.makedirs(package_path, exist_ok=True) + + help_package(path=package_path, name=package_path.stem, within="") + help_package_order(path=package_path, package_list=[ + 'coupled_model', + 'building_model', + 'hvac_model']) + # EXPORT MULTIZONE MODEL - ## This is a "static" model for now, means no elements are created - # dynamically but only the parameters are changed based on render function - templ_path_building = Path(bim2sim.__file__).parent / \ - 'assets/templates/modelica/tmplSpawn.txt' + # This is a "static" model for now, means no elements are created + # dynamically but only the parameters are changed based on render + # function + templ_path_building = Path( + bim2sim.__file__).parent / \ + 'assets/templates/modelica/tmplSpawn.txt' with open(templ_path_building) as f: template_bldg_str = f.read() @@ -33,10 +47,12 @@ def run(self, elements: dict, weather_file_modelica: Path, zone_names = self.get_zone_names() idf_path = (self.paths.export / "EnergyPlus/SimResults" / self.prj_name / str(self.prj_name + ".idf")) + # TODO multithreading lock needed? see modelica/__init__.py for example # with lock: building_template_data = template_bldg.render( - model_name='building_simulation', + within='bim2sim_spawn', + model_name='building_model', model_comment='test2', weather_path_ep=self.to_modelica_spawn(weather_path_ep), weather_path_mos=self.to_modelica_spawn(weather_path_mos), @@ -51,9 +67,7 @@ def run(self, elements: dict, weather_file_modelica: Path, # ... # ) - - - export_path = self.paths.export / 'testmodel.mo' + export_path = package_path / 'building_model.mo' # user_logger.info("Saving '%s' to '%s'", self.name, _path) with codecs.open(export_path, "w", "utf-8") as file: file.write(building_template_data) @@ -63,7 +77,6 @@ def run(self, elements: dict, weather_file_modelica: Path, # This is the main model that should holds building_simulation and # hvac_simulation - def get_zone_names(self): # TODO #1: get names from IDF or EP process for ep zones in # correct order @@ -75,6 +88,61 @@ def get_zone_names(self): "was successful.") return zone_list + # def _help_package(self, name: str, uses: str = None, within: str = None): + # """creates a package.mo file + # + # private function, do not call + # + # Parameters + # ---------- + # + # name : string + # name of the Modelica package + # within : string + # path of Modelica package containing this package + # + # """ + # + # template_path_package = Path(bim2sim.__file__).parent / \ + # "assets/templates/modelica/package" + # package_template = Template(filename=str(template_path_package)) + # with open(self.paths.export / 'package.mo', 'w') as out_file: + # out_file.write(package_template.render_unicode( + # name=name, + # within=within, + # uses=uses)) + # out_file.close() + + # def _help_package_order(self, package_list, addition=None, extra=None): + # """creates a package.order file + # + # private function, do not call + # + # Parameters + # ---------- + # + # package_list : [string] + # name of all models or packages contained in the package + # addition : string + # if there should be a suffix in front of package_list.string it can + # be specified + # extra : string + # an extra package or model not contained in package_list can be + # specified + # + # """ + # + # template_package_order_path = Path(bim2sim.__file__).parent / \ + # "assets/templates/modelica/package_order" + # package_order_template = Template(filename=str( + # template_package_order_path)) + # with open(self.paths.export / 'package.order', 'w') as out_file: + # out_file.write(package_order_template.render_unicode( + # list=package_list, + # addition=addition, + # extra=extra)) + # out_file.close() + @staticmethod def to_modelica_spawn(parameter): """converts parameter to modelica readable string""" @@ -88,10 +156,13 @@ def to_modelica_spawn(parameter): return '"%s"' % parameter if isinstance(parameter, (list, tuple, set)): return "{%s}" % ( - ",".join((ExportModelicaSpawnStatic.to_modelica_spawn(par) for par in parameter))) + ",".join( + (ExportModelicaSpawnStatic.to_modelica_spawn(par) for par + in parameter))) if isinstance(parameter, Path): return \ - f"Modelica.Utilities.Files.loadResource(\"{str(parameter)}\")"\ + f"Modelica.Utilities.Files.loadResource(\"" \ + f"{str(parameter)}\")" \ .replace("\\", "\\\\") return str(parameter) From 391bcfbed972035345749f7ba8e45e752b6e7337 Mon Sep 17 00:00:00 2001 From: David Jansen Date: Thu, 25 Jan 2024 17:20:36 +0100 Subject: [PATCH 022/125] start implementation of record mapping for modelica --- .../templates/modelica/tmplModel_wip.txt | 57 ++++++++++++++++ bim2sim/export/modelica/__init__.py | 30 ++++++++- .../bim2sim_aixlib/models/__init__.py | 66 ++++++++++++++++--- 3 files changed, 142 insertions(+), 11 deletions(-) create mode 100644 bim2sim/assets/templates/modelica/tmplModel_wip.txt diff --git a/bim2sim/assets/templates/modelica/tmplModel_wip.txt b/bim2sim/assets/templates/modelica/tmplModel_wip.txt new file mode 100644 index 0000000000..65b29ee76b --- /dev/null +++ b/bim2sim/assets/templates/modelica/tmplModel_wip.txt @@ -0,0 +1,57 @@ +model ${model.name} "${model.comment}" + extends Modelica.Icons.Example; + import SI = Modelica.Units.SI; + +%if unknowns: + // unknown parameters: + %for param in unknowns: + // ${param} + %endfor +%endif + +% for instance in model.elements: + ${inst(instance)} + +def inst(instance) +% endfor +<%def name="inst(instance)", filter="trim"> + ${instance.path} ${instance.name}\ + %if instance.params: +( + %endif +<% i = 0 %>\ + % for par, value in instance.modelica_params.items(): +<% i+=1 %>\ +# TODO @svenne add records here +# TODO ${record.__name__( ${par} = ${'"missing - replace with meaningful value"' if value is None else value}${"," if i < len(instance.params) else ")"}) +# TODO put next line into function + ${par} = ${'"missing - replace with meaningful value"' if value is None else value}${"," if i < len(instance.params) else ")"} + % endfor + "${instance.comment}" + annotation (Placement(transformation(extent={{${instance.position[0]-10},${instance.position[1]-10}},{${instance.position[0]+10},${instance.position[1]+10}}}))); + + +equation +% for con1, con2, pos1, pos2 in model.connections: + connect(${con1}, ${con2}) + annotation (Line(points={{${pos1[0]},${pos1[1]}},{38,-6}}, color={0,0,127}));; +% endfor + + annotation ( +%if unknowns: + Diagram(graphics={Text( + extent={{-100,100},{100,60}}, + lineColor={238,46,47}, + textString="${len(unknowns)} unknown parameters! +see comments for details.")}), +%endif + experiment(StopTime=36000) + ); +end ${model.name}; + + +AixLib.Model.Model1 model1name ( +par1 = 5, +par2 = 3, +par3 = 1 +) "blabla" \ No newline at end of file diff --git a/bim2sim/export/modelica/__init__.py b/bim2sim/export/modelica/__init__.py index 79d679135a..6d814ffaf3 100644 --- a/bim2sim/export/modelica/__init__.py +++ b/bim2sim/export/modelica/__init__.py @@ -196,6 +196,7 @@ def __init__(self, element: Element): self.position = (80, 80) self.params = {} + self.records = [] self.requested: Dict[str, Tuple[Callable, str, str]] = {} self.connections = [] @@ -343,7 +344,7 @@ def _convert_param(param: pint.Quantity, special_units) -> pint.Quantity: @property def modelica_params(self): - """Returns param dict converted with to_modelica""" + """Returns param dict converted with to_modelica.""" mp = {k: self.to_modelica(v) for k, v in self.params.items()} return mp @@ -421,6 +422,33 @@ def __repr__(self): return "<%s %s>" % (self.path, self.name) +class ModelicaRecord: + """Mapping for records in Modelica. + + As records have a name and a key, value pair, we need a seperate class to + cover the structure in python. + + Args: + name: str with the name of the record in Modelica model + record_content: dict or ModelicaRecord for nested records + """ + def __init__( + self, + name: str, + record_content:[dict, Type["ModelicaRecord"]] + ): + self.name = name + self.record_content = self.handle_content(record_content) + + def handle_content(self, record_content): + # handle nested ModelicaRecords + if isinstance(record_content, ModelicaRecord): + self.handle_content(record_content.record_content) + # record_content = record_content.record_content + return {k: Instance.to_modelica(v) for k, v + in record_content.items()} + + class Dummy(Instance): path = "Path.to.Dummy" represents = elem.Dummy diff --git a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py index f9c933fdd0..009d5a56d0 100644 --- a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py +++ b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py @@ -2,8 +2,8 @@ from bim2sim.elements.aggregation import hvac_aggregations from bim2sim.export import modelica from bim2sim.elements import hvac_elements as hvac -from bim2sim.elements import bps_elements as bps from bim2sim.elements.mapping.units import ureg +from bim2sim.export.modelica import ModelicaRecord class AixLib(modelica.Instance): @@ -66,19 +66,65 @@ def request_params(self): self.params['redeclare package Medium'] = 'AixLib.Media.Water' self.request_param( "rated_mass_flow", - self.check_numeric(min_value=0 * ureg['kg/second'])) + check=self.check_numeric(min_value=0 * ureg['kg/second']), + export_name="m_flow_nominal", + export_unit=ureg["kg/second"] + ) self.request_param( "rated_pressure_difference", - self.check_numeric(min_value=0 * ureg['newton/m**2'])) + self.check_numeric(min_value=0 * ureg['newton/m**2']), + export_name="dp_nominal", + export_unit=ureg['newton/m**2'] + ) + # generic pump operation curve # todo renders as "V_flow" only in Modelica - self.params["per.pressure"] =\ - f"V_flow={{0," \ - f" {self.element.rated_mass_flow}/1000," \ - f" {self.element.rated_mass_flow} /1000/0.7}}," \ - f" dp={{ {self.element.rated_pressure_difference} / 0.7," \ - f" {self.element.rated_pressure_difference}," \ - f"0}}" + # Structure is record(record(param_1=value_1, param_2=value_2)) + # Create a record structure in bim2sim to map this instead of a + # hardcoded string + # Look at (Modelica)Instance.records and change this + + # pressure = { + # "V_flow": [0, "m_flow_nominal/1.2", "2*m_flow_nominal/1.2"], + # "dp": ["2*dp_nominal", "dp_nominal", "0"] + # } + # per = { + # pressure.__name__: pressure + # } + + # rec_pressure = ModelicaRecord(name="pressure", record_content={ + # "V_flow": [0, "m_flow_nominal/1.2", "2*m_flow_nominal/1.2"], + # "dp": ["2*dp_nominal", "dp_nominal", "0"] + # }) + # TODO + # 1. finish template + # 2. use in other components e.g. storage + rec_per = ModelicaRecord( + name="per", + record_content=ModelicaRecord( + name="pressure", + record_content={ + "V_flow": [ + 0, + "m_flow_nominal/1.2", + "2*m_flow_nominal/1.2" + ], + "dp": [ + "2*dp_nominal", + "dp_nominal", + "0" + ] + } + ) + ) + self.records.append(rec_per) + + + # self.records.append( + # "per(pressure(V_flow={0,m_flow_nominal, " + # "2*m_flow_nominal}/1.2, " + # "dp={2*dp_nominal,dp_nominal,0}))") + # ToDo remove decisions from tests if not asking this anymore # self.request_param("rated_height", From 9d0513ab1fe37bfd9ab4e03bd02a41d090418e2b Mon Sep 17 00:00:00 2001 From: sfreund1 Date: Tue, 30 Jan 2024 08:22:10 +0100 Subject: [PATCH 023/125] Adding and editing docstrings in Modelica export package --- bim2sim/export/modelica/__init__.py | 61 ++++++++++++++++------------- 1 file changed, 34 insertions(+), 27 deletions(-) diff --git a/bim2sim/export/modelica/__init__.py b/bim2sim/export/modelica/__init__.py index 6d814ffaf3..f59f569d48 100644 --- a/bim2sim/export/modelica/__init__.py +++ b/bim2sim/export/modelica/__init__.py @@ -41,22 +41,19 @@ def clean_string(string: str) -> str: return string.replace('$', '_') -def help_package(path: Path, name: str, uses: str = None, - within: str = None): - """creates a package.mo file - - private function, do not call +def help_package(path: Path, name: str, uses: str = None, within: str = None): + """Creates a package.mo file. Parameters ---------- - path : string + path : Path path of where the package.mo should be placed name : string name of the Modelica package + uses : within : string path of Modelica package containing this package - """ template_path_package = Path(bim2sim.__file__).parent / \ @@ -71,15 +68,15 @@ def help_package(path: Path, name: str, uses: str = None, def help_package_order(path: Path, package_list: List[str], addition=None, - extra=None): - """creates a package.order file - - private function, do not call + extra=None): + """Creates a package.order file. Parameters ---------- - package_list : [string] + path : Path + path of where the package.mo should be placed + package_list : string name of all models or packages contained in the package addition : string if there should be a suffix in front of package_list.string it can @@ -87,7 +84,6 @@ def help_package_order(path: Path, package_list: List[str], addition=None, extra : string an extra package or model not contained in package_list can be specified - """ template_package_order_path = Path(bim2sim.__file__).parent / \ @@ -151,7 +147,7 @@ def set_positions(self, elements, connections): return connections_positions def code(self): - """returns Modelica code""" + """Returns Modelica code.""" with lock: return template.render(model=self, unknowns=self.unknown_params()) @@ -281,14 +277,25 @@ def factory(element): def request_param(self, name: str, check, export_name: str = None, export_unit: str = ''): - """Parameter gets marked as required and will be checked. - - Hint: run collect_params() to collect actual values after requests. - - :param name: name of parameter to request - :param check: validation function for parameter - :param export_name: name of parameter in export. Defaults to name - :param export_unit: unit of parameter in export. Converts to SI units if not specified otherwise""" + """Requests a parameter for validation and export. + + Marks the specified parameter as required and performs validation using + the provided check function. + + Hint: Run collect_params() to collect actual values after making + requests. + + Args: + name (str): Name of the parameter to request. + check: Validation function for the parameter. + export_name (str, optional): Name of the parameter in export. + Defaults to name. + export_unit (str, optional): Unit of the parameter in export. + Converts to SI units if not specified otherwise. + + Returns: + None + """ self.element.request(name) self.requested[name] = (check, export_name or name, export_unit) @@ -303,8 +310,8 @@ def collect_params(self): First checks if the parameter is a list or a quantity, next uses the check function provided by the request_param function to check every value of the parameter, afterwards converts the parameter values to the - special units provided by the request_param function, finally stores the - parameter on the model instance.""" + special units provided by the request_param function, finally stores + the parameter on the model instance.""" for name, (check, export_name, special_units) in self.requested.items(): param = getattr(self.element, name) @@ -425,12 +432,12 @@ def __repr__(self): class ModelicaRecord: """Mapping for records in Modelica. - As records have a name and a key, value pair, we need a seperate class to + As records have a name and a key, value pair, we need a separate class to cover the structure in python. Args: - name: str with the name of the record in Modelica model - record_content: dict or ModelicaRecord for nested records + name: (str) The name of the record in Modelica model + record_content: (dict, ModelicaRecord) for nested records """ def __init__( self, From 1cf12852cee2966be594343da792fd8a2a19dc9c Mon Sep 17 00:00:00 2001 From: David Jansen Date: Tue, 30 Jan 2024 11:41:49 +0100 Subject: [PATCH 024/125] add base for modular tests of modelica model export --- bim2sim/export/modelica/__init__.py | 4 +- .../test/unit/kernel/task/export.py | 138 ++++++++++++++---- bim2sim/tasks/hvac/export.py | 2 + 3 files changed, 116 insertions(+), 28 deletions(-) diff --git a/bim2sim/export/modelica/__init__.py b/bim2sim/export/modelica/__init__.py index 6d814ffaf3..1a1ff4be91 100644 --- a/bim2sim/export/modelica/__init__.py +++ b/bim2sim/export/modelica/__init__.py @@ -124,8 +124,8 @@ def set_positions(self, elements, connections): # calculte instance position positions = np.array( - [inst.element.position for inst in elements - if inst.element.position is not None]) + [inst.element.position if inst.element.position is not None else + (0, 0) for inst in elements]) pos_min = np.min(positions, axis=0) pos_max = np.max(positions, axis=0) pos_delta = pos_max - pos_min diff --git a/bim2sim/plugins/PluginAixLib/test/unit/kernel/task/export.py b/bim2sim/plugins/PluginAixLib/test/unit/kernel/task/export.py index e756fa9093..62af36a0f5 100644 --- a/bim2sim/plugins/PluginAixLib/test/unit/kernel/task/export.py +++ b/bim2sim/plugins/PluginAixLib/test/unit/kernel/task/export.py @@ -1,34 +1,120 @@ +import tempfile +import unittest +from pathlib import Path +from unittest import mock + import bim2sim.elements.aggregation.hvac_aggregations +from bim2sim.elements.graphs.hvac_graph import HvacGraph from bim2sim.kernel.decision.console import ConsoleDecisionHandler +from bim2sim.kernel.decision.decisionhandler import DebugDecisionHandler -from test.unit.elements.aggregation.test_parallelpumps import ParallelPumpHelper +from test.unit.elements.aggregation.test_parallelpumps import \ + ParallelPumpHelper +from test.unit.elements.aggregation.test_pipestrand import StrandHelper from bim2sim.tasks.hvac import Export from bim2sim.sim_settings import PlantSimSettings from bim2sim.plugins.PluginAixLib.bim2sim_aixlib import LoadLibrariesAixLib +from test.unit.elements.helper import SetupHelperHVAC +from bim2sim.elements import hvac_elements as hvac +from bim2sim.elements.mapping.units import ureg + + + + +class SetupHelperAixLibComponents(SetupHelperHVAC): + + def get_pipe(self): + pipe = self.element_generator( + hvac.Pipe, + diameter=0.03 * ureg.m, + length=1 * ureg.m) + return HvacGraph([pipe]) + + def get_pump(self): + pump = self.element_generator( + hvac.Pump, + rated_mass_flow=1, + rated_pressure_difference=100) + return HvacGraph([pump]) + + def get_Boiler(self): + self.element_generator(hvac.Boiler, ...) + + +class TestAixLibExport(unittest.TestCase): + export_task = None + loaded_libs = None + helper = None + + @classmethod + def setUpClass(cls): + playground = mock.Mock() + project = mock.Mock + playground.project = project + paths = mock.Mock + # load libraries as these are required for export + lib_aixlib = LoadLibrariesAixLib(playground) + cls.loaded_libs = lib_aixlib.run()[0] + # instantiate export task and set required values via mocks + cls.export_task = Export(playground) + cls.export_task.prj_name = 'Test' + # cls.export_task.paths = paths + # temp_dir = tempfile.TemporaryDirectory( + # prefix='bim2sim_') + # # cls.export_task.paths.export = temp_dir.name + # cls.export_task.paths.export = Path("D:/01_Kurzablage/st_exportasdvfiyhxcv") + cls.setup_helper_aixlib = SetupHelperAixLibComponents() + def test_pipe_export(self): + # TODO solve problem with path (maybe via project mock) + graph = self.setup_helper_aixlib.get_pipe() + temp_dir = tempfile.TemporaryDirectory( + prefix='bim2sim_') + self.export_task.paths = temp_dir.name + answers = () + modelica_model = DebugDecisionHandler(answers).handle( + self.export_task.run(self.loaded_libs, graph)) + # assertEqual + pipe_ele_params = { + 'diameter': graph.elements[0].diameter, + 'length': graph.elements[0].length + } + # ConsoleDecisionHandler().handle(export_task.run(loaded_libs, graph)) + pipe_modelica_params = { + 'diameter': modelica_model[0].elements[0].params['diameter'], + 'length': modelica_model[0].elements[0].params['length'] + } + self.assertDictEqual(pipe_ele_params, pipe_modelica_params) -# class TestAixLibExport(): - - # TODO primary goal is to export the parallelpump test and see how connections come out - # therefore we need to run a tasks without any project and can't use the existing DebugDecisioHandler due to missing project - # for generic usage in the future we need a class like DebugDecisionHandler to run single tasks, or maybe inistiate a DebugPlayground - -if __name__ == '__main__': -# def parallelpump_export(self): - print('TESTTEST') - parallelPumpHelper = ParallelPumpHelper() - graph, flags = parallelPumpHelper.get_setup_pumps4() - matches, meta = bim2sim.elements.aggregation.hvac_aggregations.ParallelPump.find_matches(graph) - agg_pump = bim2sim.elements.aggregation.hvac_aggregations.ParallelPump(graph, matches[0], **meta[0]) - graph.merge( - mapping=agg_pump.get_replacement_mapping(), - inner_connections=agg_pump.inner_connections, - ) - workflow = PlantSimSettings() - lib_aixlib = LoadLibrariesAixLib() - export_task = Export() - ConsoleDecisionHandler().handle(export_task.run(workflow, lib_aixlib.run(workflow)[0], graph)) - - # for test, answers in zip(test, answers) in export_task.run(workflow, lib_aixlib.run(workflow)[0], graph): - # print('test') - print('test') + # + # parallelPumpHelper = ParallelPumpHelper() + # graph, flags = parallelPumpHelper.get_setup_pumps4() + # matches, meta = bim2sim.elements.aggregation.hvac_aggregations + # .ParallelPump.find_matches(graph) + # agg_pump = bim2sim.elements.aggregation.hvac_aggregations + # .ParallelPump(graph, matches[0], **meta[0]) + # graph.merge( + # mapping=agg_pump.get_replacement_mapping(), + # inner_connections=agg_pump.inner_connections, + # ) + # playground = mock.Mock() + # project = mock.Mock + # playground.project = project + # paths = mock.Mock + # # load libraries as these are required for export + # lib_aixlib = LoadLibrariesAixLib(playground) + # loaded_libs = lib_aixlib.run()[0] + # # instantiate export task and set required values via mocks + # export_task = Export(playground) + # export_task.prj_name = 'Test' + # export_task.paths = paths + # export_task.paths.export = Path("D:/01_Kurzablage/test_export") + # # run export task with ConsoleDecisionHandler + # answers = (1,1,1) + # # DebugDecisionHandler(answers).handle(export_task.run(loaded_libs, + # graph)) + # ConsoleDecisionHandler().handle(export_task.run(loaded_libs, graph)) + # # for test, answers in zip(test, answers) in export_task.run(workflow, + # lib_aixlib.run(workflow)[0], graph): + # # print('test') + # print('test') diff --git a/bim2sim/tasks/hvac/export.py b/bim2sim/tasks/hvac/export.py index 60c53cd61f..7731f17d62 100644 --- a/bim2sim/tasks/hvac/export.py +++ b/bim2sim/tasks/hvac/export.py @@ -11,6 +11,7 @@ class Export(ITask): """Export to Dymola/Modelica""" reads = ('libraries', 'graph') + touches = ('modelica_model',) final = True def run(self, libraries: tuple, graph: HvacGraph): @@ -42,6 +43,7 @@ def run(self, libraries: tuple, graph: HvacGraph): connections=connection_port_names, ) modelica_model.save(self.paths.export) + return modelica_model, @staticmethod def create_connections(graph: HvacGraph, export_elements: dict) -> list: From 3747fbd96c2fd29241788ec29e435d4541ddf1cb Mon Sep 17 00:00:00 2001 From: sfreund1 Date: Tue, 30 Jan 2024 13:33:02 +0100 Subject: [PATCH 025/125] fixed export path for tests of modelica model export --- .../test/unit/kernel/task/export.py | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/bim2sim/plugins/PluginAixLib/test/unit/kernel/task/export.py b/bim2sim/plugins/PluginAixLib/test/unit/kernel/task/export.py index 62af36a0f5..e8d3aa504e 100644 --- a/bim2sim/plugins/PluginAixLib/test/unit/kernel/task/export.py +++ b/bim2sim/plugins/PluginAixLib/test/unit/kernel/task/export.py @@ -3,7 +3,6 @@ from pathlib import Path from unittest import mock -import bim2sim.elements.aggregation.hvac_aggregations from bim2sim.elements.graphs.hvac_graph import HvacGraph from bim2sim.kernel.decision.console import ConsoleDecisionHandler from bim2sim.kernel.decision.decisionhandler import DebugDecisionHandler @@ -12,15 +11,12 @@ ParallelPumpHelper from test.unit.elements.aggregation.test_pipestrand import StrandHelper from bim2sim.tasks.hvac import Export -from bim2sim.sim_settings import PlantSimSettings from bim2sim.plugins.PluginAixLib.bim2sim_aixlib import LoadLibrariesAixLib from test.unit.elements.helper import SetupHelperHVAC from bim2sim.elements import hvac_elements as hvac from bim2sim.elements.mapping.units import ureg - - class SetupHelperAixLibComponents(SetupHelperHVAC): def get_pipe(self): @@ -45,36 +41,40 @@ class TestAixLibExport(unittest.TestCase): export_task = None loaded_libs = None helper = None + export_path = None @classmethod - def setUpClass(cls): + def setUpClass(cls) -> None: + # Set up playground, project and paths via mocks playground = mock.Mock() - project = mock.Mock + project = mock.Mock() + paths = mock.Mock() playground.project = project - paths = mock.Mock - # load libraries as these are required for export + + # Load libraries as these are required for export lib_aixlib = LoadLibrariesAixLib(playground) cls.loaded_libs = lib_aixlib.run()[0] - # instantiate export task and set required values via mocks + + # Instantiate export task and set required values via mocks cls.export_task = Export(playground) - cls.export_task.prj_name = 'Test' - # cls.export_task.paths = paths - # temp_dir = tempfile.TemporaryDirectory( - # prefix='bim2sim_') - # # cls.export_task.paths.export = temp_dir.name - # cls.export_task.paths.export = Path("D:/01_Kurzablage/st_exportasdvfiyhxcv") - cls.setup_helper_aixlib = SetupHelperAixLibComponents() + cls.export_task.prj_name = 'TestAixLibExport' + cls.export_task.paths = paths + + cls.helper = SetupHelperAixLibComponents() + + def setUp(self) -> None: + # Set export path to temporary path + self.export_path = tempfile.TemporaryDirectory(prefix='bim2sim') + self.export_task.paths.export = self.export_path.name + + def tearDown(self) -> None: + self.helper.reset() def test_pipe_export(self): - # TODO solve problem with path (maybe via project mock) - graph = self.setup_helper_aixlib.get_pipe() - temp_dir = tempfile.TemporaryDirectory( - prefix='bim2sim_') - self.export_task.paths = temp_dir.name + graph = self.helper.get_pipe() answers = () modelica_model = DebugDecisionHandler(answers).handle( self.export_task.run(self.loaded_libs, graph)) - # assertEqual pipe_ele_params = { 'diameter': graph.elements[0].diameter, 'length': graph.elements[0].length From 5853d3d15e968b2d64208dd1050e7c7f2ead2a4e Mon Sep 17 00:00:00 2001 From: sfreund1 Date: Tue, 6 Feb 2024 08:19:35 +0100 Subject: [PATCH 026/125] Added unit test for modelica export of pump to AixLib (wip) --- bim2sim/elements/hvac_elements.py | 1 - bim2sim/export/modelica/__init__.py | 2 +- .../plugins/PluginAixLib/test/unit/kernel/task/export.py | 8 ++++++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/bim2sim/elements/hvac_elements.py b/bim2sim/elements/hvac_elements.py index 9affee7dd3..a372224418 100644 --- a/bim2sim/elements/hvac_elements.py +++ b/bim2sim/elements/hvac_elements.py @@ -218,7 +218,6 @@ def volume(self): vol = self.calc_volume_from_ifc_shape() return vol - def get_ports(self) -> list: """Returns a list of ports of this product.""" ports = ifc2py_get_ports(self.ifc) diff --git a/bim2sim/export/modelica/__init__.py b/bim2sim/export/modelica/__init__.py index df36f3f3f3..cec3f5ed71 100644 --- a/bim2sim/export/modelica/__init__.py +++ b/bim2sim/export/modelica/__init__.py @@ -442,7 +442,7 @@ class ModelicaRecord: def __init__( self, name: str, - record_content:[dict, Type["ModelicaRecord"]] + record_content: [dict, Type["ModelicaRecord"]] ): self.name = name self.record_content = self.handle_content(record_content) diff --git a/bim2sim/plugins/PluginAixLib/test/unit/kernel/task/export.py b/bim2sim/plugins/PluginAixLib/test/unit/kernel/task/export.py index e8d3aa504e..49017f60e2 100644 --- a/bim2sim/plugins/PluginAixLib/test/unit/kernel/task/export.py +++ b/bim2sim/plugins/PluginAixLib/test/unit/kernel/task/export.py @@ -86,6 +86,14 @@ def test_pipe_export(self): } self.assertDictEqual(pipe_ele_params, pipe_modelica_params) + def test_pump_export(self): + graph = self.helper.get_pump() + answers = () + modelica_model = DebugDecisionHandler(answers).handle( + self.export_task.run(self.loaded_libs, graph)) + print('') + + # # parallelPumpHelper = ParallelPumpHelper() # graph, flags = parallelPumpHelper.get_setup_pumps4() From 97cb725d144a6bc731dcbbcbdc9288fe4a80f0f5 Mon Sep 17 00:00:00 2001 From: David Paul Jansen Date: Tue, 12 Mar 2024 12:15:13 +0100 Subject: [PATCH 027/125] rename templates for spawn --- .../assets/templates/modelica/tmplModel.txt | 4 ++ .../templates/modelica/tmplModel_wip.txt | 57 ------------------- .../{tmplSpawn.txt => tmplSpawnBuilding.txt} | 0 3 files changed, 4 insertions(+), 57 deletions(-) delete mode 100644 bim2sim/assets/templates/modelica/tmplModel_wip.txt rename bim2sim/assets/templates/modelica/{tmplSpawn.txt => tmplSpawnBuilding.txt} (100%) diff --git a/bim2sim/assets/templates/modelica/tmplModel.txt b/bim2sim/assets/templates/modelica/tmplModel.txt index 570969296e..323ff38158 100644 --- a/bim2sim/assets/templates/modelica/tmplModel.txt +++ b/bim2sim/assets/templates/modelica/tmplModel.txt @@ -37,6 +37,10 @@ equation annotation (Line(points={{${pos1[0]},${pos1[1]}},{38,-6}}, color={0,0,127}));; % endfor +// heatport connections +// % for + + annotation ( %if unknowns: Diagram(graphics={Text( diff --git a/bim2sim/assets/templates/modelica/tmplModel_wip.txt b/bim2sim/assets/templates/modelica/tmplModel_wip.txt deleted file mode 100644 index 65b29ee76b..0000000000 --- a/bim2sim/assets/templates/modelica/tmplModel_wip.txt +++ /dev/null @@ -1,57 +0,0 @@ -model ${model.name} "${model.comment}" - extends Modelica.Icons.Example; - import SI = Modelica.Units.SI; - -%if unknowns: - // unknown parameters: - %for param in unknowns: - // ${param} - %endfor -%endif - -% for instance in model.elements: - ${inst(instance)} - -def inst(instance) -% endfor -<%def name="inst(instance)", filter="trim"> - ${instance.path} ${instance.name}\ - %if instance.params: -( - %endif -<% i = 0 %>\ - % for par, value in instance.modelica_params.items(): -<% i+=1 %>\ -# TODO @svenne add records here -# TODO ${record.__name__( ${par} = ${'"missing - replace with meaningful value"' if value is None else value}${"," if i < len(instance.params) else ")"}) -# TODO put next line into function - ${par} = ${'"missing - replace with meaningful value"' if value is None else value}${"," if i < len(instance.params) else ")"} - % endfor - "${instance.comment}" - annotation (Placement(transformation(extent={{${instance.position[0]-10},${instance.position[1]-10}},{${instance.position[0]+10},${instance.position[1]+10}}}))); - - -equation -% for con1, con2, pos1, pos2 in model.connections: - connect(${con1}, ${con2}) - annotation (Line(points={{${pos1[0]},${pos1[1]}},{38,-6}}, color={0,0,127}));; -% endfor - - annotation ( -%if unknowns: - Diagram(graphics={Text( - extent={{-100,100},{100,60}}, - lineColor={238,46,47}, - textString="${len(unknowns)} unknown parameters! -see comments for details.")}), -%endif - experiment(StopTime=36000) - ); -end ${model.name}; - - -AixLib.Model.Model1 model1name ( -par1 = 5, -par2 = 3, -par3 = 1 -) "blabla" \ No newline at end of file diff --git a/bim2sim/assets/templates/modelica/tmplSpawn.txt b/bim2sim/assets/templates/modelica/tmplSpawnBuilding.txt similarity index 100% rename from bim2sim/assets/templates/modelica/tmplSpawn.txt rename to bim2sim/assets/templates/modelica/tmplSpawnBuilding.txt From 93723e4ba64b3ea0738dadf79a6ef945e335887c Mon Sep 17 00:00:00 2001 From: David Paul Jansen Date: Tue, 12 Mar 2024 12:16:18 +0100 Subject: [PATCH 028/125] add dummy heat port connection function --- bim2sim/tasks/hvac/export.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bim2sim/tasks/hvac/export.py b/bim2sim/tasks/hvac/export.py index 64c1b4d484..fd8bc59173 100644 --- a/bim2sim/tasks/hvac/export.py +++ b/bim2sim/tasks/hvac/export.py @@ -32,6 +32,8 @@ def run(self, libraries: tuple, graph: HvacGraph): connection_port_names = self.create_connections(graph, export_elements) + connection_heat_ports = self.create_heat_port_connections(...) + self.logger.info( "Creating Modelica model with %d model elements and %d connections.", len(export_elements), len(connection_port_names)) @@ -87,3 +89,6 @@ def create_connections(graph: HvacGraph, export_elements: dict) -> list: distributor.export_params['n'] = int(distributors_n[distributor] / 2 - 1) return connection_port_names + + def create_heat_port_connections(self): + pass \ No newline at end of file From 5a9b9b3ffcf14383217ba222638a964ae3c7d5d1 Mon Sep 17 00:00:00 2001 From: David Paul Jansen Date: Tue, 11 Jun 2024 09:15:50 +0200 Subject: [PATCH 029/125] add dummy task for heat port connections in hvac --- .../bim2sim_spawn/tasks/export_co_sim_static.py | 2 +- bim2sim/tasks/base.py | 7 +++++-- bim2sim/tasks/hvac/export.py | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_co_sim_static.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_co_sim_static.py index 1106eb8f36..bdb7c79482 100644 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_co_sim_static.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_co_sim_static.py @@ -37,7 +37,7 @@ def run(self, elements: dict, weather_file_modelica: Path, # function templ_path_building = Path( bim2sim.__file__).parent / \ - 'assets/templates/modelica/tmplSpawn.txt' + 'assets/templates/modelica/tmplSpawnBuilding.txt' with open(templ_path_building) as f: template_bldg_str = f.read() diff --git a/bim2sim/tasks/base.py b/bim2sim/tasks/base.py index ba3b82d248..0541f4fd3c 100644 --- a/bim2sim/tasks/base.py +++ b/bim2sim/tasks/base.py @@ -149,8 +149,11 @@ def run_task(self, task: ITask) -> Generator[DecisionBunch, None, None]: # normal case n_res = len(result) if result is not None else 0 if len(task.touches) != n_res: - raise TaskFailed("Mismatch in '%s' result. Required items: %d (%s). Please make sure that required" - " inputs (reads) are created in previous tasks." % (task, n_res, task.touches)) + raise TaskFailed( + f"Mismatch in results of '{task}'." + f"Requiring {len(task.touches)} results, but only " + f"{n_res} are available. " + f"Required results are: ({task.touches}).") # assign results to state if n_res: diff --git a/bim2sim/tasks/hvac/export.py b/bim2sim/tasks/hvac/export.py index fd8bc59173..9a6e01a4bf 100644 --- a/bim2sim/tasks/hvac/export.py +++ b/bim2sim/tasks/hvac/export.py @@ -32,7 +32,7 @@ def run(self, libraries: tuple, graph: HvacGraph): connection_port_names = self.create_connections(graph, export_elements) - connection_heat_ports = self.create_heat_port_connections(...) + connection_heat_ports = self.create_heat_port_connections() self.logger.info( "Creating Modelica model with %d model elements and %d connections.", From 2602c0b775409f2e6a5b1e8c3f4bad4cc9eab46b Mon Sep 17 00:00:00 2001 From: David Paul Jansen Date: Tue, 11 Jun 2024 12:17:03 +0200 Subject: [PATCH 030/125] basic export for hvac with heat ports --- .../assets/templates/modelica/tmplModel.txt | 12 +++++- .../templates/modelica/tmplSpawnBuilding.txt | 13 +++--- bim2sim/export/modelica/__init__.py | 23 +++++++++-- .../bim2sim_aixlib/models/__init__.py | 27 +++++++++---- .../unit/{kernel/task => tasks}/__init__.py | 0 .../{kernel/task => tasks}/test_export.py | 22 ++++++++++ .../PluginSpawn/bim2sim_spawn/__init__.py | 10 ++--- .../bim2sim_spawn/tasks/__init__.py | 2 +- ...sim_static.py => export_spawn_building.py} | 9 +++-- .../tasks/export_spawn_total.py} | 0 bim2sim/sim_settings.py | 7 ++++ bim2sim/tasks/common/export_modelica.py | 2 +- bim2sim/tasks/hvac/export.py | 40 +++++++++++++++++-- test/unit/elements/helper.py | 7 ++++ 14 files changed, 141 insertions(+), 33 deletions(-) rename bim2sim/plugins/PluginAixLib/test/unit/{kernel/task => tasks}/__init__.py (100%) rename bim2sim/plugins/PluginAixLib/test/unit/{kernel/task => tasks}/test_export.py (78%) rename bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/{export_co_sim_static.py => export_spawn_building.py} (96%) rename bim2sim/plugins/{PluginAixLib/test/unit/kernel/task/export.py => PluginSpawn/bim2sim_spawn/tasks/export_spawn_total.py} (100%) diff --git a/bim2sim/assets/templates/modelica/tmplModel.txt b/bim2sim/assets/templates/modelica/tmplModel.txt index 323ff38158..3c7f09b8ca 100644 --- a/bim2sim/assets/templates/modelica/tmplModel.txt +++ b/bim2sim/assets/templates/modelica/tmplModel.txt @@ -31,6 +31,13 @@ model ${model.name} "${model.comment}" annotation (Placement(transformation(extent={{${instance.position[0]-10},${instance.position[1]-10}},{${instance.position[0]+10},${instance.position[1]+10}}}))); +// Only for SpawnOfEnergyPlus +// TODO conditional +Modelica.Thermal.HeatTransfer.Interfaces.HeatPort_a heatPortOuterCon[${len(model.connections_heat_ports)}] + annotation (Placement(transformation(extent={{-110,10},{-90,30}})));; +Modelica.Thermal.HeatTransfer.Interfaces.HeatPort_a heatPortOuterRad[${len(model.connections_heat_ports)}] + annotation (Placement(transformation(extent={{-110,-22},{-90,-2}}))); + equation % for con1, con2, pos1, pos2 in model.connections: connect(${con1}, ${con2}) @@ -38,8 +45,9 @@ equation % endfor // heatport connections -// % for - +% for cons in model.connections_heat_ports: + connect${str(cons).replace("'","")}; +% endfor annotation ( %if unknowns: diff --git a/bim2sim/assets/templates/modelica/tmplSpawnBuilding.txt b/bim2sim/assets/templates/modelica/tmplSpawnBuilding.txt index 802267c0d4..37970b0099 100644 --- a/bim2sim/assets/templates/modelica/tmplSpawnBuilding.txt +++ b/bim2sim/assets/templates/modelica/tmplSpawnBuilding.txt @@ -57,11 +57,12 @@ model ${model_name} "${model_comment}" annotation (Placement(transformation(extent={{-6,32},{14,52}}))); // Interfaces - Modelica.Thermal.HeatTransfer.Interfaces.HeatPort_a heaPorAir[nZones] - "Heat port to air volume for each zone" + Modelica.Thermal.HeatTransfer.Interfaces.HeatPort_a heaPorCon[nZones] + "Convective heat port to air volume for each zone" annotation (Placement(transformation(extent={{-110,-10},{-90,10}}))); - - + Modelica.Thermal.HeatTransfer.Interfaces.HeatPort_a heaPorRad[nZones] + "Radiative heat port to air volume for each zone" + annotation (Placement(transformation(extent={{-110,-28},{-90,-8}}))); Modelica.Blocks.Sources.Constant const[nZones,3](each k=0) "TODO" annotation (Placement(transformation(extent={{-100,16},{-80,36}}))); @@ -89,8 +90,10 @@ equation annotation (Line(points={{-23.1111,68},{-14,68},{-14,68},{-6,68}}, color={0,127,255})); - connect(heaPorAir, zon.heaPorAir) annotation (Line(points={{-100,0},{0,0}}, + connect(heaPorCon, zon.heaPorAir) annotation (Line(points={{-100,0},{0,0}}, color={191,0,0})); + connect(zon.heaPorRad, heaPorRad) annotation (Line(points={{0,-6},{-82,-6},{-82, + -18},{-100,-18}}, color={191,0,0})); connect(const.y, zon.qGai_flow) annotation (Line(points={{-79,26},{-32,26},{-32, 10},{-22,10}}, color={0,0,127})); diff --git a/bim2sim/export/modelica/__init__.py b/bim2sim/export/modelica/__init__.py index 25a3299ee4..95b9b8a412 100644 --- a/bim2sim/export/modelica/__init__.py +++ b/bim2sim/export/modelica/__init__.py @@ -101,7 +101,8 @@ def help_package_order(path: Path, package_list: List[str], addition=None, class Model: """Modelica model""" - def __init__(self, name, comment, elements: list, connections: list): + def __init__(self, name, comment, elements: list, connections: list, + connections_heat_ports: list): self.name = name self.comment = comment self.elements = elements @@ -110,6 +111,8 @@ def __init__(self, name, comment, elements: list, connections: list): self.size_y = (-100, 100) self.connections = self.set_positions(elements, connections) + # TODO positions for heatports? + self.connections_heat_ports = connections_heat_ports def set_positions(self, elements, connections): """Sets position of elements @@ -146,7 +149,7 @@ def set_positions(self, elements, connections): ) return connections_positions - def code(self): + def render_modelica_code(self): """Returns Modelica code.""" with lock: return template.render(model=self, unknowns=self.unknown_params()) @@ -169,7 +172,7 @@ def save(self, path: str): if not _path.endswith(".mo"): _path += ".mo" - data = self.code() + data = self.render_modelica_code() user_logger.info("Saving '%s' to '%s'", self.name, _path) with codecs.open(_path, "w", "utf-8") as file: @@ -485,10 +488,22 @@ def get_port_name(self, port): """Returns name of port""" return "port_unknown" + def get_heat_port_names(self): + """Returns names of heat ports if existing""" + return {} + def get_full_port_name(self, port): """Returns name of port including model name""" return "%s.%s" % (self.name, self.get_port_name(port)) + def get_full_heat_port_names(self): + """Returns names of heat ports including model name""" + full_heat_port_names = {} + for heat_type, heat_port_name in self.get_heat_port_names().items(): + full_heat_port_names[heat_type] = "%s.%s" % ( + self.name, heat_port_name) + return full_heat_port_names + @staticmethod def check_numeric(min_value=None, max_value=None): """Generic check function generator @@ -574,5 +589,5 @@ class Radiator(Instance): model = Model("System", "Test", [inst1, inst2], conns) - print(model.code()) + print(model.render_modelica_code()) # model.save(r"C:\Entwicklung\temp") diff --git a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py index 117b097154..8349a91656 100644 --- a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py +++ b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py @@ -5,7 +5,7 @@ from bim2sim.elements.mapping.units import ureg from bim2sim.export.modelica import ModelicaRecord - +# TODO get_port_name functions: use verbose_flow_direction instead index class AixLib(modelica.Instance): library = "AixLib" @@ -58,6 +58,20 @@ def request_params(self): "Q_flow_nominal") # self.params["T_nominal"] = (80, 60, 20) + def get_port_name(self, port): + if port.verbose_flow_direction == 'SINK': + return 'port_a' + if port.verbose_flow_direction == 'SOURCE': + return 'port_b' + else: + return super().get_port_name(port) + + def get_heat_port_names(self): + return { + "con": "heatPortCon", + "rad": "heatPortRad" + } + class Pump(AixLib): path = "AixLib.Fluid.Movers.SpeedControlled_y" @@ -122,6 +136,11 @@ def get_port_name(self, port): else: return super().get_port_name(port) + def get_heat_port_names(self): + return { + "con": "heatPortCon", + } + class Consumer(AixLib): path = "AixLib.Systems.HydraulicModules.SimpleConsumer" @@ -294,11 +313,6 @@ def request_params(self): "dp_Valve"] = 10000 # Todo get from hydraulic circuit def get_port_name(self, port): - try: - index = self.element.ports.index(port) - except ValueError: - # unknown port - index = -1 if port.verbose_flow_direction == 'SINK': return 'port_a' if port.verbose_flow_direction == 'SOURCE': @@ -306,7 +320,6 @@ def get_port_name(self, port): else: return super().get_port_name(port) - class Distributor(AixLib): path = "AixLib.Fluid.HeatExchangers.ActiveWalls.Distributor" represents = [hvac.Distributor] diff --git a/bim2sim/plugins/PluginAixLib/test/unit/kernel/task/__init__.py b/bim2sim/plugins/PluginAixLib/test/unit/tasks/__init__.py similarity index 100% rename from bim2sim/plugins/PluginAixLib/test/unit/kernel/task/__init__.py rename to bim2sim/plugins/PluginAixLib/test/unit/tasks/__init__.py diff --git a/bim2sim/plugins/PluginAixLib/test/unit/kernel/task/test_export.py b/bim2sim/plugins/PluginAixLib/test/unit/tasks/test_export.py similarity index 78% rename from bim2sim/plugins/PluginAixLib/test/unit/kernel/task/test_export.py rename to bim2sim/plugins/PluginAixLib/test/unit/tasks/test_export.py index 740b46f9f8..a3b8905562 100644 --- a/bim2sim/plugins/PluginAixLib/test/unit/kernel/task/test_export.py +++ b/bim2sim/plugins/PluginAixLib/test/unit/tasks/test_export.py @@ -1,10 +1,12 @@ import tempfile +from bim2sim import ConsoleDecisionHandler from bim2sim.kernel.decision.decisionhandler import DebugDecisionHandler from bim2sim.plugins.PluginAixLib.bim2sim_aixlib import LoadLibrariesAixLib from bim2sim.elements.mapping.units import ureg from test.unit.tasks.hvac.test_export import TestStandardLibraryExports +from test.unit.elements.aggregation.test_consumer import ConsumerHelper class TestAixLibExport(TestStandardLibraryExports): @@ -20,10 +22,13 @@ def setUp(self) -> None: # Set export path to temporary path self.export_path = tempfile.TemporaryDirectory(prefix='bim2sim') self.export_task.paths.export = self.export_path.name + # TODO does this work? + self.export_task.playground.sim_settings.outer_heat_ports = True def tearDown(self) -> None: self.helper.reset() + def test_pump_export(self): graph = self.helper.get_pump() answers = () @@ -74,3 +79,20 @@ def test_pump_export(self): pump_modelica_params_expected, pump_modelica_params) # TODO #624 Write tests for all components of AixLib + + def test_radiator_export(self): + graph = self.helper.get_radiator() + answers = () + + modelica_model = DebugDecisionHandler(answers).handle( + self.export_task.run(self.loaded_libs, graph)) + print('test') + + def test_outer_heat_ports(self): + answers = () + self.helper = ConsumerHelper() + graph, flags = self.helper.get_setup_system2() + + modelica_model = ConsoleDecisionHandler().handle( + self.export_task.run(self.loaded_libs, graph)) + print('test') \ No newline at end of file diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/__init__.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/__init__.py index fc8cbfe545..a738e05138 100644 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/__init__.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/__init__.py @@ -14,20 +14,18 @@ class PluginSpawnOfEP(Plugin): # common.CheckIfc, common.CreateElements, bps.CreateSpaceBoundaries, + bps.CorrectSpaceBoundaries, + bps.AddSpaceBoundaries2B, bps.FilterTZ, - bps.ProcessSlabsRoofs, + # bps.ProcessSlabsRoofs, common.BindStoreys, bps.EnrichUseConditions, bps.VerifyLayersMaterials, # LOD.full bps.EnrichMaterial, # LOD.full - ep_tasks.EPGeomPreprocessing, - ep_tasks.AddSpaceBoundaries2B, common.Weather, ep_tasks.CreateIdf, # ep_tasks.IdfPostprocessing, # ep_tasks.ExportIdfForCfd, # ep_tasks.RunEnergyPlusSimulation, - # spawn_tasks.CreateSpawnElements, - # TODO remove this? - spawn_tasks.ExportModelicaSpawnStatic, + spawn_tasks.ExportSpawnBuilding, ] diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/__init__.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/__init__.py index ea46f84741..1e1fdeeac0 100644 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/__init__.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/__init__.py @@ -1 +1 @@ -from .export_co_sim_static import ExportModelicaSpawnStatic +from .export_spawn_building import ExportSpawnBuilding diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_co_sim_static.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_building.py similarity index 96% rename from bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_co_sim_static.py rename to bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_building.py index bdb7c79482..cc30b77f11 100644 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_co_sim_static.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_building.py @@ -11,11 +11,11 @@ from bim2sim.tasks.base import ITask -class ExportModelicaSpawnStatic(ITask): - """Export to Dymola/Modelica""" +class ExportSpawnBuilding(ITask): + """Export building for SpawnOfEnergyPlus model to Modelica""" reads = ('elements', 'weather_file_modelica', 'weather_file_ep') - # reads = ('libraries', 'elements',) + touches = ('zone_names',) final = True def run(self, elements: dict, weather_file_modelica: Path, @@ -76,6 +76,7 @@ def run(self, elements: dict, weather_file_modelica: Path, # EXPORT MAIN MODEL # This is the main model that should holds building_simulation and # hvac_simulation + return zone_names def get_zone_names(self): # TODO #1: get names from IDF or EP process for ep zones in @@ -157,7 +158,7 @@ def to_modelica_spawn(parameter): if isinstance(parameter, (list, tuple, set)): return "{%s}" % ( ",".join( - (ExportModelicaSpawnStatic.to_modelica_spawn(par) for par + (ExportSpawnBuilding.to_modelica_spawn(par) for par in parameter))) if isinstance(parameter, Path): return \ diff --git a/bim2sim/plugins/PluginAixLib/test/unit/kernel/task/export.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_total.py similarity index 100% rename from bim2sim/plugins/PluginAixLib/test/unit/kernel/task/export.py rename to bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_total.py diff --git a/bim2sim/sim_settings.py b/bim2sim/sim_settings.py index 36ed6e6249..46f0165ff8 100644 --- a/bim2sim/sim_settings.py +++ b/bim2sim/sim_settings.py @@ -526,6 +526,13 @@ def check_mandatory(self): for_frontend=True ) + # TODO move to EP Spawn sim settings + outer_heat_ports = BooleanSetting( + default=False, + description='Add outer heat ports to allow connections to other ' + 'models.', + for_frontend=True + ) class PlantSimSettings(BaseSimSettings): def __init__(self): diff --git a/bim2sim/tasks/common/export_modelica.py b/bim2sim/tasks/common/export_modelica.py index 5f4fca0dd7..48519440ac 100644 --- a/bim2sim/tasks/common/export_modelica.py +++ b/bim2sim/tasks/common/export_modelica.py @@ -3,7 +3,7 @@ from bim2sim.elements.base_elements import ProductBased from bim2sim.export import modelica from bim2sim.tasks.base import ITask - +# TODO is this obsolete? class ExportModelica(ITask): """Export to Dymola/Modelica""" diff --git a/bim2sim/tasks/hvac/export.py b/bim2sim/tasks/hvac/export.py index 9a6e01a4bf..aa27822afa 100644 --- a/bim2sim/tasks/hvac/export.py +++ b/bim2sim/tasks/hvac/export.py @@ -32,7 +32,14 @@ def run(self, libraries: tuple, graph: HvacGraph): connection_port_names = self.create_connections(graph, export_elements) - connection_heat_ports = self.create_heat_port_connections() + connections_heat_ports = [] + + if self.playground.sim_settings.outer_heat_ports: + connections_heat_ports.extend( + self.create_inner_heat_port_connections()) + + connections_heat_ports.extend(self.create_outer_heat_port_connections( + list(export_elements.values()))) self.logger.info( "Creating Modelica model with %d model elements and %d connections.", @@ -43,6 +50,7 @@ def run(self, libraries: tuple, graph: HvacGraph): comment=f"Autogenerated by BIM2SIM on {datetime.now():%Y-%m-%d %H:%M:%S%z}", elements=list(export_elements.values()), connections=connection_port_names, + connections_heat_ports=connections_heat_ports ) modelica_model.save(self.paths.export) return modelica_model, @@ -90,5 +98,31 @@ def create_connections(graph: HvacGraph, export_elements: dict) -> list: return connection_port_names - def create_heat_port_connections(self): - pass \ No newline at end of file + @staticmethod + def create_outer_heat_port_connections(export_elements: list) -> list: + """Creates connections to an outer heat port for further connections""" + # ToDo only connect heat ports that might not be connected already by + # create_inner_heat_port_connections() function + + # ToDo annotations/placements are missing. Those needs to be added to + # visualize connections + heat_port_connections = [] + i = 1 + for ele in export_elements: + if ele.get_heat_port_names(): + full_names = ele.get_full_heat_port_names() + # TODO this needs to be reworked, + # maybe create connections only if rad and con both exist? + # names for heat ports are fixed and defined in template + if "con" in full_names: + heat_port_connections.append( + (f"heatPortOuterCon[{i}]", full_names["con"])) + if "rad" in full_names: + heat_port_connections.append( + (f"heatPortOuterRad[{i}]", full_names["rad"])) + i += 1 + return heat_port_connections + + def create_inner_heat_port_connections(self) -> list: + # TODO if this is needed + return [] diff --git a/test/unit/elements/helper.py b/test/unit/elements/helper.py index 91c0ee06e4..8241156d2f 100644 --- a/test/unit/elements/helper.py +++ b/test/unit/elements/helper.py @@ -164,8 +164,15 @@ def get_pump(self): return HvacGraph([pump]) def get_Boiler(self): + # TODO: obsolete? self.element_generator(hvac.Boiler, ...) + def get_radiator(self): + radiator = self.element_generator( + hvac.SpaceHeater, rated_power=1 * ureg.kilowatt) + return HvacGraph([radiator]) + + class SetupHelperBPS(SetupHelper): def element_generator(self, element_cls, flags=None, **kwargs): # with mock.patch.object(bps.BPSProduct, 'get_ports', return_value=[]): From 02ba4a8f5dcaabbb9ffd14b429157543ee636c8f Mon Sep 17 00:00:00 2001 From: David Paul Jansen Date: Tue, 18 Jun 2024 10:16:33 +0200 Subject: [PATCH 031/125] add a basic heatport class --- bim2sim/export/modelica/__init__.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/bim2sim/export/modelica/__init__.py b/bim2sim/export/modelica/__init__.py index 95b9b8a412..33d358e2db 100644 --- a/bim2sim/export/modelica/__init__.py +++ b/bim2sim/export/modelica/__init__.py @@ -569,6 +569,18 @@ class Dummy(Instance): represents = elem.Dummy +class HeatPort: + """Simplified representation of a heat port in Modelica. + + This does not represent a bim2sim element, as IFC doesn't have the concept + of heat ports. This class is just for better differentiation between + radiative and convective heat ports. + """ + + def __init__(self, heat_transfer_type: str): + self.heat_transfer_type = heat_transfer_type + + if __name__ == "__main__": class Radiator(Instance): path = "Heating.Consumers.Radiators.Radiator" From 6e229ca4073cc3f830d10111ab0d7d1394e0218e Mon Sep 17 00:00:00 2001 From: David Paul Jansen Date: Tue, 18 Jun 2024 10:29:52 +0200 Subject: [PATCH 032/125] make sure heat transfer type has only certain values --- bim2sim/export/modelica/__init__.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/bim2sim/export/modelica/__init__.py b/bim2sim/export/modelica/__init__.py index 33d358e2db..ec86dc5f90 100644 --- a/bim2sim/export/modelica/__init__.py +++ b/bim2sim/export/modelica/__init__.py @@ -574,12 +574,24 @@ class HeatPort: This does not represent a bim2sim element, as IFC doesn't have the concept of heat ports. This class is just for better differentiation between - radiative and convective heat ports. + radiative, convective and generic heat ports. """ def __init__(self, heat_transfer_type: str): self.heat_transfer_type = heat_transfer_type + @property + def heat_transfer_type(self): + return self._heat_transfer_type + + @heat_transfer_type.setter + def heat_transfer_type(self, value): + if value.lower() not in ["convective", "radiative", "generic"]: + raise AttributeError(f'Can not set heat_transfer_type to {value},' + f'only "convective", "radiative" and ' + f'"generic are allowed') + self._heat_transfer_type = value + if __name__ == "__main__": class Radiator(Instance): From 12394f93c3d13eb0e95513664aa50504649ea3dd Mon Sep 17 00:00:00 2001 From: David Paul Jansen Date: Tue, 18 Jun 2024 10:38:34 +0200 Subject: [PATCH 033/125] use enum for heat transfer type --- bim2sim/export/modelica/__init__.py | 36 +++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/bim2sim/export/modelica/__init__.py b/bim2sim/export/modelica/__init__.py index ec86dc5f90..cf0057cdec 100644 --- a/bim2sim/export/modelica/__init__.py +++ b/bim2sim/export/modelica/__init__.py @@ -3,6 +3,7 @@ import codecs import logging import os +from enum import Enum from pathlib import Path from threading import Lock from typing import Union, Type, Dict, Container, Tuple, Callable, List @@ -569,28 +570,49 @@ class Dummy(Instance): represents = elem.Dummy +class HeatTransferType(Enum): + CONVECTIVE = "convective" + RADIATIVE = "radiative" + GENERIC = "generic" + + class HeatPort: """Simplified representation of a heat port in Modelica. This does not represent a bim2sim element, as IFC doesn't have the concept of heat ports. This class is just for better differentiation between radiative, convective and generic heat ports. + + Args: + heat_transfer_type (HeatTransferType): The type of heat transfer. """ - def __init__(self, heat_transfer_type: str): + def __init__(self, + heat_transfer_type: Union[HeatTransferType, + str], name: str): self.heat_transfer_type = heat_transfer_type + self.name = name @property def heat_transfer_type(self): return self._heat_transfer_type @heat_transfer_type.setter - def heat_transfer_type(self, value): - if value.lower() not in ["convective", "radiative", "generic"]: - raise AttributeError(f'Can not set heat_transfer_type to {value},' - f'only "convective", "radiative" and ' - f'"generic are allowed') - self._heat_transfer_type = value + def heat_transfer_type(self, value: Union[HeatTransferType, str]): + if isinstance(value, HeatTransferType): + self._heat_transfer_type = value + elif isinstance(value, str): + try: + self._heat_transfer_type = HeatTransferType[value.upper()] + except KeyError: + raise AttributeError(f'Cannot set heat_transfer_type to {value}, ' + f'only "convective", "radiative", and ' + f'"generic" are allowed') + else: + raise AttributeError(f'Cannot set heat_transfer_type to {value}, ' + f'only instances of HeatTransferType or ' + f'strings "convective", "radiative", and ' + f'"generic" are allowed') if __name__ == "__main__": From 228a9ef28faf90be998fd55b2233d464607483ff Mon Sep 17 00:00:00 2001 From: sfreund1 Date: Tue, 18 Jun 2024 10:53:02 +0200 Subject: [PATCH 034/125] add heat port connections to export --- .../templates/modelica/tmplSpawnBuilding.txt | 2 +- bim2sim/elements/base_elements.py | 1 + .../bim2sim_aixlib/models/__init__.py | 17 +++++++++++------ bim2sim/tasks/hvac/export.py | 2 ++ 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/bim2sim/assets/templates/modelica/tmplSpawnBuilding.txt b/bim2sim/assets/templates/modelica/tmplSpawnBuilding.txt index 37970b0099..f9d310e83e 100644 --- a/bim2sim/assets/templates/modelica/tmplSpawnBuilding.txt +++ b/bim2sim/assets/templates/modelica/tmplSpawnBuilding.txt @@ -20,7 +20,7 @@ model ${model_name} "${model_comment}" epwName = ${weather_path_ep}, weaName = ${weather_path_mos}, printUnits = true) - "" annotation (Placement(transformation(extent={{-102,64},{-82,84}}))); + "" annotation (Placement(transformation(extent={{-100,60},{-80,80}}))); Buildings.Fluid.Sources.MassFlowSource_WeatherData freshairsource[nZones]( diff --git a/bim2sim/elements/base_elements.py b/bim2sim/elements/base_elements.py index 7e9d82a0aa..832a23ead0 100644 --- a/bim2sim/elements/base_elements.py +++ b/bim2sim/elements/base_elements.py @@ -572,6 +572,7 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.aggregation = None self.ports = self.get_ports() + self.heat_ports = [] self.material = None self.material_set = {} diff --git a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py index 8349a91656..65916801ce 100644 --- a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py +++ b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py @@ -3,7 +3,8 @@ from bim2sim.export import modelica from bim2sim.elements import hvac_elements as hvac from bim2sim.elements.mapping.units import ureg -from bim2sim.export.modelica import ModelicaRecord +from bim2sim.export.modelica import HeatPort + # TODO get_port_name functions: use verbose_flow_direction instead index class AixLib(modelica.Instance): @@ -52,6 +53,10 @@ class Radiator(AixLib): path = "AixLib.Fluid.HeatExchangers.Radiators.RadiatorEN442_2" represents = [hvac.SpaceHeater] + def __init__(self, element): + super().__init__(element) + self._add_heat_ports() + def request_params(self): self.request_param("rated_power", self.check_numeric(min_value=0 * ureg.kilowatt), @@ -66,12 +71,12 @@ def get_port_name(self, port): else: return super().get_port_name(port) - def get_heat_port_names(self): - return { - "con": "heatPortCon", - "rad": "heatPortRad" - } + def _add_heat_ports(self): + self.heat_ports = [HeatPort(name='heatPortCon', heat_transfer_type='convective'), + HeatPort(name='heatPortRad', heat_transfer_type='radiative')] + def get_heat_port_names(self): + return [heat_port.name for heat_port in self.heat_ports] class Pump(AixLib): path = "AixLib.Fluid.Movers.SpeedControlled_y" diff --git a/bim2sim/tasks/hvac/export.py b/bim2sim/tasks/hvac/export.py index aa27822afa..28d8cacc2c 100644 --- a/bim2sim/tasks/hvac/export.py +++ b/bim2sim/tasks/hvac/export.py @@ -4,6 +4,7 @@ from bim2sim.elements.base_elements import ProductBased from bim2sim.elements.graphs.hvac_graph import HvacGraph from bim2sim.export import modelica +from bim2sim.export.modelica import HeatTransferType from bim2sim.tasks.base import ITask @@ -109,6 +110,7 @@ def create_outer_heat_port_connections(export_elements: list) -> list: heat_port_connections = [] i = 1 for ele in export_elements: + convective_ports = [heat_port for heat_port in ele.heat_ports if heat_port.heat_transfer_type == HeatTransferType.CONVECTIVE] if ele.get_heat_port_names(): full_names = ele.get_full_heat_port_names() # TODO this needs to be reworked, From 3c7298922a0eb8db51ee3da66885e42610c1861e Mon Sep 17 00:00:00 2001 From: David Paul Jansen Date: Tue, 18 Jun 2024 11:02:02 +0200 Subject: [PATCH 035/125] add parent for heatport --- bim2sim/export/modelica/__init__.py | 8 ++++++-- .../PluginAixLib/bim2sim_aixlib/models/__init__.py | 9 +++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/bim2sim/export/modelica/__init__.py b/bim2sim/export/modelica/__init__.py index cf0057cdec..48ebbffedc 100644 --- a/bim2sim/export/modelica/__init__.py +++ b/bim2sim/export/modelica/__init__.py @@ -585,13 +585,17 @@ class HeatPort: Args: heat_transfer_type (HeatTransferType): The type of heat transfer. + name (str): name of the heat port in the parent modelica element + parent (Instance): Modelica Instance that holds this heat port """ def __init__(self, - heat_transfer_type: Union[HeatTransferType, - str], name: str): + heat_transfer_type: Union[HeatTransferType, str], + name: str, + parent: Instance): self.heat_transfer_type = heat_transfer_type self.name = name + self.parent = parent @property def heat_transfer_type(self): diff --git a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py index 65916801ce..22b1c4ea83 100644 --- a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py +++ b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py @@ -72,8 +72,13 @@ def get_port_name(self, port): return super().get_port_name(port) def _add_heat_ports(self): - self.heat_ports = [HeatPort(name='heatPortCon', heat_transfer_type='convective'), - HeatPort(name='heatPortRad', heat_transfer_type='radiative')] + self.heat_ports = [ + HeatPort(name='heatPortCon', + heat_transfer_type='convective', + parent=self), + HeatPort(name='heatPortRad', + heat_transfer_type='radiative', + parent=self)] def get_heat_port_names(self): return [heat_port.name for heat_port in self.heat_ports] From d4a9a22a15898b25405eeeb18de2dac18e2b416c Mon Sep 17 00:00:00 2001 From: sfreund1 Date: Tue, 18 Jun 2024 11:28:56 +0200 Subject: [PATCH 036/125] basic export heat port connections for radiative and convection heat transfer --- bim2sim/export/modelica/__init__.py | 9 +++--- bim2sim/tasks/hvac/export.py | 43 ++++++++++++++++++++--------- 2 files changed, 34 insertions(+), 18 deletions(-) diff --git a/bim2sim/export/modelica/__init__.py b/bim2sim/export/modelica/__init__.py index 48ebbffedc..f78af13048 100644 --- a/bim2sim/export/modelica/__init__.py +++ b/bim2sim/export/modelica/__init__.py @@ -499,11 +499,10 @@ def get_full_port_name(self, port): def get_full_heat_port_names(self): """Returns names of heat ports including model name""" - full_heat_port_names = {} - for heat_type, heat_port_name in self.get_heat_port_names().items(): - full_heat_port_names[heat_type] = "%s.%s" % ( - self.name, heat_port_name) - return full_heat_port_names + # full_heat_port_names = {} + # for heat_port_name in self.get_heat_port_names(): + # full_heat_port_names = "%s.%s" % (self.name, heat_port_name) + return "%s.%s" % (self.name, self.get_heat_port_names()) @staticmethod def check_numeric(min_value=None, max_value=None): diff --git a/bim2sim/tasks/hvac/export.py b/bim2sim/tasks/hvac/export.py index 28d8cacc2c..524729af5f 100644 --- a/bim2sim/tasks/hvac/export.py +++ b/bim2sim/tasks/hvac/export.py @@ -108,21 +108,38 @@ def create_outer_heat_port_connections(export_elements: list) -> list: # ToDo annotations/placements are missing. Those needs to be added to # visualize connections heat_port_connections = [] - i = 1 + # i = 1 + # convective_ports = [heat_port for ele in export_elements for heat_port in ele.heat_ports if + # heat_port.heat_transfer_type == HeatTransferType.CONVECTIVE] + # radiative_ports = [heat_port for ele in export_elements for heat_port in ele.heat_ports if + # heat_port.heat_transfer_type == HeatTransferType.RADIATIVE] + # for convective_port_index, convective_port in enumerate(convective_ports): + # heat_port_connections.append((f"heatPortOuterCon[{convective_port_index}]", convective_port.parent.get_full_heat_port_names())) + # for port_index, radiative_port in enumerate(radiative_ports): + # heat_port_connections.append((f"heatPortOuterRad[{port_index}]", radiative_port.parent.name)) for ele in export_elements: - convective_ports = [heat_port for heat_port in ele.heat_ports if heat_port.heat_transfer_type == HeatTransferType.CONVECTIVE] if ele.get_heat_port_names(): - full_names = ele.get_full_heat_port_names() - # TODO this needs to be reworked, - # maybe create connections only if rad and con both exist? - # names for heat ports are fixed and defined in template - if "con" in full_names: - heat_port_connections.append( - (f"heatPortOuterCon[{i}]", full_names["con"])) - if "rad" in full_names: - heat_port_connections.append( - (f"heatPortOuterRad[{i}]", full_names["rad"])) - i += 1 + full_names = ele.get_heat_port_names() + convective_ports_index = 1 + radiative_ports_index = 1 + for heat_port_index, heat_port in enumerate(ele.heat_ports): + if heat_port.heat_transfer_type == HeatTransferType.CONVECTIVE: + heat_port_connections.append((f"heatPortOuterCon[{convective_ports_index}]", full_names[heat_port_index])) + convective_ports_index += 1 + if heat_port.heat_transfer_type == HeatTransferType.RADIATIVE: + heat_port_connections.append((f"heatPortOuterRad[{radiative_ports_index}]", full_names[heat_port_index])) + radiative_ports_index += 1 + # + # # TODO this needs to be reworked, + # # maybe create connections only if rad and con both exist? + # # names for heat ports are fixed and defined in template + # if "con" in full_names: + # heat_port_connections.append( + # (f"heatPortOuterCon[{i}]", full_names["con"])) + # if "rad" in full_names: + # heat_port_connections.append( + # (f"heatPortOuterRad[{i}]", full_names["rad"])) + # i += 1 return heat_port_connections def create_inner_heat_port_connections(self) -> list: From 4ad237e01fb03f7556cd54783a2b20f3314ed712 Mon Sep 17 00:00:00 2001 From: David Paul Jansen Date: Wed, 19 Jun 2024 12:37:22 +0200 Subject: [PATCH 037/125] correct size of heatports --- .../assets/templates/modelica/tmplModel.txt | 11 +- bim2sim/export/modelica/__init__.py | 19 ++-- .../bim2sim_aixlib/models/__init__.py | 20 ++-- bim2sim/tasks/common/__init__.py | 1 - bim2sim/tasks/common/export_modelica.py | 39 ------- bim2sim/tasks/hvac/export.py | 103 +++++++++--------- 6 files changed, 74 insertions(+), 119 deletions(-) delete mode 100644 bim2sim/tasks/common/export_modelica.py diff --git a/bim2sim/assets/templates/modelica/tmplModel.txt b/bim2sim/assets/templates/modelica/tmplModel.txt index 3c7f09b8ca..9d0cf0a950 100644 --- a/bim2sim/assets/templates/modelica/tmplModel.txt +++ b/bim2sim/assets/templates/modelica/tmplModel.txt @@ -33,9 +33,9 @@ model ${model.name} "${model.comment}" // Only for SpawnOfEnergyPlus // TODO conditional -Modelica.Thermal.HeatTransfer.Interfaces.HeatPort_a heatPortOuterCon[${len(model.connections_heat_ports)}] +Modelica.Thermal.HeatTransfer.Interfaces.HeatPort_a heatPortOuterCon[${len(model.connections_heat_ports_conv)}] annotation (Placement(transformation(extent={{-110,10},{-90,30}})));; -Modelica.Thermal.HeatTransfer.Interfaces.HeatPort_a heatPortOuterRad[${len(model.connections_heat_ports)}] +Modelica.Thermal.HeatTransfer.Interfaces.HeatPort_a heatPortOuterRad[${len(model.connections_heat_ports_rad)}] annotation (Placement(transformation(extent={{-110,-22},{-90,-2}}))); equation @@ -45,8 +45,11 @@ equation % endfor // heatport connections -% for cons in model.connections_heat_ports: - connect${str(cons).replace("'","")}; +% for cons in model.connections_heat_ports_conv: + connect${str(cons).replace("'","")} annotation (Line(points={{0,0},{0,0},{0,0},{0,0},{0,0}}, color={191,0,0})); +% endfor +% for cons in model.connections_heat_ports_rad: + connect${str(cons).replace("'","")} annotation (Line(points={{0,0},{0,0},{0,0},{0,0},{0,0}}, color={191,0,0})); % endfor annotation ( diff --git a/bim2sim/export/modelica/__init__.py b/bim2sim/export/modelica/__init__.py index f78af13048..487702bc81 100644 --- a/bim2sim/export/modelica/__init__.py +++ b/bim2sim/export/modelica/__init__.py @@ -103,7 +103,8 @@ class Model: """Modelica model""" def __init__(self, name, comment, elements: list, connections: list, - connections_heat_ports: list): + connections_heat_ports_conv: list, + connections_heat_ports_rad: list): self.name = name self.comment = comment self.elements = elements @@ -112,8 +113,9 @@ def __init__(self, name, comment, elements: list, connections: list, self.size_y = (-100, 100) self.connections = self.set_positions(elements, connections) - # TODO positions for heatports? - self.connections_heat_ports = connections_heat_ports + + self.connections_heat_ports_conv = connections_heat_ports_conv + self.connections_heat_ports_rad = connections_heat_ports_rad def set_positions(self, elements, connections): """Sets position of elements @@ -203,6 +205,7 @@ def __init__(self, element: Element): self.export_params = {} self.export_records = {} self.records = [] + self.heat_ports = [] self.requested: Dict[str, Tuple[Callable, bool, Union[None, Callable], str, str]] = {} self.connections = [] @@ -497,13 +500,6 @@ def get_full_port_name(self, port): """Returns name of port including model name""" return "%s.%s" % (self.name, self.get_port_name(port)) - def get_full_heat_port_names(self): - """Returns names of heat ports including model name""" - # full_heat_port_names = {} - # for heat_port_name in self.get_heat_port_names(): - # full_heat_port_names = "%s.%s" % (self.name, heat_port_name) - return "%s.%s" % (self.name, self.get_heat_port_names()) - @staticmethod def check_numeric(min_value=None, max_value=None): """Generic check function generator @@ -617,6 +613,9 @@ def heat_transfer_type(self, value: Union[HeatTransferType, str]): f'strings "convective", "radiative", and ' f'"generic" are allowed') + def get_full_name(self): + return f"{self.parent.name}.{self.name}" + if __name__ == "__main__": class Radiator(Instance): diff --git a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py index 22b1c4ea83..44ba63ad35 100644 --- a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py +++ b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py @@ -55,7 +55,14 @@ class Radiator(AixLib): def __init__(self, element): super().__init__(element) - self._add_heat_ports() + self.heat_ports = [ + HeatPort(name='heatPortCon', + heat_transfer_type='convective', + parent=self), + HeatPort(name='heatPortRad', + heat_transfer_type='radiative', + parent=self) + ] def request_params(self): self.request_param("rated_power", @@ -71,17 +78,6 @@ def get_port_name(self, port): else: return super().get_port_name(port) - def _add_heat_ports(self): - self.heat_ports = [ - HeatPort(name='heatPortCon', - heat_transfer_type='convective', - parent=self), - HeatPort(name='heatPortRad', - heat_transfer_type='radiative', - parent=self)] - - def get_heat_port_names(self): - return [heat_port.name for heat_port in self.heat_ports] class Pump(AixLib): path = "AixLib.Fluid.Movers.SpeedControlled_y" diff --git a/bim2sim/tasks/common/__init__.py b/bim2sim/tasks/common/__init__.py index 10254a9c23..d4454543a1 100644 --- a/bim2sim/tasks/common/__init__.py +++ b/bim2sim/tasks/common/__init__.py @@ -4,6 +4,5 @@ from .check_ifc import CheckIfc, CheckIfcBPS, CheckIfcHVAC from .create_elements import CreateElements from .weather import Weather -from .export_modelica import ExportModelica from .serialize_elements import SerializeElements from .deserialize_elements import DeserializeElements diff --git a/bim2sim/tasks/common/export_modelica.py b/bim2sim/tasks/common/export_modelica.py deleted file mode 100644 index 48519440ac..0000000000 --- a/bim2sim/tasks/common/export_modelica.py +++ /dev/null @@ -1,39 +0,0 @@ -from datetime import datetime - -from bim2sim.elements.base_elements import ProductBased -from bim2sim.export import modelica -from bim2sim.tasks.base import ITask -# TODO is this obsolete? - -class ExportModelica(ITask): - """Export to Dymola/Modelica""" - - reads = ('libraries', 'elements',) - final = True - - def run(self, libraries: tuple, elements: dict): - self.logger.info("Export to Modelica code") - modelica.Instance.init_factory(libraries) - export_elements = {inst: modelica.Instance.factory(inst) - for inst in elements.values()} - - yield from ProductBased.get_pending_attribute_decisions( - elements.values()) - - for instance in export_elements.values(): - instance.collect_params() - static_connections = get_static_connections() - dyn_connections = get_dynamic_connections(export_elements) - connections = static_connections + dyn_connections - - self.logger.info( - "Creating Modelica model with %d model elements.", - len(export_elements),) - - modelica_model = modelica.Model( - name="bim2sim_"+self.prj_name, - comment=f"Autogenerated by BIM2SIM on {datetime.now():%Y-%m-%d %H:%M:%S%z}", - elements=list(export_elements.values()), - connections=connections, - ) - modelica_model.save(self.paths.export) \ No newline at end of file diff --git a/bim2sim/tasks/hvac/export.py b/bim2sim/tasks/hvac/export.py index 524729af5f..9564ec0eb7 100644 --- a/bim2sim/tasks/hvac/export.py +++ b/bim2sim/tasks/hvac/export.py @@ -23,7 +23,7 @@ def run(self, libraries: tuple, graph: HvacGraph): modelica.Instance.init_factory(libraries) export_elements = {inst: modelica.Instance.factory(inst) - for inst in elements} + for inst in elements} yield from ProductBased.get_pending_attribute_decisions( elements) @@ -33,25 +33,33 @@ def run(self, libraries: tuple, graph: HvacGraph): connection_port_names = self.create_connections(graph, export_elements) - connections_heat_ports = [] + inner_heat_port_cons_conv, inner_heat_port_cons_rad = ( + self.create_inner_heat_port_connections()) if self.playground.sim_settings.outer_heat_ports: - connections_heat_ports.extend( - self.create_inner_heat_port_connections()) - - connections_heat_ports.extend(self.create_outer_heat_port_connections( - list(export_elements.values()))) - + outer_heat_port_cons_conv, outer_heat_port_cons_rad = ( + self.create_outer_heat_port_connections( + list(export_elements.values()))) + else: + outer_heat_port_cons_conv = [] + outer_heat_port_cons_rad = [] + cons_heat_ports_conv = (outer_heat_port_cons_conv + + inner_heat_port_cons_conv) + cons_heat_ports_rad = (outer_heat_port_cons_rad + + inner_heat_port_cons_rad) self.logger.info( - "Creating Modelica model with %d model elements and %d connections.", + "Creating Modelica model with %d model elements and %d " + "connections.", len(export_elements), len(connection_port_names)) modelica_model = modelica.Model( - name="bim2sim_"+self.prj_name, - comment=f"Autogenerated by BIM2SIM on {datetime.now():%Y-%m-%d %H:%M:%S%z}", + name="bim2sim_" + self.prj_name, + comment=f"Autogenerated by BIM2SIM on " + f"{datetime.now():%Y-%m-%d %H:%M:%S%z}", elements=list(export_elements.values()), connections=connection_port_names, - connections_heat_ports=connections_heat_ports + connections_heat_ports_conv=cons_heat_ports_conv, + connections_heat_ports_rad=cons_heat_ports_rad ) modelica_model.save(self.paths.export) return modelica_model, @@ -66,7 +74,8 @@ def create_connections(graph: HvacGraph, export_elements: dict) -> list: export_elements: the modelica elements Returns: - connection_port_names: list of tuple of port names that are connected + connection_port_names: list of tuple of port names that are + connected """ connection_port_names = [] distributors_n = {} @@ -76,10 +85,11 @@ def create_connections(graph: HvacGraph, export_elements: dict) -> list: # ignore inner connections continue elements = {'a': export_elements[port_a.parent], - 'b': export_elements[port_b.parent]} + 'b': export_elements[port_b.parent]} ports_name = {'a': elements['a'].get_full_port_name(port_a), 'b': elements['b'].get_full_port_name(port_b)} - if any(isinstance(e.element, hvac.Distributor) for e in elements.values()): + if any(isinstance(e.element, hvac.Distributor) for e in + elements.values()): for key, inst in elements.items(): if type(inst.element) is hvac.Distributor: distributor = (key, inst) @@ -95,53 +105,40 @@ def create_connections(graph: HvacGraph, export_elements: dict) -> list: connection_port_names.append((ports_name['a'], ports_name['b'])) for distributor in distributors_n: - distributor.export_params['n'] = int(distributors_n[distributor] / 2 - 1) + distributor.export_params['n'] = int( + distributors_n[distributor] / 2 - 1) return connection_port_names @staticmethod - def create_outer_heat_port_connections(export_elements: list) -> list: + def create_outer_heat_port_connections(export_elements: list) -> \ + [list, list]: """Creates connections to an outer heat port for further connections""" # ToDo only connect heat ports that might not be connected already by # create_inner_heat_port_connections() function # ToDo annotations/placements are missing. Those needs to be added to # visualize connections - heat_port_connections = [] - # i = 1 - # convective_ports = [heat_port for ele in export_elements for heat_port in ele.heat_ports if - # heat_port.heat_transfer_type == HeatTransferType.CONVECTIVE] - # radiative_ports = [heat_port for ele in export_elements for heat_port in ele.heat_ports if - # heat_port.heat_transfer_type == HeatTransferType.RADIATIVE] - # for convective_port_index, convective_port in enumerate(convective_ports): - # heat_port_connections.append((f"heatPortOuterCon[{convective_port_index}]", convective_port.parent.get_full_heat_port_names())) - # for port_index, radiative_port in enumerate(radiative_ports): - # heat_port_connections.append((f"heatPortOuterRad[{port_index}]", radiative_port.parent.name)) + convective_heat_port_connections = [] + radiative_heat_port_connections = [] + convective_ports_index = 1 + radiative_ports_index = 1 for ele in export_elements: - if ele.get_heat_port_names(): - full_names = ele.get_heat_port_names() - convective_ports_index = 1 - radiative_ports_index = 1 - for heat_port_index, heat_port in enumerate(ele.heat_ports): - if heat_port.heat_transfer_type == HeatTransferType.CONVECTIVE: - heat_port_connections.append((f"heatPortOuterCon[{convective_ports_index}]", full_names[heat_port_index])) - convective_ports_index += 1 - if heat_port.heat_transfer_type == HeatTransferType.RADIATIVE: - heat_port_connections.append((f"heatPortOuterRad[{radiative_ports_index}]", full_names[heat_port_index])) - radiative_ports_index += 1 - # - # # TODO this needs to be reworked, - # # maybe create connections only if rad and con both exist? - # # names for heat ports are fixed and defined in template - # if "con" in full_names: - # heat_port_connections.append( - # (f"heatPortOuterCon[{i}]", full_names["con"])) - # if "rad" in full_names: - # heat_port_connections.append( - # (f"heatPortOuterRad[{i}]", full_names["rad"])) - # i += 1 - return heat_port_connections - - def create_inner_heat_port_connections(self) -> list: + for heat_port in ele.heat_ports: + if heat_port.heat_transfer_type == HeatTransferType.CONVECTIVE: + convective_heat_port_connections.append( + (f"heatPortOuterCon[{convective_ports_index}]", + heat_port.get_full_name())) + convective_ports_index += 1 + if heat_port.heat_transfer_type == HeatTransferType.RADIATIVE: + radiative_heat_port_connections.append( + (f"heatPortOuterRad[{radiative_ports_index}]", + heat_port.get_full_name())) + radiative_ports_index += 1 + + return (convective_heat_port_connections, + radiative_heat_port_connections) + + def create_inner_heat_port_connections(self) -> [list, list]: # TODO if this is needed - return [] + return [], [] From 19478606147b2643c02452d9394e9e87cb5b24b2 Mon Sep 17 00:00:00 2001 From: David Paul Jansen Date: Mon, 24 Jun 2024 10:16:45 +0200 Subject: [PATCH 038/125] finish test of heat ports --- bim2sim/elements/base_elements.py | 1 - .../test/unit/tasks/test_export.py | 60 ++++++++++++++----- test/unit/elements/helper.py | 13 ++-- 3 files changed, 50 insertions(+), 24 deletions(-) diff --git a/bim2sim/elements/base_elements.py b/bim2sim/elements/base_elements.py index 832a23ead0..7e9d82a0aa 100644 --- a/bim2sim/elements/base_elements.py +++ b/bim2sim/elements/base_elements.py @@ -572,7 +572,6 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.aggregation = None self.ports = self.get_ports() - self.heat_ports = [] self.material = None self.material_set = {} diff --git a/bim2sim/plugins/PluginAixLib/test/unit/tasks/test_export.py b/bim2sim/plugins/PluginAixLib/test/unit/tasks/test_export.py index a3b8905562..ccac53a3ad 100644 --- a/bim2sim/plugins/PluginAixLib/test/unit/tasks/test_export.py +++ b/bim2sim/plugins/PluginAixLib/test/unit/tasks/test_export.py @@ -1,6 +1,7 @@ import tempfile from bim2sim import ConsoleDecisionHandler +from bim2sim.export.modelica import HeatTransferType from bim2sim.kernel.decision.decisionhandler import DebugDecisionHandler from bim2sim.plugins.PluginAixLib.bim2sim_aixlib import LoadLibrariesAixLib @@ -22,13 +23,10 @@ def setUp(self) -> None: # Set export path to temporary path self.export_path = tempfile.TemporaryDirectory(prefix='bim2sim') self.export_task.paths.export = self.export_path.name - # TODO does this work? - self.export_task.playground.sim_settings.outer_heat_ports = True def tearDown(self) -> None: self.helper.reset() - def test_pump_export(self): graph = self.helper.get_pump() answers = () @@ -80,19 +78,51 @@ def test_pump_export(self): # TODO #624 Write tests for all components of AixLib - def test_radiator_export(self): - graph = self.helper.get_radiator() + def test_radiator_export_with_heat_ports(self): + """Test export of two radiators, focus on correct heat port export.""" + graph = self.helper.get_two_radiators() answers = () - modelica_model = DebugDecisionHandler(answers).handle( - self.export_task.run(self.loaded_libs, graph)) - print('test') - - def test_outer_heat_ports(self): - answers = () - self.helper = ConsumerHelper() - graph, flags = self.helper.get_setup_system2() + # export outer heat ports + self.export_task.playground.sim_settings.outer_heat_ports = True - modelica_model = ConsoleDecisionHandler().handle( + modelica_model = DebugDecisionHandler(answers).handle( self.export_task.run(self.loaded_libs, graph)) - print('test') \ No newline at end of file + # ToDo: as elements are unsorted, testing with names is not robust + # connections_heat_ports_conv_expected = [ + # ('heatPortOuterCon[1]', + # 'spaceheater_0000000000000000000001.heatPortCon'), + # ('heatPortOuterCon[2]', + # 'spaceheater_0000000000000000000004.heatPortCon')] + + # connections_heat_ports_rad_expected = [ + # ('heatPortOuterRad[1]', + # 'spaceheater_0000000000000000000001.heatPortRad'), + # ('heatPortOuterRad[2]', + # 'spaceheater_0000000000000000000004.heatPortRad')] + + # check existence of heat ports + self.assertEqual( + 2, len(modelica_model[0].elements[0].heat_ports)) + self.assertEqual( + 2, len(modelica_model[0].elements[1].heat_ports)) + + # check types of heat ports + self.assertEqual( + modelica_model[0].elements[0].heat_ports[0].heat_transfer_type, + HeatTransferType.CONVECTIVE) + self.assertEqual( + modelica_model[0].elements[0].heat_ports[1].heat_transfer_type, + HeatTransferType.RADIATIVE) + self.assertEqual( + modelica_model[0].elements[1].heat_ports[0].heat_transfer_type, + HeatTransferType.CONVECTIVE) + self.assertEqual( + modelica_model[0].elements[1].heat_ports[1].heat_transfer_type, + HeatTransferType.RADIATIVE) + + # check number of heat port connections + self.assertEqual( + 2, len(modelica_model[0].connections_heat_ports_conv)) + self.assertEqual( + 2, len(modelica_model[0].connections_heat_ports_rad)) diff --git a/test/unit/elements/helper.py b/test/unit/elements/helper.py index 8241156d2f..d7cec17b1e 100644 --- a/test/unit/elements/helper.py +++ b/test/unit/elements/helper.py @@ -163,15 +163,12 @@ def get_pump(self): rated_pressure_difference=10000 * ureg.N / (ureg.m ** 2)) return HvacGraph([pump]) - def get_Boiler(self): - # TODO: obsolete? - self.element_generator(hvac.Boiler, ...) - - def get_radiator(self): - radiator = self.element_generator( + def get_two_radiators(self): + radiator_1 = self.element_generator( hvac.SpaceHeater, rated_power=1 * ureg.kilowatt) - return HvacGraph([radiator]) - + radiator_2 = self.element_generator( + hvac.SpaceHeater, rated_power=1 * ureg.kilowatt) + return HvacGraph([radiator_1, radiator_2]) class SetupHelperBPS(SetupHelper): def element_generator(self, element_cls, flags=None, **kwargs): From 4fb141a7b2a28983dfaf3b7dd1c5914bd708a773 Mon Sep 17 00:00:00 2001 From: David Paul Jansen Date: Mon, 24 Jun 2024 12:30:42 +0200 Subject: [PATCH 039/125] start total model export implementation for spawn --- .../modelica/tmplSpawnTotalModel.txt | 11 + bim2sim/elements/hvac_elements.py | 11 + .../PluginSpawn/bim2sim_spawn/__init__.py | 8 +- .../examples/e2_simple_project_bps_spawn.py | 63 +++ .../bim2sim_spawn/models/__init__.py | 421 +----------------- .../bim2sim_spawn/tasks/__init__.py | 1 + .../tasks/export_spawn_building.py | 64 +-- .../bim2sim_spawn/tasks/export_spawn_total.py | 78 ++++ bim2sim/sim_settings.py | 8 +- bim2sim/utilities/pyocc_tools.py | 3 +- 10 files changed, 214 insertions(+), 454 deletions(-) create mode 100644 bim2sim/assets/templates/modelica/tmplSpawnTotalModel.txt create mode 100644 bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e2_simple_project_bps_spawn.py diff --git a/bim2sim/assets/templates/modelica/tmplSpawnTotalModel.txt b/bim2sim/assets/templates/modelica/tmplSpawnTotalModel.txt new file mode 100644 index 0000000000..78f4c50261 --- /dev/null +++ b/bim2sim/assets/templates/modelica/tmplSpawnTotalModel.txt @@ -0,0 +1,11 @@ +within ${within}; +model ${model_name} "${model_comment}" + import SI = Modelica.Units.SI; + building_model building_model1 + annotation (Placement(transformation(extent={{-14,28},{6,48}}))); + hvac_model hvac_model1 + annotation (Placement(transformation(extent={{-14,-62},{6,-42}}))); + annotation (Icon(coordinateSystem(preserveAspectRatio=false)), Diagram( + coordinateSystem(preserveAspectRatio=false))); +end ${model_name}; + diff --git a/bim2sim/elements/hvac_elements.py b/bim2sim/elements/hvac_elements.py index a372224418..b6fe635af1 100644 --- a/bim2sim/elements/hvac_elements.py +++ b/bim2sim/elements/hvac_elements.py @@ -8,6 +8,7 @@ from typing import Set, List, Tuple, Generator, Union, Type import numpy as np +import ifcopenshell.geom from bim2sim.kernel.decision import ListDecision, DecisionBunch from bim2sim.kernel.decorators import cached_property @@ -882,6 +883,16 @@ def expected_hvac_ports(self): def is_consumer(self): return True + @cached_property + def shape(self): + """returns topods shape of the radiator""" + settings = ifcopenshell.geom.main.settings() + settings.set(settings.USE_PYTHON_OPENCASCADE, True) + settings.set(settings.USE_WORLD_COORDS, True) + settings.set(settings.EXCLUDE_SOLIDS_AND_SURFACES, False) + settings.set(settings.INCLUDE_CURVES, True) + return ifcopenshell.geom.create_shape(settings, self.ifc).geometry + number_of_panels = attribute.Attribute( description="Number of panels of heater", default_ps=('Pset_SpaceHeaterTypeCommon', 'NumberOfPanels'), diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/__init__.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/__init__.py index a738e05138..a8b88dc48a 100644 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/__init__.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/__init__.py @@ -1,6 +1,7 @@ from bim2sim.plugins import Plugin from bim2sim.tasks import base, common, hvac, bps -from bim2sim.sim_settings import BuildingSimSettings, EnergyPlusSimSettings +from bim2sim.sim_settings import BuildingSimSettings, EnergyPlusSimSettings, \ + SpawnOfEnergyPlusSimSettings import bim2sim.plugins.PluginSpawn.bim2sim_spawn.tasks as spawn_tasks from bim2sim.plugins.PluginEnergyPlus.bim2sim_energyplus import task as ep_tasks @@ -8,7 +9,7 @@ # # TODO: this is just a concept and not working already class PluginSpawnOfEP(Plugin): name = 'spawn' - sim_settings = EnergyPlusSimSettings + sim_settings = SpawnOfEnergyPlusSimSettings default_tasks = [ common.LoadIFC, # common.CheckIfc, @@ -27,5 +28,8 @@ class PluginSpawnOfEP(Plugin): # ep_tasks.IdfPostprocessing, # ep_tasks.ExportIdfForCfd, # ep_tasks.RunEnergyPlusSimulation, + spawn_tasks.ExportSpawnBuilding, + spawn_tasks.ExportSpawnTotal, ] + # TODO: we need a merge of tasks of AixLib and EnergyPlus diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e2_simple_project_bps_spawn.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e2_simple_project_bps_spawn.py new file mode 100644 index 0000000000..9776c0658b --- /dev/null +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e2_simple_project_bps_spawn.py @@ -0,0 +1,63 @@ +import tempfile +from pathlib import Path + +import bim2sim +from bim2sim import Project, run_project, ConsoleDecisionHandler +from bim2sim.kernel.log import default_logging_setup +from bim2sim.utilities.common_functions import download_test_resources +from bim2sim.utilities.types import IFCDomain + + +def run_example_1(): + """Run a building performance simulation with the EnergyPlus backend. + + This example runs a BPS with the EnergyPlus backend. Specifies project + directory and location of the IFC file. Then, it creates a bim2sim + project with the EnergyPlus backend. Simulation settings are specified + (EnergyPlus location needs to be specified according to your system, + other settings are set to default if not specified otherwise), + before the project is executed with the previously specified settings. + """ + # Create the default logging to for quality log and bim2sim main log ( + # see logging documentation for more information + default_logging_setup() + + # Create a temp directory for the project, feel free to use a "normal" + # directory + project_path = Path( + tempfile.TemporaryDirectory(prefix='bim2sim_example_spawn').name) + + download_test_resources(IFCDomain.arch, force_new=False) + # Set the ifc path to use and define which domain the IFC belongs to + ifc_paths = { + IFCDomain.arch: + Path(bim2sim.__file__).parent.parent / + 'test/resources/arch/ifc/ExampleHOM_with_radiator.ifc', + } + + # Create a project including the folder structure for the project with + # energyplus as backend + project = Project.create(project_path, ifc_paths, 'spawn') + + # Set the install path to your EnergyPlus installation according to your + # system requirements + project.sim_settings.ep_install_path = Path( + 'C:/EnergyPlusV9-6-0/') + project.sim_settings.ep_version = "9-6-0" + project.sim_settings.weather_file_path_ep = ( + Path(bim2sim.__file__).parent.parent / + 'test/resources/weather_files/DEU_NW_Aachen.105010_TMYx.epw') + # TODO make sure that a non existing sim_setting assignment raises an error + project.sim_settings.weather_file_path_modelica = ( + Path(bim2sim.__file__).parent.parent / + 'test/resources/weather_files/DEU_NW_Aachen.105010_TMYx.mos') + + # Set other simulation settings, otherwise all settings are set to default + + # Run the project with the ConsoleDecisionHandler. This allows interactive + # input to answer upcoming questions regarding the imported IFC. + run_project(project, ConsoleDecisionHandler()) + + +if __name__ == '__main__': + run_example_1() diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/models/__init__.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/models/__init__.py index 39e216fee9..daaf5746a2 100644 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/models/__init__.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/models/__init__.py @@ -1,397 +1,24 @@ -"""Package for Python representations of HKESim models""" -import bim2sim.elements.aggregation.bps_aggregations as aggregation -import bim2sim.elements.hvac_elements as hvac -from bim2sim.elements.mapping.units import ureg - - -# class AixLib(modelica.Instance): -# library = "AixLib" -# - -class EnergyPlusFMU(AixLib): - path = "AixLib.Fluid.BoilerCHP.BoilerNotManufacturer" - represents = hvac.Boiler - - def __init__(self, element): - super().__init__(element) - - def request_params(self): - - self.params["redeclare package Medium"] = 'AixLib.Media.Water' - self.request_param("dT_water", - self.check_numeric(min_value=0 * ureg.kelvin), - "dTWaterNom") - self.request_param("return_temperature", - self.check_numeric(min_value=0 * ureg.celsius), - "TRetNom") - self.request_param("rated_power", - self.check_numeric(min_value=0 * ureg.kilowatt), - "QNom") - self.request_param("min_PLR", - self.check_numeric(min_value=0 * ureg.dimensionless), - "PLRMin") - - def get_port_name(self, port): - try: - index = self.element.ports.index(port) - except ValueError: - # unknown port - index = -1 - if port.verbose_flow_direction == 'SINK': - return 'port_a' - if port.verbose_flow_direction == 'SOURCE': - return 'port_b' - else: - return super().get_port_name(port) # ToDo: Gas connection - - -class Radiator(AixLib): - path = "AixLib.Fluid.HeatExchangers.Radiators.RadiatorEN442_2" - represents = [hvac.SpaceHeater] - - def request_params(self): - self.request_param("rated_power", - self.check_numeric(min_value=0 * ureg.kilowatt), - "Q_flow_nominal") - # self.params["T_nominal"] = (80, 60, 20) - - -class Pump(AixLib): - path = "AixLib.Fluid.Movers.SpeedControlled_y" - represents = [hvac.Pump] - - def request_params(self): - self.params['redeclare package Medium'] = 'AixLib.Media.Water' - self.request_param( - "rated_mass_flow", - self.check_numeric(min_value=0 * ureg['kg/second'])) - self.request_param( - "rated_pressure_difference", - self.check_numeric(min_value=0 * ureg['newton/m**2'])) - # generic pump operation curve - # todo renders as "V_flow" only in Modelica - self.params["per.pressure"] =\ - f"V_flow={{0," \ - f" {self.element.rated_mass_flow}/1000," \ - f" {self.element.rated_mass_flow} /1000/0.7}}," \ - f" dp={{ {self.element.rated_pressure_difference} / 0.7," \ - f" {self.element.rated_pressure_difference}," \ - f"0}}" - - # ToDo remove decisions from tests if not asking this anymore - # self.request_param("rated_height", - # self.check_numeric(min_value=0 * ureg.meter), - # "head_set") - # self.request_param("rated_volume_flow", - # self.check_numeric(min_value=0 * ureg['m**3/hour']), - # "Vflow_set", 'm**3/hour') - # self.request_param("rated_power", - # self.check_numeric(min_value=0 * ureg.watt), - # "P_norm") - - def get_port_name(self, port): - try: - index = self.element.ports.index(port) - except ValueError: - # unknown port - index = -1 - if index == 0: - return "port_a" - elif index == 1: - return "port_b" - else: - return super().get_port_name(port) - - -class Consumer(AixLib): - path = "AixLib.Systems.HydraulicModules.SimpleConsumer" - represents = [aggregation.Consumer] - - def __init__(self, element): - self.check_volume = self.check_numeric(min_value=0 * ureg.meter ** 3) - super().__init__(element) - - def request_params(self): - self.params['redeclare package Medium'] = 'AixLib.Media.Water' - self.params["functionality"] = '\"Q_flow_fixed\"' - if self.params["functionality"] == '\"Q_flow_fixed\"': - self.params["Q_flow_fixed"] = self.element.rated_power - self.params["demand_type"] = "1" - - # self.request_param("demand_type", - # self.check_none(), - # "demandType") - - self.params["hasFeedback"] = self.element.t_control - if self.element.t_control: - self.params["TInSetValue"] = self.element.flow_temperature - self.params["TInSetSou"] = "AixLib.Systems.ModularEnergySystems." \ - "Modules.ModularConsumer.Types.InputType." \ - "Constant" - - self.params["hasPump"] = self.element.has_pump - if self.element.has_pump: - self.params["TOutSetValue"] = self.element.return_temperature - self.params["TOutSetSou"] = "AixLib.Systems.ModularEnergySystems." \ - "Modules.ModularConsumer.Types.InputType." \ - "Constant" - # self.params["dT_nom"] = self.element.dT_water - # self.params["capacity"] = self.element.heat_capacity - # self.request_param("rated_power", - # self.check_numeric(min_value=0 * ureg.kilowatt), - # "Q_flow_nom") - # self.request_param("dT_water", - # self.check_numeric(min_value=0 * ureg.kelvin), - # "dT_nom") - # self.request_param("heat_capacity", - # self.check_numeric( - # min_value=0 * ureg.joule / ureg.kelvin), - # "capacity") - - def get_port_name(self, port): - try: - index = self.element.ports.index(port) - except ValueError: - # unknown port - index = -1 - if index == 1: - return "port_a" - elif index == 0: - return "port_b" - else: - return super().get_port_name(port) - - -class ConsumerHeatingDistributorModule(AixLib): - path = "AixLib.Systems.ModularEnergySystems.Modules.ModularConsumer." \ - "ConsumerDistributorModule" - represents = [aggregation.ConsumerHeatingDistributorModule] - - def __init__(self, element): - self.check_volume = self.check_numeric(min_value=0 * ureg.meter ** 3) - super().__init__(element) - - def request_params(self): - n_consumers = len(self.element.whitelist_elements) - # Parameters - self.params["T_start"] = self.element.return_temperature - self.params['redeclare package Medium'] = 'AixLib.Media.Water' - # Consumer Design - self.params['n_consumers'] = n_consumers - self.params["functionality"] = "\"Q_flow_fixed\"" - self.request_param("demand_type", - self.check_none(), - "demandType") - self.request_param("heat_capacity", - self.check_numeric( - min_value=0 * ureg.joule / ureg.kelvin), - "capacity") - - # Nominal Conditions - # todo q_flow_fixed is just dummy value as Modelica model - # needs it for any reason, check on Modelica side - self.request_param("rated_power", - self.check_numeric(min_value=0 * ureg.kilowatt), - "Q_flow_fixed") - self.params["Q_flow_nom"] = self.element.rated_power - self.request_param("dT_water", - self.check_numeric(min_value=0 * ureg.kelvin), - "dT_nom") - - # Flow temperature control (Mixture Valve) - self.params["hasFeedback"] = self.element.t_control - self.params["TInSetSou"] = "AixLib.Systems.ModularEnergySystems." \ - "Modules.ModularConsumer.Types.InputType." \ - "Constant" - self.params["TInSet"] = self.element.flow_temperature - self.params["k_ControlConsumerValve"] = [0.1] * n_consumers - self.params["Ti_ControlConsumerValve"] = [10] * n_consumers - self.params["dp_Valve"] = [1000] * n_consumers - - # Return temperature control (Pump) - self.params["hasPump"] = self.element.has_pump - self.params["TOutSet"] = self.element.return_temperature - self.params["TOutSetSou"] = "AixLib.Systems.ModularEnergySystems." \ - "Modules.ModularConsumer.Types.InputType." \ - "Constant" - self.params["k_ControlConsumerPump"] = [0.1] * n_consumers - self.params["Ti_ControlConsumerPump"] = [10] * n_consumers - self.params["dp_nominalConPump"] = [10000] * n_consumers - - def get_port_name(self, port): - try: - index = self.element.ports.index(port) - except ValueError: - # unknown port - index = -1 - if port.verbose_flow_direction == 'SINK': - return 'port_a' - if port.verbose_flow_direction == 'SOURCE': - return 'port_b' - else: - return super().get_port_name(port) - - -class BoilerAggregation(AixLib): - """Modelica AixLib representation of the GeneratorOneFluid aggregation.""" - path = "AixLib.Systems.ModularEnergySystems.Modules.ModularBoiler." \ - "ModularBoiler" - represents = [aggregation.GeneratorOneFluid] - - def __init__(self, element): - super().__init__(element) - - def request_params(self): - - self.params["redeclare package Medium"] = 'AixLib.Media.Water' - - # System setup - self.params["Pump"] = self.element.has_pump - self.params["hasFeedback"] = self.element.has_bypass - self.request_param("rated_power", - self.check_numeric(min_value=0 * ureg.kilowatt), - "QNom") - self.request_param("min_PLR", - self.check_numeric(min_value=0 * ureg.dimensionless), - "PLRMin") - - # Nominal condition - self.request_param("return_temperature", - self.check_numeric(min_value=0 * ureg.celsius), - "TRetNom") - self.request_param("dT_water", - self.check_numeric(min_value=0 * ureg.kelvin), - "dTWaterNom") - - # Feedback - self.params["dp_Valve"] = 10000 # Todo get from hydraulic circuit - - def get_port_name(self, port): - try: - index = self.element.ports.index(port) - except ValueError: - # unknown port - index = -1 - if port.verbose_flow_direction == 'SINK': - return 'port_a' - if port.verbose_flow_direction == 'SOURCE': - return 'port_b' - else: - return super().get_port_name(port) - - -class Distributor(AixLib): - path = "AixLib.Fluid.HeatExchangers.ActiveWalls.Distributor" - represents = [hvac.Distributor] - - def __init__(self, element): - super().__init__(element) - - def request_params(self): - self.params['redeclare package Medium'] = 'AixLib.Media.Water' - self.params['n'] = self.get_n_ports() - self.request_param("rated_mass_flow", - self.check_numeric(min_value=0 * ureg.kg / ureg.s), - "m_flow_nominal") - - def get_n_ports(self): - ports = {port.guid: port for port in self.element.ports if - port.connection} - return len(ports)/2 - 1 - - def get_port_name(self, port): - try: - index = self.element.ports.index(port) - except ValueError: - # unknown port - index = -1 - if (index % 2) == 0: - return "port_a_consumer" - elif (index % 2) == 1: - return "port_b_consumer" - else: - return super().get_port_name(port) - - @staticmethod - def get_new_port_name(distributor, other_inst, distributor_port, - other_port, distributors_n, distributors_ports): - if distributor not in distributors_n: - distributors_n[distributor] = 0 - distributors_ports[distributor] = {} - distributors_n[distributor] += 1 - if type(other_inst.element) is aggregation.GeneratorOneFluid: - list_name = distributor_port.split('.')[:-1] + \ - ['mainReturn' if 'port_a' in other_port - else 'mainFlow'] - else: - port_key = other_port.split('.')[-1] - if port_key not in distributors_ports[distributor]: - distributors_ports[distributor][port_key] = 0 - distributors_ports[distributor][port_key] += 1 - n = distributors_ports[distributor][port_key] - list_name = distributor_port.split('.')[:-1] + \ - ['flowPorts[%d]' % n if 'port_a' in other_port - else 'returnPorts[%d]' % n] - return '.'.join(list_name) - - -class ThreeWayValve(AixLib): - path = "AixLib.Fluid.Actuators.Valves.ThreeWayEqualPercentageLinear" - represents = [hvac.ThreeWayValve] - - def __init__(self, element): - super().__init__(element) - - def request_params(self): - self.params['redeclare package Medium'] = 'AixLib.Media.Water' - - def get_port_name(self, port): - try: - index = self.element.ports.index(port) - except ValueError: - # unknown port - index = -1 - if index == 0: - return "port_1" - elif index == 1: - return "port_2" - elif index == 2: - return "port_3" - else: - return super().get_port_name(port) - - -class Heatpump(AixLib): - path = "AixLib.Fluid.HeatPumps.HeatPump" - represents = [hvac.HeatPump] - - def __init__(self, element): - super().__init__(element) - - def request_params(self): - self.params['redeclare package Medium_con'] = 'AixLib.Media.Water' - self.params['redeclare package Medium_eva'] = 'AixLib.Media.Water' - - def get_port_name(self, port): - # TODO heat pumps might have 4 ports (if source is modeld in BIM) - if port.verbose_flow_direction == 'SINK': - return 'port_a' - if port.verbose_flow_direction == 'SOURCE': - return 'port_b' - else: - return super().get_port_name(port) - - -class Chiller(AixLib): - represents = [hvac.Chiller] - pass - - -class CHP(AixLib): - represents = [hvac.CHP] - pass - - -# class Storage(AixLib): -# represents = [hvac.Storage] -# pass +from pathlib import Path + + +def to_modelica_spawn(parameter): + """converts parameter to modelica readable string""" + if parameter is None: + return parameter + if isinstance(parameter, bool): + return 'true' if parameter else 'false' + if isinstance(parameter, (int, float)): + return str(parameter) + if isinstance(parameter, str): + return '"%s"' % parameter + if isinstance(parameter, (list, tuple, set)): + return "{%s}" % ( + ",".join( + (to_modelica_spawn(par) for par + in parameter))) + if isinstance(parameter, Path): + return \ + f"Modelica.Utilities.Files.loadResource(\"" \ + f"{str(parameter)}\")" \ + .replace("\\", "\\\\") + return str(parameter) \ No newline at end of file diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/__init__.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/__init__.py index 1e1fdeeac0..af7e1ff5c2 100644 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/__init__.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/__init__.py @@ -1 +1,2 @@ from .export_spawn_building import ExportSpawnBuilding +from .export_spawn_total import ExportSpawnTotal diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_building.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_building.py index cc30b77f11..9595c8b48a 100644 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_building.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_building.py @@ -5,9 +5,7 @@ from mako.template import Template import bim2sim -from bim2sim.elements.base_elements import ProductBased -from bim2sim.export import modelica -from bim2sim.export.modelica import help_package, help_package_order +from bim2sim.plugins.PluginSpawn.bim2sim_spawn.models import to_modelica_spawn from bim2sim.tasks.base import ITask @@ -20,21 +18,16 @@ class ExportSpawnBuilding(ITask): def run(self, elements: dict, weather_file_modelica: Path, weather_file_ep: Path): - self.logger.info("Export to Modelica code") - - package_path = self.paths.export / 'bim2sim_spawn' - os.makedirs(package_path, exist_ok=True) - - help_package(path=package_path, name=package_path.stem, within="") - help_package_order(path=package_path, package_list=[ - 'coupled_model', - 'building_model', - 'hvac_model']) + self.logger.info("Export building of Spawn modelto Modelica code") # EXPORT MULTIZONE MODEL # This is a "static" model for now, means no elements are created # dynamically but only the parameters are changed based on render # function + # TODO this should be stored central + package_path = self.paths.export / 'bim2sim_spawn' + os.makedirs(package_path, exist_ok=True) + templ_path_building = Path( bim2sim.__file__).parent / \ 'assets/templates/modelica/tmplSpawnBuilding.txt' @@ -54,29 +47,17 @@ def run(self, elements: dict, weather_file_modelica: Path, within='bim2sim_spawn', model_name='building_model', model_comment='test2', - weather_path_ep=self.to_modelica_spawn(weather_path_ep), - weather_path_mos=self.to_modelica_spawn(weather_path_mos), - zone_names=self.to_modelica_spawn(zone_names), - idf_path=self.to_modelica_spawn(idf_path), + weather_path_ep=to_modelica_spawn(weather_path_ep), + weather_path_mos=to_modelica_spawn(weather_path_mos), + zone_names=to_modelica_spawn(zone_names), + idf_path=to_modelica_spawn(idf_path), n_zones=len(zone_names) ) - - # TODO - # template_total = None - # total_template_data = template_total.render( - # ... - # ) - export_path = package_path / 'building_model.mo' # user_logger.info("Saving '%s' to '%s'", self.name, _path) with codecs.open(export_path, "w", "utf-8") as file: file.write(building_template_data) - - # TODO - # EXPORT MAIN MODEL - # This is the main model that should holds building_simulation and - # hvac_simulation - return zone_names + return zone_names, def get_zone_names(self): # TODO #1: get names from IDF or EP process for ep zones in @@ -144,28 +125,7 @@ def get_zone_names(self): # extra=extra)) # out_file.close() - @staticmethod - def to_modelica_spawn(parameter): - """converts parameter to modelica readable string""" - if parameter is None: - return parameter - if isinstance(parameter, bool): - return 'true' if parameter else 'false' - if isinstance(parameter, (int, float)): - return str(parameter) - if isinstance(parameter, str): - return '"%s"' % parameter - if isinstance(parameter, (list, tuple, set)): - return "{%s}" % ( - ",".join( - (ExportSpawnBuilding.to_modelica_spawn(par) for par - in parameter))) - if isinstance(parameter, Path): - return \ - f"Modelica.Utilities.Files.loadResource(\"" \ - f"{str(parameter)}\")" \ - .replace("\\", "\\\\") - return str(parameter) + # # def get_static_connections(self, elements): diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_total.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_total.py index e69de29bb2..de98a3975c 100644 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_total.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_total.py @@ -0,0 +1,78 @@ +import os +from datetime import datetime +from pathlib import Path +import codecs +from mako.template import Template + +import bim2sim +from bim2sim.elements.base_elements import ProductBased +from bim2sim.export import modelica +from bim2sim.export.modelica import help_package, help_package_order +from bim2sim.plugins.PluginSpawn.bim2sim_spawn.models import to_modelica_spawn +from bim2sim.tasks.base import ITask +from bim2sim.utilities.common_functions import filter_elements +from bim2sim.utilities.pyocc_tools import PyOCCTools + +class ExportSpawnTotal(ITask): + """Export total model for SpawnOfEnergyPlus model to Modelica""" + + reads = ('elements', 'weather_file_modelica', 'weather_file_ep', + 'zone_names') + final = True + + def run(self, elements: dict, weather_file_modelica: Path, + weather_file_ep: Path): + self.logger.info("Export total Spawn model to Modelica code") + + package_path = self.paths.export / 'bim2sim_spawn' + os.makedirs(package_path, exist_ok=True) + + help_package(path=package_path, name=package_path.stem, within="") + help_package_order(path=package_path, package_list=[ + 'total_model', + 'building_model', + 'hvac_model']) + + # EXPORT MULTIZONE MODEL + # This is a "static" model for now, means no elements are created + # dynamically but only the parameters are changed based on render + # function + templ_path_total = Path( + bim2sim.__file__).parent / \ + ('assets/templates/modelica/tmplSpawnTotalModel' + '.txt') + + with open(templ_path_total) as f: + template_total_str = f.read() + template_total = Template(template_total_str) + weather_path_mos = weather_file_modelica + tz_elements = filter_elements(elements, 'ThermalZone') + space_heater_elements = filter_elements(elements, + 'SpaceHeater') + # ToDO Missing: list with space heater guids for array indexing + tz_space_heater_mapping = [] + for tz in tz_elements: + for space_heater in space_heater_elements: + if PyOCCTools.obj2_in_obj1( + obj1=tz.space_shape, obj2=space_heater.shape): + #ToDo continue here + tz_space_heater_mapping.append( + (tz.guid, space_heater.guid)) + # ToDo + # + # TODO multithreading lock needed? see modelica/__init__.py for example + # with lock: + total_template_data = template_total.render( + within='bim2sim_spawn', + model_name='total_model', + model_comment='test2', + weather_path_mos=to_modelica_spawn(weather_path_mos), + bldg_to_hvac_heat_ports_connections=... + # n_zones=len(zone_names) + ) + + export_path = package_path / 'total_model.mo' + # user_logger.info("Saving '%s' to '%s'", self.name, _path) + with codecs.open(export_path, "w", "utf-8") as file: + file.write(total_template_data) + diff --git a/bim2sim/sim_settings.py b/bim2sim/sim_settings.py index 46f0165ff8..f7ccdf3fc9 100644 --- a/bim2sim/sim_settings.py +++ b/bim2sim/sim_settings.py @@ -966,5 +966,9 @@ class EnergyPlusSimSettings(BuildingSimSettings): ) -class CoSimulation(BuildingSimSettings, PlantSimSettings): - ... +class SpawnOfEnergyPlusSimSettings(EnergyPlusSimSettings, PlantSimSettings): + def __init__(self): + super().__init__() + self.relevant_elements = {*bps_elements.items, *hvac_elements.items, + Material} - {bps_elements.Plate} + diff --git a/bim2sim/utilities/pyocc_tools.py b/bim2sim/utilities/pyocc_tools.py index 8d0c0e6bc1..4edc50f548 100644 --- a/bim2sim/utilities/pyocc_tools.py +++ b/bim2sim/utilities/pyocc_tools.py @@ -30,6 +30,7 @@ class PyOCCTools: + # TODO @Veronika: why is this a class and not just a file with functions? """Class for Tools handling and modifying Python OCC Shapes""" @staticmethod @@ -556,5 +557,5 @@ def obj2_in_obj1(obj1: TopoDS_Shape, obj2: TopoDS_Shape) -> bool: shell = PyOCCTools.make_shell_from_faces(faces) obj1_solid = PyOCCTools.make_solid_from_shell(shell) obj2_center = PyOCCTools.get_center_of_volume(obj2) - return PyOCCTools.check_pnt_in_solid(obj1_solid, obj2_center) + From 555f6e4965ff99cc5758622710e2d9c91c12c4ac Mon Sep 17 00:00:00 2001 From: David Paul Jansen Date: Wed, 31 Jul 2024 09:19:22 +0200 Subject: [PATCH 040/125] start implementation of hvac into spawn model --- bim2sim/elements/graphs/hvac_graph.py | 4 +++- bim2sim/plugins/AixLib | 2 +- .../bim2sim_spawn/examples/e2_simple_project_bps_spawn.py | 7 +++++-- .../PluginSpawn/bim2sim_spawn/tasks/export_spawn_total.py | 8 +++++--- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/bim2sim/elements/graphs/hvac_graph.py b/bim2sim/elements/graphs/hvac_graph.py index 929cda7de5..58578ab08c 100644 --- a/bim2sim/elements/graphs/hvac_graph.py +++ b/bim2sim/elements/graphs/hvac_graph.py @@ -35,7 +35,9 @@ def _update_from_elements(self, elements): """ nodes = [port for instance in elements for port in instance.ports - if port.connection] + # TODO #1 add this again, this is just for testing purpose + # if port.connection + ] inner_edges = [connection for instance in elements for connection in instance.inner_connections] edges = [(port, port.connection) for port in nodes if port.connection] diff --git a/bim2sim/plugins/AixLib b/bim2sim/plugins/AixLib index 5ffb94b00f..63ace636ba 160000 --- a/bim2sim/plugins/AixLib +++ b/bim2sim/plugins/AixLib @@ -1 +1 @@ -Subproject commit 5ffb94b00fa122b21dcdaadbae10e6eceee635ff +Subproject commit 63ace636ba939d2b2fa678609ab55079f4fd6d71 diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e2_simple_project_bps_spawn.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e2_simple_project_bps_spawn.py index 9776c0658b..f984fa35a4 100644 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e2_simple_project_bps_spawn.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e2_simple_project_bps_spawn.py @@ -6,7 +6,7 @@ from bim2sim.kernel.log import default_logging_setup from bim2sim.utilities.common_functions import download_test_resources from bim2sim.utilities.types import IFCDomain - +from bim2sim.kernel.decision.decisionhandler import DebugDecisionHandler def run_example_1(): """Run a building performance simulation with the EnergyPlus backend. @@ -56,7 +56,10 @@ def run_example_1(): # Run the project with the ConsoleDecisionHandler. This allows interactive # input to answer upcoming questions regarding the imported IFC. - run_project(project, ConsoleDecisionHandler()) + answers = ('HVAC-SpaceHeater', *('Living',)*6, 2010) + handler = DebugDecisionHandler(answers) + handler.handle(project.run()) + # run_project(project, ConsoleDecisionHandler()) if __name__ == '__main__': diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_total.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_total.py index de98a3975c..d67e21dce9 100644 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_total.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_total.py @@ -13,6 +13,7 @@ from bim2sim.utilities.common_functions import filter_elements from bim2sim.utilities.pyocc_tools import PyOCCTools + class ExportSpawnTotal(ITask): """Export total model for SpawnOfEnergyPlus model to Modelica""" @@ -21,7 +22,7 @@ class ExportSpawnTotal(ITask): final = True def run(self, elements: dict, weather_file_modelica: Path, - weather_file_ep: Path): + weather_file_ep: Path, zone_names): self.logger.info("Export total Spawn model to Modelica code") package_path = self.paths.export / 'bim2sim_spawn' @@ -53,12 +54,13 @@ def run(self, elements: dict, weather_file_modelica: Path, tz_space_heater_mapping = [] for tz in tz_elements: for space_heater in space_heater_elements: + # TODO check SpaceHeater is some how semantically connected + # to the Space in the IFC, is this correct? if PyOCCTools.obj2_in_obj1( obj1=tz.space_shape, obj2=space_heater.shape): - #ToDo continue here tz_space_heater_mapping.append( (tz.guid, space_heater.guid)) - # ToDo + # ToDo radiator_names_list is missing # # TODO multithreading lock needed? see modelica/__init__.py for example # with lock: From 2a3d048a8d139828bf4645be7fd32ccbd217c921 Mon Sep 17 00:00:00 2001 From: David Paul Jansen Date: Wed, 31 Jul 2024 09:20:31 +0200 Subject: [PATCH 041/125] add first example for radiator in building --- .../e3_single_radiator_in_building.py | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 bim2sim/plugins/PluginAixLib/bim2sim_aixlib/examples/e3_single_radiator_in_building.py diff --git a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/examples/e3_single_radiator_in_building.py b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/examples/e3_single_radiator_in_building.py new file mode 100644 index 0000000000..20270920c0 --- /dev/null +++ b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/examples/e3_single_radiator_in_building.py @@ -0,0 +1,86 @@ +import tempfile +from pathlib import Path + +import bim2sim +from bim2sim import Project, run_project, ConsoleDecisionHandler +from bim2sim.kernel.log import default_logging_setup +from bim2sim.utilities.common_functions import download_test_resources +from bim2sim.utilities.types import IFCDomain +from bim2sim_aixlib import LoadLibrariesAixLib + + +def run_example_simple_hvac_aixlib(): + """Run an HVAC simulation with the AixLib backend. + """ + + # Create the default logging to for quality log and bim2sim main log ( + # see logging documentation for more information + default_logging_setup() + + # Create a temp directory for the project, feel free to use a "normal" + # directory + project_path = Path( + tempfile.TemporaryDirectory( + prefix='bim2sim_example_simple_aixlib_3').name) + + # download additional test resources for arch domain, you might want to set + # force_new to True to update your test resources + download_test_resources(IFCDomain.hydraulic, force_new=False) + + # Set path of ifc for hydraulic domain with the fresh downloaded test models + ifc_paths = { + IFCDomain.hydraulic: + Path(bim2sim.__file__).parent.parent / + 'test/resources/arch/ifc/' + 'ExampleHOM_with_radiator_ports_connected.ifc' + } + # Create a project including the folder structure for the project with + # teaser as backend and no specified workflow (default workflow is taken) + project = Project.create(project_path, ifc_paths, 'aixlib') + + # set weather file data + project.sim_settings.weather_file_path_modelica = ( + Path(bim2sim.__file__).parent.parent / + 'test/resources/weather_files/DEU_NW_Aachen.105010_TMYx.mos') + + # specify simulation settings + project.sim_settings.aggregations = [ + # 'UnderfloorHeating', + # 'Consumer', + # 'PipeStrand', + # 'ParallelPump', + # 'ConsumerHeatingDistributorModule', + # 'GeneratorOneFluid' + ] + project.sim_settings.group_unidentified = 'name' + from bim2sim.tasks import base, common, hvac + project.plugin_cls.default_tasks = [ + common.LoadIFC, + common.CheckIfc, + common.CreateElements, + hvac.ConnectElements, + hvac.MakeGraph, + LoadLibrariesAixLib, + hvac.Export, + ] + + # Run the project with the ConsoleDecisionHandler. This allows interactive + # input to answer upcoming questions regarding the imported IFC. + # Correct decision for identification of elements and useful parameters for + # missing attributes are written below + run_project(project, ConsoleDecisionHandler()) + +# IfcBuildingElementProxy: skip +# Rücklaufverschraubung: 'HVAC-PipeFitting', +# Apparate (M_606) 'HVAC-Distributor', +# 3-Wege-Regelventil PN16: 'HVAC-ThreeWayValve', +# True * 6 +# efficiency: 0.95 +# flow_temperature: 70 +# nominal_power_consumption: 200 +# return_temperature: 50 +# heat_capacity: 10 * 7 + + +if __name__ == '__main__': + run_example_simple_hvac_aixlib() From 97fbbb96d5df32cf2c44858ff44d2a467255675d Mon Sep 17 00:00:00 2001 From: David Paul Jansen Date: Mon, 12 Aug 2024 11:12:39 +0200 Subject: [PATCH 042/125] add example for mixed IFC for hydraulic --- .../e3_single_radiator_in_building.py | 54 ++++++++++++------- 1 file changed, 35 insertions(+), 19 deletions(-) diff --git a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/examples/e3_single_radiator_in_building.py b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/examples/e3_single_radiator_in_building.py index 20270920c0..c856e4ef82 100644 --- a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/examples/e3_single_radiator_in_building.py +++ b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/examples/e3_single_radiator_in_building.py @@ -3,6 +3,7 @@ import bim2sim from bim2sim import Project, run_project, ConsoleDecisionHandler +from bim2sim.kernel.decision.decisionhandler import DebugDecisionHandler from bim2sim.kernel.log import default_logging_setup from bim2sim.utilities.common_functions import download_test_resources from bim2sim.utilities.types import IFCDomain @@ -31,8 +32,8 @@ def run_example_simple_hvac_aixlib(): ifc_paths = { IFCDomain.hydraulic: Path(bim2sim.__file__).parent.parent / - 'test/resources/arch/ifc/' - 'ExampleHOM_with_radiator_ports_connected.ifc' + 'test/resources/mixed/ifc/' + 'b03_heating_with_building.ifc' } # Create a project including the folder structure for the project with # teaser as backend and no specified workflow (default workflow is taken) @@ -44,31 +45,46 @@ def run_example_simple_hvac_aixlib(): 'test/resources/weather_files/DEU_NW_Aachen.105010_TMYx.mos') # specify simulation settings - project.sim_settings.aggregations = [ - # 'UnderfloorHeating', - # 'Consumer', - # 'PipeStrand', - # 'ParallelPump', - # 'ConsumerHeatingDistributorModule', - # 'GeneratorOneFluid' - ] + # project.sim_settings.aggregations = [ + # # 'UnderfloorHeating', + # # 'Consumer', + # # 'PipeStrand', + # # 'ParallelPump', + # # 'ConsumerHeatingDistributorModule', + # # 'GeneratorOneFluid' + # ] project.sim_settings.group_unidentified = 'name' from bim2sim.tasks import base, common, hvac - project.plugin_cls.default_tasks = [ - common.LoadIFC, - common.CheckIfc, - common.CreateElements, - hvac.ConnectElements, - hvac.MakeGraph, - LoadLibrariesAixLib, - hvac.Export, - ] + # project.plugin_cls.default_tasks = [ + # common.LoadIFC, + # common.CheckIfc, + # common.CreateElements, + # hvac.ConnectElements, + # hvac.MakeGraph, + # LoadLibrariesAixLib, + # hvac.Export, + # ] + answers = ('HVAC-PipeFitting', 'HVAC-Distributor', + 'HVAC-ThreeWayValve', + # 8 dead ends + *(True,) * 6, + # boiler efficiency, flow temp, power consumption, + # return temp + 0.95, 70, 79, 50, + *(500, 50,) * 7, + # rated_mass_flow for distributor, rated of boiler pump + 1, + # rated_mass_flow for boiler pump, rated dp of boiler pump + 0.9, 4500, + # body mass and heat capacity for all space heaters + ) # Run the project with the ConsoleDecisionHandler. This allows interactive # input to answer upcoming questions regarding the imported IFC. # Correct decision for identification of elements and useful parameters for # missing attributes are written below run_project(project, ConsoleDecisionHandler()) + # run_project(project, DebugDecisionHandler(answers)) # IfcBuildingElementProxy: skip # Rücklaufverschraubung: 'HVAC-PipeFitting', From c2b70dd889ab66257a14da84fd26bd39b5e36d3c Mon Sep 17 00:00:00 2001 From: David Paul Jansen Date: Mon, 12 Aug 2024 11:49:50 +0200 Subject: [PATCH 043/125] correct errors from merge --- bim2sim/assets/templates/modelica/tmplModel.txt | 13 ++++++------- test/unit/tasks/common/test_weather.py | 5 +++-- test/unit/tasks/hvac/test_export.py | 3 +-- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/bim2sim/assets/templates/modelica/tmplModel.txt b/bim2sim/assets/templates/modelica/tmplModel.txt index b1a4c89e27..27d4f5285d 100644 --- a/bim2sim/assets/templates/modelica/tmplModel.txt +++ b/bim2sim/assets/templates/modelica/tmplModel.txt @@ -30,31 +30,30 @@ model ${model.name} "${model.comment}" // Only for SpawnOfEnergyPlus // TODO conditional Modelica.Thermal.HeatTransfer.Interfaces.HeatPort_a heatPortOuterCon[${len(model.connections_heat_ports_conv)}] - annotation (Placement(transformation(extent={{-110,10},{-90,30}})));; + annotation (Placement(transformation(extent={{-110,10},{-90,30}}))); Modelica.Thermal.HeatTransfer.Interfaces.HeatPort_a heatPortOuterRad[${len(model.connections_heat_ports_rad)}] annotation (Placement(transformation(extent={{-110,-22},{-90,-2}}))); equation % for con1, con2, pos1, pos2 in model.connections: connect(${con1}, ${con2}) - annotation (Line(points={{${pos1[0]},${pos1[1]}},{38,-6}}, color={0,0,127}));; -% endfor\ + annotation (Line(points={{${pos1[0]},${pos1[1]}},{38,-6}}, color={0,0,127})); +% endfor // heatport connections % for cons in model.connections_heat_ports_conv: connect${str(cons).replace("'","")} annotation (Line(points={{0,0},{0,0},{0,0},{0,0},{0,0}}, color={191,0,0})); -% endfor\ +% endfor % for cons in model.connections_heat_ports_rad: connect${str(cons).replace("'","")} annotation (Line(points={{0,0},{0,0},{0,0},{0,0},{0,0}}, color={191,0,0})); -% endfor\ +% endfor annotation( %if unknowns: Diagram(graphics={Text( extent={{-100,100},{100,60}}, lineColor={238,46,47}, - textString="${len(unknowns)} unknown parameters! - see comments for details.")}), + textString="${len(unknowns)} unknown parameters! See comments for details.")}), %endif experiment(StopTime=36000)); end ${model.name}; \ No newline at end of file diff --git a/test/unit/tasks/common/test_weather.py b/test/unit/tasks/common/test_weather.py index e0a1aee0fb..7a17dea157 100644 --- a/test/unit/tasks/common/test_weather.py +++ b/test/unit/tasks/common/test_weather.py @@ -53,7 +53,8 @@ def test_weather_modelica(self): handler = DebugDecisionHandler([]) handler.handle(self.project.run(cleanup=False)) try: - weather_file = self.project.playground.state['weather_file'] + weather_file = self.project.playground.state[ + 'weather_file_modelica'] except Exception: raise ValueError(f"No weather file set through Weather task. An" f"error occurred.") @@ -72,7 +73,7 @@ def test_weather_energyplus(self): handler = DebugDecisionHandler([]) handler.handle(self.project.run(cleanup=False)) try: - weather_file = self.project.playground.state['weather_file'] + weather_file = self.project.playground.state['weather_file_ep'] except Exception: raise ValueError(f"No weather file set through Weather task. An" f"error occurred.") diff --git a/test/unit/tasks/hvac/test_export.py b/test/unit/tasks/hvac/test_export.py index 11d9539824..e2ba8615b1 100644 --- a/test/unit/tasks/hvac/test_export.py +++ b/test/unit/tasks/hvac/test_export.py @@ -8,8 +8,7 @@ from bim2sim.elements.hvac_elements import HVACProduct, Pump from bim2sim.elements.mapping.units import ureg -from bim2sim.export.modelica import ModelicaElement, ModelicaParameter, \ - parse_to_modelica +from bim2sim.export.modelica import ModelicaElement, parse_to_modelica from bim2sim.kernel.decision.decisionhandler import DebugDecisionHandler from bim2sim.tasks.hvac import Export, LoadLibrariesStandardLibrary from test.unit.elements.helper import SetupHelperHVAC From b918f22f24120360afa10dcd9cf24be0644e041b Mon Sep 17 00:00:00 2001 From: David Paul Jansen Date: Mon, 12 Aug 2024 13:35:49 +0200 Subject: [PATCH 044/125] adjust weather check --- bim2sim/export/modelica/__init__.py | 1 + .../bim2sim_aixlib/models/__init__.py | 3 --- bim2sim/tasks/common/weather.py | 18 ++++++++++-------- test/unit/tasks/hvac/test_export.py | 3 ++- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/bim2sim/export/modelica/__init__.py b/bim2sim/export/modelica/__init__.py index 5ade72e8ba..8526c679db 100644 --- a/bim2sim/export/modelica/__init__.py +++ b/bim2sim/export/modelica/__init__.py @@ -264,6 +264,7 @@ def __init__(self, element: HVACProduct): self.guid = self._get_clean_guid() self.name = self._get_name() self.comment = self.get_comment() + self.heat_ports = [] def _get_clean_guid(self) -> str: """ Gets a clean GUID of the element. diff --git a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py index c6370541b7..6f895cd9e5 100644 --- a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py +++ b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py @@ -70,9 +70,6 @@ def __init__(self, element): heat_transfer_type='radiative', parent=self) ] - - def __init__(self, element): - super().__init__(element) self._set_parameter(name='redeclare package Medium', unit=None, required=False, diff --git a/bim2sim/tasks/common/weather.py b/bim2sim/tasks/common/weather.py index 52770655ce..e447e84734 100644 --- a/bim2sim/tasks/common/weather.py +++ b/bim2sim/tasks/common/weather.py @@ -37,17 +37,19 @@ def check_weather_file(self, weather_file_modelica, weather_file_ep): expected_endings = { 'energyplus': ['.epw'], 'comfort': ['.epw'], - 'spawn': ['.epw', '.mos'] + 'spawn': ['.epw', '.mos'], + 'teaser': ['.mos'], + 'aixlib': ['.mos'], + 'hkesim': ['.mos'] } - # all other plugins need .mos file - if plugin_name not in expected_endings: - expected_endings[plugin_name] = ['.mos'] + # Ensure the correct number of files are checked + files_to_check = [weather_file_modelica] + if plugin_name in ['energyplus', 'comfort', 'spawn']: + files_to_check.insert(0, weather_file_ep) - for file, expected_suffix in zip( - [weather_file_ep, weather_file_modelica], - expected_endings[plugin_name] - ): + for file, expected_suffix in zip(files_to_check, + expected_endings[plugin_name]): if not file: raise ValueError( f"For Plugin {plugin_name} no weather file" diff --git a/test/unit/tasks/hvac/test_export.py b/test/unit/tasks/hvac/test_export.py index e2ba8615b1..f7849a18bb 100644 --- a/test/unit/tasks/hvac/test_export.py +++ b/test/unit/tasks/hvac/test_export.py @@ -74,7 +74,8 @@ def run_parameter_test(self, graph: HvacGraph, modelica_model: list, expected_strings = [f'{param[1]}={values[index]}' for index, param in enumerate(parameters)] for expected_string in expected_strings: - self.assertIn(expected_string, modelica_model[0].code()) + self.assertIn(expected_string, + modelica_model[0].render_modelica_code()) def test_to_modelica(self): element = HVACProduct() From 0ec52ab324f52e2712f6d28dfc2307d810078901 Mon Sep 17 00:00:00 2001 From: David Paul Jansen Date: Mon, 12 Aug 2024 13:49:37 +0200 Subject: [PATCH 045/125] improve weather check --- bim2sim/tasks/common/weather.py | 44 +++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/bim2sim/tasks/common/weather.py b/bim2sim/tasks/common/weather.py index e447e84734..0d7aa0da13 100644 --- a/bim2sim/tasks/common/weather.py +++ b/bim2sim/tasks/common/weather.py @@ -1,3 +1,5 @@ +from pathlib import Path + from bim2sim.tasks.base import ITask from bim2sim.utilities.common_functions import filter_elements @@ -33,7 +35,7 @@ def run(self, elements): def check_weather_file(self, weather_file_modelica, weather_file_ep): """Check if the file exists and has the correct ending.""" plugin_name = self.playground.project.plugin_cls.name.lower() - + # Define the expected file endings for each plugin expected_endings = { 'energyplus': ['.epw'], 'comfort': ['.epw'], @@ -43,24 +45,34 @@ def check_weather_file(self, weather_file_modelica, weather_file_ep): 'hkesim': ['.mos'] } - # Ensure the correct number of files are checked - files_to_check = [weather_file_modelica] - if plugin_name in ['energyplus', 'comfort', 'spawn']: - files_to_check.insert(0, weather_file_ep) + # Get the expected endings for the plugin_name + if plugin_name not in expected_endings: + raise ValueError(f"Unknown plugin_name '{plugin_name}'") + + required_endings = expected_endings[plugin_name] - for file, expected_suffix in zip(files_to_check, - expected_endings[plugin_name]): - if not file: + # If both are required, ensure both files are provided + if '.epw' in required_endings and '.mos' in required_endings: + if not weather_file_ep or not weather_file_modelica: raise ValueError( - f"For Plugin {plugin_name} no weather file" - f" with ending {expected_suffix}" - f" has been assigned, check your sim_settings.") - if not file.suffix == expected_suffix: + f"{plugin_name} requires both '.epw' and '.mos' " + f"weather files.") + + # Check if the correct weather file is provided + if '.epw' in required_endings: + if (not weather_file_ep or not isinstance(weather_file_ep, Path) or + not weather_file_ep.suffix == '.epw'): + raise ValueError( + f"{plugin_name} requires a weather file with '.epw' " + f"extension.") + + if '.mos' in required_endings: + if not weather_file_modelica or not isinstance( + weather_file_modelica, + Path) or not weather_file_modelica.suffix == '.mos': raise ValueError( - f"{plugin_name} weather file should have ending " - f"'{expected_suffix}', but a {file.suffix} file was" - f" provided." - ) + f"{plugin_name} requires a weather file with '.mos'" + f" extension.") def get_location_lat_long_from_ifc(self, elements: dict) -> [float]: """ From 5b88477ffacb2fc335d7c6ad9e2ab700b077efe0 Mon Sep 17 00:00:00 2001 From: sfreund1 Date: Mon, 19 Aug 2024 09:08:40 +0200 Subject: [PATCH 046/125] Resolving merge conflicts --- .../PluginAixLib/bim2sim_aixlib/models/__init__.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py index 6f895cd9e5..578ce53ecf 100644 --- a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py +++ b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py @@ -98,14 +98,6 @@ def get_port_name(self, port): else: return super().get_port_name(port) - def get_port_name(self, port): - if port.verbose_flow_direction == 'SINK': - return 'port_a' - if port.verbose_flow_direction == 'SOURCE': - return 'port_b' - else: - return super().get_port_name(port) - class Pump(AixLib): path = "AixLib.Fluid.Movers.SpeedControlled_y" From 1108ad7bdaab35af1ea0ededa03170c53298baff Mon Sep 17 00:00:00 2001 From: David Paul Jansen Date: Mon, 19 Aug 2024 09:45:16 +0200 Subject: [PATCH 047/125] make heatports in modelica template conditional --- bim2sim/assets/templates/modelica/tmplModel.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bim2sim/assets/templates/modelica/tmplModel.txt b/bim2sim/assets/templates/modelica/tmplModel.txt index 27d4f5285d..e0d6d29054 100644 --- a/bim2sim/assets/templates/modelica/tmplModel.txt +++ b/bim2sim/assets/templates/modelica/tmplModel.txt @@ -28,11 +28,14 @@ model ${model.name} "${model.comment}" \ \ // Only for SpawnOfEnergyPlus -// TODO conditional +% if len(model.connections_heat_ports_conv) > 0: Modelica.Thermal.HeatTransfer.Interfaces.HeatPort_a heatPortOuterCon[${len(model.connections_heat_ports_conv)}] annotation (Placement(transformation(extent={{-110,10},{-90,30}}))); +% endif +% if len(model.connections_heat_ports_rad) > 0: Modelica.Thermal.HeatTransfer.Interfaces.HeatPort_a heatPortOuterRad[${len(model.connections_heat_ports_rad)}] annotation (Placement(transformation(extent={{-110,-22},{-90,-2}}))); +% endif equation % for con1, con2, pos1, pos2 in model.connections: From 187c9c3d1705fe8e3d91d7a7262273c08f0cf812 Mon Sep 17 00:00:00 2001 From: David Paul Jansen Date: Mon, 19 Aug 2024 09:46:47 +0200 Subject: [PATCH 048/125] further work on example --- .../bim2sim_aixlib/examples/e3_single_radiator_in_building.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/examples/e3_single_radiator_in_building.py b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/examples/e3_single_radiator_in_building.py index c856e4ef82..ad1453dce5 100644 --- a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/examples/e3_single_radiator_in_building.py +++ b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/examples/e3_single_radiator_in_building.py @@ -83,8 +83,8 @@ def run_example_simple_hvac_aixlib(): # input to answer upcoming questions regarding the imported IFC. # Correct decision for identification of elements and useful parameters for # missing attributes are written below - run_project(project, ConsoleDecisionHandler()) - # run_project(project, DebugDecisionHandler(answers)) + # run_project(project, ConsoleDecisionHandler()) + run_project(project, DebugDecisionHandler(answers)) # IfcBuildingElementProxy: skip # Rücklaufverschraubung: 'HVAC-PipeFitting', From 5a661d41dc9f611d72efbf2d7ad5199fb30e9d83 Mon Sep 17 00:00:00 2001 From: David Paul Jansen Date: Mon, 19 Aug 2024 11:51:30 +0200 Subject: [PATCH 049/125] update work on spawn --- bim2sim/elements/base_elements.py | 1 + bim2sim/elements/bps_elements.py | 5 +-- bim2sim/export/modelica/__init__.py | 9 ++-- .../PluginSpawn/bim2sim_spawn/__init__.py | 30 +++++++------ .../examples/e2_simple_project_bps_spawn.py | 31 +++++++++++--- bim2sim/tasks/common/create_elements.py | 6 ++- bim2sim/tasks/hvac/connect_elements.py | 12 ++++-- bim2sim/utilities/common_functions.py | 42 ++++++++++++++----- 8 files changed, 97 insertions(+), 39 deletions(-) diff --git a/bim2sim/elements/base_elements.py b/bim2sim/elements/base_elements.py index 570a1aba29..50d5e9689a 100644 --- a/bim2sim/elements/base_elements.py +++ b/bim2sim/elements/base_elements.py @@ -581,6 +581,7 @@ def __init__(self, *args, **kwargs): self.ports = self.get_ports() self.material = None self.material_set = {} + self.storeys = [] def __init_subclass__(cls, **kwargs): # set key for each class diff --git a/bim2sim/elements/bps_elements.py b/bim2sim/elements/bps_elements.py index cfedf560d6..86e72a03e7 100644 --- a/bim2sim/elements/bps_elements.py +++ b/bim2sim/elements/bps_elements.py @@ -51,7 +51,6 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.thermal_zones = [] self.space_boundaries = [] - self.storeys = [] self.material = None self.disaggregations = [] self.building = None @@ -1775,7 +1774,7 @@ def __init__(self, *args, **kwargs): self.elements = [] ifc_types = {"IfcBuilding": ['*']} - from_ifc_domains = [IFCDomain.arch] + from_ifc_domains = [IFCDomain.arch, IFCDomain.mixed] conditions = [ condition.RangeCondition('year_of_construction', @@ -1847,7 +1846,7 @@ def _get_avg_storey_height(self, name): class Storey(BPSProduct): ifc_types = {'IfcBuildingStorey': ['*']} - from_ifc_domains = [IFCDomain.arch] + from_ifc_domains = [IFCDomain.arch, IFCDomain.mixed] def __init__(self, *args, **kwargs): """storey __init__ function""" diff --git a/bim2sim/export/modelica/__init__.py b/bim2sim/export/modelica/__init__.py index 8526c679db..589a6321a7 100644 --- a/bim2sim/export/modelica/__init__.py +++ b/bim2sim/export/modelica/__init__.py @@ -689,9 +689,12 @@ def parse_to_modelica(name: Union[str, None], value: Any) -> Union[str, None]: elif isinstance(value, str): return f'{prefix}{value}' elif isinstance(value, (list, tuple, set)): - return (prefix + "{%s}" - % (",".join((parse_to_modelica(None, par) - for par in value)))) + if any(x is None for x in value): + return None + else: + return (prefix + "{%s}" + % (",".join((parse_to_modelica(None, par) + for par in value)))) # Handle modelica records elif isinstance(value, dict): record_str = f'{name}(' diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/__init__.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/__init__.py index a8b88dc48a..f4c1c26b47 100644 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/__init__.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/__init__.py @@ -3,7 +3,9 @@ from bim2sim.sim_settings import BuildingSimSettings, EnergyPlusSimSettings, \ SpawnOfEnergyPlusSimSettings import bim2sim.plugins.PluginSpawn.bim2sim_spawn.tasks as spawn_tasks -from bim2sim.plugins.PluginEnergyPlus.bim2sim_energyplus import task as ep_tasks +from bim2sim.plugins.PluginEnergyPlus.bim2sim_energyplus import \ + task as ep_tasks +from bim2sim_aixlib import LoadLibrariesAixLib # # TODO: this is just a concept and not working already @@ -12,22 +14,26 @@ class PluginSpawnOfEP(Plugin): sim_settings = SpawnOfEnergyPlusSimSettings default_tasks = [ common.LoadIFC, - # common.CheckIfc, - common.CreateElements, + common.CheckIfc, + common.CreateElementsOnIfcTypes, bps.CreateSpaceBoundaries, - bps.CorrectSpaceBoundaries, bps.AddSpaceBoundaries2B, - bps.FilterTZ, - # bps.ProcessSlabsRoofs, - common.BindStoreys, + bps.CorrectSpaceBoundaries, + common.CreateRelations, + bps.DisaggregationCreationAndTypeCheck, + bps.EnrichMaterial, bps.EnrichUseConditions, - bps.VerifyLayersMaterials, # LOD.full - bps.EnrichMaterial, # LOD.full common.Weather, ep_tasks.CreateIdf, - # ep_tasks.IdfPostprocessing, - # ep_tasks.ExportIdfForCfd, - # ep_tasks.RunEnergyPlusSimulation, + + hvac.ConnectElements, + hvac.MakeGraph, + hvac.ExpansionTanks, + hvac.Reduce, + hvac.DeadEnds, + LoadLibrariesAixLib, + hvac.Export, + spawn_tasks.ExportSpawnBuilding, spawn_tasks.ExportSpawnTotal, diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e2_simple_project_bps_spawn.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e2_simple_project_bps_spawn.py index f984fa35a4..ae8620b007 100644 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e2_simple_project_bps_spawn.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e2_simple_project_bps_spawn.py @@ -27,12 +27,13 @@ def run_example_1(): project_path = Path( tempfile.TemporaryDirectory(prefix='bim2sim_example_spawn').name) - download_test_resources(IFCDomain.arch, force_new=False) + download_test_resources(IFCDomain.mixed, force_new=False) # Set the ifc path to use and define which domain the IFC belongs to ifc_paths = { - IFCDomain.arch: + IFCDomain.mixed: Path(bim2sim.__file__).parent.parent / - 'test/resources/arch/ifc/ExampleHOM_with_radiator.ifc', + 'test/resources/mixed/ifc/' + 'b03_heating_with_building_blenderBIM.ifc' } # Create a project including the folder structure for the project with @@ -53,14 +54,34 @@ def run_example_1(): 'test/resources/weather_files/DEU_NW_Aachen.105010_TMYx.mos') # Set other simulation settings, otherwise all settings are set to default + project.sim_settings.aggregations = [ + 'Consumer', + 'PipeStrand', + 'ParallelPump', + # 'ConsumerHeatingDistributorModule', + 'GeneratorOneFluid' + ] + # Run the project with the ConsoleDecisionHandler. This allows interactive # input to answer upcoming questions regarding the imported IFC. - answers = ('HVAC-SpaceHeater', *('Living',)*6, 2010) + answers = ( + 'HVAC-PipeFitting', # 35 + 'HVAC-Distributor', # 26 + 'HVAC-ThreeWayValve', # 39 + 2010, + *(True,) * 6, + # 0.95, 70, 79, 50, + # *(500, 50,) * 7, + # 1, + # 0.9, 4500, + ) + + project.sim_settings.group_unidentified = 'name' + handler = DebugDecisionHandler(answers) handler.handle(project.run()) # run_project(project, ConsoleDecisionHandler()) - if __name__ == '__main__': run_example_1() diff --git a/bim2sim/tasks/common/create_elements.py b/bim2sim/tasks/common/create_elements.py index c4aee8f5f1..dc60e8de96 100644 --- a/bim2sim/tasks/common/create_elements.py +++ b/bim2sim/tasks/common/create_elements.py @@ -86,8 +86,9 @@ def run(self, ifc_files: [IfcFileClass]): relevant_elements, ifc_file.ifc_units, ['Description']) + # TODO using sorted(unknown_entities) as hotfix entity_class_dict, unknown_entities = yield from self.filter_by_text( - text_filter, unknown_entities, ifc_file.ifc_units) + text_filter, sorted(unknown_entities), ifc_file.ifc_units) entity_best_guess_dict.update(entity_class_dict) # TODO why do we run this two times, once without and once with # force=True @@ -103,8 +104,9 @@ def run(self, ifc_files: [IfcFileClass]): len(unknown_entities)) # identification of remaining entities by user + # TODO using sorted(unknown_entities) as hotfix entity_class_dict, unknown_entities = yield from self.set_class_by_user( - unknown_entities, + sorted(unknown_entities), self.playground.sim_settings, entity_best_guess_dict) entity_best_guess_dict.update(entity_class_dict) diff --git a/bim2sim/tasks/hvac/connect_elements.py b/bim2sim/tasks/hvac/connect_elements.py index 3dddd0f20a..55bb4900b2 100644 --- a/bim2sim/tasks/hvac/connect_elements.py +++ b/bim2sim/tasks/hvac/connect_elements.py @@ -9,7 +9,7 @@ from bim2sim.elements.base_elements import Port, ProductBased from bim2sim.kernel.decision import DecisionBunch from bim2sim.tasks.base import ITask, Playground - +from bim2sim.utilities.common_functions import filter_elements, all_subclasses quality_logger = logging.getLogger('bim2sim.QualityReport') @@ -48,11 +48,15 @@ def run(self, elements: dict) -> dict: # Check ports self.logger.info("Checking ports of elements ...") - self.check_element_ports(elements) + + hvac_elements = filter_elements( + elements, hvac.HVACProduct, create_dict=True, + include_sub_classes=True) + self.check_element_ports(hvac_elements) # Make connections by relations self.logger.info("Connecting the relevant elements") self.logger.info(" - Connecting by relations ...") - all_ports = [port for item in elements.values() for port in item.ports] + all_ports = [port for item in hvac_elements.values() for port in item.ports] rel_connections = self.connections_by_relation(all_ports) self.logger.info(" - Found %d potential connections.", len(rel_connections)) @@ -96,7 +100,7 @@ def run(self, elements: dict) -> dict: self.logger.warning( "Connecting by bounding box is not implemented.") # Check inner connections - yield from self.check_inner_connections(elements.values()) + yield from self.check_inner_connections(hvac_elements.values()) # TODO: manually add / modify connections return elements, diff --git a/bim2sim/utilities/common_functions.py b/bim2sim/utilities/common_functions.py index 4516c0c5ef..22c1950390 100644 --- a/bim2sim/utilities/common_functions.py +++ b/bim2sim/utilities/common_functions.py @@ -3,14 +3,18 @@ import math import re import zipfile -from urllib.request import urlopen from pathlib import Path -from typing import Union from time import sleep +from typing import TYPE_CHECKING, Type +from typing import Union +from urllib.request import urlopen import bim2sim from bim2sim.utilities.types import IFCDomain +if TYPE_CHECKING: + from bim2sim.elements.base_elements import IFCBased + assets = Path(bim2sim.__file__).parent / 'assets' @@ -255,24 +259,29 @@ def get_type_building_elements_hvac(): return type_building_elements -def filter_elements(elements: Union[dict, list], type_name, create_dict=False)\ - -> Union[list, dict]: +def filter_elements(elements: Union[dict, list], + type_name: Union[str, Type['IFCBased']], create_dict=False, + include_sub_classes=False) -> Union[list, dict]: """Filters the inspected elements by type name (e.g. Wall) and - returns them as list or dict if wanted + returns them as a list or dict if wanted Args: elements: dict or list with all bim2sim elements type_name: str or element type to filter for - create_dict (Boolean): True if a dict instead of a list should be - created + create_dict (Boolean): True if a dict instead of a list should be created + include_sub_classes (Boolean): True if all subclasses of the given + type_name are included as well Returns: elements_filtered: list of all bim2sim elements of type type_name """ from bim2sim.elements.base_elements import SerializedElement + elements_filtered = [] - list_elements = elements.values() if type(elements) is dict \ - else elements + list_elements = elements.values() if isinstance(elements, dict) else elements + + # Handle filtering based on type_name being a string or class if isinstance(type_name, str): + # Direct string comparison, subclasses are irrelevant for instance in list_elements: if isinstance(instance, SerializedElement): if instance.element_type == type_name: @@ -281,12 +290,25 @@ def filter_elements(elements: Union[dict, list], type_name, create_dict=False)\ if type_name in type(instance).__name__: elements_filtered.append(instance) else: + # type_name is a class type, include subclasses if requested + if include_sub_classes: + type_classes = all_subclasses(type_name, include_self=True) + else: + type_classes = [type_name] + for instance in list_elements: if isinstance(instance, SerializedElement): - if instance.element_type == type_name.__name__: + # Check the class type or subclass match + if any(isinstance(instance, cls) for cls in type_classes): elements_filtered.append(instance) if type_name is type(instance): elements_filtered.append(instance) + else: + # Direct instance check for non-SerializedElement instances + if any(isinstance(instance, cls) for cls in type_classes): + elements_filtered.append(instance) + + # Return the results in the desired format if not create_dict: return elements_filtered else: From 723ea089e7f3096c66f3e2cff8d4e7c995dd7b2a Mon Sep 17 00:00:00 2001 From: David Paul Jansen Date: Mon, 19 Aug 2024 11:52:06 +0200 Subject: [PATCH 050/125] update work on spawn --- .../examples/e3_single_radiator_in_building.py | 8 ++++---- .../bim2sim_energyplus/task/ep_create_idf.py | 4 ++-- bim2sim/sim_settings.py | 2 +- bim2sim/tasks/hvac/make_graph.py | 7 ++++++- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/examples/e3_single_radiator_in_building.py b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/examples/e3_single_radiator_in_building.py index ad1453dce5..840b11a70d 100644 --- a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/examples/e3_single_radiator_in_building.py +++ b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/examples/e3_single_radiator_in_building.py @@ -70,12 +70,12 @@ def run_example_simple_hvac_aixlib(): *(True,) * 6, # boiler efficiency, flow temp, power consumption, # return temp - 0.95, 70, 79, 50, - *(500, 50,) * 7, + # 0.95, 70, 79, 50, + # *(500, 50,) * 7, # rated_mass_flow for distributor, rated of boiler pump - 1, + # 1, # rated_mass_flow for boiler pump, rated dp of boiler pump - 0.9, 4500, + # 0.9, 4500, # body mass and heat capacity for all space heaters ) diff --git a/bim2sim/plugins/PluginEnergyPlus/bim2sim_energyplus/task/ep_create_idf.py b/bim2sim/plugins/PluginEnergyPlus/bim2sim_energyplus/task/ep_create_idf.py index dee8c0c71c..faab184c21 100644 --- a/bim2sim/plugins/PluginEnergyPlus/bim2sim_energyplus/task/ep_create_idf.py +++ b/bim2sim/plugins/PluginEnergyPlus/bim2sim_energyplus/task/ep_create_idf.py @@ -51,7 +51,7 @@ def __init__(self, playground): super().__init__(playground) self.idf = None - def run(self, elements: dict, weather_file: Path) -> tuple[IDF, Path]: + def run(self, elements: dict, weather_file_ep: Path) -> tuple[IDF, Path]: """Execute all methods to export an IDF from BIM2SIM. This task includes all functions for exporting EnergyPlus Input files @@ -72,7 +72,7 @@ def run(self, elements: dict, weather_file: Path) -> tuple[IDF, Path]: Args: elements (dict): dictionary in the format dict[guid: element], holds preprocessed elements including space boundaries. - weather_file (Path): path to weather file in .epw data format + weather_file_ep (Path): path to weather file in .epw data format Returns: idf (IDF): EnergyPlus input file sim_results_path (Path): path to the simulation results. diff --git a/bim2sim/sim_settings.py b/bim2sim/sim_settings.py index 8cfc0da061..b808a3bbfb 100644 --- a/bim2sim/sim_settings.py +++ b/bim2sim/sim_settings.py @@ -989,7 +989,7 @@ class SpawnOfEnergyPlusSimSettings(EnergyPlusSimSettings, PlantSimSettings): def __init__(self): super().__init__() self.relevant_elements = {*bps_elements.items, *hvac_elements.items, - Material} - {bps_elements.Plate} + Material} add_natural_ventilation = BooleanSetting( default=True, diff --git a/bim2sim/tasks/hvac/make_graph.py b/bim2sim/tasks/hvac/make_graph.py index 986c65a54e..c290626693 100644 --- a/bim2sim/tasks/hvac/make_graph.py +++ b/bim2sim/tasks/hvac/make_graph.py @@ -1,6 +1,8 @@ from bim2sim.elements.base_elements import Material from bim2sim.elements.graphs.hvac_graph import HvacGraph from bim2sim.tasks.base import ITask +from bim2sim.utilities.common_functions import filter_elements +from bim2sim.elements.hvac_elements import HVACProduct class MakeGraph(ITask): @@ -21,7 +23,10 @@ def run(self, elements: dict): The created HVAC graph. """ self.logger.info("Creating graph from IFC elements") + hvac_elements = filter_elements( + elements, HVACProduct, create_dict=True, + include_sub_classes=True) not_mat_elements = \ - {k: v for k, v in elements.items() if not isinstance(v, Material)} + {k: v for k, v in hvac_elements.items() if not isinstance(v, Material)} graph = HvacGraph(not_mat_elements.values()) return graph, From d7d3265d0e612c8c44973d0d334f21dbee49f36e Mon Sep 17 00:00:00 2001 From: David Paul Jansen Date: Mon, 19 Aug 2024 12:03:22 +0200 Subject: [PATCH 051/125] merge dev into branch --- .../PluginSpawn/bim2sim_spawn/__init__.py | 10 +++++----- .../examples/e2_simple_project_bps_spawn.py | 4 ++-- .../PluginSpawn/bim2sim_spawn/sim_settings.py | 20 +++++++++++++++++++ 3 files changed, 27 insertions(+), 7 deletions(-) create mode 100644 bim2sim/plugins/PluginSpawn/bim2sim_spawn/sim_settings.py diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/__init__.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/__init__.py index f4c1c26b47..b9f31a4351 100644 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/__init__.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/__init__.py @@ -1,10 +1,10 @@ -from bim2sim.plugins import Plugin -from bim2sim.tasks import base, common, hvac, bps -from bim2sim.sim_settings import BuildingSimSettings, EnergyPlusSimSettings, \ - SpawnOfEnergyPlusSimSettings -import bim2sim.plugins.PluginSpawn.bim2sim_spawn.tasks as spawn_tasks +import bim2sim.plugins.PluginSpawn.bim2sim_spawn.tasks as spawn_tasks +from bim2sim.plugins import Plugin from bim2sim.plugins.PluginEnergyPlus.bim2sim_energyplus import \ task as ep_tasks +from bim2sim.plugins.PluginSpawn.bim2sim_spawn.sim_settings import \ + SpawnOfEnergyPlusSimSettings +from bim2sim.tasks import common, hvac, bps from bim2sim_aixlib import LoadLibrariesAixLib diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e2_simple_project_bps_spawn.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e2_simple_project_bps_spawn.py index ae8620b007..cc5ec05c2e 100644 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e2_simple_project_bps_spawn.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e2_simple_project_bps_spawn.py @@ -55,7 +55,7 @@ def run_example_1(): # Set other simulation settings, otherwise all settings are set to default project.sim_settings.aggregations = [ - 'Consumer', + # 'Consumer', 'PipeStrand', 'ParallelPump', # 'ConsumerHeatingDistributorModule', @@ -70,7 +70,7 @@ def run_example_1(): 'HVAC-Distributor', # 26 'HVAC-ThreeWayValve', # 39 2010, - *(True,) * 6, + *(True,) * 7, # 0.95, 70, 79, 50, # *(500, 50,) * 7, # 1, diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/sim_settings.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/sim_settings.py new file mode 100644 index 0000000000..8e03ac9d9d --- /dev/null +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/sim_settings.py @@ -0,0 +1,20 @@ +from bim2sim.elements import bps_elements, hvac_elements +from bim2sim.elements.base_elements import Material +from bim2sim.sim_settings import BooleanSetting, \ + PlantSimSettings +from bim2sim.plugins.PluginEnergyPlus.bim2sim_energyplus.sim_settings import \ + EnergyPlusSimSettings + + +class SpawnOfEnergyPlusSimSettings(EnergyPlusSimSettings, PlantSimSettings): + def __init__(self): + super().__init__() + self.relevant_elements = {*bps_elements.items, *hvac_elements.items, + Material} + + add_natural_ventilation = BooleanSetting( + default=True, + description='Add natural ventilation to the building. Natural ' + 'ventilation is not available when cooling is activated.', + for_frontend=True + ) From 4e8fbdd3a3011581fb06ce8946da4c1d4e4bb1e6 Mon Sep 17 00:00:00 2001 From: David Paul Jansen Date: Tue, 27 Aug 2024 14:46:22 +0200 Subject: [PATCH 052/125] adjust example for answers of not aggregating consumers --- .../examples/e2_simple_project_bps_spawn.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e2_simple_project_bps_spawn.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e2_simple_project_bps_spawn.py index cc5ec05c2e..60658cd81f 100644 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e2_simple_project_bps_spawn.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e2_simple_project_bps_spawn.py @@ -52,6 +52,8 @@ def run_example_1(): project.sim_settings.weather_file_path_modelica = ( Path(bim2sim.__file__).parent.parent / 'test/resources/weather_files/DEU_NW_Aachen.105010_TMYx.mos') + # Generate outer heat ports for spawn HVAC sub model + project.sim_settings.outer_heat_ports = True # Set other simulation settings, otherwise all settings are set to default project.sim_settings.aggregations = [ @@ -71,10 +73,10 @@ def run_example_1(): 'HVAC-ThreeWayValve', # 39 2010, *(True,) * 7, - # 0.95, 70, 79, 50, - # *(500, 50,) * 7, - # 1, - # 0.9, 4500, + *(1,)*13, # volume of junctions + *(1,)*4, # rated_pressure_difference + rated_volume_flow for 2 pumps + *(70,50,)*7, # flow and return temp for 7 space heaters + *(1,)*2 # nominal_mass_flow_rate, nominal_pressure_difference for ThreeWayValve ) project.sim_settings.group_unidentified = 'name' @@ -83,5 +85,6 @@ def run_example_1(): handler.handle(project.run()) # run_project(project, ConsoleDecisionHandler()) + if __name__ == '__main__': run_example_1() From de21c8064c0a078d5cdc6459985e5357553ed603 Mon Sep 17 00:00:00 2001 From: David Paul Jansen Date: Tue, 27 Aug 2024 14:46:35 +0200 Subject: [PATCH 053/125] adjust export for Distributor --- bim2sim/tasks/hvac/export.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bim2sim/tasks/hvac/export.py b/bim2sim/tasks/hvac/export.py index 7c95c456d8..e6018cd74b 100644 --- a/bim2sim/tasks/hvac/export.py +++ b/bim2sim/tasks/hvac/export.py @@ -129,9 +129,9 @@ def create_connections(graph: HvacGraph, export_elements: dict) -> list: connection_port_names.append((ports_name['a'], ports_name['b'])) - for distributor in distributors_n: - distributor.export_parameters['n'] = int( - distributors_n[distributor] / 2 - 1) + # for distributor in distributors_n: + # distributor.parameters['n'] = int( + # distributors_n[distributor] / 2 - 1) return connection_port_names From 174240d11f39f48adb3ee076677c0e581f8b7f1b Mon Sep 17 00:00:00 2001 From: David Paul Jansen Date: Tue, 27 Aug 2024 14:51:20 +0200 Subject: [PATCH 054/125] adjust boiler parameters for aixlib and fix outlet/inlet --- .../assets/finder/template_LuArtX_Carf.json | 23 +++++++++++++-- .../elements/aggregation/hvac_aggregations.py | 4 +-- bim2sim/elements/hvac_elements.py | 8 +++--- .../bim2sim_aixlib/models/__init__.py | 28 +++++++++++-------- 4 files changed, 44 insertions(+), 19 deletions(-) diff --git a/bim2sim/assets/finder/template_LuArtX_Carf.json b/bim2sim/assets/finder/template_LuArtX_Carf.json index b570f532f7..621eb8461d 100644 --- a/bim2sim/assets/finder/template_LuArtX_Carf.json +++ b/bim2sim/assets/finder/template_LuArtX_Carf.json @@ -15,8 +15,27 @@ }, "Boiler": { "default_ps": { - "rated_power": ["CarF", "eff.Leistung"], - "nominal_efficiency": ["CarF", "Wirkungsgrad"] + "rated_power" : [ + ["CarF", "eff.Leistung"], + ["VDI 700-Wärmeerzeuger", "Nennleistung"] + ], + "nominal_efficiency": ["CarF", "Wirkungsgrad"], + "flow_temperature": + ["VDI 710.01-Basisdaten", "Maximale Vorlauftemperatur"], + "water_volume": ["VDI 700-Wärmeerzeuger", "Wasserinhalt"], + "dry_mass": ["VDI 700-Wärmeerzeuger", "Netto-Gesamtmasse"], + "minimal_part_load_ratio": + ["VDI 700-Wärmeerzeuger", "untere Modulationsgrenze"] + } + }, + "HeatPump": { + "default_ps": { + "rated_power": ["VDI 700-Produktelementdaten", "Heizleistung (kW)"], + "is_reversible": ["VDI 700-Produktelementdaten", "Kühlfunktion"], + "rated_cooling_power": ["VDI 700-Produktelementdaten", "Kühlleistung"], + "COP": ["VDI 700-Produktelementdaten", "Leistungszahl"], + "internal_pump": ["VDI 710.05-Luft-Wasser-Wärmepumpe", "Heizkreispumpe intern"], + "vdi_performance_data_table": ["VDI-Tables", "Leistungsdaten"] } } } \ No newline at end of file diff --git a/bim2sim/elements/aggregation/hvac_aggregations.py b/bim2sim/elements/aggregation/hvac_aggregations.py index 4c05ec93a5..d6a720e727 100644 --- a/bim2sim/elements/aggregation/hvac_aggregations.py +++ b/bim2sim/elements/aggregation/hvac_aggregations.py @@ -1484,7 +1484,7 @@ def _calc_flow_temperature(self, name) -> ureg.Quantity: in self.whitelist_elements) / len(self.whitelist_elements) flow_temperature = attribute.Attribute( - description="Nominal inlet temperature", + description="Nominal flow temperature", unit=ureg.kelvin, functions=[_calc_flow_temperature], dependant_elements='whitelist_elements' @@ -1497,7 +1497,7 @@ def _calc_return_temperature(self, name) -> ureg.Quantity: in self.whitelist_elements) / len(self.whitelist_elements) return_temperature = attribute.Attribute( - description="Nominal outlet temperature", + description="Nominal return temperature", unit=ureg.kelvin, functions=[_calc_return_temperature], dependant_elements='whitelist_elements' diff --git a/bim2sim/elements/hvac_elements.py b/bim2sim/elements/hvac_elements.py index 2f9a92c1e8..9147642d4a 100644 --- a/bim2sim/elements/hvac_elements.py +++ b/bim2sim/elements/hvac_elements.py @@ -657,13 +657,13 @@ def _calc_min_PLR(self, name) -> ureg.Quantity: unit=ureg.dimensionless, functions=[_calc_min_PLR], ) - flow_temperature = attribute.Attribute( - description="Nominal inlet temperature", + return_temperature = attribute.Attribute( + description="Nominal return temperature", default_ps=('Pset_BoilerTypeCommon', 'WaterInletTemperatureRange'), unit=ureg.celsius, ) - return_temperature = attribute.Attribute( - description="Nominal outlet temperature", + flow_temperature = attribute.Attribute( + description="Nominal flow temperature", default_ps=('Pset_BoilerTypeCommon', 'OutletTemperatureRange'), unit=ureg.celsius, ) diff --git a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py index 578ce53ecf..4c75c710ec 100644 --- a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py +++ b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py @@ -309,8 +309,7 @@ def get_port_name(self, port): class BoilerAggregation(AixLib): # TODO: the model does not exists in AiLib """Modelica AixLib representation of the GeneratorOneFluid aggregation.""" - path = "AixLib.Systems.ModularEnergySystems.Modules.ModularBoiler." \ - "ModularBoiler" + path = "AixLib.Systems.ModularEnergySystems.ModularBoiler.ModularBoiler" represents = [hvac_aggregations.GeneratorOneFluid] def __init__(self, element): @@ -327,24 +326,30 @@ def __init__(self, element): unit=None, required=False, attributes=['has_bypass']) - self._set_parameter(name='QNom', + self._set_parameter(name='Q_flow_nominal', unit=ureg.watt, required=False, check=check_numeric(min_value=0 * ureg.watt), attributes=['rated_power']) - self._set_parameter(name='PLRMin', + self._set_parameter(name='FirRatMin', unit=ureg.dimensionless, required=False, check=check_numeric( min_value=0 * ureg.dimensionless), attributes=['min_PLR']) - self._set_parameter(name='TRetNom', + self._set_parameter(name='TRet_nominal', unit=ureg.kelvin, required=False, check=check_numeric( min_value=0 * ureg.kelvin), attributes=['return_temperature']) - self._set_parameter(name='dTWaterNom', + self._set_parameter(name='TSup_nominal', + unit=ureg.kelvin, + required=False, + check=check_numeric( + min_value=0 * ureg.kelvin), + attributes=['flow_temperature']) + self._set_parameter(name='dT_nominal', unit=ureg.kelvin, required=False, check=check_numeric(min_value=0 * ureg.kelvin), @@ -362,21 +367,22 @@ def get_port_name(self, port): else: return super().get_port_name(port) + class Distributor(AixLib): path = "AixLib.Fluid.HeatExchangers.ActiveWalls.Distributor" represents = [hvac.Distributor] def __init__(self, element: hvac.Distributor): super().__init__(element) - n_ports = self.get_n_ports() + # n_ports = self.get_n_ports() self._set_parameter(name='redeclare package Medium', unit=None, required=False, value=MEDIUM_WATER) - self._set_parameter(name='n', - unit=None, - required=False, - value=n_ports) + # self._set_parameter(name='n', + # unit=None, + # required=False, + # value=n_ports) self._set_parameter(name='m_flow_nominal', unit=ureg.kg / ureg.s, required=False, From 451ee95ef34ab36c8a48dde7212994cbba488730 Mon Sep 17 00:00:00 2001 From: David Paul Jansen Date: Tue, 27 Aug 2024 15:04:43 +0200 Subject: [PATCH 055/125] split up modelica export into model creation and export tasks --- bim2sim/tasks/hvac/export.py | 121 ++++++++++++++++++++++++++++------- 1 file changed, 99 insertions(+), 22 deletions(-) diff --git a/bim2sim/tasks/hvac/export.py b/bim2sim/tasks/hvac/export.py index e6018cd74b..bc81d4c251 100644 --- a/bim2sim/tasks/hvac/export.py +++ b/bim2sim/tasks/hvac/export.py @@ -1,4 +1,8 @@ +import re from datetime import datetime +from pathlib import Path + +from mako.template import Template from bim2sim.elements import hvac_elements as hvac from bim2sim.elements.base_elements import ProductBased @@ -6,13 +10,15 @@ from bim2sim.export import modelica from bim2sim.export.modelica import HeatTransferType, ModelicaParameter from bim2sim.tasks.base import ITask +from bim2sim.export.modelica import help_package, help_package_order -class Export(ITask): +class CreateModelicaModel(ITask): """Export to Dymola/Modelica""" reads = ('libraries', 'graph') - touches = ('modelica_model',) + touches = ('modelica_model','connections', 'cons_heat_ports_conv', + 'cons_heat_ports_rad') final = True def run(self, libraries: tuple, graph: HvacGraph): @@ -36,14 +42,14 @@ def run(self, libraries: tuple, graph: HvacGraph): self.logger.info("Export to Modelica code") elements = graph.elements - connections = graph.get_connections() - modelica.ModelicaElement.init_factory(libraries) export_elements = {inst: modelica.ModelicaElement.factory(inst) for inst in elements} + connections = self.create_connections(graph, export_elements) # Perform decisions for requested but not existing attributes yield from ProductBased.get_pending_attribute_decisions(elements) + # Perform decisions for required but not existing modelica parameters yield from ModelicaParameter.get_pending_parameter_decisions() # All parameters are checked against the specified check function and @@ -51,8 +57,6 @@ def run(self, libraries: tuple, graph: HvacGraph): for instance in export_elements.values(): instance.collect_params() - connection_port_names = self.create_connections(graph, export_elements) - inner_heat_port_cons_conv, inner_heat_port_cons_rad = ( self.create_inner_heat_port_connections()) @@ -67,23 +71,10 @@ def run(self, libraries: tuple, graph: HvacGraph): inner_heat_port_cons_conv) cons_heat_ports_rad = (outer_heat_port_cons_rad + inner_heat_port_cons_rad) - self.logger.info( - "Creating Modelica model with %d model elements " - "and %d " - "connections.", - len(export_elements), len(connection_port_names)) - modelica_model = modelica.ModelicaModel( - name="bim2sim_" + self.prj_name, - comment=f"Autogenerated by BIM2SIM on " - f"{datetime.now():%Y-%m-%d %H:%M:%S%z}", - modelica_elements=list(export_elements.values()), - connections=connection_port_names, - connections_heat_ports_conv=cons_heat_ports_conv, - connections_heat_ports_rad=cons_heat_ports_rad - ) - modelica_model.save(self.paths.export) - return modelica_model, + # TODO #1 integrate heat ports in connections + return (export_elements, connections, + cons_heat_ports_conv, cons_heat_ports_rad) @staticmethod def create_connections(graph: HvacGraph, export_elements: dict) -> list: @@ -167,3 +158,89 @@ def create_outer_heat_port_connections(export_elements: list) -> \ def create_inner_heat_port_connections(self) -> [list, list]: # TODO if this is needed return [], [] + + +class Export(ITask): + """Export to Dymola/Modelica""" + + reads = ('export_elements', 'connections','cons_heat_ports_conv', + 'cons_heat_ports_rad') + touches = ('modelica_model',) + final = True + + def run(self, export_elements: dict, connections: list, + cons_heat_ports_conv: list, cons_heat_ports_rad: list): + """Export modelica elements and connections to Modelica code. + + This method performs the following steps: + 1. Creates a Modelica model with the exported elements and connections. + 2. Saves the Modelica model to the specified export path. + + Args: + export_elements: + connections: + cons_heat_ports_conv: + cons_heat_ports_rad: + + Returns: + modelica_model: + """ + self.logger.info( + "Creating Modelica model with %d model elements " + "and %d connections.", + len(export_elements), len(connections)) + regex = re.compile("[^a-zA-z0-9]") + package_name = regex.sub( + "", "bim2sim_aixlib_" + self.prj_name) + # TODO + # Check if regression is needed: + if self.playground.sim_settings.regression_variables: + regression = True + else: + regression = False + model_name = 'Hydraulic' + # TODO regression handling in template + modelica_model = modelica.ModelicaModel( + name=model_name, + comment=f"Autogenerated by BIM2SIM on " + f"{datetime.now():%Y-%m-%d %H:%M:%S%z}", + modelica_elements=list(export_elements.values()), + connections=connections, + connections_heat_ports_conv=cons_heat_ports_conv, + connections_heat_ports_rad=cons_heat_ports_rad + ) + # create base package structure + export_pkg_path = self.paths.export / Path(package_name) + Path.mkdir(export_pkg_path, exist_ok=True) + help_package( + path=export_pkg_path, + name=export_pkg_path.stem, + within="") + help_package_order(path=export_pkg_path, package_list=[ + model_name, + # 'building_model', + # 'hvac_model' + ]) + Path.mkdir(export_pkg_path / model_name, exist_ok=True) + # export hydraulic model itself + modelica_model.save_pkg(export_pkg_path / model_name) + # Create folders for regression testing if needed + if self.playground.sim_settings.regression_variables: + dir_dymola = export_pkg_path / "Resources" / "Scripts" / "Dymola" + dir_dymola.mkdir(parents=True, exist_ok=True) + test_script_template = Template( + filename=str(self.paths.assets / + 'templates/modelica/modelicaTestScript')) + regression_test_path = ( + dir_dymola / model_name / f"{model_name}.mos") + (dir_dymola / model_name).mkdir(exist_ok=True) + with open(regression_test_path, 'w') as out_file: + out_file.write(test_script_template.render_unicode( + package_name=package_name, + model_name=model_name, + stop_time=3600 * 24 * 365, + regression_variables= + self.playground.sim_settings.regression_variables, + )) + out_file.close() + return modelica_model, From 93a2a93450fb13082c7e52d989ce1a6299a60b87 Mon Sep 17 00:00:00 2001 From: David Paul Jansen Date: Tue, 27 Aug 2024 16:40:29 +0200 Subject: [PATCH 056/125] continue implementation of zone to radiator mapping --- .../assets/templates/modelica/tmplModel.txt | 2 + .../modelica/tmplSpawnTotalModel.txt | 15 +++- bim2sim/export/modelica/__init__.py | 31 +++++--- .../PluginAixLib/bim2sim_aixlib/__init__.py | 2 + .../PluginSpawn/bim2sim_spawn/__init__.py | 3 +- .../tasks/export_spawn_building.py | 10 +-- .../bim2sim_spawn/tasks/export_spawn_total.py | 72 +++++++++++++++---- bim2sim/tasks/hvac/__init__.py | 1 + bim2sim/tasks/hvac/export.py | 32 +-------- 9 files changed, 106 insertions(+), 62 deletions(-) diff --git a/bim2sim/assets/templates/modelica/tmplModel.txt b/bim2sim/assets/templates/modelica/tmplModel.txt index e0d6d29054..211327bc8e 100644 --- a/bim2sim/assets/templates/modelica/tmplModel.txt +++ b/bim2sim/assets/templates/modelica/tmplModel.txt @@ -1,5 +1,7 @@ model ${model.name} "${model.comment}" +% if not model.connections_heat_ports_conv or not model.connections_heat_ports_rad: extends Modelica.Icons.Example; +% endif import SI = Modelica.Units.SI;\ %if unknowns: diff --git a/bim2sim/assets/templates/modelica/tmplSpawnTotalModel.txt b/bim2sim/assets/templates/modelica/tmplSpawnTotalModel.txt index 78f4c50261..9f2374c6f6 100644 --- a/bim2sim/assets/templates/modelica/tmplSpawnTotalModel.txt +++ b/bim2sim/assets/templates/modelica/tmplSpawnTotalModel.txt @@ -1,10 +1,21 @@ within ${within}; model ${model_name} "${model_comment}" import SI = Modelica.Units.SI; - building_model building_model1 + ${model_name_building} ${model_name_building.lower()} annotation (Placement(transformation(extent={{-14,28},{6,48}}))); - hvac_model hvac_model1 + ${model_name_hydraulic} ${model_name_hydraulic.lower()} annotation (Placement(transformation(extent={{-14,-62},{6,-42}}))); +equation + + // heatport connections + % for cons in cons_heat_ports_conv_building_hvac: + connect${str(cons).replace("'","")} annotation (Line(points={{0,0},{0,0},{0,0},{0,0},{0,0}}, color={191,0,0})); + % endfor + % for cons in cons_heat_ports_rad_building_hvac: + connect${str(cons).replace("'","")} annotation (Line(points={{0,0},{0,0},{0,0},{0,0},{0,0}}, color={191,0,0})); + % endfor + + annotation (Icon(coordinateSystem(preserveAspectRatio=false)), Diagram( coordinateSystem(preserveAspectRatio=false))); end ${model_name}; diff --git a/bim2sim/export/modelica/__init__.py b/bim2sim/export/modelica/__init__.py index 589a6321a7..63c1f577f9 100644 --- a/bim2sim/export/modelica/__init__.py +++ b/bim2sim/export/modelica/__init__.py @@ -1,7 +1,5 @@ """Package for Modelica export""" -import codecs import logging -import os from enum import Enum from pathlib import Path from threading import Lock @@ -200,25 +198,38 @@ def unknown_params(self) -> list: unknown_parameters.extend(unknown_parameter) return unknown_parameters - def save(self, path: str): + def save(self, path: Path): """ Save the model as Modelica file. Args: - path (str): The path where the Modelica file should be saved. + path (Path): The path where the Modelica file should be saved. """ - _path = os.path.normpath(path) - if os.path.isdir(_path): - _path = os.path.join(_path, self.name) + _path = path.resolve() - if not _path.endswith(".mo"): - _path += ".mo" + if _path.is_dir(): + _path = _path / self.name + + if not str(_path).endswith(".mo"): + _path = _path.with_suffix(".mo") data = self.render_modelica_code() user_logger.info("Saving '%s' to '%s'", self.name, _path) - with codecs.open(_path, "w", "utf-8") as file: + with _path.open("w", encoding="utf-8") as file: file.write(data) + def save_pkg(self, pkg_path: Path): + + pkg_name = pkg_path.stem + help_package(path=pkg_path, name=pkg_name, + within=pkg_path.parent.stem) + help_package_order(path=pkg_path, package_list=[ + pkg_name, + # 'building_model', + # 'hvac_model' + ]) + self.save(pkg_path / pkg_name) + class ModelicaElement: """ Modelica model element diff --git a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/__init__.py b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/__init__.py index 337546f3e5..66302be4d5 100644 --- a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/__init__.py +++ b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/__init__.py @@ -33,7 +33,9 @@ class PluginAixLib(Plugin): hvac.Reduce, hvac.DeadEnds, LoadLibrariesAixLib, + hvac.CreateModelicaModel, hvac.Export, + ] def create_modelica_table_from_list(self, curve): diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/__init__.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/__init__.py index b9f31a4351..d34ccc6ec0 100644 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/__init__.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/__init__.py @@ -32,8 +32,7 @@ class PluginSpawnOfEP(Plugin): hvac.Reduce, hvac.DeadEnds, LoadLibrariesAixLib, - hvac.Export, - + hvac.CreateModelicaModel, spawn_tasks.ExportSpawnBuilding, spawn_tasks.ExportSpawnTotal, diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_building.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_building.py index 9595c8b48a..b2be6b710f 100644 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_building.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_building.py @@ -13,7 +13,7 @@ class ExportSpawnBuilding(ITask): """Export building for SpawnOfEnergyPlus model to Modelica""" reads = ('elements', 'weather_file_modelica', 'weather_file_ep') - touches = ('zone_names',) + touches = ('zone_names', 'model_name_building') final = True def run(self, elements: dict, weather_file_modelica: Path, @@ -43,9 +43,10 @@ def run(self, elements: dict, weather_file_modelica: Path, # TODO multithreading lock needed? see modelica/__init__.py for example # with lock: + model_name_building = 'BuildingModel' building_template_data = template_bldg.render( within='bim2sim_spawn', - model_name='building_model', + model_name=model_name_building, model_comment='test2', weather_path_ep=to_modelica_spawn(weather_path_ep), weather_path_mos=to_modelica_spawn(weather_path_mos), @@ -53,11 +54,12 @@ def run(self, elements: dict, weather_file_modelica: Path, idf_path=to_modelica_spawn(idf_path), n_zones=len(zone_names) ) - export_path = package_path / 'building_model.mo' + export_path = package_path / f"{model_name_building}.mo" # user_logger.info("Saving '%s' to '%s'", self.name, _path) with codecs.open(export_path, "w", "utf-8") as file: file.write(building_template_data) - return zone_names, + + return zone_names, model_name_building def get_zone_names(self): # TODO #1: get names from IDF or EP process for ep zones in diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_total.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_total.py index d67e21dce9..9e7db0282d 100644 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_total.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_total.py @@ -18,21 +18,25 @@ class ExportSpawnTotal(ITask): """Export total model for SpawnOfEnergyPlus model to Modelica""" reads = ('elements', 'weather_file_modelica', 'weather_file_ep', - 'zone_names') + 'zone_names', 'model_name_building', 'export_elements', 'connections', + 'cons_heat_ports_conv', 'cons_heat_ports_rad') final = True def run(self, elements: dict, weather_file_modelica: Path, - weather_file_ep: Path, zone_names): + weather_file_ep: Path, zone_names, model_name_building, + export_elements, connections, + cons_heat_ports_conv, cons_heat_ports_rad): self.logger.info("Export total Spawn model to Modelica code") package_path = self.paths.export / 'bim2sim_spawn' os.makedirs(package_path, exist_ok=True) - + model_name_hydraulic = 'HVACModel' + model_name_total = 'TotalModel' help_package(path=package_path, name=package_path.stem, within="") help_package_order(path=package_path, package_list=[ - 'total_model', - 'building_model', - 'hvac_model']) + model_name_total, + model_name_building, + model_name_hydraulic]) # EXPORT MULTIZONE MODEL # This is a "static" model for now, means no elements are created @@ -50,30 +54,68 @@ def run(self, elements: dict, weather_file_modelica: Path, tz_elements = filter_elements(elements, 'ThermalZone') space_heater_elements = filter_elements(elements, 'SpaceHeater') - # ToDO Missing: list with space heater guids for array indexing tz_space_heater_mapping = [] for tz in tz_elements: for space_heater in space_heater_elements: - # TODO check SpaceHeater is some how semantically connected - # to the Space in the IFC, is this correct? if PyOCCTools.obj2_in_obj1( obj1=tz.space_shape, obj2=space_heater.shape): tz_space_heater_mapping.append( (tz.guid, space_heater.guid)) - # ToDo radiator_names_list is missing - # + # TODO this is gpt bullshit, refactor! + # Create a map from space heater IDs to zone IDs + space_heater_to_zone = {heater: zone for zone, heater in + tz_space_heater_mapping} + + # Create the conv and rad mappings + cons_heat_ports_conv_building_hvac = [] + cons_heat_ports_rad_building_hvac = [] + + # Fill cons_heat_ports_conv_building_hvac + for hvac_port, heater_conn in cons_heat_ports_conv: + heater_id = (heater_conn.split('.')[0].replace('spaceheater_', '')).replace('_', '$') + zone_id = space_heater_to_zone[heater_id] + building_port = f"{model_name_building.lower()}.heaPorCon[{tz_space_heater_mapping.index((zone_id, heater_id)) + 1}]" + cons_heat_ports_conv_building_hvac.append( + (building_port, f"{model_name_hydraulic.lower()}.{hvac_port}")) + + # Fill cons_heat_ports_rad_building_hvac + for hvac_port, heater_conn in cons_heat_ports_rad: + heater_id = (heater_conn.split('.')[0].replace('spaceheater_', '')).replace('_', '$') + zone_id = space_heater_to_zone[heater_id] + building_port = f"{model_name_building.lower()}.heaPorRad[{tz_space_heater_mapping.index((zone_id, heater_id)) + 1}]" + cons_heat_ports_rad_building_hvac.append( + (building_port, f"{model_name_hydraulic.lower()}.{hvac_port}")) + + # hvac/hydraulic model + + modelica_model = modelica.ModelicaModel( + name=model_name_hydraulic, + comment=f"Autogenerated by BIM2SIM on " + f"{datetime.now():%Y-%m-%d %H:%M:%S%z}", + modelica_elements=list(export_elements.values()), + connections=connections, + connections_heat_ports_conv=cons_heat_ports_conv, + connections_heat_ports_rad=cons_heat_ports_rad + ) + modelica_model.save(package_path / f"{model_name_hydraulic}.mo") + # TODO multithreading lock needed? see modelica/__init__.py for example # with lock: + total_template_data = template_total.render( within='bim2sim_spawn', - model_name='total_model', + model_name=model_name_total, model_comment='test2', weather_path_mos=to_modelica_spawn(weather_path_mos), - bldg_to_hvac_heat_ports_connections=... - # n_zones=len(zone_names) + model_name_building=model_name_building, + model_name_hydraulic=model_name_hydraulic, + cons_heat_ports_conv_building_hvac= + cons_heat_ports_conv_building_hvac, + cons_heat_ports_rad_building_hvac= + cons_heat_ports_rad_building_hvac, ) - export_path = package_path / 'total_model.mo' + export_path = package_path / f"{model_name_total}.mo" # user_logger.info("Saving '%s' to '%s'", self.name, _path) with codecs.open(export_path, "w", "utf-8") as file: file.write(total_template_data) diff --git a/bim2sim/tasks/hvac/__init__.py b/bim2sim/tasks/hvac/__init__.py index d52f5c87ba..c4c9bb7fa8 100644 --- a/bim2sim/tasks/hvac/__init__.py +++ b/bim2sim/tasks/hvac/__init__.py @@ -4,6 +4,7 @@ from .obsolete import Enrich, DetectCycles from .make_graph import MakeGraph from .reduce import Reduce +from .export import CreateModelicaModel from .export import Export from .connect_elements import ConnectElements from .load_standardlibrary import LoadLibrariesStandardLibrary diff --git a/bim2sim/tasks/hvac/export.py b/bim2sim/tasks/hvac/export.py index bc81d4c251..7a066d19a3 100644 --- a/bim2sim/tasks/hvac/export.py +++ b/bim2sim/tasks/hvac/export.py @@ -17,9 +17,8 @@ class CreateModelicaModel(ITask): """Export to Dymola/Modelica""" reads = ('libraries', 'graph') - touches = ('modelica_model','connections', 'cons_heat_ports_conv', + touches = ('export_elements', 'connections', 'cons_heat_ports_conv', 'cons_heat_ports_rad') - final = True def run(self, libraries: tuple, graph: HvacGraph): """Export HVAC graph to Modelica code. @@ -139,6 +138,7 @@ def create_outer_heat_port_connections(export_elements: list) -> \ radiative_heat_port_connections = [] convective_ports_index = 1 radiative_ports_index = 1 + for ele in export_elements: for heat_port in ele.heat_ports: if heat_port.heat_transfer_type == HeatTransferType.CONVECTIVE: @@ -163,7 +163,7 @@ def create_inner_heat_port_connections(self) -> [list, list]: class Export(ITask): """Export to Dymola/Modelica""" - reads = ('export_elements', 'connections','cons_heat_ports_conv', + reads = ('export_elements', 'connections', 'cons_heat_ports_conv', 'cons_heat_ports_rad') touches = ('modelica_model',) final = True @@ -192,14 +192,7 @@ def run(self, export_elements: dict, connections: list, regex = re.compile("[^a-zA-z0-9]") package_name = regex.sub( "", "bim2sim_aixlib_" + self.prj_name) - # TODO - # Check if regression is needed: - if self.playground.sim_settings.regression_variables: - regression = True - else: - regression = False model_name = 'Hydraulic' - # TODO regression handling in template modelica_model = modelica.ModelicaModel( name=model_name, comment=f"Autogenerated by BIM2SIM on " @@ -224,23 +217,4 @@ def run(self, export_elements: dict, connections: list, Path.mkdir(export_pkg_path / model_name, exist_ok=True) # export hydraulic model itself modelica_model.save_pkg(export_pkg_path / model_name) - # Create folders for regression testing if needed - if self.playground.sim_settings.regression_variables: - dir_dymola = export_pkg_path / "Resources" / "Scripts" / "Dymola" - dir_dymola.mkdir(parents=True, exist_ok=True) - test_script_template = Template( - filename=str(self.paths.assets / - 'templates/modelica/modelicaTestScript')) - regression_test_path = ( - dir_dymola / model_name / f"{model_name}.mos") - (dir_dymola / model_name).mkdir(exist_ok=True) - with open(regression_test_path, 'w') as out_file: - out_file.write(test_script_template.render_unicode( - package_name=package_name, - model_name=model_name, - stop_time=3600 * 24 * 365, - regression_variables= - self.playground.sim_settings.regression_variables, - )) - out_file.close() return modelica_model, From 689dfb08781f7bb6c95c1e66f58207c3950e91e9 Mon Sep 17 00:00:00 2001 From: David Paul Jansen Date: Wed, 28 Aug 2024 09:06:11 +0200 Subject: [PATCH 057/125] finish export for spawn --- .../bim2sim_spawn/tasks/export_spawn_total.py | 284 +++++++++++++----- bim2sim/tasks/hvac/export.py | 2 + 2 files changed, 214 insertions(+), 72 deletions(-) diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_total.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_total.py index 9e7db0282d..64724d71f5 100644 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_total.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_total.py @@ -1,11 +1,14 @@ +import codecs import os +from collections import defaultdict from datetime import datetime from pathlib import Path -import codecs +from typing import List, Tuple, Optional, Dict +from threading import Lock + from mako.template import Template import bim2sim -from bim2sim.elements.base_elements import ProductBased from bim2sim.export import modelica from bim2sim.export.modelica import help_package, help_package_order from bim2sim.plugins.PluginSpawn.bim2sim_spawn.models import to_modelica_spawn @@ -13,81 +16,143 @@ from bim2sim.utilities.common_functions import filter_elements from bim2sim.utilities.pyocc_tools import PyOCCTools +TEMPLATE_PATH_TOTAL = Path(bim2sim.__file__).parent / \ + 'assets/templates/modelica/tmplSpawnTotalModel.txt' +TEMPLATE_TOTAL_STR = TEMPLATE_PATH_TOTAL.read_text() +TEMPLATE_TOTAL = Template(TEMPLATE_TOTAL_STR) +LOCK = Lock() + class ExportSpawnTotal(ITask): """Export total model for SpawnOfEnergyPlus model to Modelica""" - reads = ('elements', 'weather_file_modelica', 'weather_file_ep', - 'zone_names', 'model_name_building', 'export_elements', 'connections', - 'cons_heat_ports_conv', 'cons_heat_ports_rad') + reads = ( + 'elements', 'weather_file_modelica', 'weather_file_ep', + 'zone_names', 'model_name_building', 'export_elements', + 'connections', 'cons_heat_ports_conv', 'cons_heat_ports_rad' + ) final = True def run(self, elements: dict, weather_file_modelica: Path, weather_file_ep: Path, zone_names, model_name_building, export_elements, connections, - cons_heat_ports_conv, cons_heat_ports_rad): + cons_heat_ports_conv: List[Tuple[str, str]], + cons_heat_ports_rad: List[Tuple[str, str]]): + """Run the export process to generate the Modelica code. + + Args: + elements (dict): The elements data. + weather_file_modelica (Path): Path to the Modelica weather file. + weather_file_ep (Path): Path to the EnergyPlus weather file. + zone_names: Zone names data. + model_name_building (str): The name of the building model. + export_elements: Elements to export. + connections: Connections data. + cons_heat_ports_conv: List of convective heat port connections. + cons_heat_ports_rad: List of radiative heat port connections. + """ self.logger.info("Export total Spawn model to Modelica code") package_path = self.paths.export / 'bim2sim_spawn' - os.makedirs(package_path, exist_ok=True) + self._prepare_package_path(package_path) model_name_hydraulic = 'HVACModel' model_name_total = 'TotalModel' - help_package(path=package_path, name=package_path.stem, within="") - help_package_order(path=package_path, package_list=[ - model_name_total, - model_name_building, - model_name_hydraulic]) - - # EXPORT MULTIZONE MODEL - # This is a "static" model for now, means no elements are created - # dynamically but only the parameters are changed based on render - # function - templ_path_total = Path( - bim2sim.__file__).parent / \ - ('assets/templates/modelica/tmplSpawnTotalModel' - '.txt') - - with open(templ_path_total) as f: - template_total_str = f.read() - template_total = Template(template_total_str) + + self._create_modelica_help_package( + package_path, model_name_total, model_name_building, + model_name_hydraulic + ) + weather_path_mos = weather_file_modelica tz_elements = filter_elements(elements, 'ThermalZone') - space_heater_elements = filter_elements(elements, - 'SpaceHeater') - tz_space_heater_mapping = [] + space_heater_elements = filter_elements(elements, 'SpaceHeater') + + zone_to_heaters = self._group_space_heaters_by_zone( + tz_elements, space_heater_elements + ) + + cons_heat_ports_conv_building_hvac = self.get_port_mapping( + cons_heat_ports_conv, "Con", model_name_building, + model_name_hydraulic, zone_to_heaters + ) + cons_heat_ports_rad_building_hvac = self.get_port_mapping( + cons_heat_ports_rad, "Rad", model_name_building, + model_name_hydraulic, zone_to_heaters + ) + + self._save_modelica_model( + export_elements, connections, cons_heat_ports_conv, + cons_heat_ports_rad, model_name_hydraulic, package_path + ) + + self._save_total_modelica_model( + model_name_total, model_name_building, model_name_hydraulic, + cons_heat_ports_conv_building_hvac, + cons_heat_ports_rad_building_hvac, + weather_path_mos, package_path + ) + + @staticmethod + def _prepare_package_path(package_path: Path): + """Prepare the package directory for saving files. + + Args: + package_path (Path): The path to the package directory. + """ + os.makedirs(package_path, exist_ok=True) + + @staticmethod + def _create_modelica_help_package( + package_path: Path, model_name_total: str, + model_name_building: str, model_name_hydraulic: str): + """Create the Modelica help package. + + Args: + package_path (Path): The path to the package directory. + model_name_total (str): The name of the total model. + model_name_building (str): The name of the building model. + model_name_hydraulic (str): The name of the hydraulic model. + """ + help_package(path=package_path, name=package_path.stem, within="") + help_package_order(path=package_path, package_list=[ + model_name_total, model_name_building, model_name_hydraulic + ]) + + @staticmethod + def _group_space_heaters_by_zone( + tz_elements: List, space_heater_elements: List) -> Dict[str, List[str]]: + """Group space heaters by their respective zones. + + Args: + tz_elements (List): List of thermal zone elements. + space_heater_elements (List): List of space heater elements. + + Returns: + Dict[str, List[str]]: A dictionary mapping zone GUIDs to lists of + heater GUIDs. + """ + zone_to_heaters = defaultdict(list) for tz in tz_elements: for space_heater in space_heater_elements: if PyOCCTools.obj2_in_obj1( obj1=tz.space_shape, obj2=space_heater.shape): - tz_space_heater_mapping.append( - (tz.guid, space_heater.guid)) - # TODO this is gpt bullshit, refactor! - # Create a map from space heater IDs to zone IDs - space_heater_to_zone = {heater: zone for zone, heater in - tz_space_heater_mapping} - - # Create the conv and rad mappings - cons_heat_ports_conv_building_hvac = [] - cons_heat_ports_rad_building_hvac = [] - - # Fill cons_heat_ports_conv_building_hvac - for hvac_port, heater_conn in cons_heat_ports_conv: - heater_id = (heater_conn.split('.')[0].replace('spaceheater_', '')).replace('_', '$') - zone_id = space_heater_to_zone[heater_id] - building_port = f"{model_name_building.lower()}.heaPorCon[{tz_space_heater_mapping.index((zone_id, heater_id)) + 1}]" - cons_heat_ports_conv_building_hvac.append( - (building_port, f"{model_name_hydraulic.lower()}.{hvac_port}")) - - # Fill cons_heat_ports_rad_building_hvac - for hvac_port, heater_conn in cons_heat_ports_rad: - heater_id = (heater_conn.split('.')[0].replace('spaceheater_', '')).replace('_', '$') - zone_id = space_heater_to_zone[heater_id] - building_port = f"{model_name_building.lower()}.heaPorRad[{tz_space_heater_mapping.index((zone_id, heater_id)) + 1}]" - cons_heat_ports_rad_building_hvac.append( - (building_port, f"{model_name_hydraulic.lower()}.{hvac_port}")) - - # hvac/hydraulic model + zone_to_heaters[tz.guid].append(space_heater.guid) + return zone_to_heaters + @staticmethod + def _save_modelica_model( + export_elements, connections, cons_heat_ports_conv, + cons_heat_ports_rad, model_name_hydraulic: str, package_path: Path): + """Save the Modelica model file. + + Args: + export_elements: Elements to export. + connections: Connections data. + cons_heat_ports_conv: List of convective heat port connections. + cons_heat_ports_rad: List of radiative heat port connections. + model_name_hydraulic (str): The name of the hydraulic model. + package_path (Path): The path to the package directory. + """ modelica_model = modelica.ModelicaModel( name=model_name_hydraulic, comment=f"Autogenerated by BIM2SIM on " @@ -99,24 +164,99 @@ def run(self, elements: dict, weather_file_modelica: Path, ) modelica_model.save(package_path / f"{model_name_hydraulic}.mo") - # TODO multithreading lock needed? see modelica/__init__.py for example - # with lock: - - total_template_data = template_total.render( - within='bim2sim_spawn', - model_name=model_name_total, - model_comment='test2', - weather_path_mos=to_modelica_spawn(weather_path_mos), - model_name_building=model_name_building, - model_name_hydraulic=model_name_hydraulic, - cons_heat_ports_conv_building_hvac= - cons_heat_ports_conv_building_hvac, - cons_heat_ports_rad_building_hvac= - cons_heat_ports_rad_building_hvac, - ) + def _save_total_modelica_model( + self, model_name_total: str, model_name_building: str, + model_name_hydraulic: str, + cons_heat_ports_conv_building_hvac: List[Tuple[str, str]], + cons_heat_ports_rad_building_hvac: List[Tuple[str, str]], + weather_path_mos: Path, package_path: Path): + """Render and save the total Modelica model file using a template. + + Args: + model_name_total (str): The name of the total model. + model_name_building (str): The name of the building model. + model_name_hydraulic (str): The name of the hydraulic model. + cons_heat_ports_conv_building_hvac (List[Tuple[str, str]]): + List of convective heat port mappings. + cons_heat_ports_rad_building_hvac (List[Tuple[str, str]]): + List of radiative heat port mappings. + weather_path_mos (Path): Path to the Modelica weather file. + package_path (Path): The path to the package directory. + """ + with LOCK: + total_template_data = TEMPLATE_TOTAL.render( + within='bim2sim_spawn', + model_name=model_name_total, + model_comment='test2', + weather_path_mos=to_modelica_spawn(weather_path_mos), + model_name_building=model_name_building, + model_name_hydraulic=model_name_hydraulic, + cons_heat_ports_conv_building_hvac= + cons_heat_ports_conv_building_hvac, + cons_heat_ports_rad_building_hvac= + cons_heat_ports_rad_building_hvac + ) export_path = package_path / f"{model_name_total}.mo" - # user_logger.info("Saving '%s' to '%s'", self.name, _path) + self.logger.info(f"Saving {model_name_total} Modelica model to " + f"{export_path}") with codecs.open(export_path, "w", "utf-8") as file: file.write(total_template_data) + @classmethod + def get_port_mapping( + cls, cons_heat_ports: List[Tuple[str, str]], port_type: str, + model_name_building: str, model_name_hydraulic: str, + zone_to_heaters: Dict[str, List[str]]) -> List[Tuple[str, str]]: + """Mapping between building heat ports and HVAC heat ports. + + Args: + cons_heat_ports (List[Tuple[str, str]]): A list of tuples where + each tuple contains the HVAC outer port name and the + corresponding space heater name. + port_type (str): The type of port to map, e.g., "Con" for + convective or "Rad" for radiative. + model_name_building (str): The name of the building model. + model_name_hydraulic (str): The name of the hydraulic model. + zone_to_heaters (Dict[str, List[str]]): A dictionary mapping zone + GUIDs to lists of heater GUIDs. + + Returns: + List[Tuple[str, str]]: A list of tuples where each tuple contains + the mapped building port and the corresponding hydraulic port in + the HVAC model. + """ + mapping = [] + for hvac_outer_port, space_heater_name in cons_heat_ports: + heater_guid = space_heater_name.split('.')[0].replace( + 'spaceheater_', '').replace('_', '$') + building_index = cls.get_building_index( + zone_to_heaters, heater_guid) + building_port = (f"{model_name_building.lower()}." + f"heaPor{port_type}[{building_index}]") + hydraulic_port = (f"{model_name_hydraulic.lower()}." + f"{hvac_outer_port}") + mapping.append((building_port, hydraulic_port)) + return mapping + + @staticmethod + def get_building_index( + zone_to_heaters: Dict[str, List[str]], + heater_guid: str) -> Optional[int]: + """Get the index of the building in the zone_to_heaters dictionary. + + Args: + zone_to_heaters (Dict[str, List[str]]): A dictionary mapping zone + GUIDs to lists of heater GUIDs. + heater_guid (str): The GUID of the heater to search for. + + Returns: + Optional[int]: The index (1-based) of the zone that contains the + heater with the specified GUID. + Returns None if the heater GUID is not found in any zone. + """ + for index, (zone_guid, heater_list) in enumerate( + zone_to_heaters.items(), start=1): + if heater_guid in heater_list: + return index + return None diff --git a/bim2sim/tasks/hvac/export.py b/bim2sim/tasks/hvac/export.py index 7a066d19a3..cfd38c5c30 100644 --- a/bim2sim/tasks/hvac/export.py +++ b/bim2sim/tasks/hvac/export.py @@ -40,6 +40,8 @@ def run(self, libraries: tuple, graph: HvacGraph): self.logger.info("Export to Modelica code") elements = graph.elements + # sort elements + elements = sorted(elements, key=lambda x: x.guid) modelica.ModelicaElement.init_factory(libraries) export_elements = {inst: modelica.ModelicaElement.factory(inst) From 7ac25cda5117f56c88c04724a77def79b024e376 Mon Sep 17 00:00:00 2001 From: David Paul Jansen Date: Wed, 28 Aug 2024 09:06:41 +0200 Subject: [PATCH 058/125] improve imports --- .../PluginSpawn/bim2sim_spawn/tasks/export_spawn_total.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_total.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_total.py index 64724d71f5..967e80f91a 100644 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_total.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_total.py @@ -3,8 +3,8 @@ from collections import defaultdict from datetime import datetime from pathlib import Path -from typing import List, Tuple, Optional, Dict from threading import Lock +from typing import List, Tuple, Optional, Dict from mako.template import Template From fa428f88421c8621dbc6cb073da0012db70d7641 Mon Sep 17 00:00:00 2001 From: David Paul Jansen Date: Wed, 28 Aug 2024 09:11:03 +0200 Subject: [PATCH 059/125] improve example --- bim2sim/export/modelica/__init__.py | 3 +- .../examples/e1_simple_project_bps_spawn.py | 55 ++++++++---- .../examples/e2_simple_project_bps_spawn.py | 90 ------------------- 3 files changed, 41 insertions(+), 107 deletions(-) delete mode 100644 bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e2_simple_project_bps_spawn.py diff --git a/bim2sim/export/modelica/__init__.py b/bim2sim/export/modelica/__init__.py index 63c1f577f9..73e9fa5569 100644 --- a/bim2sim/export/modelica/__init__.py +++ b/bim2sim/export/modelica/__init__.py @@ -3,8 +3,9 @@ from enum import Enum from pathlib import Path from threading import Lock -from typing import (Tuple, Union, Type, Dict, Container, Callable, List, Any, +from typing import (Union, Type, Dict, Container, Callable, List, Any, Iterable) + import numpy as np import pint from mako.template import Template diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e1_simple_project_bps_spawn.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e1_simple_project_bps_spawn.py index 7cb6e79d58..a01c265299 100644 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e1_simple_project_bps_spawn.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e1_simple_project_bps_spawn.py @@ -2,21 +2,21 @@ from pathlib import Path import bim2sim -from bim2sim import Project, run_project, ConsoleDecisionHandler +from bim2sim import Project +from bim2sim.kernel.decision.decisionhandler import DebugDecisionHandler from bim2sim.kernel.log import default_logging_setup from bim2sim.utilities.common_functions import download_test_resources from bim2sim.utilities.types import IFCDomain -def run_example_1(): - """Run a building performance simulation with the EnergyPlus backend. +def run_example_spawn_1(): + """Export a SpawnOfEnergyPlus simulation model. - This example runs a BPS with the EnergyPlus backend. Specifies project - directory and location of the IFC file. Then, it creates a bim2sim - project with the EnergyPlus backend. Simulation settings are specified - (EnergyPlus location needs to be specified according to your system, - other settings are set to default if not specified otherwise), - before the project is executed with the previously specified settings. + + This example exports a SpawnOfEnergyPlus Co-Simulation model. The HVAC + model is generated via the PluginAixLib using the AixLib Modelica library. + The building model is generated using PluginEnergyPlus. The used IFC file + holds both, HVAC and building in one file. """ # Create the default logging to for quality log and bim2sim main log ( # see logging documentation for more information @@ -27,12 +27,13 @@ def run_example_1(): project_path = Path( tempfile.TemporaryDirectory(prefix='bim2sim_example_spawn').name) - download_test_resources(IFCDomain.arch, force_new=False) + download_test_resources(IFCDomain.mixed, force_new=False) # Set the ifc path to use and define which domain the IFC belongs to ifc_paths = { - IFCDomain.arch: + IFCDomain.mixed: Path(bim2sim.__file__).parent.parent / - 'test/resources/arch/ifc/AC20-FZK-Haus.ifc', + 'test/resources/mixed/ifc/' + 'b03_heating_with_building_blenderBIM.ifc' } # Create a project including the folder structure for the project with @@ -51,13 +52,35 @@ def run_example_1(): project.sim_settings.weather_file_path_modelica = ( Path(bim2sim.__file__).parent.parent / 'test/resources/weather_files/DEU_NW_Aachen.105010_TMYx.mos') + # Generate outer heat ports for spawn HVAC sub model + project.sim_settings.outer_heat_ports = True # Set other simulation settings, otherwise all settings are set to default + project.sim_settings.aggregations = [ + 'PipeStrand', + 'ParallelPump', + 'GeneratorOneFluid' + ] + + project.sim_settings.group_unidentified = 'name' + + # Run the project with the DebugDecisionHandler with pre-filled answers. + answers = ( + 'HVAC-PipeFitting', # Identify PipeFitting + 'HVAC-Distributor', # Identify Distributor + 'HVAC-ThreeWayValve', # Identify ThreeWayValve + 2010, # year of construction of building + *(True,) * 7, # 7 real dead ends found + *(1,)*13, # volume of junctions + *(1,)*4, # rated_pressure_difference + rated_volume_flow for 2 pumps + *(70,50,)*7, # flow and return temp for 7 space heaters + *(1,)*2 # nominal_mass_flow_rate, nominal_pressure_difference for + # ThreeWayValve + ) - # Run the project with the ConsoleDecisionHandler. This allows interactive - # input to answer upcoming questions regarding the imported IFC. - run_project(project, ConsoleDecisionHandler()) + handler = DebugDecisionHandler(answers) + handler.handle(project.run()) if __name__ == '__main__': - run_example_1() + run_example_spawn_1() diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e2_simple_project_bps_spawn.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e2_simple_project_bps_spawn.py deleted file mode 100644 index 60658cd81f..0000000000 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e2_simple_project_bps_spawn.py +++ /dev/null @@ -1,90 +0,0 @@ -import tempfile -from pathlib import Path - -import bim2sim -from bim2sim import Project, run_project, ConsoleDecisionHandler -from bim2sim.kernel.log import default_logging_setup -from bim2sim.utilities.common_functions import download_test_resources -from bim2sim.utilities.types import IFCDomain -from bim2sim.kernel.decision.decisionhandler import DebugDecisionHandler - -def run_example_1(): - """Run a building performance simulation with the EnergyPlus backend. - - This example runs a BPS with the EnergyPlus backend. Specifies project - directory and location of the IFC file. Then, it creates a bim2sim - project with the EnergyPlus backend. Simulation settings are specified - (EnergyPlus location needs to be specified according to your system, - other settings are set to default if not specified otherwise), - before the project is executed with the previously specified settings. - """ - # Create the default logging to for quality log and bim2sim main log ( - # see logging documentation for more information - default_logging_setup() - - # Create a temp directory for the project, feel free to use a "normal" - # directory - project_path = Path( - tempfile.TemporaryDirectory(prefix='bim2sim_example_spawn').name) - - download_test_resources(IFCDomain.mixed, force_new=False) - # Set the ifc path to use and define which domain the IFC belongs to - ifc_paths = { - IFCDomain.mixed: - Path(bim2sim.__file__).parent.parent / - 'test/resources/mixed/ifc/' - 'b03_heating_with_building_blenderBIM.ifc' - } - - # Create a project including the folder structure for the project with - # energyplus as backend - project = Project.create(project_path, ifc_paths, 'spawn') - - # Set the install path to your EnergyPlus installation according to your - # system requirements - project.sim_settings.ep_install_path = Path( - 'C:/EnergyPlusV9-6-0/') - project.sim_settings.ep_version = "9-6-0" - project.sim_settings.weather_file_path_ep = ( - Path(bim2sim.__file__).parent.parent / - 'test/resources/weather_files/DEU_NW_Aachen.105010_TMYx.epw') - # TODO make sure that a non existing sim_setting assignment raises an error - project.sim_settings.weather_file_path_modelica = ( - Path(bim2sim.__file__).parent.parent / - 'test/resources/weather_files/DEU_NW_Aachen.105010_TMYx.mos') - # Generate outer heat ports for spawn HVAC sub model - project.sim_settings.outer_heat_ports = True - - # Set other simulation settings, otherwise all settings are set to default - project.sim_settings.aggregations = [ - # 'Consumer', - 'PipeStrand', - 'ParallelPump', - # 'ConsumerHeatingDistributorModule', - 'GeneratorOneFluid' - ] - - - # Run the project with the ConsoleDecisionHandler. This allows interactive - # input to answer upcoming questions regarding the imported IFC. - answers = ( - 'HVAC-PipeFitting', # 35 - 'HVAC-Distributor', # 26 - 'HVAC-ThreeWayValve', # 39 - 2010, - *(True,) * 7, - *(1,)*13, # volume of junctions - *(1,)*4, # rated_pressure_difference + rated_volume_flow for 2 pumps - *(70,50,)*7, # flow and return temp for 7 space heaters - *(1,)*2 # nominal_mass_flow_rate, nominal_pressure_difference for ThreeWayValve - ) - - project.sim_settings.group_unidentified = 'name' - - handler = DebugDecisionHandler(answers) - handler.handle(project.run()) - # run_project(project, ConsoleDecisionHandler()) - - -if __name__ == '__main__': - run_example_1() From 617323aeaf6323bbfc5214de733632a89b440093 Mon Sep 17 00:00:00 2001 From: sfreund1 Date: Tue, 3 Sep 2024 08:37:58 +0200 Subject: [PATCH 060/125] Improved type hinting --- .../bim2sim_spawn/tasks/export_spawn_total.py | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_total.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_total.py index 967e80f91a..3241b32182 100644 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_total.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_total.py @@ -9,8 +9,11 @@ from mako.template import Template import bim2sim +from bim2sim.elements.base_elements import ProductBased +from bim2sim.elements.hvac_elements import HVACProduct from bim2sim.export import modelica -from bim2sim.export.modelica import help_package, help_package_order +from bim2sim.export.modelica import help_package, help_package_order, \ + ModelicaElement from bim2sim.plugins.PluginSpawn.bim2sim_spawn.models import to_modelica_spawn from bim2sim.tasks.base import ITask from bim2sim.utilities.common_functions import filter_elements @@ -33,20 +36,25 @@ class ExportSpawnTotal(ITask): ) final = True - def run(self, elements: dict, weather_file_modelica: Path, - weather_file_ep: Path, zone_names, model_name_building, - export_elements, connections, + def run(self, + elements: Dict[str, ProductBased], + weather_file_modelica: Path, + weather_file_ep: Path, + zone_names: List[str], + model_name_building: str, + export_elements: Dict[HVACProduct, ModelicaElement], + connections: List[Tuple[str, str]], cons_heat_ports_conv: List[Tuple[str, str]], cons_heat_ports_rad: List[Tuple[str, str]]): """Run the export process to generate the Modelica code. Args: - elements (dict): The elements data. - weather_file_modelica (Path): Path to the Modelica weather file. - weather_file_ep (Path): Path to the EnergyPlus weather file. + elements: The elements' data. + weather_file_modelica: Path to the Modelica weather file. + weather_file_ep: Path to the EnergyPlus weather file. zone_names: Zone names data. - model_name_building (str): The name of the building model. - export_elements: Elements to export. + model_name_building: The name of the building model. + export_elements: HVAC elements to export. connections: Connections data. cons_heat_ports_conv: List of convective heat port connections. cons_heat_ports_rad: List of radiative heat port connections. From daa00ebdaf68b7a35dea6fc758298f6c67fc0282 Mon Sep 17 00:00:00 2001 From: sfreund1 Date: Tue, 3 Sep 2024 10:02:26 +0200 Subject: [PATCH 061/125] Improves readability and documentation of CreateModelicaModel --- bim2sim/tasks/hvac/export.py | 127 ++++++++++++++++++++++++----------- 1 file changed, 87 insertions(+), 40 deletions(-) diff --git a/bim2sim/tasks/hvac/export.py b/bim2sim/tasks/hvac/export.py index cfd38c5c30..7030d469e4 100644 --- a/bim2sim/tasks/hvac/export.py +++ b/bim2sim/tasks/hvac/export.py @@ -1,14 +1,17 @@ import re from datetime import datetime from pathlib import Path +from typing import Tuple, List, Dict from mako.template import Template from bim2sim.elements import hvac_elements as hvac from bim2sim.elements.base_elements import ProductBased from bim2sim.elements.graphs.hvac_graph import HvacGraph +from bim2sim.elements.hvac_elements import HVACProduct from bim2sim.export import modelica -from bim2sim.export.modelica import HeatTransferType, ModelicaParameter +from bim2sim.export.modelica import HeatTransferType, ModelicaParameter, \ + ModelicaElement from bim2sim.tasks.base import ITask from bim2sim.export.modelica import help_package, help_package_order @@ -30,55 +33,69 @@ def run(self, libraries: tuple, graph: HvacGraph): 3. Collects and exports parameters for each Modelica instance. 4. Creates connections between Modelica instances based on the HVAC graph. - 5. Creates a Modelica model with the exported elements and connections. - 6. Saves the Modelica model to the specified export path. + 5. Creates heat port connections (both inner and outer). + 6. Returning the created elements and connections. Args: libraries: Tuple of libraries to be used in Modelica. graph: The HVAC graph to be exported. - """ - - self.logger.info("Export to Modelica code") - elements = graph.elements - # sort elements - elements = sorted(elements, key=lambda x: x.guid) + Returns: + export_elements: A mapping of HVAC elements to their corresponding + Modelica instances. + connections: A list of connections between the Modelica instances. + cons_heat_ports_conv: A list of convective heat port connections. + cons_heat_ports_rad: A list of radiative heat port connections. + """ + # Initialize Modelica factory and create Modelica instances + self.logger.info("Exporting HVAC graph to Modelica code") + elements = sorted(graph.elements, key=lambda x: x.guid) modelica.ModelicaElement.init_factory(libraries) - export_elements = {inst: modelica.ModelicaElement.factory(inst) - for inst in elements} - connections = self.create_connections(graph, export_elements) + export_elements = self._create_export_elements(elements) - # Perform decisions for requested but not existing attributes - yield from ProductBased.get_pending_attribute_decisions(elements) - # Perform decisions for required but not existing modelica parameters - yield from ModelicaParameter.get_pending_parameter_decisions() + # Create connections based on HVAC graph + connections = self._create_connections(graph, export_elements) - # All parameters are checked against the specified check function and - # exported with the correct unit - for instance in export_elements.values(): - instance.collect_params() + # Handle pending attribute and parameter decisions + yield from self._handle_pending_decisions(elements) - inner_heat_port_cons_conv, inner_heat_port_cons_rad = ( - self.create_inner_heat_port_connections()) + # Collect parameters for each Modelica instance + self._collect_parameters(export_elements) - if self.playground.sim_settings.outer_heat_ports: - outer_heat_port_cons_conv, outer_heat_port_cons_rad = ( - self.create_outer_heat_port_connections( - list(export_elements.values()))) - else: - outer_heat_port_cons_conv = [] - outer_heat_port_cons_rad = [] - cons_heat_ports_conv = (outer_heat_port_cons_conv + - inner_heat_port_cons_conv) - cons_heat_ports_rad = (outer_heat_port_cons_rad + - inner_heat_port_cons_rad) + # Create heat port connections + cons_heat_ports_conv, cons_heat_ports_rad = ( + self._create_heat_port_connections(export_elements)) - # TODO #1 integrate heat ports in connections + # # TODO #1 integrate heat ports in connections + # connections.extend(cons_heat_ports_conv) + # connections.extend(cons_heat_ports_rad) return (export_elements, connections, cons_heat_ports_conv, cons_heat_ports_rad) @staticmethod - def create_connections(graph: HvacGraph, export_elements: dict) -> list: + def _create_export_elements(elements: List[HVACProduct] + ) -> Dict[HVACProduct, ModelicaElement]: + """Create Modelica instances for each HVAC element.""" + return {inst: modelica.ModelicaElement.factory(inst) + for inst in elements} + + @staticmethod + def _handle_pending_decisions(elements: List[HVACProduct]): + """Handle pending attribute and parameter decisions.""" + yield from ProductBased.get_pending_attribute_decisions(elements) + yield from ModelicaParameter.get_pending_parameter_decisions() + + @staticmethod + def _collect_parameters( + export_elements: Dict[HVACProduct, ModelicaElement]): + """Collect and export parameters for each Modelica instance.""" + for instance in export_elements.values(): + instance.collect_params() + + @staticmethod + def _create_connections(graph: HvacGraph, + export_elements: Dict[HVACProduct, ModelicaElement] + ) -> List[Tuple[str, str]]: """Creates a list of connections for the corresponding Modelica model. This method iterates over the edges of the HVAC graph and creates a @@ -90,8 +107,8 @@ def create_connections(graph: HvacGraph, export_elements: dict) -> list: export_elements: the Modelica elements Returns: - connection_port_names: list of tuple of port names that are - connected. + connection_port_names: list of tuple of port names that are + connected. """ connection_port_names = [] distributors_n = {} @@ -127,10 +144,40 @@ def create_connections(graph: HvacGraph, export_elements: dict) -> list: return connection_port_names + def _create_heat_port_connections( + self, export_elements: Dict[HVACProduct, ModelicaElement] + ) -> Tuple[List[Tuple[str, str]], List[Tuple[str, str]]]: + """Create inner and outer heat port connections.""" + inner_heat_port_cons_conv, inner_heat_port_cons_rad = ( + self.create_inner_heat_port_connections()) + + if self.playground.sim_settings.outer_heat_ports: + outer_heat_port_cons_conv, outer_heat_port_cons_rad = ( + self.create_outer_heat_port_connections( + list(export_elements.values()))) + else: + outer_heat_port_cons_conv, outer_heat_port_cons_rad = [], [] + + cons_heat_ports_conv = (outer_heat_port_cons_conv + + inner_heat_port_cons_conv) + cons_heat_ports_rad = (outer_heat_port_cons_rad + + inner_heat_port_cons_rad) + + return cons_heat_ports_conv, cons_heat_ports_rad + @staticmethod - def create_outer_heat_port_connections(export_elements: list) -> \ - [list, list]: - """Creates connections to an outer heat port for further connections""" + def create_outer_heat_port_connections( + export_elements: List[ModelicaElement] + ) -> Tuple[List[Tuple[str, str]], List[Tuple[str, str]]]: + """Creates connections to an outer heat port for further connections + + Connects heat ports of the export elements to outer heat ports + (convective and radiative). Returns two lists containing these + connections. + + Returns: + Two lists containing convective and radiative heat port connections. + """ # ToDo only connect heat ports that might not be connected already by # create_inner_heat_port_connections() function From 85a0475bc1f7539d4889168d9948ea6c19b6814c Mon Sep 17 00:00:00 2001 From: David Paul Jansen Date: Tue, 3 Sep 2024 13:11:37 +0200 Subject: [PATCH 062/125] improve export and resolve problems with attributes and functions --- .../elements/aggregation/hvac_aggregations.py | 108 +++++++++++------- bim2sim/elements/hvac_elements.py | 3 +- bim2sim/export/modelica/__init__.py | 8 +- .../bim2sim_aixlib/models/__init__.py | 40 ++++--- .../examples/e1_simple_project_bps_spawn.py | 19 +-- 5 files changed, 108 insertions(+), 70 deletions(-) diff --git a/bim2sim/elements/aggregation/hvac_aggregations.py b/bim2sim/elements/aggregation/hvac_aggregations.py index d6a720e727..ed4da0cd2d 100644 --- a/bim2sim/elements/aggregation/hvac_aggregations.py +++ b/bim2sim/elements/aggregation/hvac_aggregations.py @@ -659,10 +659,9 @@ def pump_elements(self) -> list: def _calc_rated_power(self, name) -> ureg.Quantity: """Calculate the rated power adding the rated power of the pump-like elements""" - if all(ele.rated_power for ele in self.pump_elements): - return sum([ele.rated_power for ele in self.pump_elements]) - else: - return None + value = all(ele.rated_power for ele in self.pump_elements) + if value: + return value rated_power = attribute.Attribute( unit=ureg.kilowatt, @@ -686,7 +685,9 @@ def _calc_rated_height(self, name) -> ureg.Quantity: def _calc_volume_flow(self, name) -> ureg.Quantity: """Calculate the volume flow, adding the volume flow of the pump-like elements""" - return sum([ele.rated_volume_flow for ele in self.pump_elements]) + value = sum([ele.rated_volume_flow for ele in self.pump_elements]) + if value: + return value rated_volume_flow = attribute.Attribute( description='rated volume flow', @@ -697,7 +698,9 @@ def _calc_volume_flow(self, name) -> ureg.Quantity: def _calc_diameter(self, name) -> ureg.Quantity: """Calculate the diameter, using the pump-like elements diameter""" - return sum(item.diameter ** 2 for item in self.pump_elements) ** 0.5 + value = sum(item.diameter ** 2 for item in self.pump_elements) ** 0.5 + if value: + return value diameter = attribute.Attribute( description='diameter', @@ -823,7 +826,9 @@ def _calc_rated_power(self, name) -> ureg.Quantity: """ Calculate the rated power adding the rated power of the whitelist_classes elements. """ - return sum([ele.rated_power for ele in self.whitelist_elements]) + value = sum([ele.rated_power for ele in self.whitelist_elements]) + if value: + return value rated_power = attribute.Attribute( description="rated power", @@ -841,7 +846,9 @@ def _calc_rated_pump_power(self, name) -> ureg.Quantity: """ Calculate the rated pump power adding the rated power of the pump-like elements. """ - return sum([ele.rated_power for ele in self.pump_elements]) + value = sum([ele.rated_power for ele in self.pump_elements]) + if value: + return value rated_pump_power = attribute.Attribute( description="rated pump power", @@ -854,7 +861,9 @@ def _calc_volume_flow(self, name) -> ureg.Quantity: """ Calculate the volume flow, adding the volume flow of the pump-like elements. """ - return sum([ele.rated_volume_flow for ele in self.pump_elements]) + value = sum([ele.rated_volume_flow for ele in self.pump_elements]) + if value: + return value rated_volume_flow = attribute.Attribute( description="rated volume flow", @@ -867,8 +876,11 @@ def _calc_flow_temperature(self, name) -> ureg.Quantity: """ Calculate the flow temperature, using the flow temperature of the whitelist_classes elements. """ - return sum(ele.flow_temperature.to_base_units() for ele - in self.whitelist_elements) / len(self.whitelist_elements) + value= (sum(ele.flow_temperature.to_base_units() for ele + in self.whitelist_elements if ele.flow_temperature) + / len(self.whitelist_elements)) + if value: + return value flow_temperature = attribute.Attribute( description="temperature inlet", @@ -881,8 +893,10 @@ def _calc_return_temperature(self, name) -> ureg.Quantity: """ Calculate the return temperature, using the return temperature of the whitelist_classes elements. """ - return sum(ele.return_temperature.to_base_units() for ele + value = sum(ele.return_temperature.to_base_units() for ele in self.whitelist_elements) / len(self.whitelist_elements) + if value: + return value return_temperature = attribute.Attribute( description="temperature outlet", @@ -893,7 +907,8 @@ def _calc_return_temperature(self, name) -> ureg.Quantity: def _calc_dT_water(self, name): """ Water dt of consumer.""" - return self.flow_temperature - self.return_temperature + if self.flow_temperature and self.return_temperature: + return self.flow_temperature - self.return_temperature dT_water = attribute.Attribute( description="Nominal temperature difference", @@ -903,7 +918,9 @@ def _calc_dT_water(self, name): def _calc_body_mass(self, name): """ Body mass of consumer.""" - return sum(ele.body_mass for ele in self.whitelist_elements) + value = sum(ele.body_mass for ele in self.whitelist_elements) + if value: + return value body_mass = attribute.Attribute( description="Body mass of Consumer", @@ -913,8 +930,9 @@ def _calc_body_mass(self, name): def _calc_heat_capacity(self, name): """ Heat capacity of consumer.""" - return sum(ele.heat_capacity for ele in - self.whitelist_elements) + value = sum(ele.heat_capacity for ele in self.whitelist_elements) + if value: + return value heat_capacity = attribute.Attribute( description="Heat capacity of Consumer", @@ -1114,12 +1132,6 @@ def whitelist_elements(self) -> list: """list of whitelist_classes elements present on the aggregation""" return [ele for ele in self.elements if type(ele) in self.whitelist_classes] - def _calc_flow_temperature(self, name) -> list: - """Calculate the flow temperature, using the flow temperature of the - whitelist_classes elements""" - return [ele.flow_temperature.to_base_units() for ele - in self.whitelist_elements] - def _calc_has_pump(self, name) -> list[bool]: """Returns a list with boolean for every consumer if it has a pump.""" return [con.has_pump for con in self.whitelist_elements] @@ -1127,7 +1139,7 @@ def _calc_has_pump(self, name) -> list[bool]: flow_temperature = attribute.Attribute( description="temperature inlet", unit=ureg.kelvin, - functions=[_calc_flow_temperature], + functions=[Consumer._calc_flow_temperature], dependant_elements='whitelist_elements' ) @@ -1446,7 +1458,10 @@ def not_whitelist_elements(self) -> list: def _calc_rated_power(self, name) -> ureg.Quantity: """ Calculate the rated power adding the rated power of the whitelist_classes elements.""" - return sum([ele.rated_power for ele in self.whitelist_elements]) + value = sum([ele.rated_power for ele in self.whitelist_elements + if ele.rated_power]) + if value: + return value rated_power = attribute.Attribute( unit=ureg.kilowatt, @@ -1458,7 +1473,10 @@ def _calc_rated_power(self, name) -> ureg.Quantity: def _calc_min_power(self, name): """ Calculates the min power, adding the min power of the whitelist_elements.""" - return sum([ele.min_power for ele in self.whitelist_elements]) + min_powers = [ele.min_power for ele in self.whitelist_elements + if ele.min_power] + if min_powers: + return min(min_powers) min_power = attribute.Attribute( unit=ureg.kilowatt, @@ -1469,7 +1487,8 @@ def _calc_min_power(self, name): def _calc_min_PLR(self, name): """ Calculates the min PLR, using the min power and rated power.""" - return self.min_power / self.rated_power + if self.min_power and self.rated_power: + return self.min_power / self.rated_power min_PLR = attribute.Attribute( description="Minimum part load ratio", @@ -1477,35 +1496,33 @@ def _calc_min_PLR(self, name): functions=[_calc_min_PLR], ) - def _calc_flow_temperature(self, name) -> ureg.Quantity: - """ Calculate the flow temperature, using the flow temperature of the - whitelist_classes elements.""" - return sum(ele.flow_temperature.to_base_units() for ele - in self.whitelist_elements) / len(self.whitelist_elements) - flow_temperature = attribute.Attribute( description="Nominal flow temperature", - unit=ureg.kelvin, - functions=[_calc_flow_temperature], + unit=ureg.celsius, + functions=[Consumer._calc_flow_temperature], dependant_elements='whitelist_elements' ) def _calc_return_temperature(self, name) -> ureg.Quantity: """ Calculate the return temperature, using the return temperature of the whitelist_classes elements.""" - return sum(ele.return_temperature.to_base_units() for ele - in self.whitelist_elements) / len(self.whitelist_elements) + value = (sum(ele.return_temperature.to_base_units() for ele + in self.whitelist_elements if ele.return_temperature) + / len(self.whitelist_elements)) + if value: + return value return_temperature = attribute.Attribute( description="Nominal return temperature", - unit=ureg.kelvin, + unit=ureg.celsius, functions=[_calc_return_temperature], dependant_elements='whitelist_elements' ) def _calc_dT_water(self, name): """ Rated power of boiler.""" - return abs(self.return_temperature - self.flow_temperature) + if self.return_temperature and self.flow_temperature: + return abs(self.return_temperature - self.flow_temperature) dT_water = attribute.Attribute( description="Nominal temperature difference", @@ -1516,8 +1533,10 @@ def _calc_dT_water(self, name): def _calc_diameter(self, name) -> ureg.Quantity: """ Calculate the diameter, using the whitelist_classes elements diameter.""" - return sum( + value = sum( item.diameter ** 2 for item in self.whitelist_elements) ** 0.5 + if value: + return value diameter = attribute.Attribute( description='diameter', @@ -1551,10 +1570,9 @@ def pump_elements(self) -> list: def _calc_rated_pump_power(self, name) -> ureg.Quantity: """ Calculate the rated pump power adding the rated power of the pump-like elements.""" - if all(ele.rated_power for ele in self.pump_elements): - return sum([ele.rated_power for ele in self.pump_elements]) - else: - return None + value = all(ele.rated_power for ele in self.pump_elements) + if value: + return value rated_pump_power = attribute.Attribute( description="rated pump power", @@ -1566,7 +1584,9 @@ def _calc_rated_pump_power(self, name) -> ureg.Quantity: def _calc_volume_flow(self, name) -> ureg.Quantity: """ Calculate the volume flow, adding the volume flow of the pump-like elements.""" - return sum([ele.rated_volume_flow for ele in self.pump_elements]) + value = sum([ele.rated_volume_flow for ele in self.pump_elements]) + if value: + return value rated_volume_flow = attribute.Attribute( description="rated volume flow", diff --git a/bim2sim/elements/hvac_elements.py b/bim2sim/elements/hvac_elements.py index 9147642d4a..9827ac9704 100644 --- a/bim2sim/elements/hvac_elements.py +++ b/bim2sim/elements/hvac_elements.py @@ -639,7 +639,8 @@ def _calc_partial_load_efficiency(self, name): def _calc_min_power(self, name) -> ureg.Quantity: """Function to calculate the minimum power that boiler operates at, using the partial load efficiency and the nominal power consumption""" - return self.partial_load_efficiency * self.nominal_power_consumption + if self.partial_load_efficiency and self.nominal_power_consumption: + return self.partial_load_efficiency * self.nominal_power_consumption min_power = attribute.Attribute( description="Minimum power that boiler operates at", diff --git a/bim2sim/export/modelica/__init__.py b/bim2sim/export/modelica/__init__.py index 73e9fa5569..2d29854f1a 100644 --- a/bim2sim/export/modelica/__init__.py +++ b/bim2sim/export/modelica/__init__.py @@ -589,7 +589,8 @@ def collect(self): self.value = self._answers[self.name] elif self.attributes: attribute_value = self.get_attribute_value() - self.value = self.convert_parameter(attribute_value) + if attribute_value is not None: + self.value = self.convert_parameter(attribute_value) elif self.value is not None: self.value = self.convert_parameter(self.value) else: @@ -614,7 +615,10 @@ def value(self, value): self._value = value else: logger.warning("Parameter check failed for '%s' with value: " - "%s", self.name, self._value) + "%s of element %s with GUID %s", + self.name, self._value, + self.element.__class__.__name__, + self.element.guid) self._value = None else: self._value = value diff --git a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py index 4c75c710ec..04aa4241da 100644 --- a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py +++ b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py @@ -45,7 +45,9 @@ def __init__(self, element): required=True, attributes=['min_PLR'], check=check_numeric( - min_value=0 * ureg.dimensionless)) + min_value=0 * ureg.dimensionless, + max_value=1 * ureg.dimensionless) + ) def get_port_name(self, port): if port.verbose_flow_direction == 'SINK': @@ -80,14 +82,14 @@ def __init__(self, element): attributes=['rated_power'], check=check_numeric(min_value=0 * ureg.watt)) self._set_parameter(name='T_a_nominal', - unit=ureg.celsius, + unit=ureg.kelvin, required=True, - check=check_numeric(min_value=0 * ureg.celsius), + check=check_numeric(min_value=0 * ureg.kelvin), attributes=['flow_temperature']) self._set_parameter(name='T_b_nominal', - unit=ureg.celsius, + unit=ureg.kelvin, required=True, - check=check_numeric(min_value=0 * ureg.celsius), + check=check_numeric(min_value=0 * ureg.kelvin), attributes=['return_temperature']) def get_port_name(self, port): @@ -328,36 +330,41 @@ def __init__(self, element): attributes=['has_bypass']) self._set_parameter(name='Q_flow_nominal', unit=ureg.watt, - required=False, + required=True, check=check_numeric(min_value=0 * ureg.watt), attributes=['rated_power']) self._set_parameter(name='FirRatMin', unit=ureg.dimensionless, - required=False, + required=True, check=check_numeric( - min_value=0 * ureg.dimensionless), + min_value=0 * ureg.dimensionless, + max_value=1 * ureg.dimensionless), attributes=['min_PLR']) self._set_parameter(name='TRet_nominal', unit=ureg.kelvin, - required=False, + required=True, check=check_numeric( - min_value=0 * ureg.kelvin), + min_value=20 * ureg.celsius), attributes=['return_temperature']) self._set_parameter(name='TSup_nominal', unit=ureg.kelvin, - required=False, + required=True, check=check_numeric( - min_value=0 * ureg.kelvin), + min_value=40 * ureg.celsius), attributes=['flow_temperature']) self._set_parameter(name='dT_nominal', unit=ureg.kelvin, - required=False, + required=True, check=check_numeric(min_value=0 * ureg.kelvin), attributes=['dT_water']) self._set_parameter(name='dp_Valve', unit=ureg.pascal, required=False, - value=10000) + value=10000*ureg.pascal) + self._set_parameter(name='Kv', + unit=ureg.m**3/ureg.hour / ureg.bar**0.5, + required=False, + value=0.7*ureg.m**3/ureg.hour / ureg.bar**0.5) def get_port_name(self, port): if port.verbose_flow_direction == 'SINK': @@ -436,7 +443,7 @@ class ThreeWayValve(AixLib): def __init__(self, element): super().__init__(element) - self._set_parameter(name='redeclare package Medium_con', + self._set_parameter(name='redeclare package Medium', unit=None, required=False, value=MEDIUM_WATER) @@ -448,8 +455,9 @@ def __init__(self, element): attributes=['nominal_mass_flow_rate']) self._set_parameter(name='dpValve_nominal', unit=ureg.pascal, - required=True, + required=False, check=check_numeric(min_value=0 * ureg.pascal), + value=1000*ureg.pascal, attributes=['nominal_pressure_difference']) def get_port_name(self, port): diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e1_simple_project_bps_spawn.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e1_simple_project_bps_spawn.py index a01c265299..ebef2b0190 100644 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e1_simple_project_bps_spawn.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e1_simple_project_bps_spawn.py @@ -2,7 +2,7 @@ from pathlib import Path import bim2sim -from bim2sim import Project +from bim2sim import Project, ConsoleDecisionHandler from bim2sim.kernel.decision.decisionhandler import DebugDecisionHandler from bim2sim.kernel.log import default_logging_setup from bim2sim.utilities.common_functions import download_test_resources @@ -71,13 +71,18 @@ def run_example_spawn_1(): 'HVAC-ThreeWayValve', # Identify ThreeWayValve 2010, # year of construction of building *(True,) * 7, # 7 real dead ends found - *(1,)*13, # volume of junctions - *(1,)*4, # rated_pressure_difference + rated_volume_flow for 2 pumps - *(70,50,)*7, # flow and return temp for 7 space heaters - *(1,)*2 # nominal_mass_flow_rate, nominal_pressure_difference for - # ThreeWayValve + *(0.001,)*13, # volume of junctions + 2000, 175, # rated_pressure_difference + rated_volume_flow pump of 1st storey (big) + 4000, 200, # rated_pressure_difference + rated_volume_flow for 2nd storey + *(70, 50,)*7, # flow and return temp for 7 space heaters + 0.056, # nominal_mass_flow_rate 2nd storey TRV (kg/s), + 20, # dT water of boiler + 70, # nominal flow temperature of boiler + 0.3, # minimal part load range of boiler + 8.5, # nominal power of boiler (in kW) + 50, # nominal return temperature of boiler ) - + # handler = ConsoleDecisionHandler() handler = DebugDecisionHandler(answers) handler.handle(project.run()) From 50319c3ec95d70809449731fd593ea305dd5eeea Mon Sep 17 00:00:00 2001 From: David Paul Jansen Date: Fri, 18 Oct 2024 08:48:02 +0200 Subject: [PATCH 063/125] improve imports --- bim2sim/utilities/common_functions.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bim2sim/utilities/common_functions.py b/bim2sim/utilities/common_functions.py index 6cee3853e0..8bb0b6532f 100644 --- a/bim2sim/utilities/common_functions.py +++ b/bim2sim/utilities/common_functions.py @@ -6,10 +6,11 @@ import zipfile from pathlib import Path from time import sleep -import git from typing import TYPE_CHECKING, Type, Union from urllib.request import urlopen +import git + import bim2sim from bim2sim.utilities.types import IFCDomain From e5f61ffcebbb8a34964f0bdc5df1ecfd7ce244bb Mon Sep 17 00:00:00 2001 From: David Paul Jansen Date: Fri, 18 Oct 2024 09:17:54 +0200 Subject: [PATCH 064/125] add fixes for replacement of sim_setting weather_file by weather_file_ep and weather_file_modelica --- .../test/integration/test_aixlib.py | 4 +- .../e1_simple_project_comfort_energyplus.py | 2 +- .../e3_load_comfort_simulation_results.py | 2 +- .../test/integration/test_comfort.py | 7 ++- .../test/regression/test_comfort.py | 6 +- .../e2_complex_project_bps_energyplus.py | 2 +- .../e3_load_energyplus_simulation_results.py | 2 +- .../test/unit/task/test_weather.py | 4 +- .../test/integration/test_hkesim.py | 4 +- .../examples/e2_complex_project_teaser.py | 2 +- .../e3_load_teaser_simulation_results.py | 2 +- .../examples/e4_visualize_zone_binding.py | 2 +- .../bim2sim_teaser/task/create_result_df.py | 2 +- .../bim2sim_teaser/task/create_teaser_prj.py | 2 +- .../test/integration/test_teaser.py | 5 +- .../test/unit/task/test_weather.py | 11 ++-- bim2sim/tasks/common/weather.py | 55 ++++--------------- 17 files changed, 46 insertions(+), 68 deletions(-) diff --git a/bim2sim/plugins/PluginAixLib/test/integration/test_aixlib.py b/bim2sim/plugins/PluginAixLib/test/integration/test_aixlib.py index ab19a1bcf4..e77a6e387f 100644 --- a/bim2sim/plugins/PluginAixLib/test/integration/test_aixlib.py +++ b/bim2sim/plugins/PluginAixLib/test/integration/test_aixlib.py @@ -20,9 +20,9 @@ def model_domain_path(self) -> str: def set_test_weather_file(self): """Set the weather file path.""" - self.project.sim_settings.weather_file_path_ep = ( + self.project.sim_settings.weather_file_path_modelica = ( self.test_resources_path() / - 'weather_files/DEU_NW_Aachen.105010_TMYx.epw') + 'weather_files/DEU_NW_Aachen.105010_TMYx.mos') class TestIntegrationAixLib(IntegrationBaseAixLib, unittest.TestCase): diff --git a/bim2sim/plugins/PluginComfort/bim2sim_comfort/examples/e1_simple_project_comfort_energyplus.py b/bim2sim/plugins/PluginComfort/bim2sim_comfort/examples/e1_simple_project_comfort_energyplus.py index 810ba54ac8..d7b1902dd6 100644 --- a/bim2sim/plugins/PluginComfort/bim2sim_comfort/examples/e1_simple_project_comfort_energyplus.py +++ b/bim2sim/plugins/PluginComfort/bim2sim_comfort/examples/e1_simple_project_comfort_energyplus.py @@ -39,7 +39,7 @@ def run_example_1(): project = Project.create(project_path, ifc_paths, 'comfort') # set weather file data - project.sim_settings.weather_file_path = ( + project.sim_settings.weather_file_path_ep = ( Path(bim2sim.__file__).parent.parent / 'test/resources/weather_files/DEU_NW_Aachen.105010_TMYx.epw') diff --git a/bim2sim/plugins/PluginComfort/bim2sim_comfort/examples/e3_load_comfort_simulation_results.py b/bim2sim/plugins/PluginComfort/bim2sim_comfort/examples/e3_load_comfort_simulation_results.py index d4beb5af7e..70bc674601 100644 --- a/bim2sim/plugins/PluginComfort/bim2sim_comfort/examples/e3_load_comfort_simulation_results.py +++ b/bim2sim/plugins/PluginComfort/bim2sim_comfort/examples/e3_load_comfort_simulation_results.py @@ -40,7 +40,7 @@ def run_example_load_existing_project(): project = Project.create(project_path, plugin='comfort') # set weather file data - project.sim_settings.weather_file_path = ( + project.sim_settings.weather_file_path_ep = ( Path(bim2sim.__file__).parent.parent / 'test/resources/weather_files/DEU_NW_Aachen.105010_TMYx.mos') # Run a simulation directly with dymola after model creation diff --git a/bim2sim/plugins/PluginComfort/test/integration/test_comfort.py b/bim2sim/plugins/PluginComfort/test/integration/test_comfort.py index ebbdb902b4..dde25f805a 100644 --- a/bim2sim/plugins/PluginComfort/test/integration/test_comfort.py +++ b/bim2sim/plugins/PluginComfort/test/integration/test_comfort.py @@ -49,12 +49,13 @@ def tearDown(self): sys.stderr = self.old_stderr super().tearDown() - def model_domain_path(self) -> str: return 'arch' - def weather_file_path(self) -> Path: - return (self.test_resources_path() / + def set_test_weather_file(self): + """Set the weather file path.""" + self.project.sim_settings.weather_file_path_ep = ( + self.test_resources_path() / 'weather_files/DEU_NW_Aachen.105010_TMYx.epw') diff --git a/bim2sim/plugins/PluginComfort/test/regression/test_comfort.py b/bim2sim/plugins/PluginComfort/test/regression/test_comfort.py index f1c0595884..c3751edee3 100644 --- a/bim2sim/plugins/PluginComfort/test/regression/test_comfort.py +++ b/bim2sim/plugins/PluginComfort/test/regression/test_comfort.py @@ -33,8 +33,10 @@ def tearDown(self): sys.stderr = self.old_stderr super().tearDown() - def weather_file_path(self) -> Path: - return (self.test_resources_path() / + def set_test_weather_file(self): + """Set the weather file path.""" + self.project.sim_settings.weather_file_path_ep = ( + self.test_resources_path() / 'weather_files/DEU_NW_Aachen.105010_TMYx.epw') def create_regression_setup(self): diff --git a/bim2sim/plugins/PluginEnergyPlus/bim2sim_energyplus/examples/e2_complex_project_bps_energyplus.py b/bim2sim/plugins/PluginEnergyPlus/bim2sim_energyplus/examples/e2_complex_project_bps_energyplus.py index 4a6d7381b4..31f5fc0d8a 100644 --- a/bim2sim/plugins/PluginEnergyPlus/bim2sim_energyplus/examples/e2_complex_project_bps_energyplus.py +++ b/bim2sim/plugins/PluginEnergyPlus/bim2sim_energyplus/examples/e2_complex_project_bps_energyplus.py @@ -59,7 +59,7 @@ def run_example_complex_building_teaser(): 'Alu- oder Stahlfenster, Waermeschutzverglasung, zweifach' # set weather file data - project.sim_settings.weather_file_path = ( + project.sim_settings.weather_file_path_ep = ( Path(bim2sim.__file__).parent.parent / 'test/resources/weather_files/DEU_NW_Aachen.105010_TMYx.epw') # Run a simulation directly with dymola after model creation diff --git a/bim2sim/plugins/PluginEnergyPlus/bim2sim_energyplus/examples/e3_load_energyplus_simulation_results.py b/bim2sim/plugins/PluginEnergyPlus/bim2sim_energyplus/examples/e3_load_energyplus_simulation_results.py index cf99d6aedb..857ccafa7a 100644 --- a/bim2sim/plugins/PluginEnergyPlus/bim2sim_energyplus/examples/e3_load_energyplus_simulation_results.py +++ b/bim2sim/plugins/PluginEnergyPlus/bim2sim_energyplus/examples/e3_load_energyplus_simulation_results.py @@ -38,7 +38,7 @@ def run_example_load_existing_project(): project = Project.create(project_path, plugin='energyplus') # set weather file data - project.sim_settings.weather_file_path = ( + project.sim_settings.weather_file_path_ep = ( Path(bim2sim.__file__).parent.parent / 'test/resources/weather_files/DEU_NW_Aachen.105010_TMYx.mos') # Run a simulation directly with dymola after model creation diff --git a/bim2sim/plugins/PluginEnergyPlus/test/unit/task/test_weather.py b/bim2sim/plugins/PluginEnergyPlus/test/unit/task/test_weather.py index 7c1d42f8c8..d74de1faf9 100644 --- a/bim2sim/plugins/PluginEnergyPlus/test/unit/task/test_weather.py +++ b/bim2sim/plugins/PluginEnergyPlus/test/unit/task/test_weather.py @@ -22,7 +22,9 @@ class PluginWeatherDummyEP(Plugin): ] -test_rsrc_path = Path(__file__).parent.parent.parent.parent / 'resources' +test_rsrc_path = (Path( + __file__).parent.parent.parent.parent.parent.parent.parent / + 'test/resources') class TestWeather(unittest.TestCase): diff --git a/bim2sim/plugins/PluginHKESim/test/integration/test_hkesim.py b/bim2sim/plugins/PluginHKESim/test/integration/test_hkesim.py index fe48628367..c8c79ce68a 100644 --- a/bim2sim/plugins/PluginHKESim/test/integration/test_hkesim.py +++ b/bim2sim/plugins/PluginHKESim/test/integration/test_hkesim.py @@ -20,9 +20,9 @@ def model_domain_path(self) -> str: def set_test_weather_file(self): """Set the weather file path.""" - self.project.sim_settings.weather_file_path_ep = ( + self.project.sim_settings.weather_file_path_modelica = ( self.test_resources_path() / - 'weather_files/DEU_NW_Aachen.105010_TMYx.epw') + 'weather_files/DEU_NW_Aachen.105010_TMYx.mos') class TestIntegrationHKESIM(IntegrationBaseHKESIM, unittest.TestCase): diff --git a/bim2sim/plugins/PluginTEASER/bim2sim_teaser/examples/e2_complex_project_teaser.py b/bim2sim/plugins/PluginTEASER/bim2sim_teaser/examples/e2_complex_project_teaser.py index dcfcd006a2..71f5aacc2c 100644 --- a/bim2sim/plugins/PluginTEASER/bim2sim_teaser/examples/e2_complex_project_teaser.py +++ b/bim2sim/plugins/PluginTEASER/bim2sim_teaser/examples/e2_complex_project_teaser.py @@ -60,7 +60,7 @@ def run_example_complex_building_teaser(): 'Alu- oder Stahlfenster, Waermeschutzverglasung, zweifach' # set weather file data - project.sim_settings.weather_file_path = ( + project.sim_settings.weather_file_path_modelica = ( Path(bim2sim.__file__).parent.parent / 'test/resources/weather_files/DEU_NW_Aachen.105010_TMYx.mos') # Run a simulation directly with dymola after model creation diff --git a/bim2sim/plugins/PluginTEASER/bim2sim_teaser/examples/e3_load_teaser_simulation_results.py b/bim2sim/plugins/PluginTEASER/bim2sim_teaser/examples/e3_load_teaser_simulation_results.py index 2e3d5bd87f..58f86d181a 100644 --- a/bim2sim/plugins/PluginTEASER/bim2sim_teaser/examples/e3_load_teaser_simulation_results.py +++ b/bim2sim/plugins/PluginTEASER/bim2sim_teaser/examples/e3_load_teaser_simulation_results.py @@ -40,7 +40,7 @@ def run_example_load_existing_project(): # TODO those 2 are not used but are needed currently as otherwise the # plotting tasks will be executed and weather file is mandatory # set weather file data - project.sim_settings.weather_file_path = ( + project.sim_settings.weather_file_path_modelica = ( Path(bim2sim.__file__).parent.parent / 'test/resources/weather_files/DEU_NW_Aachen.105010_TMYx.mos') # Run a simulation directly with dymola after model creation diff --git a/bim2sim/plugins/PluginTEASER/bim2sim_teaser/examples/e4_visualize_zone_binding.py b/bim2sim/plugins/PluginTEASER/bim2sim_teaser/examples/e4_visualize_zone_binding.py index 6d52fb4684..9e6759319c 100644 --- a/bim2sim/plugins/PluginTEASER/bim2sim_teaser/examples/e4_visualize_zone_binding.py +++ b/bim2sim/plugins/PluginTEASER/bim2sim_teaser/examples/e4_visualize_zone_binding.py @@ -64,7 +64,7 @@ def visualize_zoning_of_complex_building(): "customUsagesFM_ARC_DigitalHub_with_SB89.json") # set weather file data - project.sim_settings.weather_file_path = ( + project.sim_settings.weather_file_path_modelica = ( Path(bim2sim.__file__).parent.parent / 'test/resources/weather_files/DEU_NW_Aachen.105010_TMYx.mos') # Run a simulation directly with dymola after model creation diff --git a/bim2sim/plugins/PluginTEASER/bim2sim_teaser/task/create_result_df.py b/bim2sim/plugins/PluginTEASER/bim2sim_teaser/task/create_result_df.py index 9bba2e32cf..fd884ac071 100644 --- a/bim2sim/plugins/PluginTEASER/bim2sim_teaser/task/create_result_df.py +++ b/bim2sim/plugins/PluginTEASER/bim2sim_teaser/task/create_result_df.py @@ -76,7 +76,7 @@ def run(self, sim_results_path, bldg_names, elements): defined by the sim_settings, are exported to the dataframe. Args: - teaser_mat_result_paths: path to simulation result file + sim_results_path: path to simulation result file bldg_names (list): list of all buildings elements: bim2sim elements created based on ifc data Returns: diff --git a/bim2sim/plugins/PluginTEASER/bim2sim_teaser/task/create_teaser_prj.py b/bim2sim/plugins/PluginTEASER/bim2sim_teaser/task/create_teaser_prj.py index 8eed619247..a073ca2e0c 100644 --- a/bim2sim/plugins/PluginTEASER/bim2sim_teaser/task/create_teaser_prj.py +++ b/bim2sim/plugins/PluginTEASER/bim2sim_teaser/task/create_teaser_prj.py @@ -28,7 +28,7 @@ def run(self, libraries, elements, weather_file_modelica): libraries: previous loaded libraries. In the case this is the TEASER library elements: dict[guid: element] with `bim2sim` elements - weather_file: path to weather file + weather_file_modelica: path to weather file Returns: teaser_prj: teaser project instance diff --git a/bim2sim/plugins/PluginTEASER/test/integration/test_teaser.py b/bim2sim/plugins/PluginTEASER/test/integration/test_teaser.py index d8c5487be5..d3e176861c 100644 --- a/bim2sim/plugins/PluginTEASER/test/integration/test_teaser.py +++ b/bim2sim/plugins/PluginTEASER/test/integration/test_teaser.py @@ -14,9 +14,10 @@ def model_domain_path(self) -> str: def set_test_weather_file(self): """Set the weather file path.""" - self.project.sim_settings.weather_file_path_ep = ( + self.project.sim_settings.weather_file_path_modelica = ( self.test_resources_path() / - 'weather_files/DEU_NW_Aachen.105010_TMYx.epw') + 'weather_files/DEU_NW_Aachen.105010_TMYx.mos') + class TestIntegrationTEASER(IntegrationBaseTEASER, unittest.TestCase): def test_run_kitoffice_spaces_medium_layers_low(self): diff --git a/bim2sim/plugins/PluginTEASER/test/unit/task/test_weather.py b/bim2sim/plugins/PluginTEASER/test/unit/task/test_weather.py index 403dff38c3..6a8cfe5296 100644 --- a/bim2sim/plugins/PluginTEASER/test/unit/task/test_weather.py +++ b/bim2sim/plugins/PluginTEASER/test/unit/task/test_weather.py @@ -22,7 +22,9 @@ class PluginWeatherDummyTEASER(Plugin): ] -test_rsrc_path = Path(__file__).parent.parent.parent.parent / 'resources' +test_rsrc_path = (Path( + __file__).parent.parent.parent.parent.parent.parent.parent / + 'test/resources') class TestWeather(unittest.TestCase): @@ -39,14 +41,15 @@ def test_weather_modelica(self): IFCDomain.arch: test_rsrc_path / 'arch/ifc/AC20-FZK-Haus.ifc'} self.project = Project.create(self.test_dir.name, ifc_paths, plugin=PluginWeatherDummyTEASER) - self.project.sim_settings.weather_file_path = ( + self.project.sim_settings.weather_file_path_modelica = ( test_rsrc_path / 'weather_files/DEU_NW_Aachen.105010_TMYx.mos') handler = DebugDecisionHandler([]) handler.handle(self.project.run(cleanup=False)) try: - weather_file = self.project.playground.state['weather_file'] + weather_file = self.project.playground.state[ + 'weather_file_modelica'] except Exception: raise ValueError(f"No weather file set through Weather task. An" f"error occurred.") self.assertEquals(weather_file, - self.project.sim_settings.weather_file_path) + self.project.sim_settings.weather_file_path_modelica) diff --git a/bim2sim/tasks/common/weather.py b/bim2sim/tasks/common/weather.py index 5af54fadb2..6560ebdf91 100644 --- a/bim2sim/tasks/common/weather.py +++ b/bim2sim/tasks/common/weather.py @@ -1,10 +1,8 @@ from pathlib import Path from bim2sim.tasks.base import ITask -from bim2sim.utilities.common_functions import filter_elements -from typing import Any -from pathlib import WindowsPath, Path -from typing import Optional + + class Weather(ITask): """Task to get the weather file for later simulation""" reads = ('elements',) @@ -12,10 +10,6 @@ class Weather(ITask): def run(self, elements: dict): self.logger.info("Setting weather file.") - weather_file: Optional[WindowsPath] = None - # try to get weather file from settings - if self.playground.sim_settings.weather_file_path: - weather_file = self.playground.sim_settings.weather_file_path weather_file_modelica = None weather_file_ep = None # try to get weather file from settings for modelica and energyplus @@ -25,6 +19,16 @@ def run(self, elements: dict): if self.playground.sim_settings.weather_file_path_ep: weather_file_ep = self.playground.sim_settings.weather_file_path_ep + # try to get TRY weather file for location of IFC + if not weather_file_ep and not weather_file_modelica: + raise NotImplementedError("Waiting for response from DWD if we can" + "implement this") + # lat, long = self.get_location_lat_long_from_ifc(elements) + # weather_file = self.get_weatherfile_from_dwd(lat, long) + self.check_weather_file(weather_file_modelica, weather_file_ep) + if self.playground.sim_settings.weather_file_path_ep: + weather_file_ep = self.playground.sim_settings.weather_file_path_ep + # try to get TRY weather file for location of IFC if not weather_file_ep and not weather_file_modelica: raise NotImplementedError("Waiting for response from DWD if we can" @@ -50,11 +54,6 @@ def check_weather_file(self, weather_file_modelica, weather_file_ep): 'hkesim': ['.mos'] } - def check_file_ending(self, weather_file: WindowsPath): - """Check if the file ending fits the simulation model type.""" - plugin_name = self.playground.project.plugin_cls.name - if plugin_name in ['EnergyPlus', 'Comfort']: - if not weather_file.suffix == '.epw': # Get the expected endings for the plugin_name if plugin_name not in expected_endings: raise ValueError(f"Unknown plugin_name '{plugin_name}'") @@ -84,36 +83,6 @@ def check_file_ending(self, weather_file: WindowsPath): f"{plugin_name} requires a weather file with '.mos'" f" extension.") - def get_location_lat_long_from_ifc(self, elements: dict) -> [float]: - """ - Returns the location in form of latitude and longitude based on IfcSite. - - The location of the site and therefore the building are taken from the - IfcSite in form of latitude and longitude. Latitude and Longitude each - are a tuple of (degrees, minutes, seconds) and, optionally, - millionths of seconds. See IfcSite Documentation for further - information. - Args: - elements: dict with bim2sim elements - - Returns: - latitude, longitude: two float values for latitude and longitude - """ - site = filter_elements(elements, 'Site') - if len(site) > 1: - self.logger.warning( - "More than one IfcSite in the provided IFC file(s). We are" - "using the location of the first IfcSite found for weather " - "file definition.") - latitude = site[0].location_latitude - longitude = site[0].location_longitude - return latitude, longitude - - def get_weatherfile_from_dwd(self, lat: tuple, long: tuple): - # TODO implement scraper, if DWD allows it - raise NotImplementedError("Waiting for response from DWD if we can" - "implement this") - def get_location_name(self, latitude: tuple, longitude: tuple) -> str: """Returns the name of the location based on latitude and longitude. From e76d779e5bc7715f2436b360c54a1b06e47a2ec3 Mon Sep 17 00:00:00 2001 From: David Paul Jansen Date: Fri, 18 Oct 2024 09:49:21 +0200 Subject: [PATCH 065/125] fix base tests for spawn --- .../elements/aggregation/hvac_aggregations.py | 2 +- test/unit/tasks/hvac/test_export.py | 46 +++++++++++++------ 2 files changed, 33 insertions(+), 15 deletions(-) diff --git a/bim2sim/elements/aggregation/hvac_aggregations.py b/bim2sim/elements/aggregation/hvac_aggregations.py index 6e22c7b61c..015a3fadad 100644 --- a/bim2sim/elements/aggregation/hvac_aggregations.py +++ b/bim2sim/elements/aggregation/hvac_aggregations.py @@ -659,7 +659,7 @@ def pump_elements(self) -> list: def _calc_rated_power(self, name) -> ureg.Quantity: """Calculate the rated power adding the rated power of the pump-like elements""" - value = all(ele.rated_power for ele in self.pump_elements) + value = sum(ele.rated_power for ele in self.pump_elements) if value: return value diff --git a/test/unit/tasks/hvac/test_export.py b/test/unit/tasks/hvac/test_export.py index f7849a18bb..6cc3c6c09c 100644 --- a/test/unit/tasks/hvac/test_export.py +++ b/test/unit/tasks/hvac/test_export.py @@ -10,7 +10,8 @@ from bim2sim.export.modelica import ModelicaElement, parse_to_modelica from bim2sim.kernel.decision.decisionhandler import DebugDecisionHandler -from bim2sim.tasks.hvac import Export, LoadLibrariesStandardLibrary +from bim2sim.tasks.hvac import Export, LoadLibrariesStandardLibrary, \ + CreateModelicaModel from test.unit.elements.helper import SetupHelperHVAC @@ -33,6 +34,11 @@ def setUpClass(cls) -> None: lib_msl = LoadLibrariesStandardLibrary(cls.playground) cls.loaded_libs = lib_msl.run()[0] + # Instantiate modelica create task and set required values via mocks + cls.create_modelica_model_task = CreateModelicaModel(cls.playground) + cls.create_modelica_model_task.prj_name = 'TestStandardLibrary' + cls.create_modelica_model_task.paths = paths + # Instantiate export task and set required values via mocks cls.export_task = Export(cls.playground) cls.export_task.prj_name = 'TestStandardLibrary' @@ -43,11 +49,20 @@ def setUpClass(cls) -> None: def setUp(self) -> None: # Set export path to temporary path self.export_path = tempfile.TemporaryDirectory(prefix='bim2sim') + self.export_task.paths.export = self.export_path.name def tearDown(self) -> None: self.helper.reset() + def run_export(self, graph, answers=()): + (export_elements, connections, cons_heat_ports_conv, + cons_heat_ports_rad) = DebugDecisionHandler(answers).handle( + self.create_modelica_model_task.run(self.loaded_libs, graph)) + return self.export_task.run( + export_elements, connections, cons_heat_ports_conv, + cons_heat_ports_rad) + def run_parameter_test(self, graph: HvacGraph, modelica_model: list, parameters: List[Tuple[str, str]], expected_units: list): @@ -129,8 +144,7 @@ def test_missing_required_parameter(self): graph, pipe = self.helper.get_simple_pipe() answers = () with self.assertRaises(AssertionError): - DebugDecisionHandler(answers).handle( - self.export_task.run(self.loaded_libs, graph)) + modelica_model = self.run_export(graph, answers) def test_check_function(self): """ Test if the check function for a parameter works. The exported @@ -140,18 +154,24 @@ def test_check_function(self): graph, pipe = self.helper.get_simple_pipe() pipe.diameter = -1 * ureg.meter answers = () - modelica_model = DebugDecisionHandler(answers).handle( - self.export_task.run(self.loaded_libs, graph)) + (export_elements, connections, cons_heat_ports_conv, + cons_heat_ports_rad) = DebugDecisionHandler( + answers).handle( + self.create_modelica_model_task.run(self.loaded_libs, graph)) + modelica_model = self.export_task.run( + export_elements, connections, cons_heat_ports_conv, + cons_heat_ports_rad) self.assertIsNone( - modelica_model[0].modelica_elements[0].parameters['diameter'].value) + modelica_model[0].modelica_elements[0].parameters[ + 'diameter'].value) self.assertIsNotNone( modelica_model[0].modelica_elements[0].parameters['length'].value) def test_pipe_export(self): graph, pipe = self.helper.get_simple_pipe() pipe.diameter = 0.2 * ureg.meter - modelica_model = DebugDecisionHandler(answers=()).handle( - self.export_task.run(self.loaded_libs, graph)) + modelica_model = self.run_export(graph) + # Test for expected and exported parameters parameters = [('diameter', 'diameter'), ('length', 'length')] expected_units = [ureg.m, ureg.m] @@ -161,8 +181,8 @@ def test_pipe_export(self): def test_valve_export(self): graph = self.helper.get_simple_valve() answers = (1 * ureg.kg / ureg.h,) - modelica_model = DebugDecisionHandler(answers).handle( - self.export_task.run(self.loaded_libs, graph)) + modelica_model = self.run_export(graph, answers) + parameters = [('nominal_pressure_difference', 'dp_nominal'), ('nominal_mass_flow_rate', 'm_flow_nominal')] expected_units = [ureg.bar, ureg.kg / ureg.s] @@ -172,8 +192,7 @@ def test_valve_export(self): def test_junction_export(self): graph = self.helper.get_simple_junction() answers = () - modelica_model = DebugDecisionHandler(answers).handle( - self.export_task.run(self.loaded_libs, graph)) + modelica_model = self.run_export(graph, answers) # Test for expected and exported parameters parameters = [('volume', 'V')] expected_units = [ureg.m ** 3] @@ -183,8 +202,7 @@ def test_junction_export(self): def test_storage_export(self): graph = self.helper.get_simple_storage() answers = () - modelica_model = DebugDecisionHandler(answers).handle( - self.export_task.run(self.loaded_libs, graph)) + modelica_model = self.run_export(graph, answers) # Test for expected and exported parameters parameters = [('volume', 'V')] expected_units = [ureg.m ** 3] From fe563e87e9cd6ae18f3fb9000f1e56aadded96d4 Mon Sep 17 00:00:00 2001 From: David Paul Jansen Date: Fri, 18 Oct 2024 09:59:52 +0200 Subject: [PATCH 066/125] try to fix coverage error by using .txt ending --- bim2sim/assets/templates/modelica/{package => package.txt} | 0 .../templates/modelica/{package_order => package_order.txt} | 0 bim2sim/export/modelica/__init__.py | 4 ++-- 3 files changed, 2 insertions(+), 2 deletions(-) rename bim2sim/assets/templates/modelica/{package => package.txt} (100%) rename bim2sim/assets/templates/modelica/{package_order => package_order.txt} (100%) diff --git a/bim2sim/assets/templates/modelica/package b/bim2sim/assets/templates/modelica/package.txt similarity index 100% rename from bim2sim/assets/templates/modelica/package rename to bim2sim/assets/templates/modelica/package.txt diff --git a/bim2sim/assets/templates/modelica/package_order b/bim2sim/assets/templates/modelica/package_order.txt similarity index 100% rename from bim2sim/assets/templates/modelica/package_order rename to bim2sim/assets/templates/modelica/package_order.txt diff --git a/bim2sim/export/modelica/__init__.py b/bim2sim/export/modelica/__init__.py index 2d29854f1a..63f5f9d0cd 100644 --- a/bim2sim/export/modelica/__init__.py +++ b/bim2sim/export/modelica/__init__.py @@ -58,7 +58,7 @@ def help_package(path: Path, name: str, uses: str = None, within: str = None): """ template_path_package = Path(bim2sim.__file__).parent / \ - "assets/templates/modelica/package" + "assets/templates/modelica/package.txt" package_template = Template(filename=str(template_path_package)) with open(path / 'package.mo', 'w') as out_file: out_file.write(package_template.render_unicode( @@ -88,7 +88,7 @@ def help_package_order(path: Path, package_list: List[str], addition=None, """ template_package_order_path = Path(bim2sim.__file__).parent / \ - "assets/templates/modelica/package_order" + "assets/templates/modelica/package_order.txt" package_order_template = Template(filename=str( template_package_order_path)) with open(path / 'package.order', 'w') as out_file: From 313b6566809e465811001aaec052e7cee1aa9b1d Mon Sep 17 00:00:00 2001 From: David Paul Jansen Date: Fri, 18 Oct 2024 10:07:14 +0200 Subject: [PATCH 067/125] remove modelica templates folder to clean ci --- bim2sim/assets/templates/modelica/package.txt | 14 --- .../templates/modelica/package_order.txt | 10 -- .../assets/templates/modelica/tmplModel.txt | 64 ----------- .../templates/modelica/tmplSpawnBuilding.txt | 106 ------------------ .../modelica/tmplSpawnTotalModel.txt | 22 ---- 5 files changed, 216 deletions(-) delete mode 100644 bim2sim/assets/templates/modelica/package.txt delete mode 100644 bim2sim/assets/templates/modelica/package_order.txt delete mode 100644 bim2sim/assets/templates/modelica/tmplModel.txt delete mode 100644 bim2sim/assets/templates/modelica/tmplSpawnBuilding.txt delete mode 100644 bim2sim/assets/templates/modelica/tmplSpawnTotalModel.txt diff --git a/bim2sim/assets/templates/modelica/package.txt b/bim2sim/assets/templates/modelica/package.txt deleted file mode 100644 index afbf594abb..0000000000 --- a/bim2sim/assets/templates/modelica/package.txt +++ /dev/null @@ -1,14 +0,0 @@ -%if within is not None: -within ${within}; -%endif -package ${name} - extends Modelica.Icons.Package; - - %if uses is not None: - annotation (uses( - %for use in uses: - ${use}${',' if not loop.last else '),'}\ - %endfor - version="1"); - %endif -end ${name}; \ No newline at end of file diff --git a/bim2sim/assets/templates/modelica/package_order.txt b/bim2sim/assets/templates/modelica/package_order.txt deleted file mode 100644 index 3536bb05ac..0000000000 --- a/bim2sim/assets/templates/modelica/package_order.txt +++ /dev/null @@ -1,10 +0,0 @@ -%if extra != None: -${extra} -%endif -%for i in list: -%if addition != None: -${addition}${i.replace(" ", "")} -%else: -${i.replace(" ", "")} -%endif -%endfor \ No newline at end of file diff --git a/bim2sim/assets/templates/modelica/tmplModel.txt b/bim2sim/assets/templates/modelica/tmplModel.txt deleted file mode 100644 index 211327bc8e..0000000000 --- a/bim2sim/assets/templates/modelica/tmplModel.txt +++ /dev/null @@ -1,64 +0,0 @@ -model ${model.name} "${model.comment}" -% if not model.connections_heat_ports_conv or not model.connections_heat_ports_rad: - extends Modelica.Icons.Example; -% endif - import SI = Modelica.Units.SI;\ - -%if unknowns: - // unknown parameters: - %for param in unknowns: - // ${param} - %endfor -%endif - -% for instance in model.modelica_elements: - ${inst(instance)} -% endfor - -<%def name="inst(instance)", filter="trim"> - ${instance.path} ${instance.name}\ - %if instance.modelica_parameters: -(\ - %endif - <% i = 0 %> - % for key, value in instance.modelica_parameters.items(): - <% i+=1 %>${f'{key}="missing - replace with meaningful value"' if value is None else value}${"," if i < len(instance.modelica_parameters) else ")"} - % endfor - "${instance.comment}" annotation (Placement(transformation(extent= - {{${instance.position[0]-10},${instance.position[1]-10}}, - {${instance.position[0]+10},${instance.position[1]+10}}}))); -\ -\ -// Only for SpawnOfEnergyPlus -% if len(model.connections_heat_ports_conv) > 0: -Modelica.Thermal.HeatTransfer.Interfaces.HeatPort_a heatPortOuterCon[${len(model.connections_heat_ports_conv)}] - annotation (Placement(transformation(extent={{-110,10},{-90,30}}))); -% endif -% if len(model.connections_heat_ports_rad) > 0: -Modelica.Thermal.HeatTransfer.Interfaces.HeatPort_a heatPortOuterRad[${len(model.connections_heat_ports_rad)}] - annotation (Placement(transformation(extent={{-110,-22},{-90,-2}}))); -% endif - -equation -% for con1, con2, pos1, pos2 in model.connections: - connect(${con1}, ${con2}) - annotation (Line(points={{${pos1[0]},${pos1[1]}},{38,-6}}, color={0,0,127})); -% endfor - -// heatport connections -% for cons in model.connections_heat_ports_conv: - connect${str(cons).replace("'","")} annotation (Line(points={{0,0},{0,0},{0,0},{0,0},{0,0}}, color={191,0,0})); -% endfor -% for cons in model.connections_heat_ports_rad: - connect${str(cons).replace("'","")} annotation (Line(points={{0,0},{0,0},{0,0},{0,0},{0,0}}, color={191,0,0})); -% endfor - -annotation( -%if unknowns: - Diagram(graphics={Text( - extent={{-100,100},{100,60}}, - lineColor={238,46,47}, - textString="${len(unknowns)} unknown parameters! See comments for details.")}), -%endif -experiment(StopTime=36000)); -end ${model.name}; \ No newline at end of file diff --git a/bim2sim/assets/templates/modelica/tmplSpawnBuilding.txt b/bim2sim/assets/templates/modelica/tmplSpawnBuilding.txt deleted file mode 100644 index f9d310e83e..0000000000 --- a/bim2sim/assets/templates/modelica/tmplSpawnBuilding.txt +++ /dev/null @@ -1,106 +0,0 @@ -within ${within}; -model ${model_name} "${model_comment}" - import SI = Modelica.Units.SI; - - // TODO - parameter Integer nPorts=2 - "Number of fluid ports (equals to 2 for one inlet and one outlet)" - annotation (Evaluate=true,Dialog(connectorSizing=true,tab="General",group="Ports")); - - parameter Integer nZones = ${n_zones}; - - final parameter Modelica.Units.SI.MassFlowRate mOut_flow[nZones]=0.3/3600 * fill(1, nZones) * 1.2 - "Outside air infiltration for each exterior room"; - - parameter String zoneNames[nZones] = ${zone_names} - "Name of the thermal zone as specified in the EnergyPlus input"; - - inner Buildings.ThermalZones.EnergyPlus_9_6_0.Building building ( - idfName = ${idf_path}, - epwName = ${weather_path_ep}, - weaName = ${weather_path_mos}, - printUnits = true) - "" annotation (Placement(transformation(extent={{-100,60},{-80,80}}))); - - - Buildings.Fluid.Sources.MassFlowSource_WeatherData freshairsource[nZones]( - redeclare package Medium = Buildings.Media.Air, - m_flow=mOut_flow, - each nPorts=1) "" - annotation (Placement(transformation(extent={{-43.1111,58},{-23.1111,78}}))); - - Buildings.ThermalZones.EnergyPlus_9_6_0.ThermalZone zon[nZones]( - zoneName=zoneNames, - redeclare package Medium = Buildings.Media.Air, - each use_C_flow=false, - each nPorts=nPorts) - annotation (Placement(transformation(extent={{-20,-20},{20,20}}))); - -// Infiltration - Buildings.Fluid.Sources.Outside out[nZones]( - redeclare package Medium = Buildings.Media.Air, - each nPorts=1) "Outside condition" - annotation (Placement(transformation(extent={{-44,32},{-24,52}}))); - - Buildings.Fluid.FixedResistances.PressureDrop resOut[nZones]( - redeclare each package Medium = Buildings.Media.Air, - each m_flow_nominal=sum(mOut_flow), - each dp_nominal=10, - each linearized=true) "Small flow resistance for inlet" - annotation (Placement(transformation(extent={{-6,58},{14,78}}))); - - Buildings.Fluid.FixedResistances.PressureDrop resIn[nZones]( - redeclare package Medium = Buildings.Media.Air, - each m_flow_nominal=sum(mOut_flow), - each dp_nominal=10, - each linearized=true) "Small flow resistance for outlet" - annotation (Placement(transformation(extent={{-6,32},{14,52}}))); - -// Interfaces - Modelica.Thermal.HeatTransfer.Interfaces.HeatPort_a heaPorCon[nZones] - "Convective heat port to air volume for each zone" - annotation (Placement(transformation(extent={{-110,-10},{-90,10}}))); - Modelica.Thermal.HeatTransfer.Interfaces.HeatPort_a heaPorRad[nZones] - "Radiative heat port to air volume for each zone" - annotation (Placement(transformation(extent={{-110,-28},{-90,-8}}))); - - Modelica.Blocks.Sources.Constant const[nZones,3](each k=0) "TODO" - annotation (Placement(transformation(extent={{-100,16},{-80,36}}))); -equation - - for i in 1:nZones loop - connect(building.weaBus, freshairsource[i].weaBus) annotation (Line( - points={{-80,68},{-52,68},{-52,68.2},{-43.1111,68.2}}, - color={255,204,51}, - thickness=0.5)); - connect(building.weaBus, out[i].weaBus) annotation (Line( - points={{-80,68},{-52,68},{-52,42.2},{-44,42.2}}, - color={255,204,51}, - thickness=0.5)); - connect(resOut[i].port_b, zon[i].ports[1]) annotation (Line(points={{14,68}, - {98,68},{98,-28},{-1,-28},{-1,-19.1}}, color={0,127,255})); - connect(out[i].ports[1], resIn[i].port_a) annotation (Line(points={{-24,42}, - {-6,42}}, color={0,127,255})); - connect(resIn[i].port_b, zon[i].ports[2]) annotation (Line(points={{14,42},{ - 98,42},{98,-28},{1,-28},{1,-19.1}}, color={0,127,255})); - end for; - - - connect(freshairsource[:].ports[1], resOut[:].port_a) - annotation (Line(points={{-23.1111,68},{-14,68},{-14,68},{-6,68}}, - color={0,127,255})); - - connect(heaPorCon, zon.heaPorAir) annotation (Line(points={{-100,0},{0,0}}, - color={191,0,0})); - connect(zon.heaPorRad, heaPorRad) annotation (Line(points={{0,-6},{-82,-6},{-82, - -18},{-100,-18}}, color={191,0,0})); - - connect(const.y, zon.qGai_flow) annotation (Line(points={{-79,26},{-32,26},{-32, - 10},{-22,10}}, color={0,0,127})); - annotation ( - experiment(StopTime=36000), uses( - Modelica(version="4.0.0"), - Buildings(version="10.0.0"), - AixLib(version="1.3.2"))); - -end ${model_name}; diff --git a/bim2sim/assets/templates/modelica/tmplSpawnTotalModel.txt b/bim2sim/assets/templates/modelica/tmplSpawnTotalModel.txt deleted file mode 100644 index 9f2374c6f6..0000000000 --- a/bim2sim/assets/templates/modelica/tmplSpawnTotalModel.txt +++ /dev/null @@ -1,22 +0,0 @@ -within ${within}; -model ${model_name} "${model_comment}" - import SI = Modelica.Units.SI; - ${model_name_building} ${model_name_building.lower()} - annotation (Placement(transformation(extent={{-14,28},{6,48}}))); - ${model_name_hydraulic} ${model_name_hydraulic.lower()} - annotation (Placement(transformation(extent={{-14,-62},{6,-42}}))); -equation - - // heatport connections - % for cons in cons_heat_ports_conv_building_hvac: - connect${str(cons).replace("'","")} annotation (Line(points={{0,0},{0,0},{0,0},{0,0},{0,0}}, color={191,0,0})); - % endfor - % for cons in cons_heat_ports_rad_building_hvac: - connect${str(cons).replace("'","")} annotation (Line(points={{0,0},{0,0},{0,0},{0,0},{0,0}}, color={191,0,0})); - % endfor - - - annotation (Icon(coordinateSystem(preserveAspectRatio=false)), Diagram( - coordinateSystem(preserveAspectRatio=false))); -end ${model_name}; - From a3f392fdcfd0d9de63839fd7d652e2fd058cdfe2 Mon Sep 17 00:00:00 2001 From: David Paul Jansen Date: Fri, 18 Oct 2024 10:07:56 +0200 Subject: [PATCH 068/125] readd modelica templates folder --- bim2sim/assets/templates/modelica/package.txt | 14 +++ .../templates/modelica/package_order.txt | 10 ++ .../assets/templates/modelica/tmplModel.txt | 64 +++++++++++ .../templates/modelica/tmplSpawnBuilding.txt | 106 ++++++++++++++++++ .../modelica/tmplSpawnTotalModel.txt | 22 ++++ 5 files changed, 216 insertions(+) create mode 100644 bim2sim/assets/templates/modelica/package.txt create mode 100644 bim2sim/assets/templates/modelica/package_order.txt create mode 100644 bim2sim/assets/templates/modelica/tmplModel.txt create mode 100644 bim2sim/assets/templates/modelica/tmplSpawnBuilding.txt create mode 100644 bim2sim/assets/templates/modelica/tmplSpawnTotalModel.txt diff --git a/bim2sim/assets/templates/modelica/package.txt b/bim2sim/assets/templates/modelica/package.txt new file mode 100644 index 0000000000..afbf594abb --- /dev/null +++ b/bim2sim/assets/templates/modelica/package.txt @@ -0,0 +1,14 @@ +%if within is not None: +within ${within}; +%endif +package ${name} + extends Modelica.Icons.Package; + + %if uses is not None: + annotation (uses( + %for use in uses: + ${use}${',' if not loop.last else '),'}\ + %endfor + version="1"); + %endif +end ${name}; \ No newline at end of file diff --git a/bim2sim/assets/templates/modelica/package_order.txt b/bim2sim/assets/templates/modelica/package_order.txt new file mode 100644 index 0000000000..3536bb05ac --- /dev/null +++ b/bim2sim/assets/templates/modelica/package_order.txt @@ -0,0 +1,10 @@ +%if extra != None: +${extra} +%endif +%for i in list: +%if addition != None: +${addition}${i.replace(" ", "")} +%else: +${i.replace(" ", "")} +%endif +%endfor \ No newline at end of file diff --git a/bim2sim/assets/templates/modelica/tmplModel.txt b/bim2sim/assets/templates/modelica/tmplModel.txt new file mode 100644 index 0000000000..211327bc8e --- /dev/null +++ b/bim2sim/assets/templates/modelica/tmplModel.txt @@ -0,0 +1,64 @@ +model ${model.name} "${model.comment}" +% if not model.connections_heat_ports_conv or not model.connections_heat_ports_rad: + extends Modelica.Icons.Example; +% endif + import SI = Modelica.Units.SI;\ + +%if unknowns: + // unknown parameters: + %for param in unknowns: + // ${param} + %endfor +%endif + +% for instance in model.modelica_elements: + ${inst(instance)} +% endfor + +<%def name="inst(instance)", filter="trim"> + ${instance.path} ${instance.name}\ + %if instance.modelica_parameters: +(\ + %endif + <% i = 0 %> + % for key, value in instance.modelica_parameters.items(): + <% i+=1 %>${f'{key}="missing - replace with meaningful value"' if value is None else value}${"," if i < len(instance.modelica_parameters) else ")"} + % endfor + "${instance.comment}" annotation (Placement(transformation(extent= + {{${instance.position[0]-10},${instance.position[1]-10}}, + {${instance.position[0]+10},${instance.position[1]+10}}}))); +\ +\ +// Only for SpawnOfEnergyPlus +% if len(model.connections_heat_ports_conv) > 0: +Modelica.Thermal.HeatTransfer.Interfaces.HeatPort_a heatPortOuterCon[${len(model.connections_heat_ports_conv)}] + annotation (Placement(transformation(extent={{-110,10},{-90,30}}))); +% endif +% if len(model.connections_heat_ports_rad) > 0: +Modelica.Thermal.HeatTransfer.Interfaces.HeatPort_a heatPortOuterRad[${len(model.connections_heat_ports_rad)}] + annotation (Placement(transformation(extent={{-110,-22},{-90,-2}}))); +% endif + +equation +% for con1, con2, pos1, pos2 in model.connections: + connect(${con1}, ${con2}) + annotation (Line(points={{${pos1[0]},${pos1[1]}},{38,-6}}, color={0,0,127})); +% endfor + +// heatport connections +% for cons in model.connections_heat_ports_conv: + connect${str(cons).replace("'","")} annotation (Line(points={{0,0},{0,0},{0,0},{0,0},{0,0}}, color={191,0,0})); +% endfor +% for cons in model.connections_heat_ports_rad: + connect${str(cons).replace("'","")} annotation (Line(points={{0,0},{0,0},{0,0},{0,0},{0,0}}, color={191,0,0})); +% endfor + +annotation( +%if unknowns: + Diagram(graphics={Text( + extent={{-100,100},{100,60}}, + lineColor={238,46,47}, + textString="${len(unknowns)} unknown parameters! See comments for details.")}), +%endif +experiment(StopTime=36000)); +end ${model.name}; \ No newline at end of file diff --git a/bim2sim/assets/templates/modelica/tmplSpawnBuilding.txt b/bim2sim/assets/templates/modelica/tmplSpawnBuilding.txt new file mode 100644 index 0000000000..f9d310e83e --- /dev/null +++ b/bim2sim/assets/templates/modelica/tmplSpawnBuilding.txt @@ -0,0 +1,106 @@ +within ${within}; +model ${model_name} "${model_comment}" + import SI = Modelica.Units.SI; + + // TODO + parameter Integer nPorts=2 + "Number of fluid ports (equals to 2 for one inlet and one outlet)" + annotation (Evaluate=true,Dialog(connectorSizing=true,tab="General",group="Ports")); + + parameter Integer nZones = ${n_zones}; + + final parameter Modelica.Units.SI.MassFlowRate mOut_flow[nZones]=0.3/3600 * fill(1, nZones) * 1.2 + "Outside air infiltration for each exterior room"; + + parameter String zoneNames[nZones] = ${zone_names} + "Name of the thermal zone as specified in the EnergyPlus input"; + + inner Buildings.ThermalZones.EnergyPlus_9_6_0.Building building ( + idfName = ${idf_path}, + epwName = ${weather_path_ep}, + weaName = ${weather_path_mos}, + printUnits = true) + "" annotation (Placement(transformation(extent={{-100,60},{-80,80}}))); + + + Buildings.Fluid.Sources.MassFlowSource_WeatherData freshairsource[nZones]( + redeclare package Medium = Buildings.Media.Air, + m_flow=mOut_flow, + each nPorts=1) "" + annotation (Placement(transformation(extent={{-43.1111,58},{-23.1111,78}}))); + + Buildings.ThermalZones.EnergyPlus_9_6_0.ThermalZone zon[nZones]( + zoneName=zoneNames, + redeclare package Medium = Buildings.Media.Air, + each use_C_flow=false, + each nPorts=nPorts) + annotation (Placement(transformation(extent={{-20,-20},{20,20}}))); + +// Infiltration + Buildings.Fluid.Sources.Outside out[nZones]( + redeclare package Medium = Buildings.Media.Air, + each nPorts=1) "Outside condition" + annotation (Placement(transformation(extent={{-44,32},{-24,52}}))); + + Buildings.Fluid.FixedResistances.PressureDrop resOut[nZones]( + redeclare each package Medium = Buildings.Media.Air, + each m_flow_nominal=sum(mOut_flow), + each dp_nominal=10, + each linearized=true) "Small flow resistance for inlet" + annotation (Placement(transformation(extent={{-6,58},{14,78}}))); + + Buildings.Fluid.FixedResistances.PressureDrop resIn[nZones]( + redeclare package Medium = Buildings.Media.Air, + each m_flow_nominal=sum(mOut_flow), + each dp_nominal=10, + each linearized=true) "Small flow resistance for outlet" + annotation (Placement(transformation(extent={{-6,32},{14,52}}))); + +// Interfaces + Modelica.Thermal.HeatTransfer.Interfaces.HeatPort_a heaPorCon[nZones] + "Convective heat port to air volume for each zone" + annotation (Placement(transformation(extent={{-110,-10},{-90,10}}))); + Modelica.Thermal.HeatTransfer.Interfaces.HeatPort_a heaPorRad[nZones] + "Radiative heat port to air volume for each zone" + annotation (Placement(transformation(extent={{-110,-28},{-90,-8}}))); + + Modelica.Blocks.Sources.Constant const[nZones,3](each k=0) "TODO" + annotation (Placement(transformation(extent={{-100,16},{-80,36}}))); +equation + + for i in 1:nZones loop + connect(building.weaBus, freshairsource[i].weaBus) annotation (Line( + points={{-80,68},{-52,68},{-52,68.2},{-43.1111,68.2}}, + color={255,204,51}, + thickness=0.5)); + connect(building.weaBus, out[i].weaBus) annotation (Line( + points={{-80,68},{-52,68},{-52,42.2},{-44,42.2}}, + color={255,204,51}, + thickness=0.5)); + connect(resOut[i].port_b, zon[i].ports[1]) annotation (Line(points={{14,68}, + {98,68},{98,-28},{-1,-28},{-1,-19.1}}, color={0,127,255})); + connect(out[i].ports[1], resIn[i].port_a) annotation (Line(points={{-24,42}, + {-6,42}}, color={0,127,255})); + connect(resIn[i].port_b, zon[i].ports[2]) annotation (Line(points={{14,42},{ + 98,42},{98,-28},{1,-28},{1,-19.1}}, color={0,127,255})); + end for; + + + connect(freshairsource[:].ports[1], resOut[:].port_a) + annotation (Line(points={{-23.1111,68},{-14,68},{-14,68},{-6,68}}, + color={0,127,255})); + + connect(heaPorCon, zon.heaPorAir) annotation (Line(points={{-100,0},{0,0}}, + color={191,0,0})); + connect(zon.heaPorRad, heaPorRad) annotation (Line(points={{0,-6},{-82,-6},{-82, + -18},{-100,-18}}, color={191,0,0})); + + connect(const.y, zon.qGai_flow) annotation (Line(points={{-79,26},{-32,26},{-32, + 10},{-22,10}}, color={0,0,127})); + annotation ( + experiment(StopTime=36000), uses( + Modelica(version="4.0.0"), + Buildings(version="10.0.0"), + AixLib(version="1.3.2"))); + +end ${model_name}; diff --git a/bim2sim/assets/templates/modelica/tmplSpawnTotalModel.txt b/bim2sim/assets/templates/modelica/tmplSpawnTotalModel.txt new file mode 100644 index 0000000000..9f2374c6f6 --- /dev/null +++ b/bim2sim/assets/templates/modelica/tmplSpawnTotalModel.txt @@ -0,0 +1,22 @@ +within ${within}; +model ${model_name} "${model_comment}" + import SI = Modelica.Units.SI; + ${model_name_building} ${model_name_building.lower()} + annotation (Placement(transformation(extent={{-14,28},{6,48}}))); + ${model_name_hydraulic} ${model_name_hydraulic.lower()} + annotation (Placement(transformation(extent={{-14,-62},{6,-42}}))); +equation + + // heatport connections + % for cons in cons_heat_ports_conv_building_hvac: + connect${str(cons).replace("'","")} annotation (Line(points={{0,0},{0,0},{0,0},{0,0},{0,0}}, color={191,0,0})); + % endfor + % for cons in cons_heat_ports_rad_building_hvac: + connect${str(cons).replace("'","")} annotation (Line(points={{0,0},{0,0},{0,0},{0,0},{0,0}}, color={191,0,0})); + % endfor + + + annotation (Icon(coordinateSystem(preserveAspectRatio=false)), Diagram( + coordinateSystem(preserveAspectRatio=false))); +end ${model_name}; + From 3f7d595a82ea89a70ebbae889090d532a4c02314 Mon Sep 17 00:00:00 2001 From: David Paul Jansen Date: Fri, 18 Oct 2024 11:31:22 +0200 Subject: [PATCH 069/125] temporary fix test for aixlib but further work needed in #733 --- bim2sim/elements/hvac_elements.py | 1 + bim2sim/export/modelica/standardlibrary.py | 2 ++ .../bim2sim_aixlib/models/__init__.py | 18 ++++++++++++++++++ .../test/integration/test_aixlib.py | 5 +++-- bim2sim/tasks/hvac/reduce.py | 1 + 5 files changed, 25 insertions(+), 2 deletions(-) diff --git a/bim2sim/elements/hvac_elements.py b/bim2sim/elements/hvac_elements.py index 9827ac9704..48655a1c7a 100644 --- a/bim2sim/elements/hvac_elements.py +++ b/bim2sim/elements/hvac_elements.py @@ -40,6 +40,7 @@ class HVACPort(Port): re.IGNORECASE) # TODO: extend pattern rl_pattern = re.compile('.*rücklauf.*', re.IGNORECASE) + # TODO #733 Clean port flow side setup def __init__( self, *args, groups: Set = None, flow_direction: int = 0, **kwargs): diff --git a/bim2sim/export/modelica/standardlibrary.py b/bim2sim/export/modelica/standardlibrary.py index b699976fcc..6e8db56124 100644 --- a/bim2sim/export/modelica/standardlibrary.py +++ b/bim2sim/export/modelica/standardlibrary.py @@ -55,6 +55,8 @@ def get_port_name(self, port): return 'port_a' if port.verbose_flow_direction == 'SOURCE': return 'port_b' + # TODO #733 find port if sourceandsink or sinkdansource + # if port.flow_direction == 0. # SOURCEANDSINK and SINKANDSOURCE else: return super().get_port_name(port) diff --git a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py index 04aa4241da..074af4260d 100644 --- a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py +++ b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py @@ -576,3 +576,21 @@ def __init__(self, element): value={'hTank': self.parameters['hTank'], "dTank": self.parameters['dTank']}) + # def get_port_name(self, port): + # TODO #733 + # TODO function to determine ports. One Idea would be to use the + # geometric positions of ports to determine input and output. + # Top port with input: fluidportTop1 + # Bottom port with input: fluidportBottom1 + # Top port with output: fluidportTop2 + # Bottom port with input: fluidportBottom2 + + # Additionally, the number of ports can be used to determine if its a + # direct loaded or indirect loaded storage: + # 4 ports -> direct, + # 6 or any other even number > 4 -> indirect load + # with n=n_ports /2 -4 -> number of heating coils + # Then again used height of port to determine port name + # top port of first heating coil: portHC1In + # bottom port of first heating coil: portHC1Out + # etc. diff --git a/bim2sim/plugins/PluginAixLib/test/integration/test_aixlib.py b/bim2sim/plugins/PluginAixLib/test/integration/test_aixlib.py index e77a6e387f..38846d260c 100644 --- a/bim2sim/plugins/PluginAixLib/test/integration/test_aixlib.py +++ b/bim2sim/plugins/PluginAixLib/test/integration/test_aixlib.py @@ -35,8 +35,9 @@ def test_vereinshaus1_aixlib(self): project = self.create_project(ifc_names, 'aixlib') answers = ('HVAC-HeatPump', 'HVAC-Storage', 'HVAC-Storage', '2lU4kSSzH16v7KPrwcL7KZ', '0t2j$jKmf74PQpOI0ZmPCc', - # 1x expansion tank and 17x dead end - *(True,) * 18, + # TODO #733 + # 1x expansion tank and 20x dead end + *(True,) * 21, # boiler efficiency 0.9, # boiler flow temperature diff --git a/bim2sim/tasks/hvac/reduce.py b/bim2sim/tasks/hvac/reduce.py index 5f6bfe141b..a1ec6792e0 100644 --- a/bim2sim/tasks/hvac/reduce.py +++ b/bim2sim/tasks/hvac/reduce.py @@ -102,6 +102,7 @@ def set_flow_sides(graph: HvacGraph): DecisionBunch: A collection of decisions may be yielded during the task. """ + # TODO #733 # TODO: needs testing! # TODO: at least one master element required accepted = [] From 61a3bd6386e92d3a2cd98e3d69efca5d1a47ea3f Mon Sep 17 00:00:00 2001 From: David Paul Jansen Date: Fri, 18 Oct 2024 11:45:48 +0200 Subject: [PATCH 070/125] fix cfd test --- bim2sim/plugins/PluginCFD/test/integration/test_cfd.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bim2sim/plugins/PluginCFD/test/integration/test_cfd.py b/bim2sim/plugins/PluginCFD/test/integration/test_cfd.py index 678949d347..ed9139fe18 100644 --- a/bim2sim/plugins/PluginCFD/test/integration/test_cfd.py +++ b/bim2sim/plugins/PluginCFD/test/integration/test_cfd.py @@ -14,6 +14,9 @@ def tearDown(self): def model_domain_path(self) -> str: return 'arch' + def set_test_weather_file(self): + return None + class TestIntegrationCFD(IntegrationBaseCFD, unittest.TestCase): From 422ad894de63c7ec4da128c1541a531d368cdeab Mon Sep 17 00:00:00 2001 From: David Paul Jansen Date: Fri, 18 Oct 2024 14:21:39 +0200 Subject: [PATCH 071/125] fix hkesim tests --- .gitlab-ci.yml | 2 +- .../elements/aggregation/hvac_aggregations.py | 29 ++++++----- .../PluginHKESim/bim2sim_hkesim/__init__.py | 1 + .../bim2sim_hkesim/models/__init__.py | 2 +- .../test/integration/test_hkesim.py | 11 ++-- .../test/unit/kernel/task/test_export.py | 51 ++++++++----------- 6 files changed, 50 insertions(+), 46 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4e3d239d60..0e8f0ad829 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -464,7 +464,7 @@ test_HKESim: - mv ./* /bim2sim-coding/ - cd /bim2sim-coding - python /bim2sim-coding/test/resources/dl_test_resources.py --domain=hydraulic --force_new - - coverage run -m unittest discover /bim2sim-coding/bim2sim/plugins/PluginHKESim/test/integration + - coverage run -m unittest discover /bim2sim-coding/bim2sim/plugins/PluginHKESim/test - coverage report -i test_LCA: diff --git a/bim2sim/elements/aggregation/hvac_aggregations.py b/bim2sim/elements/aggregation/hvac_aggregations.py index 015a3fadad..d0ae4dabf4 100644 --- a/bim2sim/elements/aggregation/hvac_aggregations.py +++ b/bim2sim/elements/aggregation/hvac_aggregations.py @@ -876,9 +876,17 @@ def _calc_flow_temperature(self, name) -> ureg.Quantity: """ Calculate the flow temperature, using the flow temperature of the whitelist_classes elements. """ - value= (sum(ele.flow_temperature.to_base_units() for ele - in self.whitelist_elements if ele.flow_temperature) - / len(self.whitelist_elements)) + # TODO the following would work, but only if we want a medium + # temperature for the consumer. If we want a list, this needs to look + # different + value = (sum(ele.flow_temperature.to_base_units() for ele + in self.whitelist_elements if + ele.flow_temperature is not None) + / len([ele for ele in self.whitelist_elements if + ele.flow_temperature is not None])) + # value = (sum(ele.flow_temperature.to_base_units() for ele + # in self.whitelist_elements if ele.flow_temperature) + # / len(self.whitelist_elements)) if value: return value @@ -893,8 +901,11 @@ def _calc_return_temperature(self, name) -> ureg.Quantity: """ Calculate the return temperature, using the return temperature of the whitelist_classes elements. """ - value = sum(ele.return_temperature.to_base_units() for ele - in self.whitelist_elements) / len(self.whitelist_elements) + value = (sum(ele.return_temperature.to_base_units() for ele + in self.whitelist_elements if + ele.return_temperature is not None) + / len([ele for ele in self.whitelist_elements if + ele.return_temperature is not None])) if value: return value @@ -1148,16 +1159,10 @@ def _calc_has_pump(self, name) -> list[bool]: functions=[_calc_has_pump] ) - def _calc_return_temperature(self, name) -> list: - """Calculate the return temperature, using the return temperature of the - whitelist_classes elements""" - return [ele.return_temperature.to_base_units() for ele - in self.whitelist_elements] - return_temperature = attribute.Attribute( description="temperature outlet", unit=ureg.kelvin, - functions=[_calc_return_temperature], + functions=[Consumer._calc_return_temperature], dependant_elements='whitelist_elements' ) diff --git a/bim2sim/plugins/PluginHKESim/bim2sim_hkesim/__init__.py b/bim2sim/plugins/PluginHKESim/bim2sim_hkesim/__init__.py index 9e2efb6460..14917c2643 100644 --- a/bim2sim/plugins/PluginHKESim/bim2sim_hkesim/__init__.py +++ b/bim2sim/plugins/PluginHKESim/bim2sim_hkesim/__init__.py @@ -31,5 +31,6 @@ class PluginHKESim(Plugin): hvac.Reduce, hvac.DeadEnds, LoadLibrariesHKESim, + hvac.CreateModelicaModel, hvac.Export, ] diff --git a/bim2sim/plugins/PluginHKESim/bim2sim_hkesim/models/__init__.py b/bim2sim/plugins/PluginHKESim/bim2sim_hkesim/models/__init__.py index 152cfabdfb..e79dd37c33 100644 --- a/bim2sim/plugins/PluginHKESim/bim2sim_hkesim/models/__init__.py +++ b/bim2sim/plugins/PluginHKESim/bim2sim_hkesim/models/__init__.py @@ -149,7 +149,7 @@ def __init__(self, element): required=False, function=lambda flow_temperature, return_temperature: - (flow_temperature[0], return_temperature[0])) + (flow_temperature, return_temperature)) self._set_parameter(name='useHydraulicSeparator', unit=None, required=False, diff --git a/bim2sim/plugins/PluginHKESim/test/integration/test_hkesim.py b/bim2sim/plugins/PluginHKESim/test/integration/test_hkesim.py index c8c79ce68a..ef378217db 100644 --- a/bim2sim/plugins/PluginHKESim/test/integration/test_hkesim.py +++ b/bim2sim/plugins/PluginHKESim/test/integration/test_hkesim.py @@ -36,8 +36,8 @@ def test_run_vereinshaus1(self): project = self.create_project(ifc_names, 'hkesim') answers = ('HVAC-HeatPump', 'HVAC-Storage', 'HVAC-Storage', '2lU4kSSzH16v7KPrwcL7KZ', '0t2j$jKmf74PQpOI0ZmPCc', - # 1x expansion tank and 17x dead end - *(True,) * 18, + # 1x expansion tank and 20x dead end + *(True,) * 21, # boiler efficiency 0.9, # boiler power, @@ -113,12 +113,17 @@ def test_run_b03_heating_with_all_aggregations(self): 'HVAC-ThreeWayValve', # 6x dead ends *(True,) * 6, + # flow temperature consumer + 70, # boiler: nominal flow temperature 70, - # boiler: rated power consumption + # boiler: rated power 150, # boiler: nominal return temperature 50) + # from bim2sim import ConsoleDecisionHandler, run_project + # handler = ConsoleDecisionHandler() + # run_project(project, handler) handler = DebugDecisionHandler(answers) for decision, answer in handler.decision_answer_mapping(project.run()): decision.value = answer diff --git a/bim2sim/plugins/PluginHKESim/test/unit/kernel/task/test_export.py b/bim2sim/plugins/PluginHKESim/test/unit/kernel/task/test_export.py index 12138015b2..2a5c971433 100644 --- a/bim2sim/plugins/PluginHKESim/test/unit/kernel/task/test_export.py +++ b/bim2sim/plugins/PluginHKESim/test/unit/kernel/task/test_export.py @@ -4,7 +4,6 @@ from bim2sim.elements.aggregation.hvac_aggregations import \ ConsumerHeatingDistributorModule from bim2sim.elements.mapping.units import ureg -from bim2sim.kernel.decision.decisionhandler import DebugDecisionHandler from bim2sim.plugins.PluginHKESim.bim2sim_hkesim import LoadLibrariesHKESim from test.unit.tasks.hvac.test_export import TestStandardLibraryExports @@ -31,8 +30,7 @@ def tearDown(self) -> None: def test_boiler_export(self): graph = self.helper.get_simple_boiler() answers = () - modelica_model = DebugDecisionHandler(answers).handle( - self.export_task.run(self.loaded_libs, graph)) + modelica_model = self.run_export(graph, answers) parameters = [('rated_power', 'Q_nom'), ('return_temperature', 'T_set')] expected_units = [ureg.watt, ureg.kelvin] @@ -42,8 +40,7 @@ def test_boiler_export(self): def test_radiator_export(self): graph = self.helper.get_simple_radiator() answers = () - modelica_model = DebugDecisionHandler(answers).handle( - self.export_task.run(self.loaded_libs, graph)) + modelica_model = self.run_export(graph, answers) parameters = [('rated_power', 'Q_flow_nominal'), ('return_temperature', 'Tout_max')] expected_units = [ureg.watt, ureg.kelvin] @@ -53,8 +50,7 @@ def test_radiator_export(self): def test_pump_export(self): graph, _ = self.helper.get_simple_pump() answers = () - modelica_model = DebugDecisionHandler(answers).handle( - self.export_task.run(self.loaded_libs, graph)) + modelica_model = self.run_export(graph, answers) parameters = [('rated_height', 'head_set'), ('rated_volume_flow', 'Vflow_set'), ('rated_power', 'P_nom')] @@ -71,8 +67,7 @@ def test_consumer_heating_distributor_module_export(self): # Set up the test graph and model graph = self.helper.get_simple_consumer_heating_distributor_module() answers = () - modelica_model = DebugDecisionHandler(answers).handle( - self.export_task.run(self.loaded_libs, graph)) + modelica_model = self.run_export(graph, answers) # Get the ConsumerHeatingDistributorModule element element = next(element for element in graph.elements @@ -80,12 +75,13 @@ def test_consumer_heating_distributor_module_export(self): ConsumerHeatingDistributorModule)) # Get parameter values from element - flow_temp_0 = element.flow_temperature[0].magnitude - return_temp_0 = element.return_temperature[0].magnitude - flow_temp_1 = element.flow_temperature[1].magnitude - return_temp_1 = element.return_temperature[1].magnitude - rated_power_0 = element.rated_power[0].to(ureg.watt).magnitude - rated_power_1 = element.rated_power[1].to(ureg.watt).magnitude + flow_temp_0 = element.flow_temperature.magnitude + return_temp_0 = element.return_temperature.magnitude + flow_temp_1 = element.flow_temperature.magnitude + return_temp_1 = element.return_temperature.magnitude + consumers = iter(element.consumers) + rated_power_0 = next(consumers).rated_power.to(ureg.watt).magnitude + rated_power_1 = next(consumers).rated_power.to(ureg.watt).magnitude # Define the expected parameter strings in modelica model code expected_strings = [ @@ -103,29 +99,29 @@ def test_consumer_heating_distributor_module_export(self): # Assert that each expected string is in the modelica_model code for expected_string in expected_strings: - self.assertIn(expected_string, modelica_model[0].code()) + self.assertIn(expected_string, + modelica_model[0].render_modelica_code()) def test_boiler_module_export(self): graph = self.helper.get_simple_generator_one_fluid() answers = () - modelica_model = DebugDecisionHandler(answers).handle( - self.export_task.run(self.loaded_libs, graph)) + modelica_model = self.run_export(graph, answers) element = graph.elements[0] rated_power = element.rated_power.to(ureg.watt).magnitude - flow_temp = element.flow_temperature.magnitude - return_temp = element.return_temperature.magnitude + flow_temp = element.flow_temperature.to(ureg.kelvin).magnitude + return_temp = element.return_temperature.to(ureg.kelvin).magnitude expected_strings = [ f"Theating={{{flow_temp},{return_temp}}}", f"Qflow_nom={rated_power}", ] for expected_string in expected_strings: - self.assertIn(expected_string, modelica_model[0].code()) + self.assertIn(expected_string, + modelica_model[0].render_modelica_code()) def test_heat_pump_export(self): graph = self.helper.get_simple_heat_pump() answers = () - modelica_model = DebugDecisionHandler(answers).handle( - self.export_task.run(self.loaded_libs, graph)) + modelica_model = self.run_export(graph, answers) parameters = [('rated_power', 'Qcon_nom')] expected_units = [ureg.watt] self.run_parameter_test(graph, modelica_model, parameters, @@ -134,8 +130,7 @@ def test_heat_pump_export(self): def test_chiller_export(self): graph = self.helper.get_simple_chiller() answers = () - modelica_model = DebugDecisionHandler(answers).handle( - self.export_task.run(self.loaded_libs, graph)) + modelica_model = self.run_export(graph, answers) parameters = [('rated_power', 'Qev_nom'), ('nominal_COP', 'EER_nom')] expected_units = [ureg.watt, ureg.dimensionless] @@ -145,8 +140,7 @@ def test_chiller_export(self): def test_chp_export(self): graph = self.helper.get_simple_chp() answers = () - modelica_model = DebugDecisionHandler(answers).handle( - self.export_task.run(self.loaded_libs, graph)) + modelica_model = self.run_export(graph, answers) parameters = [('rated_power', 'P_nom')] expected_units = [ureg.watt] self.run_parameter_test(graph, modelica_model, parameters, @@ -155,8 +149,7 @@ def test_chp_export(self): def test_cooling_tower_export(self): graph = self.helper.get_simple_cooling_tower() answers = () - modelica_model = DebugDecisionHandler(answers).handle( - self.export_task.run(self.loaded_libs, graph)) + modelica_model = self.run_export(graph, answers) parameters = [('rated_power', 'Qflow_nom')] expected_units = [ureg.watt] self.run_parameter_test(graph, modelica_model, parameters, From 1c51e7823e2bfd2443339644683ae028316db9d6 Mon Sep 17 00:00:00 2001 From: David Paul Jansen Date: Fri, 18 Oct 2024 14:35:40 +0200 Subject: [PATCH 072/125] update regression test setup to weatherfile changes for TEASER and EP --- .../PluginEnergyPlus/test/regression/test_energyplus.py | 6 ++++-- bim2sim/plugins/PluginTEASER/test/regression/test_teaser.py | 6 ++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/bim2sim/plugins/PluginEnergyPlus/test/regression/test_energyplus.py b/bim2sim/plugins/PluginEnergyPlus/test/regression/test_energyplus.py index 9c4e1fe956..0bc325c7b5 100644 --- a/bim2sim/plugins/PluginEnergyPlus/test/regression/test_energyplus.py +++ b/bim2sim/plugins/PluginEnergyPlus/test/regression/test_energyplus.py @@ -33,8 +33,10 @@ def tearDown(self): sys.stderr = self.old_stderr super().tearDown() - def weather_file_path(self) -> Path: - return (self.test_resources_path() / + def set_test_weather_file(self): + """Set the weather file path.""" + self.project.sim_settings.weather_file_path_ep = ( + self.test_resources_path() / 'weather_files/DEU_NW_Aachen.105010_TMYx.epw') def create_regression_setup(self): diff --git a/bim2sim/plugins/PluginTEASER/test/regression/test_teaser.py b/bim2sim/plugins/PluginTEASER/test/regression/test_teaser.py index c24356124c..385013033c 100644 --- a/bim2sim/plugins/PluginTEASER/test/regression/test_teaser.py +++ b/bim2sim/plugins/PluginTEASER/test/regression/test_teaser.py @@ -44,6 +44,12 @@ def tearDown(self): super().tearDown() + def set_test_weather_file(self): + """Set the weather file path.""" + self.project.sim_settings.weather_file_path_ep = ( + self.test_resources_path() / + 'weather_files/DEU_NW_Aachen.105010_TMYx.epw') + def create_regression_setup( self, tolerance: float = 1E-3, From cdc3ea7bc6e5392022181ec69f80d8e48d30dcce Mon Sep 17 00:00:00 2001 From: David Paul Jansen Date: Fri, 18 Oct 2024 14:46:51 +0200 Subject: [PATCH 073/125] adjust changes in weather file test pathing --- .../PluginAixLib/test/integration/test_aixlib.py | 4 ++-- .../plugins/PluginCFD/test/integration/test_cfd.py | 3 --- .../PluginComfort/test/integration/test_comfort.py | 4 ++-- .../test/integration/test_energyplus.py | 12 ++++-------- .../PluginTEASER/test/integration/test_teaser.py | 4 ++-- bim2sim/utilities/test.py | 5 ++++- 6 files changed, 14 insertions(+), 18 deletions(-) diff --git a/bim2sim/plugins/PluginAixLib/test/integration/test_aixlib.py b/bim2sim/plugins/PluginAixLib/test/integration/test_aixlib.py index 38846d260c..b937f8406b 100644 --- a/bim2sim/plugins/PluginAixLib/test/integration/test_aixlib.py +++ b/bim2sim/plugins/PluginAixLib/test/integration/test_aixlib.py @@ -6,11 +6,11 @@ from bim2sim.export.modelica import ModelicaElement from bim2sim.elements.aggregation.hvac_aggregations import \ ConsumerHeatingDistributorModule -from bim2sim.utilities.test import IntegrationBase +from bim2sim.utilities.test import IntegrationWeatherBase from bim2sim.utilities.types import IFCDomain -class IntegrationBaseAixLib(IntegrationBase): +class IntegrationBaseAixLib(IntegrationWeatherBase): def tearDown(self): ModelicaElement.lookup = {} super().tearDown() diff --git a/bim2sim/plugins/PluginCFD/test/integration/test_cfd.py b/bim2sim/plugins/PluginCFD/test/integration/test_cfd.py index ed9139fe18..678949d347 100644 --- a/bim2sim/plugins/PluginCFD/test/integration/test_cfd.py +++ b/bim2sim/plugins/PluginCFD/test/integration/test_cfd.py @@ -14,9 +14,6 @@ def tearDown(self): def model_domain_path(self) -> str: return 'arch' - def set_test_weather_file(self): - return None - class TestIntegrationCFD(IntegrationBaseCFD, unittest.TestCase): diff --git a/bim2sim/plugins/PluginComfort/test/integration/test_comfort.py b/bim2sim/plugins/PluginComfort/test/integration/test_comfort.py index dde25f805a..f0d27fb349 100644 --- a/bim2sim/plugins/PluginComfort/test/integration/test_comfort.py +++ b/bim2sim/plugins/PluginComfort/test/integration/test_comfort.py @@ -7,7 +7,7 @@ import bim2sim from bim2sim.kernel.decision.decisionhandler import DebugDecisionHandler -from bim2sim.utilities.test import IntegrationBase +from bim2sim.utilities.test import IntegrationWeatherBase from bim2sim.utilities.types import IFCDomain # raise unittest.SkipTest("Integration tests not reliable for automated use") @@ -15,7 +15,7 @@ DEBUG_COMFORT = False -class IntegrationBaseComfort(IntegrationBase): +class IntegrationBaseComfort(IntegrationWeatherBase): # HACK: We have to remember stderr because eppy resets it currently. def setUp(self): self.old_stderr = sys.stderr diff --git a/bim2sim/plugins/PluginEnergyPlus/test/integration/test_energyplus.py b/bim2sim/plugins/PluginEnergyPlus/test/integration/test_energyplus.py index 0e43b3d849..a82dee056c 100644 --- a/bim2sim/plugins/PluginEnergyPlus/test/integration/test_energyplus.py +++ b/bim2sim/plugins/PluginEnergyPlus/test/integration/test_energyplus.py @@ -1,16 +1,12 @@ +import os import sys import unittest -from shutil import copyfile, copytree, rmtree from pathlib import Path - -import os - -from energyplus_regressions.diffs import math_diff, table_diff -from energyplus_regressions.diffs.thresh_dict import ThreshDict +from shutil import copyfile, copytree, rmtree import bim2sim from bim2sim.kernel.decision.decisionhandler import DebugDecisionHandler -from bim2sim.utilities.test import IntegrationBase +from bim2sim.utilities.test import IntegrationWeatherBase from bim2sim.utilities.types import IFCDomain # raise unittest.SkipTest("Integration tests not reliable for automated use") @@ -18,7 +14,7 @@ DEBUG_ENERGYPLUS = False -class IntegrationBaseEP(IntegrationBase): +class IntegrationBaseEP(IntegrationWeatherBase): # HACK: We have to remember stderr because eppy resets it currently. def setUp(self): self.old_stderr = sys.stderr diff --git a/bim2sim/plugins/PluginTEASER/test/integration/test_teaser.py b/bim2sim/plugins/PluginTEASER/test/integration/test_teaser.py index d3e176861c..9065ec25bf 100644 --- a/bim2sim/plugins/PluginTEASER/test/integration/test_teaser.py +++ b/bim2sim/plugins/PluginTEASER/test/integration/test_teaser.py @@ -4,11 +4,11 @@ import bim2sim from bim2sim.kernel.decision.console import ConsoleDecisionHandler from bim2sim.kernel.decision.decisionhandler import DebugDecisionHandler -from bim2sim.utilities.test import IntegrationBase +from bim2sim.utilities.test import IntegrationWeatherBase from bim2sim.utilities.types import LOD, IFCDomain, ZoningCriteria -class IntegrationBaseTEASER(IntegrationBase): +class IntegrationBaseTEASER(IntegrationWeatherBase): def model_domain_path(self) -> str: return 'arch' diff --git a/bim2sim/utilities/test.py b/bim2sim/utilities/test.py index eec82ddebc..f10839dd18 100644 --- a/bim2sim/utilities/test.py +++ b/bim2sim/utilities/test.py @@ -58,12 +58,15 @@ def test_resources_path() -> Path: def model_domain_path(self) -> Union[str, None]: return None + +class IntegrationWeatherBase(IntegrationBase): + """Base class for integration tests that need weather files.""" def set_test_weather_file(self): """Set the weather file path.""" raise NotImplementedError("") -class RegressionTestBase(IntegrationBase): +class RegressionTestBase(IntegrationWeatherBase): """Base class for regression tests.""" def setUp(self): self.results_src_dir = None From 291d3bf30b2d8853a72fa60dc0c1a9b09ab2a9b9 Mon Sep 17 00:00:00 2001 From: David Paul Jansen Date: Fri, 18 Oct 2024 14:47:50 +0200 Subject: [PATCH 074/125] fix teaser regression weather file --- bim2sim/plugins/PluginTEASER/test/regression/test_teaser.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bim2sim/plugins/PluginTEASER/test/regression/test_teaser.py b/bim2sim/plugins/PluginTEASER/test/regression/test_teaser.py index 385013033c..8bed8e7514 100644 --- a/bim2sim/plugins/PluginTEASER/test/regression/test_teaser.py +++ b/bim2sim/plugins/PluginTEASER/test/regression/test_teaser.py @@ -46,9 +46,9 @@ def tearDown(self): def set_test_weather_file(self): """Set the weather file path.""" - self.project.sim_settings.weather_file_path_ep = ( + self.project.sim_settings.weather_file_path_modelica = ( self.test_resources_path() / - 'weather_files/DEU_NW_Aachen.105010_TMYx.epw') + 'weather_files/DEU_NW_Aachen.105010_TMYx.modelica') def create_regression_setup( self, From 1133f10c76cbedf3028cae97c950a2bdc3f6ca63 Mon Sep 17 00:00:00 2001 From: sfreund1 Date: Tue, 22 Oct 2024 09:02:40 +0200 Subject: [PATCH 075/125] Improves the saving and export procedure of spawn model --- bim2sim/export/modelica/__init__.py | 17 +- .../tasks/export_spawn_building.py | 175 ++++++------------ .../bim2sim_spawn/tasks/export_spawn_total.py | 76 +++----- bim2sim/tasks/hvac/export.py | 77 +++++--- 4 files changed, 139 insertions(+), 206 deletions(-) diff --git a/bim2sim/export/modelica/__init__.py b/bim2sim/export/modelica/__init__.py index 2d29854f1a..42bea69c9e 100644 --- a/bim2sim/export/modelica/__init__.py +++ b/bim2sim/export/modelica/__init__.py @@ -57,15 +57,20 @@ def help_package(path: Path, name: str, uses: str = None, within: str = None): path of Modelica package containing this package """ - template_path_package = Path(bim2sim.__file__).parent / \ - "assets/templates/modelica/package" + # Create the directory if it doesn't exist + path.mkdir(parents=True, exist_ok=True) + + # Define the path to the template and render the package.mo + template_path_package = (Path(bim2sim.__file__).parent / + "assets/templates/modelica/package") package_template = Template(filename=str(template_path_package)) + # Write the rendered template to 'package.mo' in the specified path with open(path / 'package.mo', 'w') as out_file: out_file.write(package_template.render_unicode( name=name, within=within, uses=uses)) - out_file.close() + # out_file.close() def help_package_order(path: Path, package_list: List[str], addition=None, @@ -224,11 +229,7 @@ def save_pkg(self, pkg_path: Path): pkg_name = pkg_path.stem help_package(path=pkg_path, name=pkg_name, within=pkg_path.parent.stem) - help_package_order(path=pkg_path, package_list=[ - pkg_name, - # 'building_model', - # 'hvac_model' - ]) + help_package_order(path=pkg_path, package_list=[pkg_name]) self.save(pkg_path / pkg_name) diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_building.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_building.py index b2be6b710f..fa792f7284 100644 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_building.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_building.py @@ -1,7 +1,6 @@ -import os -from datetime import datetime -from pathlib import Path import codecs +from pathlib import Path + from mako.template import Template import bim2sim @@ -13,55 +12,74 @@ class ExportSpawnBuilding(ITask): """Export building for SpawnOfEnergyPlus model to Modelica""" reads = ('elements', 'weather_file_modelica', 'weather_file_ep') - touches = ('zone_names', 'model_name_building') - final = True + touches = ('zone_names', 'model_name_building', 'package_path') def run(self, elements: dict, weather_file_modelica: Path, weather_file_ep: Path): - self.logger.info("Export building of Spawn modelto Modelica code") - - # EXPORT MULTIZONE MODEL - # This is a "static" model for now, means no elements are created - # dynamically but only the parameters are changed based on render - # function - # TODO this should be stored central - package_path = self.paths.export / 'bim2sim_spawn' - os.makedirs(package_path, exist_ok=True) + self.logger.info("Export building of Spawn model to Modelica code") + self.model_name_building = 'BuildingModel' + + # Setup paths + package_path = self._setup_package_directory() + template_bldg = self._load_template() + + # Generate data for building template + zone_names = self._get_zone_names() + building_template_data = self._render_building_template( + template_bldg=template_bldg, + package_path=package_path, + weather_file_ep=weather_file_ep, + weather_file_modelica=weather_file_modelica, + zone_names=zone_names + ) - templ_path_building = Path( - bim2sim.__file__).parent / \ - 'assets/templates/modelica/tmplSpawnBuilding.txt' + # Write the generated Modelica code to file + export_path = Path(package_path / self.model_name_building) + self._write_to_file(export_path.with_suffix('.mo'), + building_template_data) - with open(templ_path_building) as f: - template_bldg_str = f.read() - template_bldg = Template(template_bldg_str) - weather_path_ep = weather_file_ep - weather_path_mos = weather_file_modelica - zone_names = self.get_zone_names() - idf_path = (self.paths.export / "EnergyPlus/SimResults" / - self.prj_name / str(self.prj_name + ".idf")) + return zone_names, self.model_name_building, package_path - # TODO multithreading lock needed? see modelica/__init__.py for example - # with lock: - model_name_building = 'BuildingModel' - building_template_data = template_bldg.render( - within='bim2sim_spawn', - model_name=model_name_building, - model_comment='test2', - weather_path_ep=to_modelica_spawn(weather_path_ep), - weather_path_mos=to_modelica_spawn(weather_path_mos), + def _setup_package_directory(self) -> Path: + """Creates the necessary package directory.""" + package_path = self.paths.export / 'bim2sim_spawn' + package_path.mkdir(parents=True, exist_ok=True) + return package_path + + @staticmethod + def _load_template() -> Template: + """Loads the building template for rendering.""" + template_path = (Path(bim2sim.__file__).parent / + 'assets/templates/modelica/tmplSpawnBuilding.txt') + with open(template_path, 'r', encoding='utf-8') as f: + template_str = f.read() + return Template(template_str) + + def _render_building_template(self, template_bldg: Template, + package_path: Path, weather_file_ep: Path, + weather_file_modelica: Path, + zone_names: list) -> str: + """Renders the building template with the provided data.""" + idf_path = (self.paths.export / "EnergyPlus/SimResults" / self.prj_name + / f"{self.prj_name}.idf") + return template_bldg.render( + within=package_path.stem, + model_name=self.model_name_building, + model_comment='Building model for Spawn of EnergyPlus', + weather_path_ep=to_modelica_spawn(weather_file_ep), + weather_path_mos=to_modelica_spawn(weather_file_modelica), zone_names=to_modelica_spawn(zone_names), idf_path=to_modelica_spawn(idf_path), n_zones=len(zone_names) ) - export_path = package_path / f"{model_name_building}.mo" - # user_logger.info("Saving '%s' to '%s'", self.name, _path) - with codecs.open(export_path, "w", "utf-8") as file: - file.write(building_template_data) - return zone_names, model_name_building + def _write_to_file(self, file_path: Path, content: str): + """Writes the generated content to a file.""" + with codecs.open(file_path, 'w', 'utf-8') as f: + f.write(content) + self.logger.info(f"Successfully saved Modelica file to {file_path}") - def get_zone_names(self): + def _get_zone_names(self): # TODO #1: get names from IDF or EP process for ep zones in # correct order if "ep_zone_lists" in self.playground.state: @@ -71,80 +89,3 @@ def get_zone_names(self): "please make sure that EnergyPlus model creation " "was successful.") return zone_list - - # def _help_package(self, name: str, uses: str = None, within: str = None): - # """creates a package.mo file - # - # private function, do not call - # - # Parameters - # ---------- - # - # name : string - # name of the Modelica package - # within : string - # path of Modelica package containing this package - # - # """ - # - # template_path_package = Path(bim2sim.__file__).parent / \ - # "assets/templates/modelica/package" - # package_template = Template(filename=str(template_path_package)) - # with open(self.paths.export / 'package.mo', 'w') as out_file: - # out_file.write(package_template.render_unicode( - # name=name, - # within=within, - # uses=uses)) - # out_file.close() - - # def _help_package_order(self, package_list, addition=None, extra=None): - # """creates a package.order file - # - # private function, do not call - # - # Parameters - # ---------- - # - # package_list : [string] - # name of all models or packages contained in the package - # addition : string - # if there should be a suffix in front of package_list.string it can - # be specified - # extra : string - # an extra package or model not contained in package_list can be - # specified - # - # """ - # - # template_package_order_path = Path(bim2sim.__file__).parent / \ - # "assets/templates/modelica/package_order" - # package_order_template = Template(filename=str( - # template_package_order_path)) - # with open(self.paths.export / 'package.order', 'w') as out_file: - # out_file.write(package_order_template.render_unicode( - # list=package_list, - # addition=addition, - # extra=extra)) - # out_file.close() - - - - # - # def get_static_connections(self, elements): - # connections = [] - # for inst in elements.values(): - # if isinstance(inst, SpawnBuilding): - # spawn_building = inst - # if isinstance(inst, FreshAirSource): - # fresh_air = inst - # if isinstance(inst, SpawnMultizone): - # multi = inst - # # TODO remove if as this is only temporary for development - # if spawn_building and fresh_air and multi: - # connections.append((str(spawn_building.name)+'.weaBus', - # str(fresh_air.name) +'.weaBus')) - # # TODO clarify export and arrays in modelica - # connections.append(( - # str(multi.name)+".portsExt[nZones]", - # str(fresh_air.name)+".ports[nPorts]")) - # return connections diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_total.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_total.py index 3241b32182..cfbdac596c 100644 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_total.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_total.py @@ -1,7 +1,5 @@ import codecs -import os from collections import defaultdict -from datetime import datetime from pathlib import Path from threading import Lock from typing import List, Tuple, Optional, Dict @@ -11,11 +9,11 @@ import bim2sim from bim2sim.elements.base_elements import ProductBased from bim2sim.elements.hvac_elements import HVACProduct -from bim2sim.export import modelica from bim2sim.export.modelica import help_package, help_package_order, \ ModelicaElement from bim2sim.plugins.PluginSpawn.bim2sim_spawn.models import to_modelica_spawn from bim2sim.tasks.base import ITask +from bim2sim.tasks.hvac.export import save_modelica_model from bim2sim.utilities.common_functions import filter_elements from bim2sim.utilities.pyocc_tools import PyOCCTools @@ -32,7 +30,8 @@ class ExportSpawnTotal(ITask): reads = ( 'elements', 'weather_file_modelica', 'weather_file_ep', 'zone_names', 'model_name_building', 'export_elements', - 'connections', 'cons_heat_ports_conv', 'cons_heat_ports_rad' + 'connections', 'cons_heat_ports_conv', 'cons_heat_ports_rad', + 'package_path' ) final = True @@ -45,7 +44,8 @@ def run(self, export_elements: Dict[HVACProduct, ModelicaElement], connections: List[Tuple[str, str]], cons_heat_ports_conv: List[Tuple[str, str]], - cons_heat_ports_rad: List[Tuple[str, str]]): + cons_heat_ports_rad: List[Tuple[str, str]], + package_path: Path): """Run the export process to generate the Modelica code. Args: @@ -58,19 +58,26 @@ def run(self, connections: Connections data. cons_heat_ports_conv: List of convective heat port connections. cons_heat_ports_rad: List of radiative heat port connections. + package_path: The package path of the modelica model. """ - self.logger.info("Export total Spawn model to Modelica code") - package_path = self.paths.export / 'bim2sim_spawn' - self._prepare_package_path(package_path) + + # package_path = self.paths.export / 'bim2sim_spawn' + + # Exports the hydraulic model + self.logger.info("Export HVAC Spawn model to Modelica code") model_name_hydraulic = 'HVACModel' + save_modelica_model(model_name=model_name_hydraulic, + package_path=package_path, + export_elements=list(export_elements.values()), + connections=connections, + cons_heat_ports_conv=cons_heat_ports_conv, + cons_heat_ports_rad=cons_heat_ports_rad) + + # Exports the total model + self.logger.info("Export total Spawn model to Modelica code") model_name_total = 'TotalModel' - self._create_modelica_help_package( - package_path, model_name_total, model_name_building, - model_name_hydraulic - ) - weather_path_mos = weather_file_modelica tz_elements = filter_elements(elements, 'ThermalZone') space_heater_elements = filter_elements(elements, 'SpaceHeater') @@ -88,11 +95,6 @@ def run(self, model_name_hydraulic, zone_to_heaters ) - self._save_modelica_model( - export_elements, connections, cons_heat_ports_conv, - cons_heat_ports_rad, model_name_hydraulic, package_path - ) - self._save_total_modelica_model( model_name_total, model_name_building, model_name_hydraulic, cons_heat_ports_conv_building_hvac, @@ -100,14 +102,11 @@ def run(self, weather_path_mos, package_path ) - @staticmethod - def _prepare_package_path(package_path: Path): - """Prepare the package directory for saving files. - - Args: - package_path (Path): The path to the package directory. - """ - os.makedirs(package_path, exist_ok=True) + # Creates the package files + self._create_modelica_help_package( + package_path, model_name_total, model_name_building, + model_name_hydraulic + ) @staticmethod def _create_modelica_help_package( @@ -147,31 +146,6 @@ def _group_space_heaters_by_zone( zone_to_heaters[tz.guid].append(space_heater.guid) return zone_to_heaters - @staticmethod - def _save_modelica_model( - export_elements, connections, cons_heat_ports_conv, - cons_heat_ports_rad, model_name_hydraulic: str, package_path: Path): - """Save the Modelica model file. - - Args: - export_elements: Elements to export. - connections: Connections data. - cons_heat_ports_conv: List of convective heat port connections. - cons_heat_ports_rad: List of radiative heat port connections. - model_name_hydraulic (str): The name of the hydraulic model. - package_path (Path): The path to the package directory. - """ - modelica_model = modelica.ModelicaModel( - name=model_name_hydraulic, - comment=f"Autogenerated by BIM2SIM on " - f"{datetime.now():%Y-%m-%d %H:%M:%S%z}", - modelica_elements=list(export_elements.values()), - connections=connections, - connections_heat_ports_conv=cons_heat_ports_conv, - connections_heat_ports_rad=cons_heat_ports_rad - ) - modelica_model.save(package_path / f"{model_name_hydraulic}.mo") - def _save_total_modelica_model( self, model_name_total: str, model_name_building: str, model_name_hydraulic: str, diff --git a/bim2sim/tasks/hvac/export.py b/bim2sim/tasks/hvac/export.py index 7030d469e4..3115a33bce 100644 --- a/bim2sim/tasks/hvac/export.py +++ b/bim2sim/tasks/hvac/export.py @@ -234,36 +234,53 @@ def run(self, export_elements: dict, connections: list, Returns: modelica_model: """ - self.logger.info( - "Creating Modelica model with %d model elements " - "and %d connections.", - len(export_elements), len(connections)) - regex = re.compile("[^a-zA-z0-9]") - package_name = regex.sub( - "", "bim2sim_aixlib_" + self.prj_name) + self.logger.info("Creating Modelica model with %d model elements and %d" + " connections.", len(export_elements), + len(connections)) + model_name = 'Hydraulic' - modelica_model = modelica.ModelicaModel( - name=model_name, - comment=f"Autogenerated by BIM2SIM on " - f"{datetime.now():%Y-%m-%d %H:%M:%S%z}", - modelica_elements=list(export_elements.values()), + regex = re.compile("[^a-zA-z0-9]") + package_name = regex.sub("", 'HVAC_' + self.prj_name) + + # Create base package structure + export_pkg_path = self.paths.export / Path(package_name) + # Path.mkdir(export_pkg_path, exist_ok=True) + help_package(path=export_pkg_path, name=export_pkg_path.stem, within="") + help_package_order(path=export_pkg_path, package_list=[model_name]) + # Path.mkdir(export_pkg_path / model_name, exist_ok=True) + + modelica_model = save_modelica_model( + model_name=model_name, + package_path=export_pkg_path, + export_elements=list(export_elements.values()), connections=connections, - connections_heat_ports_conv=cons_heat_ports_conv, - connections_heat_ports_rad=cons_heat_ports_rad + cons_heat_ports_conv=cons_heat_ports_conv, + cons_heat_ports_rad=cons_heat_ports_rad ) - # create base package structure - export_pkg_path = self.paths.export / Path(package_name) - Path.mkdir(export_pkg_path, exist_ok=True) - help_package( - path=export_pkg_path, - name=export_pkg_path.stem, - within="") - help_package_order(path=export_pkg_path, package_list=[ - model_name, - # 'building_model', - # 'hvac_model' - ]) - Path.mkdir(export_pkg_path / model_name, exist_ok=True) - # export hydraulic model itself - modelica_model.save_pkg(export_pkg_path / model_name) - return modelica_model, + return modelica_model + + +def save_modelica_model(model_name: str, package_path: Path, + export_elements, connections, cons_heat_ports_conv, + cons_heat_ports_rad): + """Save the Modelica model file. + + Args: + model_name (str): The name of the model. + package_path (Path): The path to the package directory. + export_elements: Elements to export. + connections: Connections data. + cons_heat_ports_conv: List of convective heat port connections. + cons_heat_ports_rad: List of radiative heat port connections. + """ + modelica_model = modelica.ModelicaModel( + name=model_name, + comment=f"Autogenerated by BIM2SIM on " + f"{datetime.now():%Y-%m-%d %H:%M:%S%z}", + modelica_elements=export_elements, + connections=connections, + connections_heat_ports_conv=cons_heat_ports_conv, + connections_heat_ports_rad=cons_heat_ports_rad + ) + modelica_model.save(Path(package_path / model_name).with_suffix('.mo')) + return modelica_model From 0eeade07a0da0df524061190431350474ca70e70 Mon Sep 17 00:00:00 2001 From: sfreund1 Date: Tue, 22 Oct 2024 11:28:04 +0200 Subject: [PATCH 076/125] Further improvement in the saving and export procedure of spawn model --- .../PluginSpawn/bim2sim_spawn/__init__.py | 2 + .../tasks/export_spawn_building.py | 94 ++++++++++---- .../bim2sim_spawn/tasks/export_spawn_total.py | 122 ++++++++---------- bim2sim/tasks/hvac/export.py | 92 ++++++++----- 4 files changed, 185 insertions(+), 125 deletions(-) diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/__init__.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/__init__.py index d34ccc6ec0..b612a82fb9 100644 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/__init__.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/__init__.py @@ -16,6 +16,7 @@ class PluginSpawnOfEP(Plugin): common.LoadIFC, common.CheckIfc, common.CreateElementsOnIfcTypes, + bps.CreateSpaceBoundaries, bps.AddSpaceBoundaries2B, bps.CorrectSpaceBoundaries, @@ -33,6 +34,7 @@ class PluginSpawnOfEP(Plugin): hvac.DeadEnds, LoadLibrariesAixLib, hvac.CreateModelicaModel, + hvac.Export, spawn_tasks.ExportSpawnBuilding, spawn_tasks.ExportSpawnTotal, diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_building.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_building.py index fa792f7284..a10f338751 100644 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_building.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_building.py @@ -11,60 +11,88 @@ class ExportSpawnBuilding(ITask): """Export building for SpawnOfEnergyPlus model to Modelica""" - reads = ('elements', 'weather_file_modelica', 'weather_file_ep') - touches = ('zone_names', 'model_name_building', 'package_path') + reads = ('elements', 'weather_file_modelica', 'weather_file_ep', + 'package_name') + touches = ('zone_names', 'model_name_building') def run(self, elements: dict, weather_file_modelica: Path, - weather_file_ep: Path): + weather_file_ep: Path, package_name: str): + """Run the export process for a building Spawn model in Modelica. + + This method prepares the Modelica file for a building model by gathering + necessary elements, generating the template data, and writing the + Modelica code to file. + + Args: + elements: Dictionary containing building elements. + weather_file_modelica: Path to the Modelica weather file. + weather_file_ep: Path to the EnergyPlus weather file. + package_name: Name of the Modelica package for exporting. + + Returns: + zone_names: List of zones for the building. + model_name_building: the name of the building model. + """ self.logger.info("Export building of Spawn model to Modelica code") - self.model_name_building = 'BuildingModel' + model_name_building = 'BuildingModel' - # Setup paths - package_path = self._setup_package_directory() - template_bldg = self._load_template() + # Setup export paths + export_package_path = self.paths.export / Path(package_name) - # Generate data for building template + # Generate building template data zone_names = self._get_zone_names() building_template_data = self._render_building_template( - template_bldg=template_bldg, - package_path=package_path, + package_path=export_package_path, + model_name=model_name_building, weather_file_ep=weather_file_ep, weather_file_modelica=weather_file_modelica, zone_names=zone_names ) # Write the generated Modelica code to file - export_path = Path(package_path / self.model_name_building) - self._write_to_file(export_path.with_suffix('.mo'), - building_template_data) + self._write_to_file( + Path(export_package_path / f"{model_name_building}.mo"), + building_template_data) - return zone_names, self.model_name_building, package_path - - def _setup_package_directory(self) -> Path: - """Creates the necessary package directory.""" - package_path = self.paths.export / 'bim2sim_spawn' - package_path.mkdir(parents=True, exist_ok=True) - return package_path + return zone_names, model_name_building @staticmethod def _load_template() -> Template: - """Loads the building template for rendering.""" + """Loads the building template for rendering. + + Returns: + Template: Mako template object for the building Modelica file. + """ template_path = (Path(bim2sim.__file__).parent / 'assets/templates/modelica/tmplSpawnBuilding.txt') with open(template_path, 'r', encoding='utf-8') as f: template_str = f.read() return Template(template_str) - def _render_building_template(self, template_bldg: Template, - package_path: Path, weather_file_ep: Path, + def _render_building_template(self, + package_path: Path, + model_name: str, + weather_file_ep: Path, weather_file_modelica: Path, zone_names: list) -> str: - """Renders the building template with the provided data.""" + """Render the building Modelica template using provided data. + + Args: + package_path: The path to the Modelica package. + model_name: The name of the building model. + weather_file_ep: The EnergyPlus weather file path. + weather_file_modelica: The Modelica weather file path. + zone_names: List of zone names to be used in the model. + + Returns: + Rendered Modelica code as a string. + """ + template_bldg = self._load_template() idf_path = (self.paths.export / "EnergyPlus/SimResults" / self.prj_name / f"{self.prj_name}.idf") return template_bldg.render( within=package_path.stem, - model_name=self.model_name_building, + model_name=model_name, model_comment='Building model for Spawn of EnergyPlus', weather_path_ep=to_modelica_spawn(weather_file_ep), weather_path_mos=to_modelica_spawn(weather_file_modelica), @@ -74,14 +102,28 @@ def _render_building_template(self, template_bldg: Template, ) def _write_to_file(self, file_path: Path, content: str): - """Writes the generated content to a file.""" + """Write the generated content to a Modelica file. + + Args: + file_path: The path to the output file. + content: The content to write into the file. + """ with codecs.open(file_path, 'w', 'utf-8') as f: f.write(content) self.logger.info(f"Successfully saved Modelica file to {file_path}") def _get_zone_names(self): + """Retrieve zone names from the playground state. + + Raises: + ValueError: If 'ep_zone_lists' is not found in the playground state. + + Returns: + list: A list of zone names for the building. + """ # TODO #1: get names from IDF or EP process for ep zones in # correct order + if "ep_zone_lists" in self.playground.state: zone_list = self.playground.state["ep_zone_lists"] else: diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_total.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_total.py index cfbdac596c..dd815a9238 100644 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_total.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_total.py @@ -13,12 +13,11 @@ ModelicaElement from bim2sim.plugins.PluginSpawn.bim2sim_spawn.models import to_modelica_spawn from bim2sim.tasks.base import ITask -from bim2sim.tasks.hvac.export import save_modelica_model from bim2sim.utilities.common_functions import filter_elements from bim2sim.utilities.pyocc_tools import PyOCCTools TEMPLATE_PATH_TOTAL = Path(bim2sim.__file__).parent / \ - 'assets/templates/modelica/tmplSpawnTotalModel.txt' + 'assets/templates/modelica/tmplSpawnTotalModel.txt' TEMPLATE_TOTAL_STR = TEMPLATE_PATH_TOTAL.read_text() TEMPLATE_TOTAL = Template(TEMPLATE_TOTAL_STR) LOCK = Lock() @@ -29,9 +28,9 @@ class ExportSpawnTotal(ITask): reads = ( 'elements', 'weather_file_modelica', 'weather_file_ep', - 'zone_names', 'model_name_building', 'export_elements', - 'connections', 'cons_heat_ports_conv', 'cons_heat_ports_rad', - 'package_path' + 'zone_names', 'model_name_hydraulic', 'model_name_building', + 'export_elements', 'connections', 'cons_heat_ports_conv', + 'cons_heat_ports_rad', 'package_name' ) final = True @@ -40,52 +39,43 @@ def run(self, weather_file_modelica: Path, weather_file_ep: Path, zone_names: List[str], + model_name_hydraulic: str, model_name_building: str, export_elements: Dict[HVACProduct, ModelicaElement], connections: List[Tuple[str, str]], cons_heat_ports_conv: List[Tuple[str, str]], cons_heat_ports_rad: List[Tuple[str, str]], - package_path: Path): + package_name: str): """Run the export process to generate the Modelica code. Args: elements: The elements' data. weather_file_modelica: Path to the Modelica weather file. weather_file_ep: Path to the EnergyPlus weather file. - zone_names: Zone names data. + zone_names: List of zone names. + model_name_hydraulic: The name of the hydraulic model. model_name_building: The name of the building model. export_elements: HVAC elements to export. - connections: Connections data. + connections: List of fluid port connections. cons_heat_ports_conv: List of convective heat port connections. cons_heat_ports_rad: List of radiative heat port connections. - package_path: The package path of the modelica model. + package_name: The package name of the modelica package. """ - - # package_path = self.paths.export / 'bim2sim_spawn' - - # Exports the hydraulic model - self.logger.info("Export HVAC Spawn model to Modelica code") - model_name_hydraulic = 'HVACModel' - save_modelica_model(model_name=model_name_hydraulic, - package_path=package_path, - export_elements=list(export_elements.values()), - connections=connections, - cons_heat_ports_conv=cons_heat_ports_conv, - cons_heat_ports_rad=cons_heat_ports_rad) - # Exports the total model self.logger.info("Export total Spawn model to Modelica code") model_name_total = 'TotalModel' - weather_path_mos = weather_file_modelica + # Filter elements by type tz_elements = filter_elements(elements, 'ThermalZone') space_heater_elements = filter_elements(elements, 'SpaceHeater') + # Group heaters by their corresponding zones zone_to_heaters = self._group_space_heaters_by_zone( tz_elements, space_heater_elements ) + # Map heat ports between building and HVAC models cons_heat_ports_conv_building_hvac = self.get_port_mapping( cons_heat_ports_conv, "Con", model_name_building, model_name_hydraulic, zone_to_heaters @@ -95,14 +85,17 @@ def run(self, model_name_hydraulic, zone_to_heaters ) + # Define package path + package_path = self.paths.export / Path(package_name) + # Save the rendered Modelica total model self._save_total_modelica_model( model_name_total, model_name_building, model_name_hydraulic, cons_heat_ports_conv_building_hvac, cons_heat_ports_rad_building_hvac, - weather_path_mos, package_path + weather_file_modelica, package_path ) - # Creates the package files + # Creates the package help files self._create_modelica_help_package( package_path, model_name_total, model_name_building, model_name_hydraulic @@ -112,31 +105,30 @@ def run(self, def _create_modelica_help_package( package_path: Path, model_name_total: str, model_name_building: str, model_name_hydraulic: str): - """Create the Modelica help package. + """Create the Modelica help package files. Args: - package_path (Path): The path to the package directory. - model_name_total (str): The name of the total model. - model_name_building (str): The name of the building model. - model_name_hydraulic (str): The name of the hydraulic model. + package_path: The path to the package directory. + model_name_total: The name of the total model. + model_name_building: The name of the building model. + model_name_hydraulic: The name of the hydraulic model. """ help_package(path=package_path, name=package_path.stem, within="") help_package_order(path=package_path, package_list=[ - model_name_total, model_name_building, model_name_hydraulic - ]) + model_name_total, model_name_building, model_name_hydraulic]) @staticmethod - def _group_space_heaters_by_zone( - tz_elements: List, space_heater_elements: List) -> Dict[str, List[str]]: + def _group_space_heaters_by_zone(tz_elements: List, + space_heater_elements: List) \ + -> Dict[str, List[str]]: """Group space heaters by their respective zones. Args: - tz_elements (List): List of thermal zone elements. - space_heater_elements (List): List of space heater elements. + tz_elements: List of thermal zone elements. + space_heater_elements: List of space heater elements. Returns: - Dict[str, List[str]]: A dictionary mapping zone GUIDs to lists of - heater GUIDs. + A dictionary mapping zone GUIDs to lists of heater GUIDs. """ zone_to_heaters = defaultdict(list) for tz in tz_elements: @@ -155,19 +147,19 @@ def _save_total_modelica_model( """Render and save the total Modelica model file using a template. Args: - model_name_total (str): The name of the total model. - model_name_building (str): The name of the building model. - model_name_hydraulic (str): The name of the hydraulic model. - cons_heat_ports_conv_building_hvac (List[Tuple[str, str]]): - List of convective heat port mappings. - cons_heat_ports_rad_building_hvac (List[Tuple[str, str]]): - List of radiative heat port mappings. - weather_path_mos (Path): Path to the Modelica weather file. - package_path (Path): The path to the package directory. + model_name_total: The name of the total model. + model_name_building: The name of the building model. + model_name_hydraulic: The name of the hydraulic model. + cons_heat_ports_conv_building_hvac: List of convective heat port + mappings. + cons_heat_ports_rad_building_hvac: List of radiative heat port + mappings. + weather_path_mos: Path to the Modelica weather file. + package_path: The path to the package directory. """ with LOCK: total_template_data = TEMPLATE_TOTAL.render( - within='bim2sim_spawn', + within=package_path.stem, model_name=model_name_total, model_comment='test2', weather_path_mos=to_modelica_spawn(weather_path_mos), @@ -193,20 +185,18 @@ def get_port_mapping( """Mapping between building heat ports and HVAC heat ports. Args: - cons_heat_ports (List[Tuple[str, str]]): A list of tuples where - each tuple contains the HVAC outer port name and the - corresponding space heater name. - port_type (str): The type of port to map, e.g., "Con" for - convective or "Rad" for radiative. - model_name_building (str): The name of the building model. - model_name_hydraulic (str): The name of the hydraulic model. - zone_to_heaters (Dict[str, List[str]]): A dictionary mapping zone - GUIDs to lists of heater GUIDs. + cons_heat_ports: A list of tuples where each tuple contains the HVAC + outer port name and the corresponding space heater name. + port_type: The type of port to map, e.g., "Con" for convective or + "Rad" for radiative. + model_name_building: The name of the building model. + model_name_hydraulic: The name of the hydraulic model. + zone_to_heaters: A dictionary mapping zone GUIDs to lists of heater + GUIDs. Returns: - List[Tuple[str, str]]: A list of tuples where each tuple contains - the mapped building port and the corresponding hydraulic port in - the HVAC model. + A list of tuples where each tuple contains the mapped building port + and the corresponding hydraulic port in the HVAC model. """ mapping = [] for hvac_outer_port, space_heater_name in cons_heat_ports: @@ -228,14 +218,14 @@ def get_building_index( """Get the index of the building in the zone_to_heaters dictionary. Args: - zone_to_heaters (Dict[str, List[str]]): A dictionary mapping zone - GUIDs to lists of heater GUIDs. - heater_guid (str): The GUID of the heater to search for. + zone_to_heaters: A dictionary mapping zone GUIDs to lists of heater + GUIDs. + heater_guid: The GUID of the heater to search for. Returns: - Optional[int]: The index (1-based) of the zone that contains the - heater with the specified GUID. - Returns None if the heater GUID is not found in any zone. + Optional: The index (1-based) of the zone that contains the heater + with the specified GUID. Returns None if the heater GUID is not + found in any zone. """ for index, (zone_guid, heater_list) in enumerate( zone_to_heaters.items(), start=1): diff --git a/bim2sim/tasks/hvac/export.py b/bim2sim/tasks/hvac/export.py index 3115a33bce..756ac8d8e7 100644 --- a/bim2sim/tasks/hvac/export.py +++ b/bim2sim/tasks/hvac/export.py @@ -3,8 +3,6 @@ from pathlib import Path from typing import Tuple, List, Dict -from mako.template import Template - from bim2sim.elements import hvac_elements as hvac from bim2sim.elements.base_elements import ProductBased from bim2sim.elements.graphs.hvac_graph import HvacGraph @@ -12,8 +10,8 @@ from bim2sim.export import modelica from bim2sim.export.modelica import HeatTransferType, ModelicaParameter, \ ModelicaElement -from bim2sim.tasks.base import ITask from bim2sim.export.modelica import help_package, help_package_order +from bim2sim.tasks.base import ITask class CreateModelicaModel(ITask): @@ -214,62 +212,90 @@ class Export(ITask): reads = ('export_elements', 'connections', 'cons_heat_ports_conv', 'cons_heat_ports_rad') - touches = ('modelica_model',) - final = True + touches = ('modelica_model', 'package_name', 'model_name_hydraulic') def run(self, export_elements: dict, connections: list, cons_heat_ports_conv: list, cons_heat_ports_rad: list): - """Export modelica elements and connections to Modelica code. + """Export Modelica elements and connections to Modelica code. - This method performs the following steps: - 1. Creates a Modelica model with the exported elements and connections. - 2. Saves the Modelica model to the specified export path. + This method creates a Modelica model, organizes the package structure, + and saves the model to the specified path. Args: - export_elements: - connections: - cons_heat_ports_conv: - cons_heat_ports_rad: + export_elements: Dictionary containing elements to export. + connections: List of fluid port connections. + cons_heat_ports_conv: List of convective heat port connections. + cons_heat_ports_rad: List of radiative heat port connections. Returns: - modelica_model: + modelica_model: The modelica model as string. + package_name: The name of the package (i.e. the folder where the + modelica model is stored.) + model_name_hydraulic: The name of the model. """ self.logger.info("Creating Modelica model with %d model elements and %d" " connections.", len(export_elements), len(connections)) + model_name_hydraulic = 'Hydraulic' + package_name = self._get_package_name() - model_name = 'Hydraulic' - regex = re.compile("[^a-zA-z0-9]") - package_name = regex.sub("", 'HVAC_' + self.prj_name) - - # Create base package structure - export_pkg_path = self.paths.export / Path(package_name) - # Path.mkdir(export_pkg_path, exist_ok=True) - help_package(path=export_pkg_path, name=export_pkg_path.stem, within="") - help_package_order(path=export_pkg_path, package_list=[model_name]) - # Path.mkdir(export_pkg_path / model_name, exist_ok=True) + # Setup package directory structure + export_package_path = self._setup_package_structure( + package_name, model_name_hydraulic + ) + # Save the Modelica model modelica_model = save_modelica_model( - model_name=model_name, - package_path=export_pkg_path, + model_name=model_name_hydraulic, + package_path=export_package_path, export_elements=list(export_elements.values()), connections=connections, cons_heat_ports_conv=cons_heat_ports_conv, cons_heat_ports_rad=cons_heat_ports_rad ) - return modelica_model + return modelica_model, package_name, model_name_hydraulic + + def _get_package_name(self) -> str: + """Generate a valid package name based on the project name. + + Returns: + str: The valid package name. + """ + regex = re.compile("[^a-zA-z0-9]") + return regex.sub("", self.prj_name) + + def _setup_package_structure(self, package_name: str, + model_name_hydraulic: str) -> Path: + """Set up the package directory structure for exporting Modelica models. + + Args: + package_name: The name of the package. + model_name_hydraulic: The name of the hydraulic model. + + Returns: + Path: The path to the export package directory. + """ + export_package_path = self.paths.export / Path(package_name) + + # Helper functions to structure the Modelica package + help_package(path=export_package_path, name=export_package_path.stem, + within="") + help_package_order(path=export_package_path, + package_list=[model_name_hydraulic]) + + return export_package_path def save_modelica_model(model_name: str, package_path: Path, export_elements, connections, cons_heat_ports_conv, cons_heat_ports_rad): - """Save the Modelica model file. + """Saves the Modelica model file. Args: - model_name (str): The name of the model. - package_path (Path): The path to the package directory. - export_elements: Elements to export. - connections: Connections data. + model_name: The name of the model. + package_path: The directory/package path where the model will be saved. + export_elements: List of elements to export. + connections: List of connections data. cons_heat_ports_conv: List of convective heat port connections. cons_heat_ports_rad: List of radiative heat port connections. """ @@ -282,5 +308,5 @@ def save_modelica_model(model_name: str, package_path: Path, connections_heat_ports_conv=cons_heat_ports_conv, connections_heat_ports_rad=cons_heat_ports_rad ) - modelica_model.save(Path(package_path / model_name).with_suffix('.mo')) + modelica_model.save(package_path / f"{model_name}.mo") return modelica_model From ccd73b4db5e5616f493edf39cd80a6e155fab94e Mon Sep 17 00:00:00 2001 From: David Paul Jansen Date: Mon, 4 Nov 2024 09:16:05 +0100 Subject: [PATCH 077/125] rm old directory that is going to be replaced by submodule --- test/resources | 1 - 1 file changed, 1 deletion(-) delete mode 160000 test/resources diff --git a/test/resources b/test/resources deleted file mode 160000 index 9b3eab858d..0000000000 --- a/test/resources +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 9b3eab858d4cb8457c9f147a188fb3fc4f7723a1 From 4803c8951f6778730fc691228e804ae2de26d989 Mon Sep 17 00:00:00 2001 From: David Paul Jansen Date: Mon, 4 Nov 2024 09:17:54 +0100 Subject: [PATCH 078/125] readd submodule --- test/resources | 1 + 1 file changed, 1 insertion(+) create mode 160000 test/resources diff --git a/test/resources b/test/resources new file mode 160000 index 0000000000..9b3eab858d --- /dev/null +++ b/test/resources @@ -0,0 +1 @@ +Subproject commit 9b3eab858d4cb8457c9f147a188fb3fc4f7723a1 From 55034c038c98b2309d408b4649fcfaf600f50b1a Mon Sep 17 00:00:00 2001 From: sfreund1 Date: Mon, 4 Nov 2024 09:21:08 +0100 Subject: [PATCH 079/125] delete resources --- test/resources | 1 - 1 file changed, 1 deletion(-) delete mode 160000 test/resources diff --git a/test/resources b/test/resources deleted file mode 160000 index 9b3eab858d..0000000000 --- a/test/resources +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 9b3eab858d4cb8457c9f147a188fb3fc4f7723a1 From c0b215247b692099b6c1f4443e293e28ae08f54e Mon Sep 17 00:00:00 2001 From: sfreund1 Date: Mon, 4 Nov 2024 09:25:32 +0100 Subject: [PATCH 080/125] add submodule test resources --- test/resources | 1 + 1 file changed, 1 insertion(+) create mode 160000 test/resources diff --git a/test/resources b/test/resources new file mode 160000 index 0000000000..9b3eab858d --- /dev/null +++ b/test/resources @@ -0,0 +1 @@ +Subproject commit 9b3eab858d4cb8457c9f147a188fb3fc4f7723a1 From fd1f49a813b42207996d7c701585360040e1020b Mon Sep 17 00:00:00 2001 From: David Paul Jansen Date: Mon, 4 Nov 2024 09:52:48 +0100 Subject: [PATCH 081/125] exclude txt files from coverage --- .gitlab-ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3d8276b094..2b272c8cde 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -641,7 +641,7 @@ build:main-energyplus:py3.11: - export BIM2SIM_LOG_LEVEL=ERROR - | if [ "$COVERAGE" = "true" ]; then - coverage run -m unittest discover ~/bim2sim-coding/test + coverage run --omit="*/*_txt*" -m unittest discover ~/bim2sim-coding/test mkdir -p /builds/EBC/EBC_all/github_ci/bim2sim/$CI_COMMIT_REF_NAME/coverage coverage html -d /builds/EBC/EBC_all/github_ci/bim2sim/$CI_COMMIT_REF_NAME/coverage coverage-badge -o /builds/EBC/EBC_all/github_ci/bim2sim/$CI_COMMIT_REF_NAME/coverage/badge.svg @@ -673,7 +673,7 @@ build:main-energyplus:py3.11: plugin_test_dir=~/bim2sim-coding/bim2sim/plugins/${plugin}/test if [ -d "${plugin_test_dir}/unit" ] || [ -d "${plugin_test_dir}/integration" ]; then test_dirs=$(find ${plugin_test_dir} -maxdepth 1 -type d \( -name "unit" -o -name "integration" \) | tr '\n' ' ') - coverage run --source=~/bim2sim-coding/bim2sim/plugins/${plugin} -m unittest discover -v ${test_dirs} + coverage run --omit="*/*_txt*" --source=~/bim2sim-coding/bim2sim/plugins/${plugin} -m unittest discover -v ${test_dirs} else echo "No unit or integration test directories found." exit 1 @@ -708,9 +708,9 @@ build:main-energyplus:py3.11: - | set +e if [[ "$CI_JOB_IMAGE" == *"dymola"* ]]; then - xvfb-run -n 77 coverage run -m unittest discover ~/bim2sim-coding/bim2sim/plugins/${plugin}/test/regression + xvfb-run -n 77 coverage run --omit="*/*_txt*" -m unittest discover ~/bim2sim-coding/bim2sim/plugins/${plugin}/test/regression else - coverage run -m unittest discover ~/bim2sim-coding/bim2sim/plugins/${plugin}/test/regression + coverage run --omit="*/*_txt*" -m unittest discover ~/bim2sim-coding/bim2sim/plugins/${plugin}/test/regression fi test_exit_code=$? set -e From d710b305d16e268b3e07a6b5d83f6e70472eaaf6 Mon Sep 17 00:00:00 2001 From: sfreund1 Date: Mon, 4 Nov 2024 10:03:44 +0100 Subject: [PATCH 082/125] fixed unit test of PluginAixLib --- .../test/unit/tasks/test_export.py | 42 ++++++++----------- test/unit/elements/helper.py | 11 ++++- 2 files changed, 26 insertions(+), 27 deletions(-) diff --git a/bim2sim/plugins/PluginAixLib/test/unit/tasks/test_export.py b/bim2sim/plugins/PluginAixLib/test/unit/tasks/test_export.py index 072050b298..d3c5516b14 100644 --- a/bim2sim/plugins/PluginAixLib/test/unit/tasks/test_export.py +++ b/bim2sim/plugins/PluginAixLib/test/unit/tasks/test_export.py @@ -34,20 +34,18 @@ def test_boiler_export(self): def test_radiator_export(self): graph = self.helper.get_simple_radiator() answers = () - modelica_model = DebugDecisionHandler(answers).handle( - self.export_task.run(self.loaded_libs, graph)) + modelica_model = self.run_export(graph, answers) parameters = [('rated_power', 'Q_flow_nominal'), ('flow_temperature', 'T_a_nominal'), ('return_temperature', 'T_b_nominal')] - expected_units = [ureg.watt, ureg.celsius, ureg.celsius] + expected_units = [ureg.watt, ureg.kelvin, ureg.kelvin] self.run_parameter_test(graph, modelica_model, parameters, expected_units) def test_pump_export(self): graph, _ = self.helper.get_simple_pump() answers = () - modelica_model = DebugDecisionHandler(answers).handle( - self.export_task.run(self.loaded_libs, graph)) + modelica_model = self.run_export(graph, answers) element = graph.elements[0] V_flow = element.rated_volume_flow.to(ureg.m ** 3 / ureg.s).magnitude dp = element.rated_pressure_difference.to(ureg.pascal).magnitude @@ -55,13 +53,12 @@ def test_pump_export(self): f"V_flow={{{0 * V_flow},{1 * V_flow},{2 * V_flow}}}," f"dp={{{2 * dp},{1 * dp},{0 * dp}}}" f"))") - self.assertIn(expected_string, modelica_model[0].code()) + self.assertIn(expected_string, modelica_model[0].render_modelica_code()) def test_consumer_export(self): graph, _ = self.helper.get_simple_consumer() answers = () - modelica_model = DebugDecisionHandler(answers).handle( - self.export_task.run(self.loaded_libs, graph)) + modelica_model = self.run_export(graph, answers) parameters = [('rated_power', 'Q_flow_fixed')] expected_units = [ureg.watt] self.run_parameter_test(graph, modelica_model, parameters, @@ -82,8 +79,7 @@ def test_consumer_distributor_export(self): def test_three_way_valve_export(self): graph = self.helper.get_simple_three_way_valve() answers = (1 * ureg.kg / ureg.s,) - modelica_model = DebugDecisionHandler(answers).handle( - self.export_task.run(self.loaded_libs, graph)) + modelica_model = self.run_export(graph, answers) parameters = [('nominal_pressure_difference', 'dpValve_nominal'), ('nominal_mass_flow_rate', 'm_flow_nominal')] expected_units = [ureg.pascal, ureg.kg / ureg.s] @@ -93,8 +89,7 @@ def test_three_way_valve_export(self): def test_heat_pump_export(self): graph = self.helper.get_simple_heat_pump() answers = () - modelica_model = DebugDecisionHandler(answers).handle( - self.export_task.run(self.loaded_libs, graph)) + modelica_model = self.run_export(graph, answers) parameters = [('rated_power', 'Q_useNominal')] expected_units = [ureg.watt] self.run_parameter_test(graph, modelica_model, parameters, @@ -103,8 +98,7 @@ def test_heat_pump_export(self): def test_chiller_export(self): graph = self.helper.get_simple_chiller() answers = () - modelica_model = DebugDecisionHandler(answers).handle( - self.export_task.run(self.loaded_libs, graph)) + modelica_model = self.run_export(graph, answers) parameters = [('rated_power', 'Q_useNominal')] expected_units = [ureg.watt] self.run_parameter_test(graph, modelica_model, parameters, @@ -117,8 +111,7 @@ def test_consumer_CHP_export(self): def test_storage_export(self): graph = self.helper.get_simple_storage() answers = () - modelica_model = DebugDecisionHandler(answers).handle( - self.export_task.run(self.loaded_libs, graph)) + modelica_model = self.run_export(graph, answers) parameters = [('height', 'hTank'), ('diameter', 'dTank')] expected_units = [ureg.meter, ureg.meter] element = graph.elements[0] @@ -128,7 +121,7 @@ def test_storage_export(self): param_value_pairs = [f"{param[1]}={value}" for param, value in zip(parameters, expected_values)] expected_string = f"data({','.join(param_value_pairs)})" - self.assertIn(expected_string, modelica_model[0].code()) + self.assertIn(expected_string, modelica_model[0].render_modelica_code()) def test_radiator_export_with_heat_ports(self): """Test export of two radiators, focus on correct heat port export.""" @@ -138,8 +131,7 @@ def test_radiator_export_with_heat_ports(self): # export outer heat ports self.export_task.playground.sim_settings.outer_heat_ports = True - modelica_model = DebugDecisionHandler(answers).handle( - self.export_task.run(self.loaded_libs, graph)) + modelica_model = self.run_export(graph, answers) # ToDo: as elements are unsorted, testing with names is not robust # connections_heat_ports_conv_expected = [ # ('heatPortOuterCon[1]', @@ -155,22 +147,22 @@ def test_radiator_export_with_heat_ports(self): # check existence of heat ports self.assertEqual( - 2, len(modelica_model[0].elements[0].heat_ports)) + 2, len(modelica_model[0].modelica_elements[0].heat_ports)) self.assertEqual( - 2, len(modelica_model[0].elements[1].heat_ports)) + 2, len(modelica_model[0].modelica_elements[1].heat_ports)) # check types of heat ports self.assertEqual( - modelica_model[0].elements[0].heat_ports[0].heat_transfer_type, + modelica_model[0].modelica_elements[0].heat_ports[0].heat_transfer_type, HeatTransferType.CONVECTIVE) self.assertEqual( - modelica_model[0].elements[0].heat_ports[1].heat_transfer_type, + modelica_model[0].modelica_elements[0].heat_ports[1].heat_transfer_type, HeatTransferType.RADIATIVE) self.assertEqual( - modelica_model[0].elements[1].heat_ports[0].heat_transfer_type, + modelica_model[0].modelica_elements[1].heat_ports[0].heat_transfer_type, HeatTransferType.CONVECTIVE) self.assertEqual( - modelica_model[0].elements[1].heat_ports[1].heat_transfer_type, + modelica_model[0].modelica_elements[1].heat_ports[1].heat_transfer_type, HeatTransferType.RADIATIVE) # check number of heat port connections diff --git a/test/unit/elements/helper.py b/test/unit/elements/helper.py index 8af7ba8542..327290d72b 100644 --- a/test/unit/elements/helper.py +++ b/test/unit/elements/helper.py @@ -136,6 +136,7 @@ def get_simple_consumer(self): consumer = self.element_generator( hvac_aggregations.Consumer, rated_power=20 * ureg.kilowatt, + rated_volume_flow=1 * ureg.meter**3 / ureg.second, base_graph=nx.Graph(), match_graph=nx.Graph() ) @@ -349,9 +350,15 @@ def get_pump(self): def get_two_radiators(self): radiator_1 = self.element_generator( - hvac.SpaceHeater, rated_power=1 * ureg.kilowatt) + hvac.SpaceHeater, rated_power=1 * ureg.kilowatt, + flow_temperature=70 * ureg.celsius, + return_temperature=50 * ureg.celsius + ) radiator_2 = self.element_generator( - hvac.SpaceHeater, rated_power=1 * ureg.kilowatt) + hvac.SpaceHeater, rated_power=1 * ureg.kilowatt, + flow_temperature=70 * ureg.celsius, + return_temperature=50 * ureg.celsius + ) return HvacGraph([radiator_1, radiator_2]) class SetupHelperBPS(SetupHelper): From 408dd00da798156ebbbb8583189368f156326337 Mon Sep 17 00:00:00 2001 From: David Paul Jansen Date: Mon, 4 Nov 2024 11:45:49 +0100 Subject: [PATCH 083/125] cleanup spawn implementation and add simple integration test --- .../templates/modelica/tmplSpawnBuilding.txt | 2 +- bim2sim/elements/bps_elements.py | 28 ----- .../bim2sim_energyplus/sim_settings.py | 1 + .../bim2sim_energyplus/task/ep_create_idf.py | 6 +- .../PluginSpawn/bim2sim_spawn/__init__.py | 10 +- .../examples/e1_simple_project_bps_spawn.py | 3 - .../bim2sim_spawn/models/__init__.py | 24 ----- .../PluginSpawn/bim2sim_spawn/sim_settings.py | 12 +-- .../tasks/export_spawn_building.py | 52 +++------ .../bim2sim_spawn/tasks/export_spawn_total.py | 8 +- bim2sim/plugins/PluginSpawn/test/__init__.py | 0 .../PluginSpawn/test/integration/__init__.py | 0 .../test/integration/test_spawn.py | 101 ++++++++++++++++++ .../test/integration/test_usage.py | 18 ++++ bim2sim/sim_settings.py | 14 +-- bim2sim/tasks/hvac/export.py | 7 +- 16 files changed, 166 insertions(+), 120 deletions(-) delete mode 100644 bim2sim/plugins/PluginSpawn/bim2sim_spawn/models/__init__.py create mode 100644 bim2sim/plugins/PluginSpawn/test/__init__.py create mode 100644 bim2sim/plugins/PluginSpawn/test/integration/__init__.py create mode 100644 bim2sim/plugins/PluginSpawn/test/integration/test_spawn.py create mode 100644 bim2sim/plugins/PluginSpawn/test/integration/test_usage.py diff --git a/bim2sim/assets/templates/modelica/tmplSpawnBuilding.txt b/bim2sim/assets/templates/modelica/tmplSpawnBuilding.txt index f9d310e83e..af475658af 100644 --- a/bim2sim/assets/templates/modelica/tmplSpawnBuilding.txt +++ b/bim2sim/assets/templates/modelica/tmplSpawnBuilding.txt @@ -12,7 +12,7 @@ model ${model_name} "${model_comment}" final parameter Modelica.Units.SI.MassFlowRate mOut_flow[nZones]=0.3/3600 * fill(1, nZones) * 1.2 "Outside air infiltration for each exterior room"; - parameter String zoneNames[nZones] = ${zone_names} + parameter String zoneNames[nZones] = ${ep_zone_lists} "Name of the thermal zone as specified in the EnergyPlus input"; inner Buildings.ThermalZones.EnergyPlus_9_6_0.Building building ( diff --git a/bim2sim/elements/bps_elements.py b/bim2sim/elements/bps_elements.py index d02b4d46b5..19639d425f 100644 --- a/bim2sim/elements/bps_elements.py +++ b/bim2sim/elements/bps_elements.py @@ -1943,34 +1943,6 @@ class SpaceBoundaryRepresentation(BPSProduct): ] -class SpawnBuilding(Element): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.idfName: Path = Path("") - self.epwName: Path = Path("") - self.weaName: Path = Path("") - self.printUnits: bool = True - - def calc_position(self) -> np.array: - return np.array([-80, 10, 5]) - - -class FreshAirSource(Element): - def calc_position(self) -> np.array: - return np.array([-40, 20, 8]) - - -class SpawnMultiZone(Element): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.Medium: str = "" - self.zone_names: list = [] - self.use_c_flow: bool = False - - - def calc_position(self) -> np.array: - return np.array([+10, 20, 12]) - # collect all domain classes items: Set[BPSProduct] = set() for name, cls in inspect.getmembers( diff --git a/bim2sim/plugins/PluginEnergyPlus/bim2sim_energyplus/sim_settings.py b/bim2sim/plugins/PluginEnergyPlus/bim2sim_energyplus/sim_settings.py index f92920d431..062a3a2744 100644 --- a/bim2sim/plugins/PluginEnergyPlus/bim2sim_energyplus/sim_settings.py +++ b/bim2sim/plugins/PluginEnergyPlus/bim2sim_energyplus/sim_settings.py @@ -44,6 +44,7 @@ class EnergyPlusSimSettings(BuildingSimSettings): choices={ '9-2-0': 'EnergyPlus Version 9-2-0', '9-4-0': 'EnergyPlus Version 9-4-0', + # '9-6-0': 'EnergyPlus Version 9-6-0', # todo #743 '22-2-0': 'EnergyPlus Version 22-2-0' # todo: Test latest version }, description='Choose EnergyPlus Version', diff --git a/bim2sim/plugins/PluginEnergyPlus/bim2sim_energyplus/task/ep_create_idf.py b/bim2sim/plugins/PluginEnergyPlus/bim2sim_energyplus/task/ep_create_idf.py index 280c86b66c..104ccb8ad0 100644 --- a/bim2sim/plugins/PluginEnergyPlus/bim2sim_energyplus/task/ep_create_idf.py +++ b/bim2sim/plugins/PluginEnergyPlus/bim2sim_energyplus/task/ep_create_idf.py @@ -4,7 +4,7 @@ import math import os from pathlib import Path, PosixPath -from typing import Union, TYPE_CHECKING +from typing import Union, TYPE_CHECKING, List from OCC.Core.BRep import BRep_Tool from OCC.Core.BRepBuilderAPI import BRepBuilderAPI_MakeFace @@ -52,7 +52,8 @@ def __init__(self, playground): super().__init__(playground) self.idf = None - def run(self, elements: dict, weather_file_ep: Path) -> tuple[IDF, Path]: + def run(self, elements: dict, weather_file_ep: Path) -> ( + tuple)[IDF, Path, List]: """Execute all methods to export an IDF from BIM2SIM. This task includes all functions for exporting EnergyPlus Input files @@ -77,6 +78,7 @@ def run(self, elements: dict, weather_file_ep: Path) -> tuple[IDF, Path]: Returns: idf (IDF): EnergyPlus input file sim_results_path (Path): path to the simulation results. + ep_zone_lists (List): List of thermal zone EP items """ logger.info("IDF generation started ...") idf, sim_results_path = self.init_idf( diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/__init__.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/__init__.py index b612a82fb9..e6d15c5736 100644 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/__init__.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/__init__.py @@ -8,8 +8,15 @@ from bim2sim_aixlib import LoadLibrariesAixLib -# # TODO: this is just a concept and not working already class PluginSpawnOfEP(Plugin): + """Plugin for SpawnOfEnergyPlus. + + This is the first plugin that uses tasks from different plugins together. + We first execute the EnergyPlus related tasks to create an IDF file. + Afterwards, we execute the HVAC tasks, using PluginAixLib (PluginHKESim + would also work) and then execute the PluginSpawn tasks to put + EnergyPlus IDF and Modelica model together. + """ name = 'spawn' sim_settings = SpawnOfEnergyPlusSimSettings default_tasks = [ @@ -39,4 +46,3 @@ class PluginSpawnOfEP(Plugin): spawn_tasks.ExportSpawnBuilding, spawn_tasks.ExportSpawnTotal, ] - # TODO: we need a merge of tasks of AixLib and EnergyPlus diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e1_simple_project_bps_spawn.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e1_simple_project_bps_spawn.py index ebef2b0190..d017680e3a 100644 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e1_simple_project_bps_spawn.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e1_simple_project_bps_spawn.py @@ -5,7 +5,6 @@ from bim2sim import Project, ConsoleDecisionHandler from bim2sim.kernel.decision.decisionhandler import DebugDecisionHandler from bim2sim.kernel.log import default_logging_setup -from bim2sim.utilities.common_functions import download_test_resources from bim2sim.utilities.types import IFCDomain @@ -27,7 +26,6 @@ def run_example_spawn_1(): project_path = Path( tempfile.TemporaryDirectory(prefix='bim2sim_example_spawn').name) - download_test_resources(IFCDomain.mixed, force_new=False) # Set the ifc path to use and define which domain the IFC belongs to ifc_paths = { IFCDomain.mixed: @@ -48,7 +46,6 @@ def run_example_spawn_1(): project.sim_settings.weather_file_path_ep = ( Path(bim2sim.__file__).parent.parent / 'test/resources/weather_files/DEU_NW_Aachen.105010_TMYx.epw') - # TODO make sure that a non existing sim_setting assignment raises an error project.sim_settings.weather_file_path_modelica = ( Path(bim2sim.__file__).parent.parent / 'test/resources/weather_files/DEU_NW_Aachen.105010_TMYx.mos') diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/models/__init__.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/models/__init__.py deleted file mode 100644 index daaf5746a2..0000000000 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/models/__init__.py +++ /dev/null @@ -1,24 +0,0 @@ -from pathlib import Path - - -def to_modelica_spawn(parameter): - """converts parameter to modelica readable string""" - if parameter is None: - return parameter - if isinstance(parameter, bool): - return 'true' if parameter else 'false' - if isinstance(parameter, (int, float)): - return str(parameter) - if isinstance(parameter, str): - return '"%s"' % parameter - if isinstance(parameter, (list, tuple, set)): - return "{%s}" % ( - ",".join( - (to_modelica_spawn(par) for par - in parameter))) - if isinstance(parameter, Path): - return \ - f"Modelica.Utilities.Files.loadResource(\"" \ - f"{str(parameter)}\")" \ - .replace("\\", "\\\\") - return str(parameter) \ No newline at end of file diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/sim_settings.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/sim_settings.py index 8e03ac9d9d..eaa11344ba 100644 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/sim_settings.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/sim_settings.py @@ -1,7 +1,6 @@ from bim2sim.elements import bps_elements, hvac_elements from bim2sim.elements.base_elements import Material -from bim2sim.sim_settings import BooleanSetting, \ - PlantSimSettings +from bim2sim.sim_settings import PlantSimSettings from bim2sim.plugins.PluginEnergyPlus.bim2sim_energyplus.sim_settings import \ EnergyPlusSimSettings @@ -11,10 +10,5 @@ def __init__(self): super().__init__() self.relevant_elements = {*bps_elements.items, *hvac_elements.items, Material} - - add_natural_ventilation = BooleanSetting( - default=True, - description='Add natural ventilation to the building. Natural ' - 'ventilation is not available when cooling is activated.', - for_frontend=True - ) + # change defaults + self.outer_heat_ports = True diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_building.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_building.py index a10f338751..f5e6a91334 100644 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_building.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_building.py @@ -1,10 +1,11 @@ import codecs from pathlib import Path +from typing import List from mako.template import Template import bim2sim -from bim2sim.plugins.PluginSpawn.bim2sim_spawn.models import to_modelica_spawn +from bim2sim.export.modelica import parse_to_modelica from bim2sim.tasks.base import ITask @@ -12,11 +13,11 @@ class ExportSpawnBuilding(ITask): """Export building for SpawnOfEnergyPlus model to Modelica""" reads = ('elements', 'weather_file_modelica', 'weather_file_ep', - 'package_name') - touches = ('zone_names', 'model_name_building') + 'package_name', 'ep_zone_lists') + touches = ('model_name_building',) def run(self, elements: dict, weather_file_modelica: Path, - weather_file_ep: Path, package_name: str): + weather_file_ep: Path, package_name: str, ep_zone_lists: List): """Run the export process for a building Spawn model in Modelica. This method prepares the Modelica file for a building model by gathering @@ -28,9 +29,9 @@ def run(self, elements: dict, weather_file_modelica: Path, weather_file_modelica: Path to the Modelica weather file. weather_file_ep: Path to the EnergyPlus weather file. package_name: Name of the Modelica package for exporting. + ep_zone_lists: List of thermal zone EP items Returns: - zone_names: List of zones for the building. model_name_building: the name of the building model. """ self.logger.info("Export building of Spawn model to Modelica code") @@ -40,13 +41,12 @@ def run(self, elements: dict, weather_file_modelica: Path, export_package_path = self.paths.export / Path(package_name) # Generate building template data - zone_names = self._get_zone_names() building_template_data = self._render_building_template( package_path=export_package_path, model_name=model_name_building, weather_file_ep=weather_file_ep, weather_file_modelica=weather_file_modelica, - zone_names=zone_names + ep_zone_lists=ep_zone_lists ) # Write the generated Modelica code to file @@ -54,7 +54,7 @@ def run(self, elements: dict, weather_file_modelica: Path, Path(export_package_path / f"{model_name_building}.mo"), building_template_data) - return zone_names, model_name_building + return model_name_building, @staticmethod def _load_template() -> Template: @@ -74,7 +74,7 @@ def _render_building_template(self, model_name: str, weather_file_ep: Path, weather_file_modelica: Path, - zone_names: list) -> str: + ep_zone_lists: list) -> str: """Render the building Modelica template using provided data. Args: @@ -82,7 +82,7 @@ def _render_building_template(self, model_name: The name of the building model. weather_file_ep: The EnergyPlus weather file path. weather_file_modelica: The Modelica weather file path. - zone_names: List of zone names to be used in the model. + ep_zone_lists: List of zone names to be used in the model. Returns: Rendered Modelica code as a string. @@ -94,11 +94,13 @@ def _render_building_template(self, within=package_path.stem, model_name=model_name, model_comment='Building model for Spawn of EnergyPlus', - weather_path_ep=to_modelica_spawn(weather_file_ep), - weather_path_mos=to_modelica_spawn(weather_file_modelica), - zone_names=to_modelica_spawn(zone_names), - idf_path=to_modelica_spawn(idf_path), - n_zones=len(zone_names) + weather_path_ep=parse_to_modelica( + name=None, value=weather_file_ep), + weather_path_mos=parse_to_modelica( + name=None, value=weather_file_modelica), + ep_zone_lists=parse_to_modelica(name=None, value=ep_zone_lists), + idf_path=parse_to_modelica(name=None, value=idf_path), + n_zones=len(ep_zone_lists) ) def _write_to_file(self, file_path: Path, content: str): @@ -111,23 +113,3 @@ def _write_to_file(self, file_path: Path, content: str): with codecs.open(file_path, 'w', 'utf-8') as f: f.write(content) self.logger.info(f"Successfully saved Modelica file to {file_path}") - - def _get_zone_names(self): - """Retrieve zone names from the playground state. - - Raises: - ValueError: If 'ep_zone_lists' is not found in the playground state. - - Returns: - list: A list of zone names for the building. - """ - # TODO #1: get names from IDF or EP process for ep zones in - # correct order - - if "ep_zone_lists" in self.playground.state: - zone_list = self.playground.state["ep_zone_lists"] - else: - raise ValueError("'ep_zone_list' not found in playground state, " - "please make sure that EnergyPlus model creation " - "was successful.") - return zone_list diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_total.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_total.py index dd815a9238..8a7ba9324e 100644 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_total.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_total.py @@ -10,8 +10,7 @@ from bim2sim.elements.base_elements import ProductBased from bim2sim.elements.hvac_elements import HVACProduct from bim2sim.export.modelica import help_package, help_package_order, \ - ModelicaElement -from bim2sim.plugins.PluginSpawn.bim2sim_spawn.models import to_modelica_spawn + ModelicaElement, parse_to_modelica from bim2sim.tasks.base import ITask from bim2sim.utilities.common_functions import filter_elements from bim2sim.utilities.pyocc_tools import PyOCCTools @@ -28,7 +27,7 @@ class ExportSpawnTotal(ITask): reads = ( 'elements', 'weather_file_modelica', 'weather_file_ep', - 'zone_names', 'model_name_hydraulic', 'model_name_building', + 'model_name_hydraulic', 'model_name_building', 'export_elements', 'connections', 'cons_heat_ports_conv', 'cons_heat_ports_rad', 'package_name' ) @@ -38,7 +37,6 @@ def run(self, elements: Dict[str, ProductBased], weather_file_modelica: Path, weather_file_ep: Path, - zone_names: List[str], model_name_hydraulic: str, model_name_building: str, export_elements: Dict[HVACProduct, ModelicaElement], @@ -52,7 +50,6 @@ def run(self, elements: The elements' data. weather_file_modelica: Path to the Modelica weather file. weather_file_ep: Path to the EnergyPlus weather file. - zone_names: List of zone names. model_name_hydraulic: The name of the hydraulic model. model_name_building: The name of the building model. export_elements: HVAC elements to export. @@ -162,7 +159,6 @@ def _save_total_modelica_model( within=package_path.stem, model_name=model_name_total, model_comment='test2', - weather_path_mos=to_modelica_spawn(weather_path_mos), model_name_building=model_name_building, model_name_hydraulic=model_name_hydraulic, cons_heat_ports_conv_building_hvac= diff --git a/bim2sim/plugins/PluginSpawn/test/__init__.py b/bim2sim/plugins/PluginSpawn/test/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/bim2sim/plugins/PluginSpawn/test/integration/__init__.py b/bim2sim/plugins/PluginSpawn/test/integration/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/bim2sim/plugins/PluginSpawn/test/integration/test_spawn.py b/bim2sim/plugins/PluginSpawn/test/integration/test_spawn.py new file mode 100644 index 0000000000..0520443b5c --- /dev/null +++ b/bim2sim/plugins/PluginSpawn/test/integration/test_spawn.py @@ -0,0 +1,101 @@ +import unittest +from pathlib import Path + +from bim2sim import ConsoleDecisionHandler, run_project +from bim2sim.kernel.decision.decisionhandler import DebugDecisionHandler +from bim2sim.export.modelica import ModelicaElement +from bim2sim.utilities.test import IntegrationWeatherBase +from bim2sim.utilities.types import IFCDomain +from bim2sim.tasks.hvac.export import Export + + +class IntegrationBaseSpawn(IntegrationWeatherBase): + def tearDown(self): + ModelicaElement.lookup = {} + super().tearDown() + + def model_domain_path(self) -> str: + return 'mixed' + + def set_test_weather_file(self): + """Set the weather file path.""" + self.project.sim_settings.weather_file_path_modelica = ( + self.test_resources_path() / + 'weather_files/DEU_NW_Aachen.105010_TMYx.mos') + + self.project.sim_settings.weather_file_path_ep = ( + self.test_resources_path() / + 'weather_files/DEU_NW_Aachen.105010_TMYx.epw') + + @staticmethod + def assertIsFile(path): + if not Path(path).resolve().is_file(): + raise AssertionError("File does not exist: %s" % str(path)) + +class TestIntegrationAixLib(IntegrationBaseSpawn, unittest.TestCase): + + def test_mixed_ifc_spawn(self): + """Run project with + KM_DPM_Vereinshaus_Gruppe62_Heizung_with_pumps.ifc""" + ifc_names = {IFCDomain.mixed: + 'b03_heating_with_building_blenderBIM.ifc'} + project = self.create_project(ifc_names, 'spawn') + + # HVAC/AixLib sim_settings + # Generate outer heat ports for spawn HVAC sub model + # Set other simulation settings, otherwise all settings are set to default + project.sim_settings.aggregations = [ + 'PipeStrand', + 'ParallelPump', + 'GeneratorOneFluid' + ] + project.sim_settings.group_unidentified = 'name' + + # EnergyPlus sim settings + project.sim_settings.ep_install_path = Path( + 'C:/EnergyPlusV9-6-0/') + project.sim_settings.ep_version = "9-6-0" + answers = ( + 'HVAC-PipeFitting', # Identify PipeFitting + 'HVAC-Distributor', # Identify Distributor + 'HVAC-ThreeWayValve', # Identify ThreeWayValve + 2010, # year of construction of building + *(True,) * 7, # 7 real dead ends found + *(0.001,) * 13, # volume of junctions + 2000, 175, + # rated_pressure_difference + rated_volume_flow pump of 1st storey (big) + 4000, 200, + # rated_pressure_difference + rated_volume_flow for 2nd storey + *(70, 50,) * 7, # flow and return temp for 7 space heaters + 0.056, # nominal_mass_flow_rate 2nd storey TRV (kg/s), + 20, # dT water of boiler + 70, # nominal flow temperature of boiler + 0.3, # minimal part load range of boiler + 8.5, # nominal power of boiler (in kW) + 50, # nominal return temperature of boiler + ) + handler = DebugDecisionHandler(answers) + for decision, answer in handler.decision_answer_mapping(project.run()): + decision.value = answer + + self.assertEqual(355, len(self.project.playground.elements)) + # check temperature values of exported boiler + self.assertEqual(70, project.playground.elements[ + '01ZLkLzum6a4lxl_1XXMh0'].aggregation.flow_temperature.m) + self.assertEqual(20, project.playground.elements[ + '01ZLkLzum6a4lxl_1XXMh0'].aggregation.dT_water.m) + # check number of thermal zones of building + self.assertEqual(5, len( + project.playground.elements[ + '0N8f6_N2WXb4$EWzeVdEh5'].thermal_zones)) + # check file structure + file_names = [ + "BuildingModel.mo", + "Hydraulic.mo", + "package.mo", + "package.order", + "TotalModel.mo" + ] + for file_name in file_names: + self.assertIsFile(project.paths.export / Export.get_package_name( + self.project.name) / file_name) diff --git a/bim2sim/plugins/PluginSpawn/test/integration/test_usage.py b/bim2sim/plugins/PluginSpawn/test/integration/test_usage.py new file mode 100644 index 0000000000..71c17acc52 --- /dev/null +++ b/bim2sim/plugins/PluginSpawn/test/integration/test_usage.py @@ -0,0 +1,18 @@ +import unittest + + +class TestUsage(unittest.TestCase): + """Tests for general use of library""" + + def test_import_plugin(self): + """Test importing Spawn plugin in python script""" + try: + from bim2sim.plugins import load_plugin, Plugin + plugin = load_plugin('bim2sim_spawn') + assert issubclass(plugin, Plugin) + except ImportError as err: + self.fail("Unable to import plugin\nreason: %s"%(err)) + + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/bim2sim/sim_settings.py b/bim2sim/sim_settings.py index 85c0d64d42..420f677805 100644 --- a/bim2sim/sim_settings.py +++ b/bim2sim/sim_settings.py @@ -544,13 +544,6 @@ def check_mandatory(self): for_frontend=True ) - # TODO move to EP Spawn sim settings - outer_heat_ports = BooleanSetting( - default=False, - description='Add outer heat ports to allow connections to other ' - 'models.', - for_frontend=True - ) class PlantSimSettings(BaseSimSettings): def __init__(self): @@ -593,6 +586,13 @@ def __init__(self): min_value=1 ) + outer_heat_ports = BooleanSetting( + default=False, + description='Add outer heat ports to allow connections to other ' + 'models.', + for_frontend=True + ) + class BuildingSimSettings(BaseSimSettings): diff --git a/bim2sim/tasks/hvac/export.py b/bim2sim/tasks/hvac/export.py index 756ac8d8e7..9ae50b9a14 100644 --- a/bim2sim/tasks/hvac/export.py +++ b/bim2sim/tasks/hvac/export.py @@ -237,7 +237,7 @@ def run(self, export_elements: dict, connections: list, " connections.", len(export_elements), len(connections)) model_name_hydraulic = 'Hydraulic' - package_name = self._get_package_name() + package_name = self.get_package_name(self.prj_name) # Setup package directory structure export_package_path = self._setup_package_structure( @@ -255,14 +255,15 @@ def run(self, export_elements: dict, connections: list, ) return modelica_model, package_name, model_name_hydraulic - def _get_package_name(self) -> str: + @staticmethod + def get_package_name(prj_name) -> str: """Generate a valid package name based on the project name. Returns: str: The valid package name. """ regex = re.compile("[^a-zA-z0-9]") - return regex.sub("", self.prj_name) + return regex.sub("", prj_name) def _setup_package_structure(self, package_name: str, model_name_hydraulic: str) -> Path: From 0e5b5cc871ca54446291456a6224f04943dc6266 Mon Sep 17 00:00:00 2001 From: David Paul Jansen Date: Tue, 5 Nov 2024 11:46:04 +0100 Subject: [PATCH 084/125] update yml --- .gitlab-ci.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1d69eb5c6c..c75f673722 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -641,7 +641,7 @@ build:main-energyplus:py3.11: - export BIM2SIM_LOG_LEVEL=ERROR - | if [ "$COVERAGE" = "true" ]; then - coverage run -m unittest discover ~/bim2sim-coding/test + coverage run --omit="*/*_txt*" -m unittest discover ~/bim2sim-coding/test mkdir -p /builds/EBC/EBC_all/github_ci/bim2sim/$CI_COMMIT_REF_NAME/coverage coverage html -d /builds/EBC/EBC_all/github_ci/bim2sim/$CI_COMMIT_REF_NAME/coverage coverage-badge -o /builds/EBC/EBC_all/github_ci/bim2sim/$CI_COMMIT_REF_NAME/coverage/badge.svg @@ -674,11 +674,11 @@ build:main-energyplus:py3.11: if [ -d "${plugin_test_dir}/unit" ] || [ -d "${plugin_test_dir}/integration" ]; then if [ -d "${plugin_test_dir}/unit" ]; then echo "Running unit tests..." - coverage run --source=~/bim2sim-coding/bim2sim/plugins/${plugin} -m unittest discover -v ${plugin_test_dir}/unit + coverage run --omit="*/*_txt*" --source=~/bim2sim-coding/bim2sim/plugins/${plugin} -m unittest discover -v ${plugin_test_dir}/unit fi if [ -d "${plugin_test_dir}/integration" ]; then echo "Running integration tests..." - coverage run --append --source=~/bim2sim-coding/bim2sim/plugins/${plugin} -m unittest discover -v ${plugin_test_dir}/integration + coverage run --omit="*/*_txt*" --append --source=~/bim2sim-coding/bim2sim/plugins/${plugin} -m unittest discover -v ${plugin_test_dir}/integration fi else echo "No unit or integration test directories found." @@ -714,9 +714,9 @@ build:main-energyplus:py3.11: - | set +e if [[ "$CI_JOB_IMAGE" == *"dymola"* ]]; then - xvfb-run -n 77 coverage run -m unittest discover ~/bim2sim-coding/bim2sim/plugins/${plugin}/test/regression + xvfb-run -n 77 coverage run --omit="*/*_txt*" -m unittest discover ~/bim2sim-coding/bim2sim/plugins/${plugin}/test/regression else - coverage run -m unittest discover ~/bim2sim-coding/bim2sim/plugins/${plugin}/test/regression + coverage run --omit="*/*_txt*" -m unittest discover ~/bim2sim-coding/bim2sim/plugins/${plugin}/test/regression fi test_exit_code=$? set -e From db0173ce0098b98c5b651f690d204f3780bf07e7 Mon Sep 17 00:00:00 2001 From: David Paul Jansen Date: Tue, 19 Nov 2024 16:32:19 +0100 Subject: [PATCH 085/125] add enums for flow_direction and flow_side and create new Task EnrichFlowDirection (not useful yet) --- bim2sim/elements/base_elements.py | 5 - bim2sim/elements/bps_elements.py | 8 - bim2sim/elements/graphs/hvac_graph.py | 102 ++--------- bim2sim/elements/hvac_elements.py | 158 +++++++++-------- .../PluginAixLib/bim2sim_aixlib/__init__.py | 1 + bim2sim/tasks/hvac/__init__.py | 1 + bim2sim/tasks/hvac/connect_elements.py | 40 ++++- bim2sim/tasks/hvac/enrich_flow_direction.py | 159 ++++++++++++++++++ bim2sim/tasks/hvac/reduce.py | 60 ------- bim2sim/utilities/types.py | 14 ++ docs/source/user-guide/PluginAixLib.md | 21 +++ 11 files changed, 333 insertions(+), 236 deletions(-) create mode 100644 bim2sim/tasks/hvac/enrich_flow_direction.py diff --git a/bim2sim/elements/base_elements.py b/bim2sim/elements/base_elements.py index 632baad6f0..e032e0e90b 100644 --- a/bim2sim/elements/base_elements.py +++ b/bim2sim/elements/base_elements.py @@ -206,11 +206,6 @@ def ifc_type(self): if self.ifc: return self.ifc.is_a() - @classmethod - def pre_validate(cls, ifc) -> bool: - """Check if ifc meets conditions to create element from it""" - raise NotImplementedError - def calc_position(self): """returns absolute position""" if hasattr(self.ifc, 'ObjectPlacement'): diff --git a/bim2sim/elements/bps_elements.py b/bim2sim/elements/bps_elements.py index 7da8f98024..5c8402aa81 100644 --- a/bim2sim/elements/bps_elements.py +++ b/bim2sim/elements/bps_elements.py @@ -617,10 +617,6 @@ def calc_position(self): return position - @classmethod - def pre_validate(cls, ifc) -> bool: - return True - def validate_creation(self) -> bool: if self.bound_area and self.bound_area < 1e-2 * ureg.meter ** 2: return True @@ -1322,10 +1318,6 @@ def get_id(prefix=""): ifcopenshell_guid = guid.new()[prefix_length + 1:] return f"{prefix}{ifcopenshell_guid}" - @classmethod - def pre_validate(cls, ifc) -> bool: - return True - def validate_creation(self) -> bool: return True diff --git a/bim2sim/elements/graphs/hvac_graph.py b/bim2sim/elements/graphs/hvac_graph.py index 62be7e0e71..38f85733f7 100644 --- a/bim2sim/elements/graphs/hvac_graph.py +++ b/bim2sim/elements/graphs/hvac_graph.py @@ -15,6 +15,7 @@ from networkx import json_graph from bim2sim.elements.base_elements import ProductBased, ElementEncoder +from bim2sim.utilities.types import FlowSide, FlowDirection logger = logging.getLogger(__name__) @@ -118,7 +119,7 @@ def get_type_chains( types: Iterable[Type[ProductBased]], include_singles: bool = False): """Get lists of consecutive elements of the given types. Elements are - ordered in the same way as the are connected. + ordered in the same way as they are connected. Args: element_graph: Graph object with elements as nodes. @@ -203,16 +204,15 @@ def plot(self, path: Path = None, ports: bool = False, dpi: int = 400, # https://plot.ly/python/network-graphs/ edge_colors_flow_side = { - 1: dict(edge_color='red'), - -1: dict(edge_color='blue'), - 0: dict(edge_color='grey'), - None: dict(edge_color='grey'), + FlowSide.supply_flow: dict(edge_color='red'), + FlowSide.return_flow: dict(edge_color='blue'), + FlowSide.unknown: dict(edge_color='grey'), } node_colors_flow_direction = { - 1: dict(node_color='white', edgecolors='blue'), - -1: dict(node_color='blue', edgecolors='black'), - 0: dict(node_color='grey', edgecolors='black'), - None: dict(node_color='grey', edgecolors='black'), + FlowDirection.source: dict(node_color='white', edgecolors='blue'), + FlowDirection.sink: dict(node_color='blue', edgecolors='black'), + FlowDirection.sink_and_source: dict(node_color='grey', edgecolors='black'), + FlowDirection.unknown: dict(node_color='grey', edgecolors='black'), } kwargs = {} @@ -293,6 +293,7 @@ def plot(self, path: Path = None, ports: bool = False, dpi: int = 400, node['label'] = node['label'].split('<')[1] except: pass + # TODO #633 use is_generator(), is_consumer() etc. node['label'] = node['label'].split('(ports')[0] if 'agg' in node['label'].lower(): node['label'] = node['label'].split('Agg0')[0] @@ -654,88 +655,7 @@ def group_parallels(graph, group_attr, cond, threshold=None): graphs.append(_graph) return graphs - def recurse_set_side(self, port, side, known: dict = None, - raise_error=True): - """Recursive set flow_side to connected ports""" - if known is None: - known = {} - - # set side suggestion - is_known = port in known - current_side = known.get(port, port.flow_side) - if not is_known: - known[port] = side - elif is_known and current_side == side: - return known - else: - # conflict - if raise_error: - raise AssertionError("Conflicting flow_side in %r" % port) - else: - logger.error("Conflicting flow_side in %r", port) - known[port] = None - return known - - # call neighbours - for neigh in self.neighbors(port): - if (neigh.parent.is_consumer() or neigh.parent.is_generator()) \ - and port.parent is neigh.parent: - # switch flag over consumers / generators - self.recurse_set_side(neigh, -side, known, raise_error) - else: - self.recurse_set_side(neigh, side, known, raise_error) - - return known - - def recurse_set_unknown_sides(self, port, visited: list = None, - masters: list = None): - """Recursive checks neighbours flow_side. - :returns tuple of - common flow_side (None if conflict) - list of checked ports - list of ports on which flow_side s are determined""" - - if visited is None: - visited = [] - if masters is None: - masters = [] - - # mark as visited to prevent deadloops - visited.append(port) - - if port.flow_side in (-1, 1): - # use port with known flow_side as master - masters.append(port) - return port.flow_side, visited, masters - - # call neighbours - neighbour_sides = {} - for neigh in self.neighbors(port): - if neigh not in visited: - if (neigh.parent.is_consumer() or neigh.parent.is_generator()) \ - and port.parent is neigh.parent: - # switch flag over consumers / generators - side, _, _ = self.recurse_set_unknown_sides( - neigh, visited, masters) - side = -side - else: - side, _, _ = self.recurse_set_unknown_sides( - neigh, visited, masters) - neighbour_sides[neigh] = side - - sides = set(neighbour_sides.values()) - if not sides: - return port.flow_side, visited, masters - elif len(sides) == 1: - # all neighbours have same site - side = sides.pop() - return side, visited, masters - elif len(sides) == 2 and 0 in sides: - side = (sides - {0}).pop() - return side, visited, masters - else: - # conflict - return None, visited, masters + @staticmethod def get_dir_paths_between(graph, nodes, include_edges=False): diff --git a/bim2sim/elements/hvac_elements.py b/bim2sim/elements/hvac_elements.py index 278cf011bc..1254407613 100644 --- a/bim2sim/elements/hvac_elements.py +++ b/bim2sim/elements/hvac_elements.py @@ -16,6 +16,8 @@ from bim2sim.elements.mapping.ifc2python import get_ports as ifc2py_get_ports from bim2sim.elements.mapping.ifc2python import get_predefined_type from bim2sim.elements.mapping.units import ureg +from bim2sim.utilities.types import FlowDirection, FlowSide + logger = logging.getLogger(__name__) quality_logger = logging.getLogger('bim2sim.QualityReport') @@ -34,19 +36,31 @@ def length_post_processing(value): class HVACPort(Port): - """Port of HVACProduct.""" - vl_pattern = re.compile('.*vorlauf.*', - re.IGNORECASE) # TODO: extend pattern - rl_pattern = re.compile('.*rücklauf.*', re.IGNORECASE) + """Port of HVACProduct. + + Definitions: + flow_direction: is the direction of the port which can be sink, source, + sink_and_source or unknown depending on the IFC data. + groups: based on IFC assignment this might be "vorlauf" or something else. + flow_side: defines if the port is part of the supply or return network. + E.g. the radiator is a splitter where one port is part of the supply and + the other port is part of the return network + + """ + vl_pattern = re.compile('.*(vorlauf|supply|feed|forward).*', re.IGNORECASE) + rl_pattern = re.compile('.*(rücklauf|return|recirculation|back).*', + re.IGNORECASE) def __init__( self, *args, groups: Set = None, - flow_direction: int = 0, **kwargs): + flow_direction: FlowDirection = FlowDirection.unknown, **kwargs): super().__init__(*args, **kwargs) self._flow_master = False - self._flow_direction = None + # self._flow_direction = None + self._flow_side = None + # groups and flow_direction coming from ifc2args kwargs self.groups = groups or set() self.flow_direction = flow_direction @@ -55,13 +69,16 @@ def ifc2args(cls, ifc) -> Tuple[tuple, dict]: args, kwargs = super().ifc2args(ifc) groups = {assg.RelatingGroup.ObjectType for assg in ifc.HasAssignments} - flow_direction = None if ifc.FlowDirection == 'SOURCE': - flow_direction = 1 + flow_direction = FlowDirection.source elif ifc.FlowDirection == 'SINK': - flow_direction = -1 + flow_direction = FlowDirection.sink elif ifc.FlowDirection in ['SINKANDSOURCE', 'SOURCEANDSINK']: - flow_direction = 0 + flow_direction = FlowDirection.sink_and_source + elif ifc.FlowDirection == 'NOTDEFINED': + flow_direction = FlowDirection.unknown + else: + flow_direction = FlowDirection.unknown kwargs['groups'] = groups kwargs['flow_direction'] = flow_direction @@ -90,10 +107,6 @@ def calc_position(self) -> np.array: quality_logger.info("Suspect position [0, 0, 0] for %s", self) return coordinates - @classmethod - def pre_validate(cls, ifc) -> bool: - return True - def validate_creation(self) -> bool: return True @@ -106,34 +119,34 @@ def flow_master(self): def flow_master(self, value: bool): self._flow_master = value - @property - def flow_direction(self): - """Flow direction of port - - -1 = medium flows into port - 1 = medium flows out of port - 0 = medium flow undirected - None = flow direction unknown""" - return self._flow_direction - - @flow_direction.setter - def flow_direction(self, value): - if self._flow_master: - raise AttributeError("Can't set flow direction for flow master.") - if value not in (-1, 0, 1, None): - raise AttributeError("Invalid value. Use one of (-1, 0, 1, None).") - self._flow_direction = value - - @property - def verbose_flow_direction(self): - """Flow direction of port""" - if self.flow_direction == -1: - return 'SINK' - if self.flow_direction == 0: - return 'SINKANDSOURCE' - if self.flow_direction == 1: - return 'SOURCE' - return 'UNKNOWN' + # @property + # def flow_direction(self): + # """Flow direction of port + # + # -1 = medium flows into port + # 1 = medium flows out of port + # 0 = medium flow undirected + # None = flow direction unknown""" + # return self._flow_direction + + # @flow_direction.setter + # def flow_direction(self, value): + # if self._flow_master: + # raise AttributeError("Can't set flow direction for flow master.") + # if value not in (-1, 0, 1, None): + # raise AttributeError("Invalid value. Use one of (-1, 0, 1, None).") + # self._flow_direction = value + + # @property + # def verbose_flow_direction(self): + # """Flow direction of port""" + # if self.flow_direction == -1: + # return 'SINK' + # if self.flow_direction == 0: + # return 'SINKANDSOURCE' + # if self.flow_direction == 1: + # return 'SOURCE' + # return 'UNKNOWN' @property def flow_side(self): @@ -150,39 +163,39 @@ def flow_side(self): @flow_side.setter def flow_side(self, value): - if value not in (-1, 0, 1): - raise ValueError("allowed values for flow_side are 1, 0, -1") previous = self._flow_side self._flow_side = value if previous: if previous != value: - logger.info("Overwriting flow_side for %r with %s" % ( - self, self.verbose_flow_side)) + logger.info( + f"Overwriting flow_side for {self} with {value.name}") else: - logger.debug( - "Set flow_side for %r to %s" % (self, self.verbose_flow_side)) - - @property - def verbose_flow_side(self): - if self.flow_side == 1: - return "VL" - if self.flow_side == -1: - return "RL" - return "UNKNOWN" + logger.debug(f"Set flow_side for {self} to {value.name}") def determine_flow_side(self): - """Check groups for hints of flow_side and returns flow_side if hints are definitely""" + """Check groups for hints of flow_side and returns flow_side if hints + are definitely. + + First the flow_direction and the type of the element + (generator/consumer) is checked for clear information. If no + information can be obtained the pattern matches are evaluated based on + the groups from IFC, that come from RelatingGroup assignment. + If there are mismatching information from flow_direction and patterns + the flow_side is set to unknown, otherwise it's set to supply_flow or + supply_flow. + """ vl = None rl = None + if self.parent.is_generator(): - if self.flow_direction == 1: + if self.flow_direction.name == "source": vl = True - elif self.flow_direction == -1: + elif self.flow_direction.name == "sink": rl = True elif self.parent.is_consumer(): - if self.flow_direction == 1: + if self.flow_direction.name == "source": rl = True - elif self.flow_direction == -1: + elif self.flow_direction.name == "sink": vl = True if not vl: vl = any(filter(self.vl_pattern.match, self.groups)) @@ -190,10 +203,10 @@ def determine_flow_side(self): rl = any(filter(self.rl_pattern.match, self.groups)) if vl and not rl: - return 1 + return FlowSide.supply_flow if rl and not vl: - return -1 - return 0 + return FlowSide.return_flow + return FlowSide.unknown class HVACProduct(ProductBased): @@ -312,8 +325,8 @@ def decide_inner_connections(self) -> Generator[DecisionBunch, None, None]: vl = port_dict[decision_vl.value] rl = port_dict[decision_rl.value] # set flow correct side - vl.flow_side = 1 - rl.flow_side = -1 + vl.flow_side = FlowSide.supply_flow + rl.flow_side = FlowSide.return_flow self.inner_connections.append((vl, rl)) def validate_ports(self): @@ -356,6 +369,9 @@ class HeatPump(HVACProduct): re.compile('W(ä|ae)rme.?pumpe', flags=re.IGNORECASE), ] + def is_generator(self): + return True + min_power = attribute.Attribute( description='Minimum power that HeatPump operates at.', unit=ureg.kilowatt, @@ -461,6 +477,10 @@ class CoolingTower(HVACProduct): re.compile('RKA', flags=re.IGNORECASE), ] + def is_consumer(self): + # TODO #733 check this + return True + min_power = attribute.Attribute( description='Minimum power that CoolingTower operates at.', unit=ureg.kilowatt, @@ -1293,6 +1313,9 @@ class AirTerminal(HVACProduct): unit=ureg.millimeter, ) + def is_consumer(self): + return True + class Medium(HVACProduct): # is deprecated? @@ -1313,6 +1336,9 @@ class CHP(HVACProduct): def expected_hvac_ports(self): return 2 + def is_generator(self): + return True + rated_power = attribute.Attribute( default_ps=('Pset_ElectricGeneratorTypeCommon', 'MaximumPowerOutput'), description="Rated power of CHP", diff --git a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/__init__.py b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/__init__.py index ac1767d968..6fde532b61 100644 --- a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/__init__.py +++ b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/__init__.py @@ -30,6 +30,7 @@ class PluginAixLib(Plugin): common.CreateElementsOnIfcTypes, hvac.ConnectElements, hvac.MakeGraph, + hvac.EnrichFlowDirection, hvac.ExpansionTanks, hvac.Reduce, hvac.DeadEnds, diff --git a/bim2sim/tasks/hvac/__init__.py b/bim2sim/tasks/hvac/__init__.py index d52f5c87ba..d3dbed899b 100644 --- a/bim2sim/tasks/hvac/__init__.py +++ b/bim2sim/tasks/hvac/__init__.py @@ -7,3 +7,4 @@ from .export import Export from .connect_elements import ConnectElements from .load_standardlibrary import LoadLibrariesStandardLibrary +from .enrich_flow_direction import EnrichFlowDirection diff --git a/bim2sim/tasks/hvac/connect_elements.py b/bim2sim/tasks/hvac/connect_elements.py index 0d48cf27af..961d5498b7 100644 --- a/bim2sim/tasks/hvac/connect_elements.py +++ b/bim2sim/tasks/hvac/connect_elements.py @@ -48,7 +48,7 @@ def run(self, elements: dict) -> dict: # Check ports self.logger.info("Checking ports of elements ...") - self.check_element_ports(elements) + self.remove_duplicate_ports(elements) # Make connections by relations self.logger.info("Connecting the relevant elements") self.logger.info(" - Connecting by relations ...") @@ -105,11 +105,38 @@ def run(self, elements: dict) -> dict: return elements, @staticmethod - def check_element_ports(elements: dict): - """Checks position of all ports for each element. + def remove_duplicate_ports(elements: dict): + """Checks position of all ports for each element and handles + overlapping ports. + + This method analyzes port positions within building elements + (e.g. pipes, fittings) and identifies overlapping ports that may + indicate data quality issues. When two ports of the same element + overlap and both connect to the same third port, they are merged into + a single bidirectional port. Args: - elements: dictionary of elements to be checked with GUID as key. + elements: Dictionary mapping GUIDs to element objects that should + be checked. + Each element must have a 'ports' attribute containing its + ports. + + Quality Checks: + - Warns if ports of the same element are closer than 1 unit + (atol=1) + - Identifies overlapping ports using numpy.allclose with rtol=1e-7 + + Port Merging: + If overlapping ports A and B are found that both connect to the + same port C: + - Port B is removed from the element + - Port A is set as bidirectional (SINKANDSOURCE) + - Port A becomes the flow master + - The change is logged for documentation + + WARNING: Poor quality of elements : Overlapping ports (port1 + and port2 @[x,y,z]) + INFO: Removing and set as SINKANDSOURCE. """ for ele in elements.values(): for port_a, port_b in itertools.combinations(ele.ports, 2): @@ -127,13 +154,14 @@ def check_element_ports(elements: dict): port not in [port_a, port_b]] if port_a in all_ports and port_b in all_ports and len( set(other_ports)) == 1: - # Both ports connected to same other port -> merge ports + # Both ports connected to same other port -> + # merge ports quality_logger.info( "Removing %s and set %s as SINKANDSOURCE.", port_b.ifc, port_a.ifc) ele.ports.remove(port_b) port_b.parent = None - port_a.flow_direction = 0 + port_a.flow_direction.value = 0 port_a.flow_master = True @staticmethod diff --git a/bim2sim/tasks/hvac/enrich_flow_direction.py b/bim2sim/tasks/hvac/enrich_flow_direction.py new file mode 100644 index 0000000000..6dbc5cd3f8 --- /dev/null +++ b/bim2sim/tasks/hvac/enrich_flow_direction.py @@ -0,0 +1,159 @@ +from bim2sim.elements.graphs.hvac_graph import HvacGraph +from bim2sim.tasks.base import ITask +import logging +from bim2sim.kernel.decision import BoolDecision, DecisionBunch + +logger = logging.getLogger(__name__) + + +class EnrichFlowDirection(ITask): + + reads = ('graph', ) + + def run(self, graph: HvacGraph): + self.set_flow_sides(graph) + + # Continue here #633 + def set_flow_sides(self, graph: HvacGraph): + """ Set flow sides for ports in HVAC graph based on known flow sides. + + This function iteratively sets flow sides for ports in the HVAC graph. + It uses a recursive method (`recurse_set_unknown_sides`) to determine + the flow side for each unset port. The function may prompt the user + for decisions in case of conflicts or unknown sides. + + Args: + graph: The HVAC graph. + + Yields: + DecisionBunch: A collection of decisions may be yielded during the + task. + """ + # TODO: needs testing! + # TODO: at least one master element required + accepted = [] + while True: + unset_port = None + for port in graph.get_nodes(): + if port.flow_side == 0 and graph.graph[port] \ + and port not in accepted: + unset_port = port + break + if unset_port: + side, visited, masters = self.recurse_set_unknown_sides( + unset_port) + if side in (-1, 1): + # apply suggestions + for port in visited: + port.flow_side = side + elif side == 0: + # TODO: ask user? + accepted.extend(visited) + elif masters: + # ask user to fix conflicts (and retry in next while loop) + for port in masters: + decision = BoolDecision( + "Use %r as VL (y) or RL (n)?" % port, + global_key= "Use_port_%s" % port.guid) + yield DecisionBunch([decision]) + use = decision.value + if use: + port.flow_side = 1 + else: + port.flow_side = -1 + else: + # can not be solved (no conflicting masters) + # TODO: ask user? + accepted.extend(visited) + else: + # done + logging.info("Flow_side set") + break + print('Test') + + # TODO not used yet + def recurse_set_side(self, port, side, known: dict = None, + raise_error=True): + """Recursive set flow_side to connected ports""" + if known is None: + known = {} + + # set side suggestion + is_known = port in known + current_side = known.get(port, port.flow_side) + if not is_known: + known[port] = side + elif is_known and current_side == side: + return known + else: + # conflict + if raise_error: + raise AssertionError("Conflicting flow_side in %r" % port) + else: + logger.error("Conflicting flow_side in %r", port) + known[port] = None + return known + + # call neighbours + for neigh in self.neighbors(port): + if (neigh.parent.is_consumer() or neigh.parent.is_generator()) \ + and port.parent is neigh.parent: + # switch flag over consumers / generators + self.recurse_set_side(neigh, -side, known, raise_error) + else: + self.recurse_set_side(neigh, side, known, raise_error) + + return known + + def recurse_set_unknown_sides(self, port, visited: list = None, + masters: list = None): + """Recursive checks neighbours flow_side. + :returns tuple of + common flow_side (None if conflict) + list of checked ports + list of ports on which flow_side s are determined""" + + if visited is None: + visited = [] + if masters is None: + masters = [] + + # mark as visited to prevent deadloops + visited.append(port) + + if port.flow_side in (-1, 1): + # use port with known flow_side as master + masters.append(port) + return port.flow_side, visited, masters + + # call neighbours + neighbour_sides = {} + for neigh in self.neighbors(port): + if neigh not in visited: + if (neigh.parent.is_consumer() or neigh.parent.is_generator()) \ + and port.parent is neigh.parent: + # switch flag over consumers / generators + side, _, _ = self.recurse_set_unknown_sides( + neigh, visited, masters) + side = -side + else: + side, _, _ = self.recurse_set_unknown_sides( + neigh, visited, masters) + neighbour_sides[neigh] = side + + sides = set(neighbour_sides.values()) + if not sides: + return port.flow_side, visited, masters + elif len(sides) == 1: + # all neighbours have same site + side = sides.pop() + return side, visited, masters + elif len(sides) == 2 and 0 in sides: + side = (sides - {0}).pop() + return side, visited, masters + else: + # conflict + return None, visited, masters + + + diff --git a/bim2sim/tasks/hvac/reduce.py b/bim2sim/tasks/hvac/reduce.py index 5f6bfe141b..c2b6267f2e 100644 --- a/bim2sim/tasks/hvac/reduce.py +++ b/bim2sim/tasks/hvac/reduce.py @@ -1,10 +1,7 @@ -import logging - from bim2sim.elements.aggregation.hvac_aggregations import UnderfloorHeating, \ Consumer, PipeStrand, ParallelPump, ConsumerHeatingDistributorModule, \ GeneratorOneFluid from bim2sim.elements.graphs.hvac_graph import HvacGraph -from bim2sim.kernel.decision import BoolDecision, DecisionBunch from bim2sim.tasks.base import ITask @@ -85,60 +82,3 @@ def run(self, graph: HvacGraph) -> (HvacGraph,): graph.plot(self.paths.export, ports=False, use_pyvis=True) return graph, - - @staticmethod - def set_flow_sides(graph: HvacGraph): - """ Set flow sides for ports in HVAC graph based on known flow sides. - - This function iteratively sets flow sides for ports in the HVAC graph. - It uses a recursive method (`recurse_set_unknown_sides`) to determine - the flow side for each unset port. The function may prompt the user - for decisions in case of conflicts or unknown sides. - - Args: - graph: The HVAC graph. - - Yields: - DecisionBunch: A collection of decisions may be yielded during the - task. - """ - # TODO: needs testing! - # TODO: at least one master element required - accepted = [] - while True: - unset_port = None - for port in graph.get_nodes(): - if port.flow_side == 0 and graph.graph[port] \ - and port not in accepted: - unset_port = port - break - if unset_port: - side, visited, masters = graph.recurse_set_unknown_sides( - unset_port) - if side in (-1, 1): - # apply suggestions - for port in visited: - port.flow_side = side - elif side == 0: - # TODO: ask user? - accepted.extend(visited) - elif masters: - # ask user to fix conflicts (and retry in next while loop) - for port in masters: - decision = BoolDecision( - "Use %r as VL (y) or RL (n)?" % port, - global_key= "Use_port_%s" % port.guid) - yield DecisionBunch([decision]) - use = decision.value - if use: - port.flow_side = 1 - else: - port.flow_side = -1 - else: - # can not be solved (no conflicting masters) - # TODO: ask user? - accepted.extend(visited) - else: - # done - logging.info("Flow_side set") - break diff --git a/bim2sim/utilities/types.py b/bim2sim/utilities/types.py index 4cd2f08926..609db7ab8b 100644 --- a/bim2sim/utilities/types.py +++ b/bim2sim/utilities/types.py @@ -65,3 +65,17 @@ class AttributeDataSource(Enum): manual_overwrite = auto() enrichment = auto() space_boundary = auto() + + +class FlowDirection(Enum): + """Used to describe the flow direction of ports.""" + sink_and_source = 0 + sink = -1 + source = 1 + unknown = 2 + + +class FlowSide(Enum): + supply_flow = 1 + return_flow = -1 + unknown = 0 diff --git a/docs/source/user-guide/PluginAixLib.md b/docs/source/user-guide/PluginAixLib.md index ec68a868b4..939292a507 100644 --- a/docs/source/user-guide/PluginAixLib.md +++ b/docs/source/user-guide/PluginAixLib.md @@ -205,6 +205,24 @@ taskDeadEnds --> taskLoadLibrariesAixLib taskLoadLibrariesAixLib --> taskExport ``` +### Port handling +#### Port creation +1. Ports get created during product creation as they are relation based +2. `ProductBased` base class has `get_ports()` function that is overwritten in `HVACProduct` +3. Ports are stored under self.ports in every HVACElement +4. IFC offers [according to schema](https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcsharedbldgserviceelements/lexical/ifcflowdirectionenum.htm) the `FlowDirection` enumeration, which can be either "SOURCE", "SINK", or "SOURCEANDSINK" but some IFC files also hold "SINKANDSOURCE". +5. Groups and port `flow_directions` are assigned via `HVACPort.ifc2args()` method +6. `flow_side` is assigned via `determine_flow_side()` function that uses patters matches for the names of "vorlauf" "rücklauf" etc. and the port `flow_direction` + +#### Port Connection +Task: `ConnectElements` +`check_element_ports()` +`connections_by_relation()` +`set_flow_sides()` +`recurse_set_side()` +`recurse_set_unknown_sides()` + + This figure is generated by the script template_mermaid.py (see [Visualization of bim2sim plugin structure](genVisPlugins)). ## How to create a project? @@ -229,3 +247,6 @@ This figure is generated by the script template_mermaid.py (see [Visualization o ### What kind of results exist? ### What programs/tools to use for further analysis? + + +# \ No newline at end of file From 5b5898593ba46be90ac2737b62580dce66e46fb2 Mon Sep 17 00:00:00 2001 From: David Paul Jansen Date: Tue, 26 Nov 2024 14:16:51 +0100 Subject: [PATCH 086/125] start adjust of flow side enrichment --- bim2sim/elements/graphs/hvac_graph.py | 5 +---- bim2sim/tasks/hvac/enrich_flow_direction.py | 13 ++++++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/bim2sim/elements/graphs/hvac_graph.py b/bim2sim/elements/graphs/hvac_graph.py index 38f85733f7..f002b89734 100644 --- a/bim2sim/elements/graphs/hvac_graph.py +++ b/bim2sim/elements/graphs/hvac_graph.py @@ -182,9 +182,6 @@ def get_connections(self): return [edge for edge in self.edges if not edge[0].parent is edge[1].parent] - # def get_nodes(self): - # """Returns list of nodes represented by graph""" - # return list(self.nodes) def plot(self, path: Path = None, ports: bool = False, dpi: int = 400, use_pyvis=False): @@ -293,7 +290,7 @@ def plot(self, path: Path = None, ports: bool = False, dpi: int = 400, node['label'] = node['label'].split('<')[1] except: pass - # TODO #633 use is_generator(), is_consumer() etc. + # TODO #733 use is_generator(), is_consumer() etc. node['label'] = node['label'].split('(ports')[0] if 'agg' in node['label'].lower(): node['label'] = node['label'].split('Agg0')[0] diff --git a/bim2sim/tasks/hvac/enrich_flow_direction.py b/bim2sim/tasks/hvac/enrich_flow_direction.py index 6dbc5cd3f8..6676fed1a0 100644 --- a/bim2sim/tasks/hvac/enrich_flow_direction.py +++ b/bim2sim/tasks/hvac/enrich_flow_direction.py @@ -2,6 +2,8 @@ from bim2sim.tasks.base import ITask import logging from bim2sim.kernel.decision import BoolDecision, DecisionBunch +from bim2sim.utilities.types import FlowSide + logger = logging.getLogger(__name__) @@ -11,9 +13,10 @@ class EnrichFlowDirection(ITask): reads = ('graph', ) def run(self, graph: HvacGraph): - self.set_flow_sides(graph) + yield from self.set_flow_sides(graph) + print('test') - # Continue here #633 + #Todo Continue here #733 def set_flow_sides(self, graph: HvacGraph): """ Set flow sides for ports in HVAC graph based on known flow sides. @@ -31,11 +34,12 @@ def set_flow_sides(self, graph: HvacGraph): """ # TODO: needs testing! # TODO: at least one master element required + print('test') accepted = [] while True: unset_port = None - for port in graph.get_nodes(): - if port.flow_side == 0 and graph.graph[port] \ + for port in list(graph.nodes): + if port.flow_side == FlowSide.unknown and graph.graph[port] \ and port not in accepted: unset_port = port break @@ -69,7 +73,6 @@ def set_flow_sides(self, graph: HvacGraph): # done logging.info("Flow_side set") break - print('Test') # TODO not used yet def recurse_set_side(self, port, side, known: dict = None, From 65c9e7d8c48f23be59eb384c4cae7167d61a890a Mon Sep 17 00:00:00 2001 From: David Paul Jansen Date: Fri, 29 Nov 2024 13:04:28 +0100 Subject: [PATCH 087/125] update test resources --- test/resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/resources b/test/resources index 9b3eab858d..fc9d0309f8 160000 --- a/test/resources +++ b/test/resources @@ -1 +1 @@ -Subproject commit 9b3eab858d4cb8457c9f147a188fb3fc4f7723a1 +Subproject commit fc9d0309f80cfff354b694b6faedd90cf21dfb34 From 45917bf72e3725f54f4da6ed744ee3201082604c Mon Sep 17 00:00:00 2001 From: David Paul Jansen Date: Thu, 15 May 2025 19:28:18 +0200 Subject: [PATCH 088/125] update modelica export tests & improve attribute function inheritance --- bim2sim/elements/hvac_elements.py | 8 ++- bim2sim/elements/mapping/attribute.py | 66 ++++++++++++++---- .../test/unit/kernel/task/test_export.py | 36 +++------- test/unit/elements/mapping/test_attribute.py | 13 +++- test/unit/tasks/__init__.py | 6 +- test/unit/tasks/hvac/test_export.py | 69 +++++++------------ 6 files changed, 109 insertions(+), 89 deletions(-) diff --git a/bim2sim/elements/hvac_elements.py b/bim2sim/elements/hvac_elements.py index d786a8f412..c3b5617160 100644 --- a/bim2sim/elements/hvac_elements.py +++ b/bim2sim/elements/hvac_elements.py @@ -923,8 +923,7 @@ def expected_hvac_ports(self): def is_consumer(self): return True - @cached_property - def shape(self): + def _get_radiator_shape(self, name): """returns topods shape of the radiator""" settings = ifcopenshell.geom.main.settings() settings.set(settings.USE_PYTHON_OPENCASCADE, True) @@ -933,6 +932,11 @@ def shape(self): settings.set(settings.INCLUDE_CURVES, True) return ifcopenshell.geom.create_shape(settings, self.ifc).geometry + shape = attribute.Attribute( + description="Returns topods shape of the radiator.", + functions=[_get_radiator_shape] + ) + number_of_panels = attribute.Attribute( description="Number of panels of heater", default_ps=('Pset_SpaceHeaterTypeCommon', 'NumberOfPanels'), diff --git a/bim2sim/elements/mapping/attribute.py b/bim2sim/elements/mapping/attribute.py index 242062531d..768b10b708 100644 --- a/bim2sim/elements/mapping/attribute.py +++ b/bim2sim/elements/mapping/attribute.py @@ -239,27 +239,63 @@ def get_from_patterns(bind, patterns, name): def get_from_functions(bind, functions: list, name: str): """Get value from functions. - First successful function calls return value is used. As we want to - allow to overwrite functions in inherited classes, we use - getattr(bind, func.__name__) to get the function from the bind. + First successful function call's return value is used. Functions can be + 1. Methods from the bind object's class hierarchy (use inheritance) + 2. Methods from external classes (call directly with bind as first arg) Args: - bind: the bind object - functions: a list of functions - name: the name of the attribute + bind: The bind object + functions: List of function objects + name: The attribute name to process """ value = None for func in functions: - func_inherited = getattr(bind, func.__name__) - try: - value = func_inherited(name) - except Exception as ex: - logger.error("Function '%s' of %s.%s raised %s", - func.__name__, bind, name, ex) - pass + # Check if the function's class is in the bind object's class + # hierarchy + if hasattr(func, '__qualname__') and '.' in func.__qualname__: + func_class_name = func.__qualname__.split('.')[0] + + # Check if the function's class is in the bind's class + # hierarchy + is_in_hierarchy = False + for cls in bind.__class__.__mro__: + if cls.__name__ == func_class_name: + is_in_hierarchy = True + break + + if is_in_hierarchy: + # Function is from bind's class hierarchy, + # use inheritance + try: + func_to_call = getattr(bind, func.__name__) + value = func_to_call(name) + except Exception as ex: + logger.error("Function '%s' of %s.%s raised %s", + func.__name__, bind, name, ex) + pass + else: + # Function is from an external class, call directly with + # bind as first arg + try: + value = func(bind, name) + except Exception as ex: + logger.error("Function '%s' of %s.%s raised %s", + func.__name__, bind, name, ex) + pass else: - if value is not None: - break + # Fallback for functions without __qualname__, use inheritance + try: + func_to_call = getattr(bind, func.__name__) + value = func_to_call(name) + except Exception as ex: + logger.error("Function '%s' of %s.%s raised %s", + func.__name__, bind, name, ex) + pass + + # Break the loop if we got a non-None value + if value is not None: + break + return value @staticmethod diff --git a/bim2sim/plugins/PluginHKESim/test/unit/kernel/task/test_export.py b/bim2sim/plugins/PluginHKESim/test/unit/kernel/task/test_export.py index 57667db6cd..68f4910397 100644 --- a/bim2sim/plugins/PluginHKESim/test/unit/kernel/task/test_export.py +++ b/bim2sim/plugins/PluginHKESim/test/unit/kernel/task/test_export.py @@ -19,9 +19,7 @@ def setUpClass(cls) -> None: def test_boiler_export(self): graph = self.helper.get_simple_boiler() - answers = () - reads = (self.loaded_libs, graph) - modelica_model = self.run_task(answers, reads) + modelica_model = self.run_export(graph) parameters = [('rated_power', 'Q_nom'), ('return_temperature', 'T_set')] expected_units = [ureg.watt, ureg.kelvin] @@ -30,9 +28,7 @@ def test_boiler_export(self): def test_radiator_export(self): graph = self.helper.get_simple_radiator() - answers = () - reads = (self.loaded_libs, graph) - modelica_model = self.run_task(answers, reads) + modelica_model = self.run_export(graph) parameters = [('rated_power', 'Q_flow_nominal'), ('return_temperature', 'Tout_max')] expected_units = [ureg.watt, ureg.kelvin] @@ -41,9 +37,7 @@ def test_radiator_export(self): def test_pump_export(self): graph, _ = self.helper.get_simple_pump() - answers = () - reads = (self.loaded_libs, graph) - modelica_model = self.run_task(answers, reads) + modelica_model = self.run_export(graph) parameters = [('rated_height', 'head_set'), ('rated_volume_flow', 'Vflow_set'), ('rated_power', 'P_nom')] @@ -59,9 +53,7 @@ def test_three_way_valve_export(self): def test_consumer_heating_distributor_module_export(self): # Set up the test graph and model graph = self.helper.get_simple_consumer_heating_distributor_module() - answers = () - reads = (self.loaded_libs, graph) - modelica_model = self.run_task(answers, reads) + modelica_model = self.run_export(graph) # Get the ConsumerHeatingDistributorModule element element = next(element for element in graph.elements if isinstance(element, @@ -97,9 +89,7 @@ def test_consumer_heating_distributor_module_export(self): def test_boiler_module_export(self): graph = self.helper.get_simple_generator_one_fluid() - answers = () - reads = (self.loaded_libs, graph) - modelica_model = self.run_task(answers, reads) + modelica_model = self.run_export(graph) element = graph.elements[0] rated_power = element.rated_power.to(ureg.watt).magnitude flow_temp = element.flow_temperature.to(ureg.kelvin).magnitude @@ -114,9 +104,7 @@ def test_boiler_module_export(self): def test_heat_pump_export(self): graph = self.helper.get_simple_heat_pump() - answers = () - reads = (self.loaded_libs, graph) - modelica_model = self.run_task(answers, reads) + modelica_model = self.run_export(graph) parameters = [('rated_power', 'Qcon_nom')] expected_units = [ureg.watt] self.run_parameter_test(graph, modelica_model, parameters, @@ -124,9 +112,7 @@ def test_heat_pump_export(self): def test_chiller_export(self): graph = self.helper.get_simple_chiller() - answers = () - reads = (self.loaded_libs, graph) - modelica_model = self.run_task(answers, reads) + modelica_model = self.run_export(graph) parameters = [('rated_power', 'Qev_nom'), ('nominal_COP', 'EER_nom')] expected_units = [ureg.watt, ureg.dimensionless] @@ -135,9 +121,7 @@ def test_chiller_export(self): def test_chp_export(self): graph = self.helper.get_simple_chp() - answers = () - reads = (self.loaded_libs, graph) - modelica_model = self.run_task(answers, reads) + modelica_model = self.run_export(graph) parameters = [('rated_power', 'P_nom')] expected_units = [ureg.watt] self.run_parameter_test(graph, modelica_model, parameters, @@ -145,9 +129,7 @@ def test_chp_export(self): def test_cooling_tower_export(self): graph = self.helper.get_simple_cooling_tower() - answers = () - reads = (self.loaded_libs, graph) - modelica_model = self.run_task(answers, reads) + modelica_model = self.run_export(graph) parameters = [('rated_power', 'Qflow_nom')] expected_units = [ureg.watt] self.run_parameter_test(graph, modelica_model, parameters, diff --git a/test/unit/elements/mapping/test_attribute.py b/test/unit/elements/mapping/test_attribute.py index 565810c0f8..68fa77e7b1 100644 --- a/test/unit/elements/mapping/test_attribute.py +++ b/test/unit/elements/mapping/test_attribute.py @@ -9,7 +9,6 @@ from test.unit.elements.helper import SetupHelperHVAC from bim2sim.utilities.types import AttributeDataSource - class TestElement(ProductBased): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -32,10 +31,19 @@ def _func1(self, name): attr7 = Attribute(attr_type=bool) +class TestElementOther(ProductBased): + def _func2(self, name): + return 45 + + class TestElementInherited(TestElement): def _func1(self, name): return 43 + attr6 = Attribute( + functions=[TestElementOther._func2] + ) + class TestAttribute(unittest.TestCase): @@ -181,6 +189,9 @@ def test_attribute_reset(self): def test_from_function_inheritance(self): self.assertEqual(43, self.subject_inherited.attr5) + def test_function_from_another_element(self): + self.assertEqual(45, self.subject_inherited.attr6) + class TestAttributeDecisions(unittest.TestCase): diff --git a/test/unit/tasks/__init__.py b/test/unit/tasks/__init__.py index 6d266ec739..2f15dfb359 100644 --- a/test/unit/tasks/__init__.py +++ b/test/unit/tasks/__init__.py @@ -1,3 +1,4 @@ +import inspect import tempfile import unittest from typing import Union @@ -54,7 +55,10 @@ def tearDown(self) -> None: self.playground.sim_settings.load_default_settings() def run_task(self, answers, reads): - return DebugDecisionHandler(answers).handle(self.test_task.run(*reads)) + if inspect.isgeneratorfunction(self.test_task.run): + return DebugDecisionHandler(answers).handle(self.test_task.run(*reads)) + else: + return self.test_task.run(*reads) @classmethod def simSettingsClass(cls) -> Union[BaseSimSettings, None]: diff --git a/test/unit/tasks/hvac/test_export.py b/test/unit/tasks/hvac/test_export.py index 4bd041e9e1..0bbc983ff8 100644 --- a/test/unit/tasks/hvac/test_export.py +++ b/test/unit/tasks/hvac/test_export.py @@ -40,31 +40,19 @@ def setUpClass(cls) -> None: # Instantiate modelica create task and set required values via mocks cls.create_modelica_model_task = CreateModelicaModel(cls.playground) cls.create_modelica_model_task.prj_name = 'TestStandardLibrary' - cls.create_modelica_model_task.paths = paths - - # Instantiate export task and set required values via mocks - cls.export_task = Export(cls.playground) - cls.export_task.prj_name = 'TestStandardLibrary' - cls.export_task.paths = paths - - cls.helper = SetupHelperHVAC() - - def setUp(self) -> None: - # Set export path to temporary path - self.export_path = tempfile.TemporaryDirectory(prefix='bim2sim') - - self.export_task.paths.export = self.export_path.name - - def tearDown(self) -> None: - self.helper.reset() + cls.create_modelica_model_task.paths = cls.test_task.paths def run_export(self, graph, answers=()): - (export_elements, connections, cons_heat_ports_conv, - cons_heat_ports_rad) = DebugDecisionHandler(answers).handle( + """Method to reduce redundant code. + + We need an extra method here, as the normal run_task method just runs + one task but in this case we need to run CreateModelicaModel model task + first and then Export. We use Export as testTask. + """ + reads_from_modelica_creation = DebugDecisionHandler(answers).handle( self.create_modelica_model_task.run(self.loaded_libs, graph)) - return self.export_task.run( - export_elements, connections, cons_heat_ports_conv, - cons_heat_ports_rad) + modelica_model = self.run_task(answers, reads_from_modelica_creation) + return modelica_model def run_parameter_test(self, graph: HvacGraph, modelica_model: list, parameters: List[Tuple[str, str]], @@ -145,10 +133,10 @@ def test_missing_required_parameter(self): """ Test if an AssertionError is raised if a required parameter is not provided.""" graph, pipe = self.helper.get_simple_pipe() - answers = () with self.assertRaises(AssertionError): - DebugDecisionHandler(answers).handle( - self.test_task.run(self.loaded_libs, graph)) + self.run_export(graph) + # DebugDecisionHandler(answers).handle( + # self.test_task.run(self.loaded_libs, graph)) def test_check_function(self): """ Test if the check function for a parameter works. The exported @@ -159,14 +147,15 @@ def test_check_function(self): pipe.diameter = -1 * ureg.meter answers = () # reads = (self.loaded_libs, graph) + modelica_model = self.run_export(graph) # modelica_model = self.run_task(answers, reads) - (export_elements, connections, cons_heat_ports_conv, - cons_heat_ports_rad) = DebugDecisionHandler( - answers).handle( - self.create_modelica_model_task.run(self.loaded_libs, graph)) - modelica_model = self.export_task.run( - export_elements, connections, cons_heat_ports_conv, - cons_heat_ports_rad) + # (export_elements, connections, cons_heat_ports_conv, + # cons_heat_ports_rad) = DebugDecisionHandler( + # answers).handle( + # self.create_modelica_model_task.run(self.loaded_libs, graph)) + # modelica_model = self.export_task.run( + # export_elements, connections, cons_heat_ports_conv, + # cons_heat_ports_rad) self.assertIsNone( modelica_model[0].modelica_elements[0].parameters[ 'diameter'].value) @@ -176,9 +165,8 @@ def test_check_function(self): def test_pipe_export(self): graph, pipe = self.helper.get_simple_pipe() pipe.diameter = 0.2 * ureg.meter - answers = () - reads = (self.loaded_libs, graph) - modelica_model = self.run_task(answers, reads) + modelica_model = self.run_export(graph) + # Test for expected and exported parameters parameters = [('diameter', 'diameter'), ('length', 'length')] expected_units = [ureg.m, ureg.m] @@ -188,8 +176,7 @@ def test_pipe_export(self): def test_valve_export(self): graph = self.helper.get_simple_valve() answers = (1 * ureg.kg / ureg.h,) - reads = (self.loaded_libs, graph) - modelica_model = self.run_task(answers, reads) + modelica_model = self.run_export(graph, answers) parameters = [('nominal_pressure_difference', 'dp_nominal'), ('nominal_mass_flow_rate', 'm_flow_nominal')] expected_units = [ureg.bar, ureg.kg / ureg.s] @@ -198,9 +185,7 @@ def test_valve_export(self): def test_junction_export(self): graph = self.helper.get_simple_junction() - answers = () - reads = (self.loaded_libs, graph) - modelica_model = self.run_task(answers, reads) + modelica_model = self.run_export(graph) # Test for expected and exported parameters parameters = [('volume', 'V')] expected_units = [ureg.m ** 3] @@ -209,9 +194,7 @@ def test_junction_export(self): def test_storage_export(self): graph = self.helper.get_simple_storage() - answers = () - reads = (self.loaded_libs, graph) - modelica_model = self.run_task(answers, reads) + modelica_model = self.run_export(graph) # Test for expected and exported parameters parameters = [('volume', 'V')] expected_units = [ureg.m ** 3] From eaaf71fc710440ea225ee6ffcecb4efab187dfff Mon Sep 17 00:00:00 2001 From: David Paul Jansen Date: Thu, 15 May 2025 19:44:19 +0200 Subject: [PATCH 089/125] adjust tests for aixlib to two stage modelica export --- .../test/unit/tasks/test_export.py | 33 +++++-------------- 1 file changed, 9 insertions(+), 24 deletions(-) diff --git a/bim2sim/plugins/PluginAixLib/test/unit/tasks/test_export.py b/bim2sim/plugins/PluginAixLib/test/unit/tasks/test_export.py index 8469c124dd..acb70fb191 100644 --- a/bim2sim/plugins/PluginAixLib/test/unit/tasks/test_export.py +++ b/bim2sim/plugins/PluginAixLib/test/unit/tasks/test_export.py @@ -23,9 +23,7 @@ def test_boiler_export(self): def test_radiator_export(self): graph = self.helper.get_simple_radiator() - answers = () - reads = (self.loaded_libs, graph) - modelica_model = self.run_task(answers, reads) + modelica_model = self.run_export(graph) parameters = [('rated_power', 'Q_flow_nominal'), ('flow_temperature', 'T_a_nominal'), ('return_temperature', 'T_b_nominal')] @@ -35,9 +33,7 @@ def test_radiator_export(self): def test_pump_export(self): graph, _ = self.helper.get_simple_pump() - answers = () - reads = (self.loaded_libs, graph) - modelica_model = self.run_task(answers, reads) + modelica_model = self.run_export(graph) element = graph.elements[0] V_flow = element.rated_volume_flow.to(ureg.m ** 3 / ureg.s).magnitude dp = element.rated_pressure_difference.to(ureg.pascal).magnitude @@ -49,9 +45,7 @@ def test_pump_export(self): def test_consumer_export(self): graph, _ = self.helper.get_simple_consumer() - answers = () - reads = (self.loaded_libs, graph) - modelica_model = self.run_task(answers, reads) + modelica_model = self.run_export(graph) parameters = [('rated_power', 'Q_flow_fixed')] expected_units = [ureg.watt] self.run_parameter_test(graph, modelica_model, parameters, @@ -72,8 +66,7 @@ def test_consumer_distributor_export(self): def test_three_way_valve_export(self): graph = self.helper.get_simple_three_way_valve() answers = (1 * ureg.kg / ureg.s,) - reads = (self.loaded_libs, graph) - modelica_model = self.run_task(answers, reads) + modelica_model = self.run_export(graph, answers) parameters = [('nominal_pressure_difference', 'dpValve_nominal'), ('nominal_mass_flow_rate', 'm_flow_nominal')] expected_units = [ureg.pascal, ureg.kg / ureg.s] @@ -82,9 +75,7 @@ def test_three_way_valve_export(self): def test_heat_pump_export(self): graph = self.helper.get_simple_heat_pump() - answers = () - reads = (self.loaded_libs, graph) - modelica_model = self.run_task(answers, reads) + modelica_model = self.run_export(graph) parameters = [('rated_power', 'Q_useNominal')] expected_units = [ureg.watt] self.run_parameter_test(graph, modelica_model, parameters, @@ -92,9 +83,7 @@ def test_heat_pump_export(self): def test_chiller_export(self): graph = self.helper.get_simple_chiller() - answers = () - reads = (self.loaded_libs, graph) - modelica_model = self.run_task(answers, reads) + modelica_model = self.run_export(graph) parameters = [('rated_power', 'Q_useNominal')] expected_units = [ureg.watt] self.run_parameter_test(graph, modelica_model, parameters, @@ -106,9 +95,7 @@ def test_consumer_CHP_export(self): def test_storage_export(self): graph = self.helper.get_simple_storage() - answers = () - reads = (self.loaded_libs, graph) - modelica_model = self.run_task(answers, reads) + modelica_model = self.run_export(graph) parameters = [('height', 'hTank'), ('diameter', 'dTank')] expected_units = [ureg.meter, ureg.meter] element = graph.elements[0] @@ -123,13 +110,11 @@ def test_storage_export(self): def test_radiator_export_with_heat_ports(self): """Test export of two radiators, focus on correct heat port export.""" graph = self.helper.get_two_radiators() - answers = () # export outer heat ports - self.export_task.playground.sim_settings.outer_heat_ports = True + self.test_task.playground.sim_settings.outer_heat_ports = True - reads = (self.loaded_libs, graph) - modelica_model = self.run_task(answers, reads) + modelica_model = self.run_export(graph) # ToDo: as elements are unsorted, testing with names is not robust # connections_heat_ports_conv_expected = [ # ('heatPortOuterCon[1]', From bd96aa4ee1615c8b893919a44c4841022afe8a86 Mon Sep 17 00:00:00 2001 From: David Paul Jansen Date: Thu, 15 May 2025 20:05:19 +0200 Subject: [PATCH 090/125] adjust weather_file_path to new split between EP and TEASER Added deprecated settings to make debugging easier --- .../e4_simple_rotated_project_energyplus.py | 2 +- .../test/unit/task/test_weather.py | 4 +-- .../examples/e5_serialize_teaser_prj.py | 4 +-- .../test/unit/task/test_weather.py | 6 ++-- test/unit/test_sim_settings.py | 34 +++++++++++++++++++ 5 files changed, 42 insertions(+), 8 deletions(-) diff --git a/bim2sim/plugins/PluginEnergyPlus/bim2sim_energyplus/examples/e4_simple_rotated_project_energyplus.py b/bim2sim/plugins/PluginEnergyPlus/bim2sim_energyplus/examples/e4_simple_rotated_project_energyplus.py index 153656c918..42713683f4 100644 --- a/bim2sim/plugins/PluginEnergyPlus/bim2sim_energyplus/examples/e4_simple_rotated_project_energyplus.py +++ b/bim2sim/plugins/PluginEnergyPlus/bim2sim_energyplus/examples/e4_simple_rotated_project_energyplus.py @@ -31,7 +31,7 @@ def run_example_4(): project = Project.create(project_path, ifc_paths, 'energyplus') # set weather file data - project.sim_settings.weather_file_path = ( + project.sim_settings.weather_file_path_ep = ( Path(bim2sim.__file__).parent.parent / 'test/resources/weather_files/DEU_NW_Aachen.105010_TMYx.epw') # Set the install path to your EnergyPlus installation according to your diff --git a/bim2sim/plugins/PluginEnergyPlus/test/unit/task/test_weather.py b/bim2sim/plugins/PluginEnergyPlus/test/unit/task/test_weather.py index 5fb3cee14e..cc9dc293a8 100644 --- a/bim2sim/plugins/PluginEnergyPlus/test/unit/task/test_weather.py +++ b/bim2sim/plugins/PluginEnergyPlus/test/unit/task/test_weather.py @@ -46,9 +46,9 @@ def test_weather_energyplus(self): handler = DebugDecisionHandler([]) handler.handle(self.project.run(cleanup=False)) try: - weather_file = self.project.playground.state['weather_file'] + weather_file = self.project.playground.state['weather_file_ep'] except Exception: raise ValueError(f"No weather file set through Weather task. An" f"error occurred.") self.assertEquals(weather_file, - self.project.sim_settings.weather_file_path) + self.project.sim_settings.weather_file_path_ep) diff --git a/bim2sim/plugins/PluginTEASER/bim2sim_teaser/examples/e5_serialize_teaser_prj.py b/bim2sim/plugins/PluginTEASER/bim2sim_teaser/examples/e5_serialize_teaser_prj.py index 05a830ad09..36226b4f74 100644 --- a/bim2sim/plugins/PluginTEASER/bim2sim_teaser/examples/e5_serialize_teaser_prj.py +++ b/bim2sim/plugins/PluginTEASER/bim2sim_teaser/examples/e5_serialize_teaser_prj.py @@ -47,12 +47,12 @@ def run_serialize_teaser_project_example(): # overwrite existing layer structures and materials based on templates project.sim_settings.layers_and_materials = LOD.low # specify templates for the layer and material overwrite - project.sim_settings.construction_class_walls = 'heavy' + project.sim_settings.construction_class_walls = 'iwu_heavy' project.sim_settings.construction_class_windows = \ 'Alu- oder Stahlfenster, Waermeschutzverglasung, zweifach' # set weather file data - project.sim_settings.weather_file_path = ( + project.sim_settings.weather_file_path_modelica = ( Path(bim2sim.__file__).parent.parent / 'test/resources/weather_files/DEU_NW_Aachen.105010_TMYx.mos') # Run a simulation directly with dymola after model creation diff --git a/bim2sim/plugins/PluginTEASER/test/unit/task/test_weather.py b/bim2sim/plugins/PluginTEASER/test/unit/task/test_weather.py index e418f4130e..75318a701d 100644 --- a/bim2sim/plugins/PluginTEASER/test/unit/task/test_weather.py +++ b/bim2sim/plugins/PluginTEASER/test/unit/task/test_weather.py @@ -41,14 +41,14 @@ def test_weather_modelica(self): IFCDomain.arch: test_rsrc_path / 'arch/ifc/AC20-FZK-Haus.ifc'} self.project = Project.create(self.test_dir.name, ifc_paths, plugin=PluginWeatherDummyTEASER) - self.project.sim_settings.weather_file_path = ( + self.project.sim_settings.weather_file_path_modelica = ( test_rsrc_path / 'weather_files/DEU_NW_Aachen.105010_TMYx.mos') handler = DebugDecisionHandler([]) handler.handle(self.project.run(cleanup=False)) try: - weather_file = self.project.playground.state['weather_file'] + weather_file = self.project.playground.state['weather_file_modelica'] except Exception: raise ValueError(f"No weather file set through Weather task. An" f"error occurred.") self.assertEquals(weather_file, - self.project.sim_settings.weather_file_path) + self.project.sim_settings.weather_file_path_modelica) diff --git a/test/unit/test_sim_settings.py b/test/unit/test_sim_settings.py index ba1ce68847..eb39ca6443 100644 --- a/test/unit/test_sim_settings.py +++ b/test/unit/test_sim_settings.py @@ -130,3 +130,37 @@ def test_new_sim_settings_creation(self): new_sim_setting.new_setting_lod = LOD.full self.assertEqual( new_sim_setting.new_setting_lod, LOD.full) + + def test_deprecated_setting(self): + """Test if deprecated settings raise appropriate error messages""" + # Create a standard settings instance + standard_wf = sim_settings.BaseSimSettings() + + # Test accessing the deprecated setting + with self.assertRaises(AttributeError) as context: + value = standard_wf.weather_file_path + + # Check that the error message contains the expected information + error_message = str(context.exception) + self.assertIn("weather_file_path", error_message) + self.assertIn("deprecated", error_message.lower()) + self.assertIn("weather_file_path_ep", error_message) + self.assertIn("weather_file_path_modelica", error_message) + + # Test setting the deprecated setting + test_path = Path(__file__) # Just a valid path for testing + with self.assertRaises(AttributeError) as context: + standard_wf.weather_file_path = test_path + + # Check that the error message contains the expected information + error_message = str(context.exception) + self.assertIn("weather_file_path", error_message) + self.assertIn("deprecated", error_message.lower()) + self.assertIn("weather_file_path_ep", error_message) + self.assertIn("weather_file_path_modelica", error_message) + + # Test that we can still set and access the new settings + standard_wf.weather_file_path_ep = test_path + standard_wf.weather_file_path_modelica = test_path + self.assertEqual(standard_wf.weather_file_path_ep, test_path) + self.assertEqual(standard_wf.weather_file_path_modelica, test_path) From 11e7a0bafe1d0a7b207bf83c3b95e238d25cb789 Mon Sep 17 00:00:00 2001 From: David Paul Jansen Date: Thu, 15 May 2025 20:06:27 +0200 Subject: [PATCH 091/125] Added deprecated settings to make debugging easier --- bim2sim/sim_settings.py | 48 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/bim2sim/sim_settings.py b/bim2sim/sim_settings.py index 97e0836c1c..99e204e8e2 100644 --- a/bim2sim/sim_settings.py +++ b/bim2sim/sim_settings.py @@ -362,6 +362,41 @@ def check_value(self, bound_simulation_settings, value): return True +class DeprecatedSetting(Setting): + """A setting that raises an error when accessed as it is deprecated.""" + + def __init__(self, replacement_settings, *args, **kwargs): + """ + Initialize a deprecated setting. + + Args: + replacement_settings: List of setting names that replace this + setting + *args, **kwargs: Arguments passed to the parent class + """ + super().__init__(*args, **kwargs) + self.replacement_settings = replacement_settings + + def initialize(self, manager): + """Sets the error message after initialization when name is known.""" + super().initialize(manager) + self.error_message = ( + f"The setting '{self.name}' is deprecated and no" + f" longer supported. Please use one of " + f" {', '.join(self.replacement_settings)} instead.") + + def __get__(self, bound_simulation_settings, owner): + """Raise an error when the deprecated setting is accessed.""" + if bound_simulation_settings is None: + return self + + raise AttributeError(self.error_message) + + def __set__(self, bound_simulation_settings, value): + """Raise an error when the deprecated setting is set.""" + raise AttributeError(self.error_message) + + class BaseSimSettings(metaclass=AutoSettingNameMeta): """Specification of basic bim2sim simulation settings which are common for all simulations""" @@ -512,6 +547,7 @@ def check_mandatory(self): for_frontend=True ) + # TODO fix mandatory for EP and MODELICA seperated weather files weather_file_path_modelica = PathSetting( default=None, @@ -536,6 +572,18 @@ def check_mandatory(self): for_frontend=True, mandatory=False ) + + weather_file_path = DeprecatedSetting( + replacement_settings=["weather_file_path_ep", + "weather_file_path_modelica"], + default=None, + description='DEPRECATED: Use weather_file_path_ep or ' + 'weather_file_path_modelica instead. ' + + 'Path to the weather file that should be used for the' + ' simulation.', + for_frontend=False + ) + building_rotation_overwrite = NumberSetting( default=0, description='Overwrite the (clockwise) building rotation angle in ' From 67ab0be4ab3e4b2ab627e0ae7df3766448d176b7 Mon Sep 17 00:00:00 2001 From: David Paul Jansen Date: Thu, 15 May 2025 20:15:10 +0200 Subject: [PATCH 092/125] add weather file for LCA tests --- bim2sim/plugins/PluginLCA/test/integration/test_lca.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bim2sim/plugins/PluginLCA/test/integration/test_lca.py b/bim2sim/plugins/PluginLCA/test/integration/test_lca.py index d41a49512e..c85eef773f 100644 --- a/bim2sim/plugins/PluginLCA/test/integration/test_lca.py +++ b/bim2sim/plugins/PluginLCA/test/integration/test_lca.py @@ -9,6 +9,11 @@ class IntegrationBaseLCA(IntegrationBase): def model_domain_path(self) -> str: return 'arch' + def set_test_weather_file(self): + """Set the weather file path.""" + self.project.sim_settings.weather_file_path_modelica = ( + self.test_resources_path() / + 'weather_files/DEU_NW_Aachen.105010_TMYx.mos') class TestIntegrationLCA(IntegrationBaseLCA, unittest.TestCase): def test_run_kitinstitute_lca(self): From c5b5c33daac8a01ce773342d5906ecfcf85d01c0 Mon Sep 17 00:00:00 2001 From: David Paul Jansen Date: Thu, 15 May 2025 20:15:19 +0200 Subject: [PATCH 093/125] add weather file for LCA tests --- bim2sim/plugins/PluginLCA/test/integration/test_lca.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bim2sim/plugins/PluginLCA/test/integration/test_lca.py b/bim2sim/plugins/PluginLCA/test/integration/test_lca.py index c85eef773f..125531b1dc 100644 --- a/bim2sim/plugins/PluginLCA/test/integration/test_lca.py +++ b/bim2sim/plugins/PluginLCA/test/integration/test_lca.py @@ -15,6 +15,7 @@ def set_test_weather_file(self): self.test_resources_path() / 'weather_files/DEU_NW_Aachen.105010_TMYx.mos') + class TestIntegrationLCA(IntegrationBaseLCA, unittest.TestCase): def test_run_kitinstitute_lca(self): """Run project with AC20-Institute-Var-2..ifc""" From bfea16a0350ecd947f3c9d89a64c515dcf811d4d Mon Sep 17 00:00:00 2001 From: David Paul Jansen Date: Thu, 15 May 2025 20:18:06 +0200 Subject: [PATCH 094/125] update EP tests weatherfile sim_setting --- bim2sim/plugins/PluginEnergyPlus/test/unit/task/test_weather.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bim2sim/plugins/PluginEnergyPlus/test/unit/task/test_weather.py b/bim2sim/plugins/PluginEnergyPlus/test/unit/task/test_weather.py index cc9dc293a8..1edcc17a4a 100644 --- a/bim2sim/plugins/PluginEnergyPlus/test/unit/task/test_weather.py +++ b/bim2sim/plugins/PluginEnergyPlus/test/unit/task/test_weather.py @@ -41,7 +41,7 @@ def test_weather_energyplus(self): IFCDomain.arch: test_rsrc_path / 'arch/ifc/AC20-FZK-Haus.ifc'} self.project = Project.create(self.test_dir.name, ifc_paths, plugin=PluginWeatherDummyEP) - self.project.sim_settings.weather_file_path = ( + self.project.sim_settings.weather_file_path_ep = ( test_rsrc_path / 'weather_files/DEU_NW_Aachen.105010_TMYx.epw') handler = DebugDecisionHandler([]) handler.handle(self.project.run(cleanup=False)) From f5f4c2d489f2983bbb3ce75793eba1df99ec056c Mon Sep 17 00:00:00 2001 From: Hoepp J <155448650+HoeppJ@users.noreply.github.com> Date: Tue, 24 Jun 2025 14:15:09 +0200 Subject: [PATCH 095/125] Added documentation and ToDos --- .../templates/modelica/tmplSpawnBuilding.txt | 2 +- .../examples/e1_simple_project_bps_spawn.py | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/bim2sim/assets/templates/modelica/tmplSpawnBuilding.txt b/bim2sim/assets/templates/modelica/tmplSpawnBuilding.txt index af475658af..9b2de58d3d 100644 --- a/bim2sim/assets/templates/modelica/tmplSpawnBuilding.txt +++ b/bim2sim/assets/templates/modelica/tmplSpawnBuilding.txt @@ -11,7 +11,7 @@ model ${model_name} "${model_comment}" final parameter Modelica.Units.SI.MassFlowRate mOut_flow[nZones]=0.3/3600 * fill(1, nZones) * 1.2 "Outside air infiltration for each exterior room"; - + // TODO Strings must be in "" in export parameter String zoneNames[nZones] = ${ep_zone_lists} "Name of the thermal zone as specified in the EnergyPlus input"; diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e1_simple_project_bps_spawn.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e1_simple_project_bps_spawn.py index d017680e3a..7de4fae7da 100644 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e1_simple_project_bps_spawn.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e1_simple_project_bps_spawn.py @@ -4,27 +4,30 @@ import bim2sim from bim2sim import Project, ConsoleDecisionHandler from bim2sim.kernel.decision.decisionhandler import DebugDecisionHandler -from bim2sim.kernel.log import default_logging_setup +# from bim2sim.kernel.log import default_logging_setup from bim2sim.utilities.types import IFCDomain def run_example_spawn_1(): """Export a SpawnOfEnergyPlus simulation model. - This example exports a SpawnOfEnergyPlus Co-Simulation model. The HVAC model is generated via the PluginAixLib using the AixLib Modelica library. The building model is generated using PluginEnergyPlus. The used IFC file holds both, HVAC and building in one file. + + Currently required versions: + EnergyPlus >= 9.6.0 + AixLib branch: issue1147_GenericBoiler (future: main) + Modelica Buildings: 11.1.0 """ # Create the default logging to for quality log and bim2sim main log ( # see logging documentation for more information - default_logging_setup() + # default_logging_setup() # Create a temp directory for the project, feel free to use a "normal" # directory - project_path = Path( - tempfile.TemporaryDirectory(prefix='bim2sim_example_spawn').name) + project_path = Path(r"D:\02_Daten\Testing\Spawn\example1") # Set the ifc path to use and define which domain the IFC belongs to ifc_paths = { @@ -40,9 +43,8 @@ def run_example_spawn_1(): # Set the install path to your EnergyPlus installation according to your # system requirements - project.sim_settings.ep_install_path = Path( - 'C:/EnergyPlusV9-6-0/') - project.sim_settings.ep_version = "9-6-0" + project.sim_settings.ep_install_path = Path("D:\99_Programme\EnergyPlus") + project.sim_settings.ep_version = "9-4-0" project.sim_settings.weather_file_path_ep = ( Path(bim2sim.__file__).parent.parent / 'test/resources/weather_files/DEU_NW_Aachen.105010_TMYx.epw') From 1a884ad289c10769cd9d8f8d8cf51bb6930ac137 Mon Sep 17 00:00:00 2001 From: Hoepp J <155448650+HoeppJ@users.noreply.github.com> Date: Tue, 24 Jun 2025 14:15:42 +0200 Subject: [PATCH 096/125] Changed AixLib path of boiler --- bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py index 074af4260d..6e1c194116 100644 --- a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py +++ b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py @@ -311,7 +311,7 @@ def get_port_name(self, port): class BoilerAggregation(AixLib): # TODO: the model does not exists in AiLib """Modelica AixLib representation of the GeneratorOneFluid aggregation.""" - path = "AixLib.Systems.ModularEnergySystems.ModularBoiler.ModularBoiler" + path = "AixLib.Systems.ScalableGenerationModules.ScalableBoiler.ScalableBoiler" represents = [hvac_aggregations.GeneratorOneFluid] def __init__(self, element): From e1c217f0a3d8bd8cdee0645415cb82a0977e64d4 Mon Sep 17 00:00:00 2001 From: Hoepp J <155448650+HoeppJ@users.noreply.github.com> Date: Wed, 25 Jun 2025 14:51:37 +0200 Subject: [PATCH 097/125] adapt parameter names to aixlib changes --- .../plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py index 6e1c194116..7e43eb1f61 100644 --- a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py +++ b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py @@ -320,11 +320,11 @@ def __init__(self, element): unit=None, required=False, value=MEDIUM_WATER) - self._set_parameter(name='hasPump', + self._set_parameter(name='hasPum', unit=None, required=False, attributes=['has_pump']) - self._set_parameter(name='hasFeedback', + self._set_parameter(name='hasFedBac', unit=None, required=False, attributes=['has_bypass']) From e5be962d4c782f03fda438523be0a890d0bb1958 Mon Sep 17 00:00:00 2001 From: Hoepp J <155448650+HoeppJ@users.noreply.github.com> Date: Thu, 26 Jun 2025 15:52:11 +0200 Subject: [PATCH 098/125] bugfix --- .../PluginAixLib/bim2sim_aixlib/models/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py index 15a1690a2b..0881e34584 100644 --- a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py +++ b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py @@ -283,9 +283,9 @@ def __init__(self, value=10000 * n_consumers) def get_port_name(self, port): - if port.verbose_flow_direction == 'SINK': + if port.flow_direction.name == 'sink': return 'port_a' - if port.verbose_flow_direction == 'SOURCE': + if port.flow_direction.name == 'source': return 'port_b' else: return super().get_port_name(port) @@ -340,9 +340,9 @@ def __init__(self, element): value=10000) def get_port_name(self, port): - if port.verbose_flow_direction == 'SINK': + if port.flow_direction.name == 'sink': return 'port_a' - if port.verbose_flow_direction == 'SOURCE': + if port.flow_direction.name == 'source': return 'port_b' else: return super().get_port_name(port) From 407c97fde142ec8ea0b0eed8ad11eb15ff20181a Mon Sep 17 00:00:00 2001 From: Hoepp J <155448650+HoeppJ@users.noreply.github.com> Date: Fri, 27 Jun 2025 14:39:58 +0200 Subject: [PATCH 099/125] bugfixes for flow direction variables --- bim2sim/export/modelica/standardlibrary.py | 4 ++-- .../bim2sim_aixlib/models/__init__.py | 24 +++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/bim2sim/export/modelica/standardlibrary.py b/bim2sim/export/modelica/standardlibrary.py index 6e8db56124..721c8df574 100644 --- a/bim2sim/export/modelica/standardlibrary.py +++ b/bim2sim/export/modelica/standardlibrary.py @@ -51,9 +51,9 @@ def __init__(self, element: Union[hvac.Pipe]): attributes=['diameter']) def get_port_name(self, port): - if port.verbose_flow_direction == 'SINK': + if port.flow_direction.name == 'sink': return 'port_a' - if port.verbose_flow_direction == 'SOURCE': + if port.flow_direction.name == 'source': return 'port_b' # TODO #733 find port if sourceandsink or sinkdansource # if port.flow_direction == 0. # SOURCEANDSINK and SINKANDSOURCE diff --git a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py index 957465858c..2fb09653b9 100644 --- a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py +++ b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py @@ -50,9 +50,9 @@ def __init__(self, element): ) def get_port_name(self, port): - if port.verbose_flow_direction == 'SINK': + if port.flow_direction.name == 'sink': return 'port_a' - if port.verbose_flow_direction == 'SOURCE': + if port.flow_direction.name == 'source': return 'port_b' else: return super().get_port_name(port) # ToDo: Gas connection @@ -93,9 +93,9 @@ def __init__(self, element): attributes=['return_temperature']) def get_port_name(self, port): - if port.verbose_flow_direction == 'SINK': + if port.flow_direction.name == 'sink': return 'port_a' - if port.verbose_flow_direction == 'SOURCE': + if port.flow_direction.name == 'source': return 'port_b' else: return super().get_port_name(port) @@ -135,9 +135,9 @@ def __init__(self, element): 'dp': self.parameters['dp']}}) def get_port_name(self, port): - if port.verbose_flow_direction == 'SINK': + if port.flow_direction.name == 'sink': return 'port_a' - if port.verbose_flow_direction == 'SOURCE': + if port.flow_direction.name == 'source': return 'port_b' else: return super().get_port_name(port) @@ -189,9 +189,9 @@ def __init__(self, element): * ureg.kg / ureg.meter ** 3)) def get_port_name(self, port): - if port.verbose_flow_direction == 'SINK': + if port.flow_direction.name == 'sink': return 'port_a' - if port.verbose_flow_direction == 'SOURCE': + if port.flow_direction.name == 'source': return 'port_b' else: return super().get_port_name(port) @@ -498,9 +498,9 @@ def __init__(self, element): def get_port_name(self, port): # TODO: heat pumps might have 4 ports (if source is modeled in BIM) - if port.verbose_flow_direction == 'SINK': + if port.flow_direction.name == 'sink': return 'port_a' - if port.verbose_flow_direction == 'SOURCE': + if port.flow_direction.name == 'source': return 'port_b' else: return super().get_port_name(port) @@ -528,9 +528,9 @@ def __init__(self, element): def get_port_name(self, port): # TODO heat pumps might have 4 ports (if source is modeld in BIM) - if port.verbose_flow_direction == 'SINK': + if port.flow_direction.name == 'sink': return 'port_a' - if port.verbose_flow_direction == 'SOURCE': + if port.flow_direction.name == 'source': return 'port_b' else: return super().get_port_name(port) From 210e1feb031014300c84c1d99e62d3c27b3b40e8 Mon Sep 17 00:00:00 2001 From: Hoepp J <155448650+HoeppJ@users.noreply.github.com> Date: Fri, 27 Jun 2025 14:40:22 +0200 Subject: [PATCH 100/125] bugfix in modelica package export --- bim2sim/export/modelica/__init__.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/bim2sim/export/modelica/__init__.py b/bim2sim/export/modelica/__init__.py index c4700a8b63..f2fdbf790a 100644 --- a/bim2sim/export/modelica/__init__.py +++ b/bim2sim/export/modelica/__init__.py @@ -96,13 +96,15 @@ def help_package_order(path: Path, package_list: List[str], addition=None, "assets/templates/modelica/package_order.txt" package_order_template = Template(filename=str( template_package_order_path)) - with open(path / 'package.order', 'w') as out_file: - out_file.write(package_order_template.render_unicode( - list=package_list, - addition=addition, - extra=extra)) - out_file.close() + rendered_content = package_order_template.render_unicode( + list=package_list, + addition=addition, + extra=extra + ) + final_output = rendered_content.rstrip() + with open(path / 'package.order', 'w', newline='\n') as out_file: + out_file.write(final_output) class ModelicaModel: """Modelica model""" From 05420f0cb9d99d5a7c91c7b1e498a30852a9ce73 Mon Sep 17 00:00:00 2001 From: Hoepp J <155448650+HoeppJ@users.noreply.github.com> Date: Fri, 27 Jun 2025 15:07:12 +0200 Subject: [PATCH 101/125] Add number of ports and m_flow_nominal to AixLib export of Distributor --- .../PluginAixLib/bim2sim_aixlib/models/__init__.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py index 2fb09653b9..2e4e5300e0 100644 --- a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py +++ b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py @@ -381,25 +381,26 @@ class Distributor(AixLib): def __init__(self, element: hvac.Distributor): super().__init__(element) - # n_ports = self.get_n_ports() + n_ports = self.get_n_ports() self._set_parameter(name='redeclare package Medium', unit=None, required=False, value=MEDIUM_WATER) - # self._set_parameter(name='n', - # unit=None, - # required=False, - # value=n_ports) + self._set_parameter(name='n', + unit=None, + required=False, + value=int(n_ports)) self._set_parameter(name='m_flow_nominal', unit=ureg.kg / ureg.s, required=False, check=check_numeric(min_value=0 * ureg.kg / ureg.s), + value=0.05 * ureg.kg / ureg.s, # ToDo this is a random value, since no specific information has been found in ifc yet attributes=['rated_mass_flow']) def get_n_ports(self): ports = {port.guid: port for port in self.element.ports if port.connection} - return len(ports) / 2 - 1 + return len(ports) / 2 - 2 def get_port_name(self, port): try: From cc6e41da40758f5514d45ba650d4bb6e746236c6 Mon Sep 17 00:00:00 2001 From: Hoepp J <155448650+HoeppJ@users.noreply.github.com> Date: Mon, 30 Jun 2025 18:48:30 +0200 Subject: [PATCH 102/125] Fixed guid error in exported Building.mo file --- .../assets/templates/modelica/tmplSpawnBuilding.txt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/bim2sim/assets/templates/modelica/tmplSpawnBuilding.txt b/bim2sim/assets/templates/modelica/tmplSpawnBuilding.txt index 9b2de58d3d..f6061391e6 100644 --- a/bim2sim/assets/templates/modelica/tmplSpawnBuilding.txt +++ b/bim2sim/assets/templates/modelica/tmplSpawnBuilding.txt @@ -12,15 +12,17 @@ model ${model_name} "${model_comment}" final parameter Modelica.Units.SI.MassFlowRate mOut_flow[nZones]=0.3/3600 * fill(1, nZones) * 1.2 "Outside air infiltration for each exterior room"; // TODO Strings must be in "" in export - parameter String zoneNames[nZones] = ${ep_zone_lists} + // parameter String zoneNames[nZones] = ${ep_zone_lists} + // "Name of the thermal zone as specified in the EnergyPlus input"; + parameter String zoneNames[nZones] = {${', '.join('"' + z + '"' for z in ep_zone_lists.strip("{}").split(','))}} "Name of the thermal zone as specified in the EnergyPlus input"; - inner Buildings.ThermalZones.EnergyPlus_9_6_0.Building building ( + inner Buildings.ThermalZones.EnergyPlus_9_6_0.Building building( idfName = ${idf_path}, epwName = ${weather_path_ep}, weaName = ${weather_path_mos}, - printUnits = true) - "" annotation (Placement(transformation(extent={{-100,60},{-80,80}}))); + printUnits = true) "" + annotation (Placement(transformation(extent={{-100,60},{-80,80}}))); Buildings.Fluid.Sources.MassFlowSource_WeatherData freshairsource[nZones]( From b871acbc620e2e016cb5cc0ac85c75c954524681 Mon Sep 17 00:00:00 2001 From: Hoepp J <155448650+HoeppJ@users.noreply.github.com> Date: Thu, 3 Jul 2025 09:46:50 +0200 Subject: [PATCH 103/125] replace redundant function verbose_flow_direction --- bim2sim/elements/hvac_elements.py | 11 ------ bim2sim/export/modelica/standardlibrary.py | 4 +-- .../bim2sim_aixlib/models/__init__.py | 2 -- .../bim2sim_hkesim/models/__init__.py | 36 +++++++++---------- 4 files changed, 20 insertions(+), 33 deletions(-) diff --git a/bim2sim/elements/hvac_elements.py b/bim2sim/elements/hvac_elements.py index b11c7cef22..c48d1ca213 100644 --- a/bim2sim/elements/hvac_elements.py +++ b/bim2sim/elements/hvac_elements.py @@ -138,17 +138,6 @@ def flow_master(self, value: bool): # raise AttributeError("Invalid value. Use one of (-1, 0, 1, None).") # self._flow_direction = value - # @property - # def verbose_flow_direction(self): - # """Flow direction of port""" - # if self.flow_direction == -1: - # return 'SINK' - # if self.flow_direction == 0: - # return 'SINKANDSOURCE' - # if self.flow_direction == 1: - # return 'SOURCE' - # return 'UNKNOWN' - @property def flow_side(self): """ diff --git a/bim2sim/export/modelica/standardlibrary.py b/bim2sim/export/modelica/standardlibrary.py index 721c8df574..1fa52f4010 100644 --- a/bim2sim/export/modelica/standardlibrary.py +++ b/bim2sim/export/modelica/standardlibrary.py @@ -83,9 +83,9 @@ def __init__(self, element): attributes=['nominal_mass_flow_rate']) def get_port_name(self, port): - if port.verbose_flow_direction == 'SINK': + if port.flow_direction.name == 'sink': return 'port_a' - if port.verbose_flow_direction == 'SOURCE': + if port.flow_direction.name == 'source': return 'port_b' else: return super().get_port_name(port) diff --git a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py index 2e4e5300e0..4a3580ecff 100644 --- a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py +++ b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/models/__init__.py @@ -9,7 +9,6 @@ MEDIUM_WATER = 'AixLib.Media.Water' -# TODO get_port_name functions: use verbose_flow_direction instead index class AixLib(modelica.ModelicaElement): library = "AixLib" @@ -309,7 +308,6 @@ def get_port_name(self, port): class BoilerAggregation(AixLib): - # TODO: the model does not exists in AiLib """Modelica AixLib representation of the GeneratorOneFluid aggregation.""" path = "AixLib.Systems.ScalableGenerationModules.ScalableBoiler.ScalableBoiler" represents = [hvac_aggregations.GeneratorOneFluid] diff --git a/bim2sim/plugins/PluginHKESim/bim2sim_hkesim/models/__init__.py b/bim2sim/plugins/PluginHKESim/bim2sim_hkesim/models/__init__.py index e79dd37c33..4b3745b36c 100644 --- a/bim2sim/plugins/PluginHKESim/bim2sim_hkesim/models/__init__.py +++ b/bim2sim/plugins/PluginHKESim/bim2sim_hkesim/models/__init__.py @@ -33,9 +33,9 @@ def __init__(self, element): attributes=['return_temperature']) def get_port_name(self, port): - if port.verbose_flow_direction == 'SINK': + if port.flow_direction.name == 'sink': return 'port_a' - if port.verbose_flow_direction == 'SOURCE': + if port.flow_direction.name == 'source': return 'port_b' else: return super().get_port_name(port) @@ -63,9 +63,9 @@ def __init__(self, element): attributes=['return_temperature']) def get_port_name(self, port): - if port.verbose_flow_direction == 'SINK': + if port.flow_direction.name == 'sink': return 'port_a' - if port.verbose_flow_direction == 'SOURCE': + if port.flow_direction.name == 'source': return 'port_b' else: return super().get_port_name(port) @@ -99,9 +99,9 @@ def __init__(self, element): check=check_numeric(min_value=0 * ureg.watt)) def get_port_name(self, port): - if port.verbose_flow_direction == 'SINK': + if port.flow_direction.name == 'sink': return 'port_a' - if port.verbose_flow_direction == 'SOURCE': + if port.flow_direction.name == 'source': return 'port_b' else: return super().get_port_name(port) @@ -188,9 +188,9 @@ def get_port_name(self, port): except ValueError: # unknown port index = -1 - if port.verbose_flow_direction == 'SINK': + if port.flow_direction.name == 'sink': return "port_a_consumer" - elif port.verbose_flow_direction == 'SOURCE': + elif port.flow_direction.name == 'source': return "port_b_consumer" elif (index % 2) == 0: return "port_a_consumer{}".format( @@ -233,9 +233,9 @@ def __init__(self, element): attributes=['has_bypass']) def get_port_name(self, port): - if port.verbose_flow_direction == 'SINK': + if port.flow_direction.name == 'sink': return 'port_a' - if port.verbose_flow_direction == 'SOURCE': + if port.flow_direction.name == 'source': return 'port_b' else: return super().get_port_name(port) @@ -263,9 +263,9 @@ def __init__(self, element): def get_port_name(self, port): # TODO: heat pump might have 4 ports (if source is modeled in BIM) - if port.verbose_flow_direction == 'SINK': + if port.flow_direction.name == 'sink': return 'port_a_con' - if port.verbose_flow_direction == 'SOURCE': + if port.flow_direction.name == 'source': return 'port_b_con' else: return super().get_port_name(port) @@ -299,9 +299,9 @@ def __init__(self, element): def get_port_name(self, port): # TODO: chiller might have 4 ports (if source is modeled in BIM) - if port.verbose_flow_direction == 'SINK': + if port.flow_direction.name == 'sink': return 'port_a_con' - if port.verbose_flow_direction == 'SOURCE': + if port.flow_direction.name == 'source': return 'port_b_con' else: return super().get_port_name(port) @@ -324,9 +324,9 @@ def __init__(self, element): attributes=['rated_power']) def get_port_name(self, port): - if port.verbose_flow_direction == 'SINK': + if port.flow_direction.name == 'sink': return 'port_a' - if port.verbose_flow_direction == 'SOURCE': + if port.flow_direction.name == 'source': return 'port_b' else: return super().get_port_name(port) @@ -349,9 +349,9 @@ def __init__(self, element): attributes=['rated_power']) def get_port_name(self, port): - if port.verbose_flow_direction == 'SINK': + if port.flow_direction.name == 'sink': return 'port_a' - if port.verbose_flow_direction == 'SOURCE': + if port.flow_direction.name == 'source': return 'port_b' else: return super().get_port_name(port) From d041a698c5833f79e2b05209f41a7801ed4d1668 Mon Sep 17 00:00:00 2001 From: Hoepp J <155448650+HoeppJ@users.noreply.github.com> Date: Fri, 11 Jul 2025 16:27:01 +0200 Subject: [PATCH 104/125] Delete obsolete --- bim2sim/assets/templates/modelica/tmplSpawnBuilding.txt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/bim2sim/assets/templates/modelica/tmplSpawnBuilding.txt b/bim2sim/assets/templates/modelica/tmplSpawnBuilding.txt index f6061391e6..85fad2258e 100644 --- a/bim2sim/assets/templates/modelica/tmplSpawnBuilding.txt +++ b/bim2sim/assets/templates/modelica/tmplSpawnBuilding.txt @@ -2,7 +2,6 @@ within ${within}; model ${model_name} "${model_comment}" import SI = Modelica.Units.SI; - // TODO parameter Integer nPorts=2 "Number of fluid ports (equals to 2 for one inlet and one outlet)" annotation (Evaluate=true,Dialog(connectorSizing=true,tab="General",group="Ports")); @@ -11,9 +10,6 @@ model ${model_name} "${model_comment}" final parameter Modelica.Units.SI.MassFlowRate mOut_flow[nZones]=0.3/3600 * fill(1, nZones) * 1.2 "Outside air infiltration for each exterior room"; - // TODO Strings must be in "" in export - // parameter String zoneNames[nZones] = ${ep_zone_lists} - // "Name of the thermal zone as specified in the EnergyPlus input"; parameter String zoneNames[nZones] = {${', '.join('"' + z + '"' for z in ep_zone_lists.strip("{}").split(','))}} "Name of the thermal zone as specified in the EnergyPlus input"; From 17e55f59ead2091cb7a3603dbe167355b83b37e5 Mon Sep 17 00:00:00 2001 From: Hoepp J <155448650+HoeppJ@users.noreply.github.com> Date: Fri, 11 Jul 2025 16:28:35 +0200 Subject: [PATCH 105/125] Match thermal zone mapping order in idf and modelica model --- .../bim2sim_spawn/tasks/export_spawn_total.py | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_total.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_total.py index 8a7ba9324e..07aaddccb2 100644 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_total.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_total.py @@ -29,7 +29,7 @@ class ExportSpawnTotal(ITask): 'elements', 'weather_file_modelica', 'weather_file_ep', 'model_name_hydraulic', 'model_name_building', 'export_elements', 'connections', 'cons_heat_ports_conv', - 'cons_heat_ports_rad', 'package_name' + 'cons_heat_ports_rad', 'package_name', 'ep_zone_lists', ) final = True @@ -43,7 +43,8 @@ def run(self, connections: List[Tuple[str, str]], cons_heat_ports_conv: List[Tuple[str, str]], cons_heat_ports_rad: List[Tuple[str, str]], - package_name: str): + package_name: str, + ep_zone_lists: List[str]): """Run the export process to generate the Modelica code. Args: @@ -57,6 +58,7 @@ def run(self, cons_heat_ports_conv: List of convective heat port connections. cons_heat_ports_rad: List of radiative heat port connections. package_name: The package name of the modelica package. + ep_zone_lists: List of zones in energy plus idf file """ # Exports the total model @@ -69,7 +71,7 @@ def run(self, # Group heaters by their corresponding zones zone_to_heaters = self._group_space_heaters_by_zone( - tz_elements, space_heater_elements + tz_elements, ep_zone_lists, space_heater_elements ) # Map heat ports between building and HVAC models @@ -116,23 +118,28 @@ def _create_modelica_help_package( @staticmethod def _group_space_heaters_by_zone(tz_elements: List, + ep_zone_lists: List, space_heater_elements: List) \ -> Dict[str, List[str]]: - """Group space heaters by their respective zones. + """Group space heaters by their respective zones according to the zone list order in the idf file Args: tz_elements: List of thermal zone elements. + ep_zone_lists: List of zones in energy plus idf file space_heater_elements: List of space heater elements. Returns: A dictionary mapping zone GUIDs to lists of heater GUIDs. """ zone_to_heaters = defaultdict(list) - for tz in tz_elements: - for space_heater in space_heater_elements: - if PyOCCTools.obj2_in_obj1( - obj1=tz.space_shape, obj2=space_heater.shape): - zone_to_heaters[tz.guid].append(space_heater.guid) + for ep_zone in ep_zone_lists: + zone_to_heaters[ep_zone] = [] + for tz in tz_elements: + if tz.guid == ep_zone: + for space_heater in space_heater_elements: + if PyOCCTools.obj2_in_obj1( + obj1=tz.space_shape, obj2=space_heater.shape): + zone_to_heaters[tz.guid].append(space_heater.guid) return zone_to_heaters def _save_total_modelica_model( From b9d116680ec8b7311cc7fba918f9319ac5d4cde7 Mon Sep 17 00:00:00 2001 From: Hoepp J <155448650+HoeppJ@users.noreply.github.com> Date: Mon, 14 Jul 2025 10:53:43 +0200 Subject: [PATCH 106/125] add sim_setting for choice between AixLib and HKESim for hvac simulation --- .../examples/e1_simple_project_bps_spawn.py | 2 ++ .../PluginSpawn/bim2sim_spawn/sim_settings.py | 12 +++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e1_simple_project_bps_spawn.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e1_simple_project_bps_spawn.py index 7de4fae7da..18181a6315 100644 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e1_simple_project_bps_spawn.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e1_simple_project_bps_spawn.py @@ -54,6 +54,8 @@ def run_example_spawn_1(): # Generate outer heat ports for spawn HVAC sub model project.sim_settings.outer_heat_ports = True + project.sim_settings.hvac_modelica_library = "AixLib" + # Set other simulation settings, otherwise all settings are set to default project.sim_settings.aggregations = [ 'PipeStrand', diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/sim_settings.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/sim_settings.py index eaa11344ba..e714cc5cf0 100644 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/sim_settings.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/sim_settings.py @@ -1,6 +1,6 @@ from bim2sim.elements import bps_elements, hvac_elements from bim2sim.elements.base_elements import Material -from bim2sim.sim_settings import PlantSimSettings +from bim2sim.sim_settings import PlantSimSettings, ChoiceSetting from bim2sim.plugins.PluginEnergyPlus.bim2sim_energyplus.sim_settings import \ EnergyPlusSimSettings @@ -12,3 +12,13 @@ def __init__(self): Material} # change defaults self.outer_heat_ports = True + + hvac_modelica_library = ChoiceSetting( + default='AixLib', + choices={ + 'AixLib': 'Using AixLib for HVAC simulation', + 'HKESim': 'Using HKESim for HVAC simulation' + }, + description='Choose Modelica library for HVAC simulation.', + for_frontend=True + ) From 085a088cba394c6bf1b30a20de73d9b315ba2fc5 Mon Sep 17 00:00:00 2001 From: Hoepp J <155448650+HoeppJ@users.noreply.github.com> Date: Mon, 17 Nov 2025 17:21:16 +0100 Subject: [PATCH 107/125] add finder templates --- .../assets/finder/template_LuArtX_Carf.json | 10 ++++++++ bim2sim/sim_settings.py | 25 +------------------ bim2sim/tasks/common/weather.py | 10 +++++--- 3 files changed, 18 insertions(+), 27 deletions(-) diff --git a/bim2sim/assets/finder/template_LuArtX_Carf.json b/bim2sim/assets/finder/template_LuArtX_Carf.json index 621eb8461d..53b96dcd11 100644 --- a/bim2sim/assets/finder/template_LuArtX_Carf.json +++ b/bim2sim/assets/finder/template_LuArtX_Carf.json @@ -37,5 +37,15 @@ "internal_pump": ["VDI 710.05-Luft-Wasser-Wärmepumpe", "Heizkreispumpe intern"], "vdi_performance_data_table": ["VDI-Tables", "Leistungsdaten"] } + }, + "AirDuct": { + "type": ["Element Specific", "ObjectType"], + "": ["MyTab1", "[BEZ] Bezeichnung"], + "cross_section": ["MyTab1", "[Querschnitt] G"], + "width": ["MyTab1", "[a] (a)"], + "height": ["MyTab1", "[b] (b)"], + "diameter": ["MyTab1", "[d1] DN"], + "length": ["MyTab1", "[l] Länge (l)"], + "flow_velocity": ["MyTab1", "[v] Luftgeschw."], } } \ No newline at end of file diff --git a/bim2sim/sim_settings.py b/bim2sim/sim_settings.py index de54842857..0e34b94b58 100644 --- a/bim2sim/sim_settings.py +++ b/bim2sim/sim_settings.py @@ -407,29 +407,6 @@ def check_mandatory(self): mandatory=False ) - weather_file_path_ep = PathSetting( - default=None, - description='Path to the weather file that should be used for the ' - 'simulation. If no path is provided, we will try to get ' - 'the location from the IFC and download a fitting weather' - ' file. For Modelica provide .mos files, for EnergyPlus ' - '.epw files. If the format does not fit, we will try to ' - 'convert.', - for_frontend=True, - mandatory=False - ) - - weather_file_path = DeprecatedSetting( - replacement_settings=["weather_file_path_ep", - "weather_file_path_modelica"], - default=None, - description='DEPRECATED: Use weather_file_path_ep or ' - 'weather_file_path_modelica instead. ' + - 'Path to the weather file that should be used for the' - ' simulation.', - for_frontend=False - ) - building_rotation_overwrite = NumberSetting( value=0, min_value=0, @@ -523,7 +500,7 @@ def __init__(self): "ports." ) outer_heat_ports = BooleanSetting( - default=False, + value=False, description='Add outer heat ports to allow connections to other ' 'models.', for_frontend=True diff --git a/bim2sim/tasks/common/weather.py b/bim2sim/tasks/common/weather.py index bf29d8344b..4fbe94c718 100644 --- a/bim2sim/tasks/common/weather.py +++ b/bim2sim/tasks/common/weather.py @@ -1,9 +1,13 @@ -from pathlib import Path - from bim2sim.tasks.base import ITask - +from bim2sim.utilities.common_functions import filter_elements +from typing import Any +from pathlib import WindowsPath, Path +from typing import Optional class Weather(ITask): + """Task to get the weather file for later simulation""" + reads = ('elements',) + touches = ('weather_file',) """Task to get the weather file for later simulation""" reads = ('elements',) touches = ('weather_file_modelica', 'weather_file_ep') From db52cca51a979175e04e10b66338f31f7fdb001f Mon Sep 17 00:00:00 2001 From: Hoepp J <155448650+HoeppJ@users.noreply.github.com> Date: Tue, 18 Nov 2025 16:41:31 +0100 Subject: [PATCH 108/125] add templates for air distribution system --- .../assets/finder/template_LuArtX_Carf.json | 659 +++++++++++++++++- bim2sim/elements/hvac_elements.py | 12 + 2 files changed, 661 insertions(+), 10 deletions(-) diff --git a/bim2sim/assets/finder/template_LuArtX_Carf.json b/bim2sim/assets/finder/template_LuArtX_Carf.json index 53b96dcd11..8d37b30f12 100644 --- a/bim2sim/assets/finder/template_LuArtX_Carf.json +++ b/bim2sim/assets/finder/template_LuArtX_Carf.json @@ -38,14 +38,653 @@ "vdi_performance_data_table": ["VDI-Tables", "Leistungsdaten"] } }, - "AirDuct": { - "type": ["Element Specific", "ObjectType"], - "": ["MyTab1", "[BEZ] Bezeichnung"], - "cross_section": ["MyTab1", "[Querschnitt] G"], - "width": ["MyTab1", "[a] (a)"], - "height": ["MyTab1", "[b] (b)"], - "diameter": ["MyTab1", "[d1] DN"], - "length": ["MyTab1", "[l] Länge (l)"], - "flow_velocity": ["MyTab1", "[v] Luftgeschw."], + "AirDuct_rectangular": { + "default_ps": { + "air_type": ["MyTab1", "[ANLAGE] Luftart"], + "height_a": ["MyTab1", "[a] (a)"], + "width_b": ["MyTab1", "[b] (b)"], + "designation": ["MyTab1", "[BEZ] Bezeichnung"], + "isolation_type": ["MyTab1", "[ISOL-TYPE] Isolationstyp"], + "isolation": ["MyTab1", "[ISOL] Isolation"], + "edge": ["MyTab1", "[KANTE] Kante"], + "code": ["MyTab1", "[KBZ] KBZ"], + "key": ["MyTab1", "[KEY] Schlüssel"], + "reference_edge": ["MyTab1", "[K] Bezugskante"], + "length": ["MyTab1", "[l] Länge"], + "material": ["MyTab1", "[MAT] Material"], + "data_mask_number": ["MyTab1", "[MSKNR] Datenmaskennummer"], + "on_list": ["MyTab1", "[ON_LST] Auf Liste"], + "pen": ["MyTab1", "[PEN] Schtift"], + "system": ["MyTab1", "[PLANT] Anlage"], + "position": ["MyTab1", "[POS] Position"], + "cross_section": ["MyTab1", "[QS] Querschnitt"], + "cross_section_area": ["MyTab1", "[QUERSCHNITT] G"], + "connection_type": ["MyTab1", "[RAHMEN]"], + "floor": ["MyTab1", "[STOCK] Stockwerk"], + "version": ["MyTab1", "[VERS] Version"], + "air_velocity": ["MyTab1", "[v] Luftgeschw."] + } + }, + "AirDuct_round": { + "default_ps": { + "radius": ["Profile", "Radius"], + "air_type": ["MyTab1", "[ANLAGE] Luftart"], + "designation": ["MyTab1", "[BEZ] Bezeichnung"], + "nominal_diameter_d1": ["MyTab1", "[d1] DN"], + "cross_section_d": ["MyTab1", "[d] Querschnitt"], + "isolation_type": ["MyTab1", "[ISOL-TYPE] Isolationstyp"], + "edge": ["MyTab1", "[KANTE] Kante"], + "code": ["MyTab1", "[KBZ] KBZ"], + "key": ["MyTab1", "[KEY] Schlüssel"], + "reference_edge": ["MyTab1", "[K] Bezugskante"], + "length": ["MyTab1", "[l] Länge"], + "list_round": ["MyTab1", "[LSTR] Auf Liste Rund"], + "material": ["MyTab1", "[MAT] Material"], + "data_mask_number": ["MyTab1", "[MSKNR] Datenmaskennummer"], + "on_list": ["MyTab1", "[ON_LST] Auf Liste"], + "pen": ["MyTab1", "[PEN] Schtift"], + "system": ["MyTab1", "[PLANT] Anlage"], + "position": ["MyTab1", "[POS] Position"], + "pipe_connection": ["MyTab1", "[RAHMEN] Verbindungsart"], + "floor": ["MyTab1", "[STOCK] Stockwerk"], + "version": ["MyTab1", "[VERS] Version"], + "air_velocity": ["MyTab1", "[v] Luftgeschw."] + } + }, + "AirDuct_rectangular_extra": { + "default_ps": { + "air_type": ["MyTab1", "[ANLAGE] Luftart"], + "outer_dimension_a": ["MyTab1", "[A] Aussenabmessung A"], + "cross_section_a": ["MyTab1", "[a] Querschnitt a"], + "outer_dimension_b": ["MyTab1", "[B] Aussenabmessung B"], + "cross_section_b": ["MyTab1", "[b] Querschnitt b"], + "label_swap_ab": ["MyTab1", "[cab] Beschriftung a/b vertauschen"], + "insert": ["MyTab1", "[e] Einschub"], + "manufacturer": ["MyTab1", "[HST] Hersteller"], + "isolation_type": ["MyTab1", "[ISOL-TYPE] Isolationstyp"], + "isolation": ["MyTab1", "[ISOL] Isolation"], + "edge": ["MyTab1", "[KANTE] Kante"], + "code": ["MyTab1", "[KBZ] KBZ"], + "key": ["MyTab1", "[KEY] Schlüssel"], + "length": ["MyTab1", "[l] Länge"], + "material": ["MyTab1", "[MAT] Material"], + "data_mask_number": ["MyTab1", "[MSKNR] Datenmaskennummer"], + "on_list": ["MyTab1", "[ON_LST] Auf Liste"], + "pen": ["MyTab1", "[PEN] Schtift"], + "system": ["MyTab1", "[PLANT] Anlage"], + "position": ["MyTab1", "[POS] Position"], + "cross_section": ["MyTab1", "[QS] Querschnitt"], + "connection_type": ["MyTab1", "[RAHMEN]"], + "floor": ["MyTab1", "[STOCK] Stockwerk"], + "type": ["MyTab1", "[TYP] Typ"], + "version": ["MyTab1", "[VERS] Version"] + } + }, + "FireDamper": { + "default_ps": { + "flow_velocity": ["Calc", "[ve] Strömungsgeschwindigkeit"], + "air_type": ["MyTab1", "[ANLAGE] Luftart"], + "drive_side": ["MyTab1", "[AS] Antriebseite"], + "remark": ["MyTab1", "[ATB] Bemerkung"], + "drive": ["MyTab1", "[ATR] Antrieb"], + "outer_dimension_a": ["MyTab1", "[A] Aussenabmessung A"], + "cross_section_a": ["MyTab1", "[a] Querschnitt a"], + "designation_is": ["MyTab1", "[BEZ] Bezeichnung (IS)"], + "outer_dimension_b": ["MyTab1", "[B] Aussenabmessung B"], + "cross_section_b": ["MyTab1", "[b] Querschnitt b"], + "label_swap_ab": ["MyTab1", "[cab] Beschriftung a/b vertauschen"], + "insert": ["MyTab1", "[e] Einschub"], + "pressure_loss_opening_1": ["MyTab1", "[DP] Druckverlust Öffnung 1"], + "manufacturer": ["MyTab1", "[HST] Hersteller"], + "isolation_type": ["MyTab1", "[ISOL-TYPE] Isolationstyp"], + "edge": ["MyTab1", "[KANTE] Kante"], + "code": ["MyTab1", "[KBZ] KBZ"], + "key": ["MyTab1", "[KEY] Schlüssel"], + "length": ["MyTab1", "[l] Länge"], + "material": ["MyTab1", "[MAT] Material"], + "data_mask_number": ["MyTab1", "[MSKNR] Datenmaskennummer"], + "on_list": ["MyTab1", "[ON_LST] Auf Liste"], + "pen": ["MyTab1", "[PEN] Schtift"], + "system": ["MyTab1", "[PLANT] Anlage"], + "position": ["MyTab1", "[POS] Position"], + "cross_section": ["MyTab1", "[QS] Querschnitt"], + "connection_type": ["MyTab1", "[RAHMEN]"], + "floor": ["MyTab1", "[STOCK] Stockwerk"], + "type": ["MyTab1", "[TYP] Typ"], + "version": ["MyTab1", "[VERS] Version"], + "air_volume": ["MyTab1", "[VS] Luftvol"], + "zeta_opening_1": ["MyTab1", "[Z] Zeta Öffnung 1"] + } + }, + "VolumeFlowController_constant": { + "default_ps": { + "radius": ["Profile", "Radius"], + "flow_velocity": ["Calc", "[ve] Strömungsgeschwindigkeit"], + "air_type": ["MyTab1", "[ANLAGE] Luftart"], + "designation_is": ["MyTab1", "[BEZ] Bezeichnung (IS)"], + "nominal_diameter_d1": ["MyTab1", "[d1] DN"], + "outer_diameter": ["MyTab1", "[D] Aussendurchmesser"], + "insert": ["MyTab1", "[e] Einschub"], + "manufacturer": ["MyTab1", "[HST] Hersteller"], + "isolation_type": ["MyTab1", "[ISOL-TYPE] Isolationstyp"], + "edge": ["MyTab1", "[KANTE] Kante"], + "code": ["MyTab1", "[KBZ] KBZ"], + "key": ["MyTab1", "[KEY] Schlüssel"], + "length": ["MyTab1", "[l] Länge"], + "material": ["MyTab1", "[MAT] Material"], + "data_mask_number": ["MyTab1", "[MSKNR] Datenmaskennummer"], + "on_list": ["MyTab1", "[ON_LST] Auf Liste"], + "pen": ["MyTab1", "[PEN] Schtift"], + "system": ["MyTab1", "[PLANT] Anlage"], + "position": ["MyTab1", "[POS] Position"], + "connection_type": ["MyTab1", "[RAHMEN]"], + "floor": ["MyTab1", "[STOCK] Stockwerk"], + "type": ["MyTab1", "[TYP] Typ"], + "version": ["MyTab1", "[VERS] Version"], + "air_volume": ["MyTab1", "[VS] Luftvol"] + } + }, + "VolumeFlowController_dynamic": { + "default_ps": { + "flow_velocity": ["Calc", "[ve] Strömungsgeschwindigkeit"], + "air_type": ["MyTab1", "[ANLAGE] Luftart"], + "outer_dimension_a": ["MyTab1", "[A] Aussenabmessung A"], + "cross_section_a": ["MyTab1", "[a] Querschnitt a"], + "designation": ["MyTab1", "[BEZ] Bezeichnung"], + "outer_dimension_b": ["MyTab1", "[B] Aussenabmessung B"], + "cross_section_b": ["MyTab1", "[b] Querschnitt b"], + "label_swap_ab": ["MyTab1", "[cab] Beschriftung a/b vertauschen"], + "insert": ["MyTab1", "[e] Einschub"], + "manufacturer": ["MyTab1", "[HST] Hersteller"], + "isolation_type": ["MyTab1", "[ISOL-TYPE] Isolationstyp"], + "isolation": ["MyTab1", "[ISOL] Isolation"], + "edge": ["MyTab1", "[KANTE] Kante"], + "code": ["MyTab1", "[KBZ] KBZ"], + "key": ["MyTab1", "[KEY] Schlüssel"], + "length": ["MyTab1", "[l] Länge"], + "material": ["MyTab1", "[MAT] Material"], + "data_mask_number": ["MyTab1", "[MSKNR] Datenmaskennummer"], + "on_list": ["MyTab1", "[ON_LST] Auf Liste"], + "pen": ["MyTab1", "[PEN] Schtift"], + "system": ["MyTab1", "[PLANT] Anlage"], + "position": ["MyTab1", "[POS] Position"], + "cross_section": ["MyTab1", "[QS] Querschnitt"], + "connection_type": ["MyTab1", "[RAHMEN]"], + "floor": ["MyTab1", "[STOCK] Stockwerk"], + "type": ["MyTab1", "[TYP] Typ"], + "version": ["MyTab1", "[VERS] Version"], + "flow_rate_range": ["MyTab1", "[VS] Vm3/h"] + } + }, + "AirDuct_round_extra": { + "default_ps": { + "air_type": ["MyTab1", "[ANLAGE] Luftart"], + "manufacturer": ["MyTab1", "[HST] Hersteller"], + "isolation_type": ["MyTab1", "[ISOL-TYPE] Isolationstyp"], + "code": ["MyTab1", "[KBZ] KBZ"], + "key": ["MyTab1", "[KEY] Schlüssel"], + "data_mask_number": ["MyTab1", "[MSKNR] Datenmaskennummer"], + "on_list": ["MyTab1", "[ON_LST] Auf Liste"], + "system": ["MyTab1", "[PLANT] Anlage"], + "position": ["MyTab1", "[POS] Position"], + "floor": ["MyTab1", "[STOCK] Stockwerk"], + "type": ["MyTab1", "[TYP] Typ"], + "version": ["MyTab1", "[VERS] Version"] + } + }, + "Silencer_round_flexible": { + "default_ps": { + "flow_velocity": ["Calc", "[ve] Strömungsgeschwindigkeit"], + "air_type": ["MyTab1", "[ANLAGE] Luftart"], + "designation_is": ["MyTab1", "[BEZ] Bezeichnung (IS)"], + "nominal_diameter_d1": ["MyTab1", "[d1] DN"], + "outer_diameter": ["MyTab1", "[D] Aussendurchmesser"], + "insert_end": ["MyTab1", "[e] Einschub Ende"], + "manufacturer": ["MyTab1", "[HST] Hersteller"], + "isolation_type": ["MyTab1", "[ISOL-TYPE] Isolationstyp"], + "edge": ["MyTab1", "[KANTE] Kante"], + "code": ["MyTab1", "[KBZ] KBZ"], + "key": ["MyTab1", "[KEY] Schlüssel"], + "length": ["MyTab1", "[l] Länge"], + "material": ["MyTab1", "[MAT] Material"], + "data_mask_number": ["MyTab1", "[MSKNR] Datenmaskennummer"], + "on_list": ["MyTab1", "[ON_LST] Auf Liste"], + "pen": ["MyTab1", "[PEN] Schtift"], + "system": ["MyTab1", "[PLANT] Anlage"], + "position": ["MyTab1", "[POS] Position"], + "connection_type": ["MyTab1", "[RAHMEN]"], + "floor": ["MyTab1", "[STOCK] Stockwerk"], + "type": ["MyTab1", "[TYP] Typ"], + "version": ["MyTab1", "[VERS] Version"] + } + }, + "AirTerminal": { + "default_ps": { + "air_type": ["MyTab1", "[ANLAGE] Luftart"], + "designation": ["MyTab1", "[BEZ] Bezeichnung"], + "outlet_grille_graphic": ["MyTab1", "[CG] Auslass-Gitter-Grafik"], + "nominal_diameter_d1": ["MyTab1", "[d1] DN"], + "manufacturer": ["MyTab1", "[HST] Hersteller"], + "isolation_type": ["MyTab1", "[ISOL-TYPE] Isolationstyp"], + "edge": ["MyTab1", "[KANTE] Kante"], + "code": ["MyTab1", "[KBZ] KBZ"], + "key": ["MyTab1", "[KEY] Schlüssel"], + "material": ["MyTab1", "[MAT] Material"], + "data_mask_number": ["MyTab1", "[MSKNR] Datenmaskennummer"], + "pen": ["MyTab1", "[PEN] Schtift"], + "system": ["MyTab1", "[PLANT] Anlage"], + "position": ["MyTab1", "[POS] Position"], + "project": ["MyTab1", "[PRJ] Projekt"], + "connection_type": ["MyTab1", "[RAHMEN]"], + "series": ["MyTab1", "[SER] Serie"], + "floor": ["MyTab1", "[STOCK] Stockwerk"], + "type": ["MyTab1", "[TYP] Typ"], + "version": ["MyTab1", "[VERS] Version"], + "flow_rate": ["MyTab1", "[VOL] Vm3/h"], + "air_velocity": ["MyTab1", "[v] Luftgeschw."] + } + }, + "Silencer_telephony ": { + "default_ps": { + "radius": ["Profile", "Radius"], + "air_type": ["MyTab1", "[ANLAGE] Luftart"], + "designation": ["MyTab1", "[BEZ] Bezeichnung"], + "nominal_diameter_d1": ["MyTab1", "[d1] DN"], + "outer_diameter": ["MyTab1", "[D] Aussendurchmesser"], + "insert": ["MyTab1", "[e] Einschub"], + "manufacturer": ["MyTab1", "[HST] Hersteller"], + "isolation_type": ["MyTab1", "[ISOL-TYPE] Isolationstyp"], + "edge": ["MyTab1", "[KANTE] Kante"], + "code": ["MyTab1", "[KBZ] KBZ"], + "key": ["MyTab1", "[KEY] Schlüssel"], + "list_round": ["MyTab1", "[LSTR] Auf Liste Rund"], + "length": ["MyTab1", "[l] Länge"], + "material": ["MyTab1", "[MAT] Material"], + "data_mask_number": ["MyTab1", "[MSKNR] Datenmaskennummer"], + "on_list": ["MyTab1", "[ON_LST] Auf Liste"], + "pen": ["MyTab1", "[PEN] Schtift"], + "system": ["MyTab1", "[PLANT] Anlage"], + "position": ["MyTab1", "[POS] Position"], + "connection_type": ["MyTab1", "[RAHMEN]"], + "rings": ["MyTab1", "[RI] Ringe"], + "floor": ["MyTab1", "[STOCK] Stockwerk"], + "type": ["MyTab1", "[TYP] Typ"], + "version": ["MyTab1", "[VERS] Version"] + } + }, + "AirDuct_round_flexible": { + "default_ps": { + "radius": ["Profile", "Radius"], + "air_type": ["MyTab1", "[ANLAGE] Luftart"], + "designation": ["MyTab1", "[BEZ] Bezeichnung"], + "nominal_diameter_d1": ["MyTab1", "[d1] DN"], + "cross_section_d": ["MyTab1", "[d] Querschnitt"], + "isolation_type": ["MyTab1", "[ISOL-TYPE] Isolationstyp"], + "edge": ["MyTab1", "[KANTE] Kante"], + "code": ["MyTab1", "[KBZ] KBZ"], + "key": ["MyTab1", "[KEY] Schlüssel"], + "length": ["MyTab1", "[l] Länge"], + "material": ["MyTab1", "[MAT] Material"], + "data_mask_number": ["MyTab1", "[MSKNR] Datenmaskennummer"], + "on_list": ["MyTab1", "[ON_LST] Auf Liste"], + "pen": ["MyTab1", "[PEN] Schtift"], + "system": ["MyTab1", "[PLANT] Anlage"], + "position": ["MyTab1", "[POS] Position"], + "pipe_connection": ["MyTab1", "[RAHMEN] Rohrverbindung"], + "rings": ["MyTab1", "[RI] Ringe"], + "floor": ["MyTab1", "[STOCK] Stockwerk"], + "version": ["MyTab1", "[VERS] Version"], + "air_velocity": ["MyTab1", "[v] Luftgeschw."] + } + }, + "AirDuct_T_piece": { + "default_ps": { + "air_type": ["MyTab1", "[ANLAGE] Luftart"], + "width_entrance_a": ["MyTab1", "[a] Breite Eingang (a)"], + "designation": ["MyTab1", "[BEZ] Bezeichnung"], + "width_start_b": ["MyTab1", "[b] Breite Anfang (b)"], + "height_entrance_b": ["MyTab1", "[b] Höhe Eingang (b)"], + "width_outlet_d": ["MyTab1", "[d] Breite Ausgang (d)"], + "width_branch_g": ["MyTab1", "[g] Breite Abzweig (g)"], + "height_branch_h": ["MyTab1", "[h] Breite Abzweig (h)"], + "isolation_type": ["MyTab1", "[ISOL-TYPE] Isolationstyp"], + "edge": ["MyTab1", "[KANTE] Kante"], + "code": ["MyTab1", "[KBZ] KBZ"], + "key": ["MyTab1", "[KEY] Schlüssel"], + "cross_tee": ["MyTab1", "[KR] Kreuzstück"], + "length": ["MyTab1", "[l] Länge"], + "material": ["MyTab1", "[MAT] Material"], + "data_mask_number": ["MyTab1", "[MSKNR] Datenmaskennummer"], + "height_branch_m": ["MyTab1", "[m] Höhe Abzweig"], + "length_entrance_n": ["MyTab1", "[n] Länge Eingang"], + "on_list": ["MyTab1", "[ON_LST] Auf Liste"], + "second_branch_length_o": ["MyTab1", "[o] 2. Verzweigung. Länge Eingang"], + "pen": ["MyTab1", "[PEN] Schtift"], + "system": ["MyTab1", "[PLANT] Anlage"], + "position": ["MyTab1", "[POS] Position"], + "second_branch_height_p": ["MyTab1", "[p] 2. Verzweigung. Höhe"], + "cross_section": ["MyTab1", "[QS] Querschnitt"], + "connection_type": ["MyTab1", "[RAHMEN]"], + "radius": ["MyTab1", "[r] Radius"], + "floor": ["MyTab1", "[STOCK] Stockwerk"], + "version": ["MyTab1", "[VERS] Version"], + "air_velocity": ["MyTab1", "[v] Luftgeschw."], + "absolute_roughness_factor": ["Pset_DuctFittingTypeCommon", "AbsoluteRoughnessFactor"] + } + }, + "AirDuct_transition_rectangular": { + "default_ps": { + "air_type": ["MyTab1", "[ANLAGE] Luftart"], + "list_as_ua": ["MyTab1", "[AUA] Auf Liste als UA"], + "height_a": ["MyTab1", "[a] Höhe (a)"], + "designation": ["MyTab1", "[BEZ] Bezeichnung"], + "width_start_b": ["MyTab1", "[b] Breite Anfang (b)"], + "height_end_c": ["MyTab1", "[c] Höhe Ende (c)"], + "width_end_d": ["MyTab1", "[d] Breite Ende (d)"], + "e_offset": ["MyTab1", "[e] e-Versatz"], + "insert_start": ["MyTab1", "[f] Einschub Anfang"], + "f_offset": ["MyTab1", "[f] f-Versatz"], + "manufacturer": ["MyTab1", "[HST] Hersteller"], + "isolation_type": ["MyTab1", "[ISOL-TYPE] Isolationstyp"], + "isolation": ["MyTab1", "[ISOL] Isolation"], + "edge": ["MyTab1", "[KANTE] Kante"], + "code": ["MyTab1", "[KBZ] KBZ"], + "key": ["MyTab1", "[KEY] Schlüssel"], + "length": ["MyTab1", "[l] Länge"], + "box_bottom_lbh": ["MyTab1", "[LBHU] BOX unten: L-B-H"], + "box_lbh": ["MyTab1", "[LBH] BOX: Länge Breite Höhe"], + "connection_length_height": ["MyTab1", "[LHA] Anschluss Länge Höhe"], + "material": ["MyTab1", "[MAT] Material"], + "data_mask_number": ["MyTab1", "[MSKNR] Datenmaskennummer"], + "extension_m": ["MyTab1", "[m] Anlängung (m)"], + "extension_n": ["MyTab1", "[n] Anlängung (n)"], + "on_list": ["MyTab1", "[ON_LST] Auf Liste"], + "pen": ["MyTab1", "[PEN] Schtift"], + "system": ["MyTab1", "[PLANT] Anlage"], + "position": ["MyTab1", "[POS] Position"], + "cross_section": ["MyTab1", "[QS] Querschnitt"], + "connection_type": ["MyTab1", "[RAHMEN]"], + "floor": ["MyTab1", "[STOCK] Stockwerk"], + "strand": ["MyTab1", "[STR] Strang"], + "version": ["MyTab1", "[VERS] Version"], + "flow_rate": ["MyTab1", "[VOL] Vm3/h"], + "air_velocity": ["MyTab1", "[v] Luftgeschw."], + "absolute_roughness_factor": ["Pset_DuctFittingTypeCommon", "AbsoluteRoughnessFactor"] + } + }, + "AirDuct_end_rectangular": { + "default_ps": { + "air_type": ["MyTab1", "[ANLAGE] Luftart"], + "width_a": ["MyTab1", "[a] (a)"], + "designation": ["MyTab1", "[BEZ] Bezeichnung"], + "height_b": ["MyTab1", "[b] (b)"], + "isolation_type": ["MyTab1", "[ISOL-TYPE] Isolationstyp"], + "isolation": ["MyTab1", "[ISOL] Isolation"], + "edge": ["MyTab1", "[KANTE] Kante"], + "code": ["MyTab1", "[KBZ] KBZ"], + "key": ["MyTab1", "[KEY] Schlüssel"], + "length": ["MyTab1", "[l] Länge"], + "material": ["MyTab1", "[MAT] Material"], + "data_mask_number": ["MyTab1", "[MSKNR] Datenmaskennummer"], + "on_list": ["MyTab1", "[ON_LST] Auf Liste"], + "pen": ["MyTab1", "[PEN] Schtift"], + "system": ["MyTab1", "[PLANT] Anlage"], + "position": ["MyTab1", "[POS] Position"], + "cross_section": ["MyTab1", "[QS] Querschnitt"], + "connection_type": ["MyTab1", "[RAHMEN]"], + "floor": ["MyTab1", "[STOCK] Stockwerk"], + "version": ["MyTab1", "[VERS] Version"] + } + }, + "AirDuct_transition_rectangular_sym": { + "default_ps": { + "air_type": ["MyTab1", "[ANLAGE] Luftart"], + "height_a": ["MyTab1", "[a] Höhe (a)"], + "designation": ["MyTab1", "[BEZ] Bezeichnung"], + "width_start_b": ["MyTab1", "[b] Breite Anfang (b)"], + "width_end_d": ["MyTab1", "[d] Breite Ende (d)"], + "insert_end": ["MyTab1", "[e] Einschub Ende"], + "insert_start": ["MyTab1", "[f] Einschub Anfang"], + "isolation_type": ["MyTab1", "[ISOL-TYPE] Isolationstyp"], + "isolation": ["MyTab1", "[ISOL] Isolation"], + "edge": ["MyTab1", "[KANTE] Kante"], + "code": ["MyTab1", "[KBZ] KBZ"], + "key": ["MyTab1", "[KEY] Schlüssel"], + "guide_vanes": ["MyTab1", "[LBT] Leitbleche"], + "number_guide_vanes": ["MyTab1", "[LB] Anzahl Leitbleche"], + "material": ["MyTab1", "[MAT] Material"], + "data_mask_number": ["MyTab1", "[MSKNR] Datenmaskennummer"], + "on_list": ["MyTab1", "[ON_LST] Auf Liste"], + "pen": ["MyTab1", "[PEN] Schtift"], + "system": ["MyTab1", "[PLANT] Anlage"], + "position": ["MyTab1", "[POS] Position"], + "cross_section": ["MyTab1", "[QS] Querschnitt"], + "connection_type": ["MyTab1", "[RAHMEN]"], + "inner_radius_is_bend": ["MyTab1", "[RK] Innerradius ist Abkantung"], + "radius": ["MyTab1", "[r] Radius"], + "floor": ["MyTab1", "[STOCK] Stockwerk"], + "version": ["MyTab1", "[VERS] Version"], + "air_velocity": ["MyTab1", "[v] Luftgeschw."], + "angle": ["MyTab1", "[w] Winkel"], + "absolute_roughness_factor": ["Pset_DuctFittingTypeCommon", "AbsoluteRoughnessFactor"] + } + }, + "AirDuct_transition_rectangular": { + "default_ps": { + "air_type": ["MyTab1", "[ANLAGE] Luftart"], + "height_a": ["MyTab1", "[a] Höhe (a)"], + "designation": ["MyTab1", "[BEZ] Bezeichnung"], + "width_start_b": ["MyTab1", "[b] Breite Anfang (b)"], + "width_end_d": ["MyTab1", "[d] Breite Ende (d)"], + "insert_end": ["MyTab1", "[e] Einschub Ende"], + "insert_start": ["MyTab1", "[f] Einschub Anfang"], + "isolation_type": ["MyTab1", "[ISOL-TYPE] Isolationstyp"], + "edge": ["MyTab1", "[KANTE] Kante"], + "code": ["MyTab1", "[KBZ] KBZ"], + "key": ["MyTab1", "[KEY] Schlüssel"], + "guide_vanes": ["MyTab1", "[LBT] Leitbleche"], + "number_guide_vanes": ["MyTab1", "[LB] Anzahl Leitbleche"], + "material": ["MyTab1", "[MAT] Material"], + "data_mask_number": ["MyTab1", "[MSKNR] Datenmaskennummer"], + "height_branch_m": ["MyTab1", "[m] Höhe Abzweig"], + "length_entrance_n": ["MyTab1", "[n] Länge Eingang"], + "second_branch_length_entrance_o": ["MyTab1", "[o] 2. Verzweigung. Länge Eingang"], + "on_list": ["MyTab1", "[ON_LST] Auf Liste"], + "pen": ["MyTab1", "[PEN] Schtift"], + "system": ["MyTab1", "[PLANT] Anlage"], + "position": ["MyTab1", "[POS] Position"], + "second_branch_height_p": ["MyTab1", "[p] 2. Verzweigung. Höhe"], + "cross_section": ["MyTab1", "[QS] Querschnitt"], + "connection_type": ["MyTab1", "[RAHMEN]"], + "inner_radius_is_bend": ["MyTab1", "[RK] Innerradius ist Abkantung"], + "radius": ["MyTab1", "[r] Radius"], + "floor": ["MyTab1", "[STOCK] Stockwerk"], + "strand": ["MyTab1", "[STR] Strang"], + "version": ["MyTab1", "[VERS] Version"], + "air_velocity": ["MyTab1", "[v] Luftgeschw."], + "angle": ["MyTab1", "[w] Winkel"], + "absolute_roughness_factor": ["Pset_DuctFittingTypeCommon", "AbsoluteRoughnessFactor"] + } + }, + "AirValve": { + "default_ps": { + "radius": ["Profile", "Radius"], + "air_type": ["MyTab1", "[ANLAGE] Luftart"], + "designation": ["MyTab1", "[BEZ] Bezeichnung"], + "outlet_grille_graphic": ["MyTab1", "[CG] Auslass-Gitter-Grafik"], + "nominal_diameter_connection": ["MyTab1", "[d1] Anschluss DN"], + "cylinder_1": ["MyTab1", "[DL1] Zyl1: DN1 DN2 Länge"], + "cylinder_2": ["MyTab1", "[DL2] Zyl2: DN1 DN2 Länge"], + "cylinder_3": ["MyTab1", "[DL3] Zyl3: DN1 DN2 Länge"], + "manufacturer": ["MyTab1", "[HST] Hersteller"], + "isolation_type": ["MyTab1", "[ISOL-TYPE] Isolationstyp"], + "edge": ["MyTab1", "[KANTE] Kante"], + "code": ["MyTab1", "[KBZ] KBZ"], + "key": ["MyTab1", "[KEY] Schlüssel"], + "cross_tee": ["MyTab1", "[KR] Kreuzstück"], + "cost_group": ["MyTab1", "[KGR] Kostengruppe"], + "data_mask_number": ["MyTab1", "[MSKNR] Datenmaskennummer"], + "outlet_type": ["MyTab1", "[OT] Typ"], + "pen": ["MyTab1", "[PEN] Schtift"], + "system": ["MyTab1", "[PLANT] Anlage"], + "project": ["MyTab1", "[PRJ] Projekt"], + "connection_type": ["MyTab1", "[RAHMEN]"], + "rings": ["MyTab1", "[RI] Ringe"], + "series": ["MyTab1", "[SER] Serie"], + "floor": ["MyTab1", "[STOCK] Stockwerk"], + "type": ["MyTab1", "[TYP] Typ"], + "version": ["MyTab1", "[VERS] Version"], + "flow_rate": ["MyTab1", "[VOL] Vm3/h"], + "air_velocity": ["MyTab1", "[v] Luftgeschw."] + } + }, + "AirDuct_rectangular_turn": { + "default_ps": { + "radius": ["Profile", "Radius"], + "air_type": ["MyTab1", "[ANLAGE] Luftart"], + "height_a": ["MyTab1", "[a] Höhe (a)"], + "designation": ["MyTab1", "[BEZ] Bezeichnung"], + "width_start_b": ["MyTab1", "[b] Breite Anfang (b)"], + "width_end_d": ["MyTab1", "[d] Breite Ende (d)"], + "width_outlet_d": ["MyTab1", "[d] Breite Ausgang (d)"], + "insert_end": ["MyTab1", "[e] Einschub Ende"], + "insert_start": ["MyTab1", "[f] Einschub Anfang"], + "isolation_type": ["MyTab1", "[ISOL-TYPE] Isolationstyp"], + "isolation": ["MyTab1", "[ISOL] Isolation"], + "edge": ["MyTab1", "[KANTE] Kante"], + "code": ["MyTab1", "[KBZ] KBZ"], + "key": ["MyTab1", "[KEY] Schlüssel"], + "guide_vanes": ["MyTab1", "[LBT] Leitbleche"], + "number_guide_vanes": ["MyTab1", "[LB] Anzahl Leitbleche"], + "material": ["MyTab1", "[MAT] Material"], + "data_mask_number": ["MyTab1", "[MSKNR] Datenmaskennummer"], + "on_list": ["MyTab1", "[ON_LST] Auf Liste"], + "fitting_length": ["MyTab1", "[PE] Passlänge e"], + "pen": ["MyTab1", "[PEN] Schtift"], + "system": ["MyTab1", "[PLANT] Anlage"], + "position": ["MyTab1", "[POS] Position"], + "cross_section": ["MyTab1", "[QS] Querschnitt"], + "connection_type": ["MyTab1", "[RAHMEN]"], + "pipe_connection": ["MyTab1", "[RAHMEN] Rohrverbindung"], + "inner_radius_is_bend": ["MyTab1", "[RK] Innerradius ist Abkantung"], + "radius": ["MyTab1", "[r] Radius"], + "floor": ["MyTab1", "[STOCK] Stockwerk"], + "version": ["MyTab1", "[VERS] Version"], + "air_velocity": ["MyTab1", "[v] Luftgeschw."], + "angle": ["MyTab1", "[w] Winkel"], + "absolute_roughness_factor": ["Pset_DuctFittingTypeCommon", "AbsoluteRoughnessFactor"] + } + }, + "AHU_Fan": { + "default_ps": { + "air_type": ["MyTab1", "[ANLAGE] Luftart"], + "outer_dimension_a": ["MyTab1", "[A] Aussenabmessung A"], + "cross_section_a": ["MyTab1", "[a] Querschnitt a"], + "designation": ["MyTab1", "[BEZ] Bezeichnung"], + "outer_dimension_b": ["MyTab1", "[B] Aussenabmessung B"], + "cross_section_b": ["MyTab1", "[b] Querschnitt b"], + "insert": ["MyTab1", "[e] Einschub"], + "isolation_type": ["MyTab1", "[ISOL-TYPE] Isolationstyp"], + "edge": ["MyTab1", "[KANTE] Kante"], + "code": ["MyTab1", "[KBZ] KBZ"], + "key": ["MyTab1", "[KEY] Schlussel"], + "graphics": ["MyTab1", "[LKFB] Grafik Oben,Unten,Vorne, Hinten..."], + "length": ["MyTab1", "[l] Länge"], + "material": ["MyTab1", "[MAT] Material"], + "data_mask_number": ["MyTab1", "[MSKNR] Datenmaskennummer"], + "on_list": ["MyTab1", "[ON_LST] Auf Liste"], + "pen": ["MyTab1", "[PEN] Schtift"], + "system": ["MyTab1", "[PLANT] Anlage"], + "position": ["MyTab1", "[POS] Position"], + "cross_section": ["MyTab1", "[QS] Querschnitt"], + "floor": ["MyTab1", "[STOCK] Stockwerk"], + "strand": ["MyTab1", "[STR] Strang"], + "version": ["MyTab1", "[VERS] Version"], + "flow_rate": ["MyTab1", "[VS] Vm3/h"] + } + }, + "AHU_AirChamber": { + "default_ps": { + "air_type": ["MyTab1", "[ANLAGE] Luftart"], + "outer_dimension_a": ["MyTab1", "[A] Aussenabmessung A"], + "cross_section_a": ["MyTab1", "[a] Querschnitt a"], + "designation": ["MyTab1", "[BEZ] Bezeichnung"], + "outer_dimension_b": ["MyTab1", "[B] Aussenabmessung B"], + "cross_section_b": ["MyTab1", "[b] Querschnitt b"], + "insert": ["MyTab1", "[e] Einschub"], + "isolation_type": ["MyTab1", "[ISOL-TYPE] Isolationstyp"], + "edge": ["MyTab1", "[KANTE] Kante"], + "code": ["MyTab1", "[KBZ] KBZ"], + "key": ["MyTab1", "[KEY] Schlussel"], + "graphics": ["MyTab1", "[LKFB] Grafik Oben,Unten,Vorne, Hinten..."], + "length": ["MyTab1", "[l] Länge"], + "material": ["MyTab1", "[MAT] Material"], + "data_mask_number": ["MyTab1", "[MSKNR] Datenmaskennummer"], + "on_list": ["MyTab1", "[ON_LST] Auf Liste"], + "pen": ["MyTab1", "[PEN] Schtift"], + "system": ["MyTab1", "[PLANT] Anlage"], + "position": ["MyTab1", "[POS] Position"], + "cross_section": ["MyTab1", "[QS] Querschnitt"], + "floor": ["MyTab1", "[STOCK] Stockwerk"], + "strand": ["MyTab1", "[STR] Strang"], + "version": ["MyTab1", "[VERS] Version"], + "flow_rate": ["MyTab1", "[VS] Vm3/h"] + } + }, + "AHU_Cooler": { + "default_ps": { + "air_type": ["MyTab1", "[ANLAGE] Luftart"], + "outer_dimension_a": ["MyTab1", "[A] Aussenabmessung A"], + "cross_section_a": ["MyTab1", "[a] Querschnitt a"], + "designation": ["MyTab1", "[BEZ] Bezeichnung"], + "outer_dimension_b": ["MyTab1", "[B] Aussenabmessung B"], + "cross_section_b": ["MyTab1", "[b] Querschnitt b"], + "insert": ["MyTab1", "[e] Einschub"], + "isolation_type": ["MyTab1", "[ISOL-TYPE] Isolationstyp"], + "edge": ["MyTab1", "[KANTE] Kante"], + "code": ["MyTab1", "[KBZ] KBZ"], + "key": ["MyTab1", "[KEY] Schlussel"], + "graphics": ["MyTab1", "[LKFB] Grafik Oben,Unten,Vorne, Hinten..."], + "length": ["MyTab1", "[l] Länge"], + "material": ["MyTab1", "[MAT] Material"], + "data_mask_number": ["MyTab1", "[MSKNR] Datenmaskennummer"], + "on_list": ["MyTab1", "[ON_LST] Auf Liste"], + "pen": ["MyTab1", "[PEN] Schtift"], + "system": ["MyTab1", "[PLANT] Anlage"], + "position": ["MyTab1", "[POS] Position"], + "cross_section": ["MyTab1", "[QS] Querschnitt"], + "floor": ["MyTab1", "[STOCK] Stockwerk"], + "strand": ["MyTab1", "[STR] Strang"], + "version": ["MyTab1", "[VERS] Version"], + "flow_rate": ["MyTab1", "[VS] Vm3/h"] + } + }, + "AHU_Silencer": { + "default_ps": { + "air_type": ["MyTab1", "[ANLAGE] Luftart"], + "outer_dimension_a": ["MyTab1", "[A] Aussenabmessung A"], + "cross_section_a": ["MyTab1", "[a] Querschnitt a"], + "designation": ["MyTab1", "[BEZ] Bezeichnung"], + "outer_dimension_b": ["MyTab1", "[B] Aussenabmessung B"], + "cross_section_b": ["MyTab1", "[b] Querschnitt b"], + "insert": ["MyTab1", "[e] Einschub"], + "isolation_type": ["MyTab1", "[ISOL-TYPE] Isolationstyp"], + "edge": ["MyTab1", "[KANTE] Kante"], + "code": ["MyTab1", "[KBZ] KBZ"], + "key": ["MyTab1", "[KEY] Schlussel"], + "graphics": ["MyTab1", "[LKFB] Grafik Oben,Unten,Vorne, Hinten..."], + "length": ["MyTab1", "[l] Länge"], + "material": ["MyTab1", "[MAT] Material"], + "data_mask_number": ["MyTab1", "[MSKNR] Datenmaskennummer"], + "on_list": ["MyTab1", "[ON_LST] Auf Liste"], + "pen": ["MyTab1", "[PEN] Schtift"], + "system": ["MyTab1", "[PLANT] Anlage"], + "position": ["MyTab1", "[POS] Position"], + "cross_section": ["MyTab1", "[QS] Querschnitt"], + "floor": ["MyTab1", "[STOCK] Stockwerk"], + "strand": ["MyTab1", "[STR] Strang"], + "version": ["MyTab1", "[VERS] Version"], + "flow_rate": ["MyTab1", "[VS] Vm3/h"] + } } -} \ No newline at end of file +} diff --git a/bim2sim/elements/hvac_elements.py b/bim2sim/elements/hvac_elements.py index 846f86962c..1fd9c2b76d 100644 --- a/bim2sim/elements/hvac_elements.py +++ b/bim2sim/elements/hvac_elements.py @@ -1314,6 +1314,10 @@ def expected_hvac_ports(self): class Duct(HVACProduct): ifc_types = {"IfcDuctSegment": ['*', 'RIGIDSEGMENT', 'FLEXIBLESEGMENT']} + @property + def expected_hvac_ports(self): + return (2, float('inf')) + pattern_ifc_type = [ re.compile('Duct.?segment', flags=re.IGNORECASE) ] @@ -1335,6 +1339,10 @@ class DuctFitting(HVACProduct): 'OBSTRUCTION', 'TRANSITION'] } + @property + def expected_hvac_ports(self): + return (0, float('inf')) + pattern_ifc_type = [ re.compile('Duct.?fitting', flags=re.IGNORECASE) ] @@ -1355,6 +1363,10 @@ class AirTerminal(HVACProduct): ['*', 'DIFFUSER', 'GRILLE', 'LOUVRE', 'REGISTER'] } + @property + def expected_hvac_ports(self): + return (1, 2) + pattern_ifc_type = [ re.compile('Air.?terminal', flags=re.IGNORECASE) ] From 24dc90a0a6d098673189a2a1ac2f73fa11152aea Mon Sep 17 00:00:00 2001 From: Hoepp J <155448650+HoeppJ@users.noreply.github.com> Date: Wed, 19 Nov 2025 16:57:56 +0100 Subject: [PATCH 109/125] add example for testing --- .../e1_simple_project_hvac_aixlib_testing.py | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 bim2sim/plugins/PluginAixLib/bim2sim_aixlib/examples/e1_simple_project_hvac_aixlib_testing.py diff --git a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/examples/e1_simple_project_hvac_aixlib_testing.py b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/examples/e1_simple_project_hvac_aixlib_testing.py new file mode 100644 index 0000000000..f2bd5f7e89 --- /dev/null +++ b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/examples/e1_simple_project_hvac_aixlib_testing.py @@ -0,0 +1,79 @@ +import tempfile +from pathlib import Path + +import bim2sim +from bim2sim import Project, run_project, ConsoleDecisionHandler +from bim2sim.utilities.types import IFCDomain + + +def run_example_simple_hvac_aixlib(): + """Run an HVAC simulation with the AixLib backend. + + This example runs an HVAC with the aixlib backend. Specifies project + directory and location of the HVAC IFC file. Then, it creates a bim2sim + project with the aixlib backend. Simulation settings are specified (here, + the aggregations are specified), before the project is executed with the + previously specified settings.""" + + # Create a temp directory for the project, feel free to use a "normal" + # directory + project_path = Path( + tempfile.TemporaryDirectory( + prefix='bim2sim_example_simple_aixlib').name) + + + # Set path of ifc for hydraulic domain with the fresh downloaded test models + ifc_paths = { + IFCDomain.ventilation: + Path(r"D:\03_Cloud\Sciebo\BIM2Praxis\IFC-Modelle\EDGE\ueberarbeitet_2025-10\BIM2PRAXIS_RLT-2025-09-11_cleaned_jho.ifc") + } + # Create a project including the folder structure for the project with + # teaser as backend and no specified workflow (default workflow is taken) + project = Project.create(project_path, ifc_paths, 'aixlib') + + # set weather file data + project.sim_settings.weather_file_path_modelica = ( + Path(bim2sim.__file__).parent.parent / + 'test/resources/weather_files/DEU_NW_Aachen.105010_TMYx.mos') + + # specify simulation settings + project.sim_settings.aggregations = [ + 'UnderfloorHeating', + 'Consumer', + 'PipeStrand', + 'ParallelPump', + 'ConsumerHeatingDistributorModule', + 'GeneratorOneFluid' + ] + project.sim_settings.group_unidentified = 'name_and_description' + + # Run the project with the ConsoleDecisionHandler. This allows interactive + # input to answer upcoming questions regarding the imported IFC. + # Correct decision for identification of elements and useful parameters for + # missing attributes are written below + run_project(project, ConsoleDecisionHandler()) + + +# Documentation of Decision answers: +# IfcBuildingElementProxy: skip + +# IfcPipeFitting, Name: Heizungsarmatur, Description: Ventilgehäuse DG mit +# Fühler, GUID: 0$QIFdmTARhKaMBTfJUvWD: 'HVAC-PipeFitting' + +# IfcPipeFitting, Name: Heizungsarmatur, Description: Rücklaufverschraubung +# DG, GUID: 03TbBCNszVXaBWMuR55Ezt: 'HVAC-PipeFitting' + +# IfcPipeFitting, Name: Apparate (M_606), Description: Apparate (M_606), +# GUID: 1259naiEpIkasmH4NcC8DL: 'HVAC-Distributor', + +# IfcValve, Name: Armatur/Flansch/Dichtung M_600, Description: +# 3-Wege-Regelventil PN16, GUID: 0A4aE_Sb7Wa4OrZ9Zwd6P3: 'HVAC-ThreeWayValve' + +# IfcValve, Name: Armatur/Flansch/Dichtung M_600, Description: Hubventil, +# GUID: 1CczU6h2kUYa3KDyi1bJj6: 'HVAC-Valve' + +# True * 6 + + +if __name__ == '__main__': + run_example_simple_hvac_aixlib() From 559bfaa94b3fe59b2f58750e030adce25e0e75d2 Mon Sep 17 00:00:00 2001 From: Hoepp J <155448650+HoeppJ@users.noreply.github.com> Date: Wed, 19 Nov 2025 18:48:15 +0100 Subject: [PATCH 110/125] add first ventilation elements to hvac_elements --- .../assets/finder/template_LuArtX_Carf.json | 96 ++ bim2sim/elements/hvac_elements.py | 1213 ++++++++++++++++- .../e1_simple_project_hvac_aixlib_testing.py | 13 +- 3 files changed, 1275 insertions(+), 47 deletions(-) diff --git a/bim2sim/assets/finder/template_LuArtX_Carf.json b/bim2sim/assets/finder/template_LuArtX_Carf.json index 8d37b30f12..2b3c105635 100644 --- a/bim2sim/assets/finder/template_LuArtX_Carf.json +++ b/bim2sim/assets/finder/template_LuArtX_Carf.json @@ -506,6 +506,102 @@ "absolute_roughness_factor": ["Pset_DuctFittingTypeCommon", "AbsoluteRoughnessFactor"] } }, + "PipeDuct_round": { + "default_ps": { + "air_type": ["MyTab1", "[ANLAGE] Luftart"], + "width_b": ["MyTab1", "[a] Breitendurchmesser B"], + "designation": ["MyTab1", "[BEZ] Bezeichnung (IS)"], + "nominal_diameter_d1": ["MyTab1", "[d1] DN"], + "isolation_type": ["MyTab1", "[ISOL-TYPE] Isolationstyp"], + "edge": ["MyTab1", "[KANTE] Kante"], + "code": ["MyTab1", "[KBZ] KBZ"], + "key": ["MyTab1", "[KEY] Schlüssel"], + "length": ["MyTab1", "[l] Länge"], + "material": ["MyTab1", "[MAT] Material"], + "data_mask_number": ["MyTab1", "[MSKNR] Datenmaskennummer"], + "on_list": ["MyTab1", "[ON_LST] Auf Liste"], + "pen": ["MyTab1", "[PEN] Schtift"], + "system": ["MyTab1", "[PLANT] Anlage"], + "position": ["MyTab1", "[POS] Position"], + "cross_section": ["MyTab1", "[QS] Querschnitt"], + "connection_type": ["MyTab1", "[RAHMEN] Rahmen"], + "floor": ["MyTab1", "[STOCK] Stockwerk"], + "version": ["MyTab1", "[VERS] Version"], + "air_velocity": ["MyTab1", "[v] Luftgeschw."] + } + }, + "AirDuct_oval": { + "default_ps": { + "air_type": ["MyTab1", "[ANLAGE] Luftart"], + "width_b": ["MyTab1", "[a] Breitendurchmesser B"], + "designation": ["MyTab1", "[BEZ] Bezeichnung (IS)"], + "height_h": ["MyTab1", "[b] Höhendurchmesser H"], + "isolation_type": ["MyTab1", "[ISOL-TYPE] Isolationstyp"], + "edge": ["MyTab1", "[KANTE] Kante"], + "code": ["MyTab1", "[KBZ] KBZ"], + "key": ["MyTab1", "[KEY] Schlüssel"], + "reference_edge": ["MyTab1", "[K] Bezugskante"], + "length": ["MyTab1", "[l] Länge"], + "material": ["MyTab1", "[MAT] Material"], + "data_mask_number": ["MyTab1", "[MSKNR] Datenmaskennummer"], + "on_list": ["MyTab1", "[ON_LST] Auf Liste"], + "pen": ["MyTab1", "[PEN] Schtift"], + "system": ["MyTab1", "[PLANT] Anlage"], + "position": ["MyTab1", "[POS] Position"], + "cross_section": ["MyTab1", "[QS] Querschnitt"], + "connection_type": ["MyTab1", "[RAHMEN] Rahmen"], + "floor": ["MyTab1", "[STOCK] Stockwerk"], + "version": ["MyTab1", "[VERS] Version"], + "air_velocity": ["MyTab1", "[v] Luftgeschw."] + } + }, + "Bend_round": { + "default_ps": { + "radius": ["Profile", "Radius"], + "air_type": ["MyTab1", "[ANLAGE] Luftart"], + "designation": ["MyTab1", "[BEZ] Bezeichnung (IS)"], + "nominal_diameter_d1": ["MyTab1", "[d1] DN"], + "neutral_axis": ["MyTab1", "[l] Neutrale Fase"], + "insertion_length": ["MyTab1", "[le2] Einstedekaenge"], + "material": ["MyTab1", "[MAT] Material"], + "data_mask_number": ["MyTab1", "[MSKNR] Datenmaskennummer"], + "on_list": ["MyTab1", "[ON_LST] Auf Liste"], + "pen": ["MyTab1", "[PEN] Schtift"], + "system": ["MyTab1", "[PLANT] Anlage"], + "position": ["MyTab1", "[POS] Position"], + "connection_type": ["MyTab1", "[N] Bogen Radius"], + "floor": ["MyTab1", "[STOCK] Stockwerk"], + "version": ["MyTab1", "[VERS] Version"], + "air_velocity": ["MyTab1", "[v] Luftgeschw."], + "angle": ["MyTab1", "[w] Winkel"], + "length": ["Pset_DuctSegmentTypeCommon", "Length"] + } + }, + "Reduction_concentric": { + "default_ps": { + "air_type": ["MyTab1", "[ANLAGE] Luftart"], + "designation": ["MyTab1", "[BEZ] Bezeichnung (IS)"], + "nominal_diameter_d1": ["MyTab1", "[d1] DN1"], + "nominal_diameter_d2": ["MyTab1", "[d2] DN2"], + "isolation_type": ["MyTab1", "[ISOL-TYPE] Isolationstyp"], + "edge": ["MyTab1", "[KANTE] Kante"], + "code": ["MyTab1", "[KBZ] KBZ"], + "key": ["MyTab1", "[KEY] Schlüssel"], + "insertion_length": ["MyTab1", "[le2] Einstedekaenge"], + "length": ["MyTab1", "[l] Länge"], + "material": ["MyTab1", "[MAT] Material"], + "data_mask_number": ["MyTab1", "[MSKNR] Datenmaskennummer"], + "on_list": ["MyTab1", "[ON_LST] Auf Liste"], + "pen": ["MyTab1", "[PEN] Schtift"], + "system": ["MyTab1", "[PLANT] Anlage"], + "position": ["MyTab1", "[POS] Position"], + "connection_type": ["MyTab1", "[RAHMEN] Verbindungsart"], + "floor": ["MyTab1", "[STOCK] Stockwerk"], + "type": ["MyTab1", "[TYP] Typ"], + "version": ["MyTab1", "[VERS] Version"], + "air_velocity": ["MyTab1", "[v] Luftgeschw."] + } + }, "AirValve": { "default_ps": { "radius": ["Profile", "Radius"], diff --git a/bim2sim/elements/hvac_elements.py b/bim2sim/elements/hvac_elements.py index 1fd9c2b76d..6cfc3ee496 100644 --- a/bim2sim/elements/hvac_elements.py +++ b/bim2sim/elements/hvac_elements.py @@ -1310,7 +1310,56 @@ class ThreeWayValve(Valve): def expected_hvac_ports(self): return 3 +class Medium(HVACProduct): + # is deprecated? + ifc_types = {"IfcDistributionSystem": ['*']} + pattern_ifc_type = [ + re.compile('Medium', flags=re.IGNORECASE) + ] + + @property + def expected_hvac_ports(self): + return 0 + + +class CHP(HVACProduct): + ifc_types = {'IfcElectricGenerator': ['CHP']} + + @property + def expected_hvac_ports(self): + return 2 + + def is_generator(self): + return True + + rated_power = attribute.Attribute( + default_ps=('Pset_ElectricGeneratorTypeCommon', 'MaximumPowerOutput'), + description="Rated power of CHP", + patterns=[ + re.compile('.*Nennleistung', flags=re.IGNORECASE), + re.compile('.*capacity', flags=re.IGNORECASE), + ], + unit=ureg.kilowatt, + ) + + efficiency = attribute.Attribute( + default_ps=( + 'Pset_ElectricGeneratorTypeCommon', 'ElectricGeneratorEfficiency'), + description="Electric efficiency of CHP", + patterns=[ + re.compile('.*electric.*efficiency', flags=re.IGNORECASE), + re.compile('.*el.*efficiency', flags=re.IGNORECASE), + ], + unit=ureg.dimensionless, + ) + + # water_volume = attribute.Attribute( + # description="Water volume CHP chp", + # unit=ureg.meter ** 3, + # ) +# Old ventilation classes +""" class Duct(HVACProduct): ifc_types = {"IfcDuctSegment": ['*', 'RIGIDSEGMENT', 'FLEXIBLESEGMENT']} @@ -1378,63 +1427,1139 @@ def expected_hvac_ports(self): def is_consumer(self): return True +""" + +class VentilationElement(HVACProduct): + """Common properties for all ventilation elements""" + air_type = attribute.Attribute( + description='Air type', + ) + system = attribute.Attribute( + description='System', + ) + position = attribute.Attribute( + description='Position', + ) + floor = attribute.Attribute( + description='Floor', + ) + version = attribute.Attribute( + description='Version', + ) + isolation_type = attribute.Attribute( + description='Insulation type', + ) + edge = attribute.Attribute( + description='Edge', + ) + data_mask_number = attribute.Attribute( + description='Data mask number', + ) + on_list = attribute.Attribute( + description='On list', + ) + pen = attribute.Attribute( + description='Pen', + ) + material = attribute.Attribute( + description='Material', + ) + designation = attribute.Attribute( + description='Designation', + ) -class Medium(HVACProduct): - # is deprecated? - ifc_types = {"IfcDistributionSystem": ['*']} +# ============================================================================ +# AIR DUCT FAMILY +# ============================================================================ + +class AirDuct(VentilationElement): + """Base class for all air ducts""" + + ifc_types = { + 'IfcDuctSegment': ['*'] + } + + length = attribute.Attribute( + description='Length', + unit=ureg.millimeter, + ) + air_velocity = attribute.Attribute( + description='Air velocity', + unit=ureg.meter / ureg.second, + ) + cross_section = attribute.Attribute( + description='Cross section', + ) + reference_edge = attribute.Attribute( + description='Reference edge', + ) + + +class RectangularDuct(AirDuct): + """Rectangular air ducts""" + pattern_ifc_type = [ - re.compile('Medium', flags=re.IGNORECASE) + re.compile('Kanal', flags=re.IGNORECASE), ] - @property - def expected_hvac_ports(self): - return 0 + height_a = attribute.Attribute( + description='Height a', + unit=ureg.millimeter, + ) + width_b = attribute.Attribute( + description='Width b', + unit=ureg.millimeter, + ) + cross_section_area = attribute.Attribute( + description='Cross section area', + unit=ureg.millimeter ** 2, + ) + isolation = attribute.Attribute( + description='Insulation thickness', + unit=ureg.millimeter, + ) +class OvalDuct(AirDuct): + """Oval air ducts (Ovalrohr)""" -class CHP(HVACProduct): - ifc_types = {'IfcElectricGenerator': ['CHP']} + pattern_ifc_type = [ + # re.compile(r'Ovalrohr\s+Bogen', flags=re.IGNORECASE), + # re.compile(r'Ovalrohr\s+Bogen(?:\s*\(\d+\))?', flags=re.IGNORECASE), + re.compile(r'Ovalrohr\s+Bogen.*\(171\)', flags=re.IGNORECASE), + ] - @property - def expected_hvac_ports(self): - return 2 + width_b = attribute.Attribute( + description='Width diameter B', + unit=ureg.millimeter, + ) + height_h = attribute.Attribute( + description='Height diameter H', + unit=ureg.millimeter, + ) - def is_generator(self): - return True +class RoundDuct(AirDuct): + """Round/circular air ducts""" - rated_power = attribute.Attribute( - default_ps=('Pset_ElectricGeneratorTypeCommon', 'MaximumPowerOutput'), - description="Rated power of CHP", - patterns=[ - re.compile('.*Nennleistung', flags=re.IGNORECASE), - re.compile('.*capacity', flags=re.IGNORECASE), - ], - unit=ureg.kilowatt, + pattern_ifc_type = [ + re.compile('Rohr', flags=re.IGNORECASE), + ] + + radius = attribute.Attribute( + description='Radius', + unit=ureg.millimeter, + ) + nominal_diameter_d1 = attribute.Attribute( + description='Nominal diameter DN', + unit=ureg.millimeter, + ) + cross_section_d = attribute.Attribute( + description='Cross section diameter', + unit=ureg.millimeter, + ) + pipe_connection = attribute.Attribute( + description='Pipe connection type', + ) + list_round = attribute.Attribute( + description='On round list', ) - efficiency = attribute.Attribute( - default_ps=( - 'Pset_ElectricGeneratorTypeCommon', 'ElectricGeneratorEfficiency'), - description="Electric efficiency of CHP", - patterns=[ - re.compile('.*electric.*efficiency', flags=re.IGNORECASE), - re.compile('.*el.*efficiency', flags=re.IGNORECASE), - ], - unit=ureg.dimensionless, + +class FlexibleRoundDuct(RoundDuct): + """Flexible round air ducts""" + + pattern_ifc_type = [ + re.compile('Flexibles_Rohr', flags=re.IGNORECASE), + ] + + rings = attribute.Attribute( + description='Number of rings', ) - # water_volume = attribute.Attribute( - # description="Water volume CHP chp", - # unit=ureg.meter ** 3, - # ) +class DuctRoundTransition(RoundDuct): + """Round transition ducts (Rohrübergang Asym.)""" + pattern_ifc_type = [ + re.compile(r'Rohrübergang\s*\(\s*RS\s*,\s*RA\s*:\s*109\s*\)', flags=re.IGNORECASE), + ] -# collect all domain classes -items: Set[HVACProduct] = set() -for name, cls in inspect.getmembers( - sys.modules[__name__], - lambda member: inspect.isclass(member) # class at all - and issubclass(member, HVACProduct) # domain subclass - and member is not HVACProduct # but not base class - and member.__module__ == __name__): # declared here - items.add(cls) + width_b = attribute.Attribute( + description='Width diameter B', + unit=ureg.millimeter, + ) + connection_type = attribute.Attribute( + description='Connection type/frame', + ) + + +# ============================================================================ +# AIR DUCT FITTINGS +# ============================================================================ + +class AirDuctFitting(VentilationElement): + """Base class for all air duct fittings""" + + ifc_types = { + 'IfcDuctFitting': ['*'] + } + + length = attribute.Attribute( + description='Length', + unit=ureg.millimeter, + ) + manufacturer = attribute.Attribute( + description='Manufacturer', + ) + type = attribute.Attribute( + description='Type', + ) + insert = attribute.Attribute( + description='Insert dimension', + unit=ureg.millimeter, + ) + + +class RectangularDuctFitting(AirDuctFitting): + """Base class for rectangular duct fittings""" + + outer_dimension_a = attribute.Attribute( + description='Outer dimension A', + unit=ureg.millimeter, + ) + cross_section_a = attribute.Attribute( + description='Cross section a', + unit=ureg.millimeter, + ) + outer_dimension_b = attribute.Attribute( + description='Outer dimension B', + unit=ureg.millimeter, + ) + cross_section_b = attribute.Attribute( + description='Cross section b', + unit=ureg.millimeter, + ) + label_swap_ab = attribute.Attribute( + description='Swap label a/b', + ) + isolation = attribute.Attribute( + description='Insulation thickness', + unit=ureg.millimeter, + ) + cross_section = attribute.Attribute( + description='Cross section', + ) + + +class RectangularDuctExtra(RectangularDuctFitting): + """Rectangular duct components (Komponente eckig)""" + + pattern_ifc_type = [ + re.compile('Komponente eckig', flags=re.IGNORECASE), + ] + + +class RoundDuctFitting(AirDuctFitting): + """Base class for round duct fittings""" + + pass + + +class RoundDuctExtra(RoundDuctFitting): + """Round duct components (Komponente rund)""" + + pattern_ifc_type = [ + re.compile('Komponente rund', flags=re.IGNORECASE), + ] + +class PipeDuctRound(RoundDuctFitting): + """Round pipe ducts (Rohrübergang Asym.)""" + + pattern_ifc_type = [ + re.compile('Rohrübergang', flags=re.IGNORECASE), + ] + + width_b = attribute.Attribute( + description='Width diameter B', + unit=ureg.millimeter, + ) + connection_type = attribute.Attribute( + description='Connection type/frame', + ) + designation_is = attribute.Attribute( + description='Designation (IS)', + ) + nominal_diameter_d1 = attribute.Attribute( + description='Nominal diameter DN', + unit=ureg.millimeter, + ) + +class RoundBend(RoundDuctFitting): + """Round duct bends (Bogen (rund))""" + + pattern_ifc_type = [ + re.compile('Bogen (rund, 116)', flags=re.IGNORECASE), + ] + + radius = attribute.Attribute( + description='Radius', + unit=ureg.millimeter, + ) + designation_is = attribute.Attribute( + description='Designation (IS)', + ) + nominal_diameter_d1 = attribute.Attribute( + description='Nominal diameter DN', + unit=ureg.millimeter, + ) + neutral_axis = attribute.Attribute( + description='Neutral axis', + unit=ureg.millimeter, + ) + insertion_length = attribute.Attribute( + description='Insertion length', + unit=ureg.millimeter, + ) + connection_type = attribute.Attribute( + description='Bend radius connection', + ) + angle = attribute.Attribute( + description='Angle', + unit=ureg.degree, + ) + air_velocity = attribute.Attribute( + description='Air velocity', + unit=ureg.meter / ureg.second, + ) + length = attribute.Attribute( + description='Length', + unit=ureg.millimeter, + ) + +class ConcentricReduction(RoundDuctFitting): + """Concentric reductions (Reduktion_konzentrisch)""" + + pattern_ifc_type = [ + re.compile('Reduktion.*konzentrisch', flags=re.IGNORECASE), + ] + + designation_is = attribute.Attribute( + description='Designation (IS)', + ) + nominal_diameter_d1 = attribute.Attribute( + description='Nominal diameter DN1', + unit=ureg.millimeter, + ) + nominal_diameter_d2 = attribute.Attribute( + description='Nominal diameter DN2', + unit=ureg.millimeter, + ) + insertion_length = attribute.Attribute( + description='Insertion length', + unit=ureg.millimeter, + ) + length = attribute.Attribute( + description='Length', + unit=ureg.millimeter, + ) + connection_type = attribute.Attribute( + description='Connection type', + ) + type = attribute.Attribute( + description='Type', + ) + air_velocity = attribute.Attribute( + description='Air velocity', + unit=ureg.meter / ureg.second, + ) + +class DuctTransition(AirDuctFitting): + """Base class for duct transitions""" + + air_velocity = attribute.Attribute( + description='Air velocity', + unit=ureg.meter / ureg.second, + ) + flow_rate = attribute.Attribute( + description='Flow rate', + unit=ureg.meter ** 3 / ureg.hour, + ) + strand = attribute.Attribute( + description='Strand', + ) + + +class RectangularTransition(DuctTransition): + """Rectangular duct transitions (Übergang Asym.)""" + + pattern_ifc_type = [ + re.compile('Übergang Asym', flags=re.IGNORECASE), + re.compile('Übergangsbogen', flags=re.IGNORECASE), + ] + + height_a = attribute.Attribute( + description='Height a', + unit=ureg.millimeter, + ) + width_start_b = attribute.Attribute( + description='Width start b', + unit=ureg.millimeter, + ) + height_end_c = attribute.Attribute( + description='Height end c', + unit=ureg.millimeter, + ) + width_end_d = attribute.Attribute( + description='Width end d', + unit=ureg.millimeter, + ) + e_offset = attribute.Attribute( + description='E-offset', + unit=ureg.millimeter, + ) + f_offset = attribute.Attribute( + description='F-offset', + unit=ureg.millimeter, + ) + insert_start = attribute.Attribute( + description='Insert start', + unit=ureg.millimeter, + ) + extension_m = attribute.Attribute( + description='Extension m', + unit=ureg.millimeter, + ) + extension_n = attribute.Attribute( + description='Extension n', + unit=ureg.millimeter, + ) + box_lbh = attribute.Attribute( + description='Box length-width-height', + ) + box_bottom_lbh = attribute.Attribute( + description='Box bottom length-width-height', + ) + connection_length_height = attribute.Attribute( + description='Connection length height', + ) + list_as_ua = attribute.Attribute( + description='List as UA', + ) + isolation = attribute.Attribute( + description='Insulation thickness', + unit=ureg.millimeter, + ) + cross_section = attribute.Attribute( + description='Cross section', + ) + absolute_roughness_factor = attribute.Attribute( + description='Absolute roughness factor', + ) + + +class SymmetricalTransition(DuctTransition): + """Symmetrical duct transitions (Bogen Sym.)""" + + pattern_ifc_type = [ + re.compile('Bogen Sym', flags=re.IGNORECASE), + ] + + height_a = attribute.Attribute( + description='Height a', + unit=ureg.millimeter, + ) + width_start_b = attribute.Attribute( + description='Width start b', + unit=ureg.millimeter, + ) + width_end_d = attribute.Attribute( + description='Width end d', + unit=ureg.millimeter, + ) + insert_end = attribute.Attribute( + description='Insert end', + unit=ureg.millimeter, + ) + insert_start = attribute.Attribute( + description='Insert start', + unit=ureg.millimeter, + ) + guide_vanes = attribute.Attribute( + description='Guide vanes', + ) + number_guide_vanes = attribute.Attribute( + description='Number of guide vanes', + ) + radius = attribute.Attribute( + description='Radius', + unit=ureg.millimeter, + ) + inner_radius_is_bend = attribute.Attribute( + description='Inner radius is bend', + ) + angle = attribute.Attribute( + description='Angle', + unit=ureg.degree, + ) + isolation = attribute.Attribute( + description='Insulation thickness', + unit=ureg.millimeter, + ) + cross_section = attribute.Attribute( + description='Cross section', + ) + absolute_roughness_factor = attribute.Attribute( + description='Absolute roughness factor', + ) + + +class RectangularElbow(AirDuctFitting): + """Rectangular elbows/bends (Winkel)""" + + pattern_ifc_type = [ + re.compile('Winkel', flags=re.IGNORECASE), + ] + + radius = attribute.Attribute( + description='Radius', + unit=ureg.millimeter, + ) + height_a = attribute.Attribute( + description='Height a', + unit=ureg.millimeter, + ) + width_start_b = attribute.Attribute( + description='Width start b', + unit=ureg.millimeter, + ) + width_end_d = attribute.Attribute( + description='Width end d', + unit=ureg.millimeter, + ) + width_outlet_d = attribute.Attribute( + description='Width outlet d', + unit=ureg.millimeter, + ) + insert_end = attribute.Attribute( + description='Insert end', + unit=ureg.millimeter, + ) + insert_start = attribute.Attribute( + description='Insert start', + unit=ureg.millimeter, + ) + guide_vanes = attribute.Attribute( + description='Guide vanes', + ) + number_guide_vanes = attribute.Attribute( + description='Number of guide vanes', + ) + inner_radius_is_bend = attribute.Attribute( + description='Inner radius is bend', + ) + angle = attribute.Attribute( + description='Angle', + unit=ureg.degree, + ) + fitting_length = attribute.Attribute( + description='Fitting length', + unit=ureg.millimeter, + ) + pipe_connection = attribute.Attribute( + description='Pipe connection', + ) + isolation = attribute.Attribute( + description='Insulation thickness', + unit=ureg.millimeter, + ) + cross_section = attribute.Attribute( + description='Cross section', + ) + air_velocity = attribute.Attribute( + description='Air velocity', + unit=ureg.meter / ureg.second, + ) + absolute_roughness_factor = attribute.Attribute( + description='Absolute roughness factor', + ) + + +class DuctEndCap(AirDuctFitting): + """Duct end caps (Endboden)""" + + pattern_ifc_type = [ + re.compile('Endboden', flags=re.IGNORECASE), + ] + + width_a = attribute.Attribute( + description='Width a', + unit=ureg.millimeter, + ) + height_b = attribute.Attribute( + description='Height b', + unit=ureg.millimeter, + ) + isolation = attribute.Attribute( + description='Insulation thickness', + unit=ureg.millimeter, + ) + cross_section = attribute.Attribute( + description='Cross section', + ) + + +class DuctTee(AirDuctFitting): + """T-pieces and branches (T-Stück)""" + + pattern_ifc_type = [ + re.compile('T-Stück (rund, 118)', flags=re.IGNORECASE), + ] + + width_entrance_a = attribute.Attribute( + description='Width entrance a', + unit=ureg.millimeter, + ) + width_start_b = attribute.Attribute( + description='Width start b', + unit=ureg.millimeter, + ) + height_entrance_b = attribute.Attribute( + description='Height entrance b', + unit=ureg.millimeter, + ) + width_outlet_d = attribute.Attribute( + description='Width outlet d', + unit=ureg.millimeter, + ) + width_branch_g = attribute.Attribute( + description='Width branch g', + unit=ureg.millimeter, + ) + height_branch_h = attribute.Attribute( + description='Height branch h', + unit=ureg.millimeter, + ) + height_branch_m = attribute.Attribute( + description='Height branch m', + unit=ureg.millimeter, + ) + length_entrance_n = attribute.Attribute( + description='Length entrance n', + unit=ureg.millimeter, + ) + second_branch_length_o = attribute.Attribute( + description='Second branch length o', + unit=ureg.millimeter, + ) + second_branch_height_p = attribute.Attribute( + description='Second branch height p', + unit=ureg.millimeter, + ) + cross_tee = attribute.Attribute( + description='Cross tee', + ) + radius = attribute.Attribute( + description='Radius', + unit=ureg.millimeter, + ) + cross_section = attribute.Attribute( + description='Cross section', + ) + air_velocity = attribute.Attribute( + description='Air velocity', + unit=ureg.meter / ureg.second, + ) + absolute_roughness_factor = attribute.Attribute( + description='Absolute roughness factor', + ) + + +# ============================================================================ +# DAMPERS AND CONTROLLERS +# ============================================================================ + +class Damper(VentilationElement): + """Base class for dampers""" + + ifc_types = { + 'IfcDamper': ['*'] + } + + manufacturer = attribute.Attribute( + description='Manufacturer', + ) + type = attribute.Attribute( + description='Type', + ) + insert = attribute.Attribute( + description='Insert dimension', + unit=ureg.millimeter, + ) + cross_section = attribute.Attribute( + description='Cross section', + ) + + +class FireDamper(Damper): + """Fire dampers (Brandschutzklappe)""" + + pattern_ifc_type = [ + re.compile('Brandschutzklappe', flags=re.IGNORECASE), + ] + + flow_velocity = attribute.Attribute( + description='Flow velocity', + unit=ureg.meter / ureg.second, + ) + drive_side = attribute.Attribute( + description='Drive side', + ) + remark = attribute.Attribute( + description='Remark', + ) + drive = attribute.Attribute( + description='Drive/actuator', + ) + outer_dimension_a = attribute.Attribute( + description='Outer dimension A', + unit=ureg.millimeter, + ) + cross_section_a = attribute.Attribute( + description='Cross section a', + unit=ureg.millimeter, + ) + designation_is = attribute.Attribute( + description='Designation (IS)', + ) + outer_dimension_b = attribute.Attribute( + description='Outer dimension B', + unit=ureg.millimeter, + ) + cross_section_b = attribute.Attribute( + description='Cross section b', + unit=ureg.millimeter, + ) + label_swap_ab = attribute.Attribute( + description='Swap label a/b', + ) + pressure_loss_opening_1 = attribute.Attribute( + description='Pressure loss opening 1', + unit=ureg.pascal, + ) + length = attribute.Attribute( + description='Length', + unit=ureg.millimeter, + ) + air_volume = attribute.Attribute( + description='Air volume', + unit=ureg.meter ** 3 / ureg.hour, + ) + zeta_opening_1 = attribute.Attribute( + description='Zeta opening 1', + ) + + +class VolumeFlowController(VentilationElement): + """Base class for volume flow controllers""" + + flow_velocity = attribute.Attribute( + description='Flow velocity', + unit=ureg.meter / ureg.second, + ) + manufacturer = attribute.Attribute( + description='Manufacturer', + ) + type = attribute.Attribute( + description='Type', + ) + insert = attribute.Attribute( + description='Insert dimension', + unit=ureg.millimeter, + ) + length = attribute.Attribute( + description='Length', + unit=ureg.millimeter, + ) + + +class ConstantVolumeFlowController(VolumeFlowController): + """Constant volume flow controllers (Konstant VSR)""" + + ifc_types = { + 'IfcFlowController': ['*'] + } + + pattern_ifc_type = [ + re.compile('Konstant VSR', flags=re.IGNORECASE), + ] + + radius = attribute.Attribute( + description='Radius', + unit=ureg.millimeter, + ) + designation_is = attribute.Attribute( + description='Designation (IS)', + ) + nominal_diameter_d1 = attribute.Attribute( + description='Nominal diameter DN', + unit=ureg.millimeter, + ) + outer_diameter = attribute.Attribute( + description='Outer diameter', + unit=ureg.millimeter, + ) + air_volume = attribute.Attribute( + description='Air volume', + unit=ureg.meter ** 3 / ureg.hour, + ) + + +class DynamicVolumeFlowController(VolumeFlowController): + """Dynamic/variable volume flow controllers (Variabel VSR)""" + + ifc_types = { + 'IfcDuctFitting': ['*'] + } + + pattern_ifc_type = [ + re.compile('Variabel VSR', flags=re.IGNORECASE), + ] + + outer_dimension_a = attribute.Attribute( + description='Outer dimension A', + unit=ureg.millimeter, + ) + cross_section_a = attribute.Attribute( + description='Cross section a', + unit=ureg.millimeter, + ) + outer_dimension_b = attribute.Attribute( + description='Outer dimension B', + unit=ureg.millimeter, + ) + cross_section_b = attribute.Attribute( + description='Cross section b', + unit=ureg.millimeter, + ) + label_swap_ab = attribute.Attribute( + description='Swap label a/b', + ) + isolation = attribute.Attribute( + description='Insulation thickness', + unit=ureg.millimeter, + ) + cross_section = attribute.Attribute( + description='Cross section', + ) + flow_rate_range = attribute.Attribute( + description='Flow rate range', + unit=ureg.meter ** 3 / ureg.hour, + ) + + +# ============================================================================ +# SILENCERS +# ============================================================================ + +class Silencer(VentilationElement): + """Base class for silencers/sound dampeners""" + + manufacturer = attribute.Attribute( + description='Manufacturer', + ) + type = attribute.Attribute( + description='Type', + ) + length = attribute.Attribute( + description='Length', + unit=ureg.millimeter, + ) + + +class FlexibleRoundSilencer(Silencer): + """Flexible round silencers (Flexibler Rohrschalldämpfer)""" + + ifc_types = { + 'IfcDuctFitting': ['*'] + } + + pattern_ifc_type = [ + re.compile('Flexibler Rohrschalldämpfer', flags=re.IGNORECASE), + ] + + flow_velocity = attribute.Attribute( + description='Flow velocity', + unit=ureg.meter / ureg.second, + ) + designation_is = attribute.Attribute( + description='Designation (IS)', + ) + nominal_diameter_d1 = attribute.Attribute( + description='Nominal diameter DN', + unit=ureg.millimeter, + ) + outer_diameter = attribute.Attribute( + description='Outer diameter', + unit=ureg.millimeter, + ) + insert_end = attribute.Attribute( + description='Insert end', + unit=ureg.millimeter, + ) + + +class TelephonySilencer(Silencer): + """Telephony silencers (Telefonie-Schalldämpfer)""" + + ifc_types = { + 'IfcDamper': ['*'] + } + + pattern_ifc_type = [ + re.compile('Telefonie-Schalldämpfer', flags=re.IGNORECASE), + ] + + radius = attribute.Attribute( + description='Radius', + unit=ureg.millimeter, + ) + nominal_diameter_d1 = attribute.Attribute( + description='Nominal diameter DN', + unit=ureg.millimeter, + ) + outer_diameter = attribute.Attribute( + description='Outer diameter', + unit=ureg.millimeter, + ) + insert = attribute.Attribute( + description='Insert dimension', + unit=ureg.millimeter, + ) + list_round = attribute.Attribute( + description='On round list', + ) + rings = attribute.Attribute( + description='Number of rings', + ) + + +# ============================================================================ +# AIR TERMINALS +# ============================================================================ + +class AirTerminal(VentilationElement): + """Air terminals/outlets (Luftdurchlasse)""" + + ifc_types = { + 'IfcAirTerminal': ['*'] + } + + pattern_ifc_type = [ + re.compile('Drallauslass (M_20011)', flags=re.IGNORECASE), + ] + + outlet_grille_graphic = attribute.Attribute( + description='Outlet grille graphic', + ) + nominal_diameter_d1 = attribute.Attribute( + description='Nominal diameter DN', + unit=ureg.millimeter, + ) + manufacturer = attribute.Attribute( + description='Manufacturer', + ) + project = attribute.Attribute( + description='Project', + ) + series = attribute.Attribute( + description='Series', + ) + type = attribute.Attribute( + description='Type', + ) + flow_rate = attribute.Attribute( + description='Flow rate', + unit=ureg.meter ** 3 / ureg.hour, + ) + air_velocity = attribute.Attribute( + description='Air velocity', + unit=ureg.meter / ureg.second, + ) + + +class AirValve(VentilationElement): + """Air valves/disc valves (Tellerventile)""" + + ifc_types = { + 'IfcDuctFitting': ['*'] + } + + pattern_ifc_type = [ + re.compile('Tellerventile', flags=re.IGNORECASE), + ] + + radius = attribute.Attribute( + description='Radius', + unit=ureg.millimeter, + ) + outlet_grille_graphic = attribute.Attribute( + description='Outlet grille graphic', + ) + nominal_diameter_connection = attribute.Attribute( + description='Nominal diameter connection', + unit=ureg.millimeter, + ) + cylinder_1 = attribute.Attribute( + description='Cylinder 1: DN1 DN2 Length', + ) + cylinder_2 = attribute.Attribute( + description='Cylinder 2: DN1 DN2 Length', + ) + cylinder_3 = attribute.Attribute( + description='Cylinder 3: DN1 DN2 Length', + ) + manufacturer = attribute.Attribute( + description='Manufacturer', + ) + cross_tee = attribute.Attribute( + description='Cross tee', + ) + cost_group = attribute.Attribute( + description='Cost group', + ) + outlet_type = attribute.Attribute( + description='Outlet type', + ) + project = attribute.Attribute( + description='Project', + ) + rings = attribute.Attribute( + description='Number of rings', + ) + series = attribute.Attribute( + description='Series', + ) + type = attribute.Attribute( + description='Type', + ) + flow_rate = attribute.Attribute( + description='Flow rate', + unit=ureg.meter ** 3 / ureg.hour, + ) + air_velocity = attribute.Attribute( + description='Air velocity', + unit=ureg.meter / ureg.second, + ) + + +# ============================================================================ +# AIR HANDLING UNIT COMPONENTS +# ============================================================================ + +class AHUComponent(VentilationElement): + """Base class for air handling unit components""" + + outer_dimension_a = attribute.Attribute( + description='Outer dimension A', + unit=ureg.millimeter, + ) + cross_section_a = attribute.Attribute( + description='Cross section a', + unit=ureg.millimeter, + ) + outer_dimension_b = attribute.Attribute( + description='Outer dimension B', + unit=ureg.millimeter, + ) + cross_section_b = attribute.Attribute( + description='Cross section b', + unit=ureg.millimeter, + ) + insert = attribute.Attribute( + description='Insert dimension', + unit=ureg.millimeter, + ) + graphics = attribute.Attribute( + description='Graphics (top, bottom, front, back)', + ) + length = attribute.Attribute( + description='Length', + unit=ureg.millimeter, + ) + cross_section = attribute.Attribute( + description='Cross section', + ) + strand = attribute.Attribute( + description='Strand', + ) + flow_rate = attribute.Attribute( + description='Flow rate', + unit=ureg.meter ** 3 / ureg.hour, + ) + + +class AHUFan(AHUComponent): + """Air handling unit fan""" + + ifc_types = { + 'IfcFan': ['*'] + } + + pattern_ifc_type = [ + re.compile('AHU_Fan', flags=re.IGNORECASE), + ] + + +class AHUAirChamber(AHUComponent): + """Air handling unit air chamber""" + ifc_types = { + 'IfcDistributionChamberElement': ['*'] + } + + pattern_ifc_type = [ + re.compile('AHU_AirChamber', flags=re.IGNORECASE), + ] + + +class AHUCooler(AHUComponent): + """Air handling unit cooler""" + ifc_types = { + 'IfcDuctFitting': ['*'] + } + + pattern_ifc_type = [ + re.compile('AHU_Cooler', flags=re.IGNORECASE), + ] + + +class AHUSilencer(AHUComponent): + """Air handling unit silencer""" + ifc_types = { + 'IfcDamper': ['*'] + } + + pattern_ifc_type = [ + re.compile('AHU_Silencer', flags=re.IGNORECASE), + ] + + +# collect all domain classes +items: Set[HVACProduct] = set() +for name, cls in inspect.getmembers( + sys.modules[__name__], + lambda member: inspect.isclass(member) # class at all + and issubclass(member, HVACProduct) # domain subclass + and member is not HVACProduct # but not base class + and member not in (VentilationElement, Silencer, VolumeFlowController, AHUComponent) # but not sub base class + and member.__module__ == __name__): # declared here + items.add(cls) + +# collect all domain classes +ventilation_items: Set[VentilationElement] = set() +for name, cls in inspect.getmembers( + sys.modules[__name__], + lambda member: inspect.isclass(member) # class at all + and issubclass(member, VentilationElement) # domain subclass + and member is not VentilationElement # but not base class + and member not in (Silencer, VolumeFlowController, AHUComponent) # but not sub base class + and member.__module__ == __name__): # declared here + ventilation_items.add(cls) diff --git a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/examples/e1_simple_project_hvac_aixlib_testing.py b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/examples/e1_simple_project_hvac_aixlib_testing.py index f2bd5f7e89..7c84907ad8 100644 --- a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/examples/e1_simple_project_hvac_aixlib_testing.py +++ b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/examples/e1_simple_project_hvac_aixlib_testing.py @@ -4,7 +4,9 @@ import bim2sim from bim2sim import Project, run_project, ConsoleDecisionHandler from bim2sim.utilities.types import IFCDomain - +from bim2sim.elements.base_elements import Material +from bim2sim.elements import bps_elements as bps_elements, \ + hvac_elements as hvac_elements def run_example_simple_hvac_aixlib(): """Run an HVAC simulation with the AixLib backend. @@ -24,13 +26,18 @@ def run_example_simple_hvac_aixlib(): # Set path of ifc for hydraulic domain with the fresh downloaded test models ifc_paths = { - IFCDomain.ventilation: - Path(r"D:\03_Cloud\Sciebo\BIM2Praxis\IFC-Modelle\EDGE\ueberarbeitet_2025-10\BIM2PRAXIS_RLT-2025-09-11_cleaned_jho.ifc") + IFCDomain.hydraulic: + Path(bim2sim.__file__).parent.parent / + 'test/resources/hydraulic/ifc/' + 'hvac_heating.ifc' } + # Create a project including the folder structure for the project with # teaser as backend and no specified workflow (default workflow is taken) project = Project.create(project_path, ifc_paths, 'aixlib') + project.sim_settings.relevant_elements = {*hvac_elements.ventilation_items, Material} + # set weather file data project.sim_settings.weather_file_path_modelica = ( Path(bim2sim.__file__).parent.parent / From e0c68d86971f63838a0a0f24b914de8c8da3fd1f Mon Sep 17 00:00:00 2001 From: Hoepp J <155448650+HoeppJ@users.noreply.github.com> Date: Wed, 19 Nov 2025 19:17:20 +0100 Subject: [PATCH 111/125] update ifc patterns --- bim2sim/elements/hvac_elements.py | 1 + bim2sim/plugins/PluginAixLib/bim2sim_aixlib/__init__.py | 2 +- .../examples/e1_simple_project_hvac_aixlib_testing.py | 6 ++---- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/bim2sim/elements/hvac_elements.py b/bim2sim/elements/hvac_elements.py index 6cfc3ee496..93e5e44d96 100644 --- a/bim2sim/elements/hvac_elements.py +++ b/bim2sim/elements/hvac_elements.py @@ -1794,6 +1794,7 @@ class RectangularTransition(DuctTransition): pattern_ifc_type = [ re.compile('Übergang Asym', flags=re.IGNORECASE), re.compile('Übergangsbogen', flags=re.IGNORECASE), + re.compile(r'Bogen \(eckig BS, BA, 107\)', flags=re.IGNORECASE), ] height_a = attribute.Attribute( diff --git a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/__init__.py b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/__init__.py index 6f3a67c5a9..d1900cf31b 100644 --- a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/__init__.py +++ b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/__init__.py @@ -26,7 +26,7 @@ class PluginAixLib(Plugin): tasks = {LoadLibrariesAixLib} default_tasks = [ common.LoadIFC, - common.CheckIfc, + # common.CheckIfc, common.CreateElementsOnIfcTypes, hvac.ConnectElements, hvac.MakeGraph, diff --git a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/examples/e1_simple_project_hvac_aixlib_testing.py b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/examples/e1_simple_project_hvac_aixlib_testing.py index 7c84907ad8..542ee4b33a 100644 --- a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/examples/e1_simple_project_hvac_aixlib_testing.py +++ b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/examples/e1_simple_project_hvac_aixlib_testing.py @@ -26,10 +26,8 @@ def run_example_simple_hvac_aixlib(): # Set path of ifc for hydraulic domain with the fresh downloaded test models ifc_paths = { - IFCDomain.hydraulic: - Path(bim2sim.__file__).parent.parent / - 'test/resources/hydraulic/ifc/' - 'hvac_heating.ifc' + IFCDomain.ventilation: + Path(r"D:\03_Cloud\Sciebo\BIM2Praxis\IFC-Modelle\EDGE\ueberarbeitet_2025-10\BIM2PRAXIS_RLT-2025-09-11_cleaned_jho.ifc") } # Create a project including the folder structure for the project with From 57f256422d7bab9851ee2b59127813f110826ecc Mon Sep 17 00:00:00 2001 From: Hoepp J <155448650+HoeppJ@users.noreply.github.com> Date: Thu, 20 Nov 2025 13:52:34 +0100 Subject: [PATCH 112/125] update ifc patterns --- .../assets/finder/template_LuArtX_Carf.json | 465 +++++++------ bim2sim/elements/hvac_elements.py | 651 +++++++++++------- 2 files changed, 680 insertions(+), 436 deletions(-) diff --git a/bim2sim/assets/finder/template_LuArtX_Carf.json b/bim2sim/assets/finder/template_LuArtX_Carf.json index 2b3c105635..50cbce3b9c 100644 --- a/bim2sim/assets/finder/template_LuArtX_Carf.json +++ b/bim2sim/assets/finder/template_LuArtX_Carf.json @@ -38,7 +38,7 @@ "vdi_performance_data_table": ["VDI-Tables", "Leistungsdaten"] } }, - "AirDuct_rectangular": { + "AirDuctRectangular": { "default_ps": { "air_type": ["MyTab1", "[ANLAGE] Luftart"], "height_a": ["MyTab1", "[a] (a)"], @@ -65,7 +65,32 @@ "air_velocity": ["MyTab1", "[v] Luftgeschw."] } }, - "AirDuct_round": { + "AirDuctOval": { + "default_ps": { + "air_type": ["MyTab1", "[ANLAGE] Luftart"], + "width_b": ["MyTab1", "[a] Breitendurchmesser B"], + "designation": ["MyTab1", "[BEZ] Bezeichnung (IS)"], + "height_h": ["MyTab1", "[b] Höhendurchmesser H"], + "isolation_type": ["MyTab1", "[ISOL-TYPE] Isolationstyp"], + "edge": ["MyTab1", "[KANTE] Kante"], + "code": ["MyTab1", "[KBZ] KBZ"], + "key": ["MyTab1", "[KEY] Schlüssel"], + "reference_edge": ["MyTab1", "[K] Bezugskante"], + "length": ["MyTab1", "[l] Länge"], + "material": ["MyTab1", "[MAT] Material"], + "data_mask_number": ["MyTab1", "[MSKNR] Datenmaskennummer"], + "on_list": ["MyTab1", "[ON_LST] Auf Liste"], + "pen": ["MyTab1", "[PEN] Schtift"], + "system": ["MyTab1", "[PLANT] Anlage"], + "position": ["MyTab1", "[POS] Position"], + "cross_section": ["MyTab1", "[QS] Querschnitt"], + "connection_type": ["MyTab1", "[RAHMEN] Rahmen"], + "floor": ["MyTab1", "[STOCK] Stockwerk"], + "version": ["MyTab1", "[VERS] Version"], + "air_velocity": ["MyTab1", "[v] Luftgeschw."] + } + }, + "AirDuctRound": { "default_ps": { "radius": ["Profile", "Radius"], "air_type": ["MyTab1", "[ANLAGE] Luftart"], @@ -91,18 +116,14 @@ "air_velocity": ["MyTab1", "[v] Luftgeschw."] } }, - "AirDuct_rectangular_extra": { + "AirDuctRoundFlexible": { "default_ps": { + "radius": ["Profile", "Radius"], "air_type": ["MyTab1", "[ANLAGE] Luftart"], - "outer_dimension_a": ["MyTab1", "[A] Aussenabmessung A"], - "cross_section_a": ["MyTab1", "[a] Querschnitt a"], - "outer_dimension_b": ["MyTab1", "[B] Aussenabmessung B"], - "cross_section_b": ["MyTab1", "[b] Querschnitt b"], - "label_swap_ab": ["MyTab1", "[cab] Beschriftung a/b vertauschen"], - "insert": ["MyTab1", "[e] Einschub"], - "manufacturer": ["MyTab1", "[HST] Hersteller"], + "designation": ["MyTab1", "[BEZ] Bezeichnung"], + "nominal_diameter_d1": ["MyTab1", "[d1] DN"], + "cross_section_d": ["MyTab1", "[d] Querschnitt"], "isolation_type": ["MyTab1", "[ISOL-TYPE] Isolationstyp"], - "isolation": ["MyTab1", "[ISOL] Isolation"], "edge": ["MyTab1", "[KANTE] Kante"], "code": ["MyTab1", "[KBZ] KBZ"], "key": ["MyTab1", "[KEY] Schlüssel"], @@ -113,30 +134,25 @@ "pen": ["MyTab1", "[PEN] Schtift"], "system": ["MyTab1", "[PLANT] Anlage"], "position": ["MyTab1", "[POS] Position"], - "cross_section": ["MyTab1", "[QS] Querschnitt"], - "connection_type": ["MyTab1", "[RAHMEN]"], + "pipe_connection": ["MyTab1", "[RAHMEN] Rohrverbindung"], + "rings": ["MyTab1", "[RI] Ringe"], "floor": ["MyTab1", "[STOCK] Stockwerk"], - "type": ["MyTab1", "[TYP] Typ"], - "version": ["MyTab1", "[VERS] Version"] + "version": ["MyTab1", "[VERS] Version"], + "air_velocity": ["MyTab1", "[v] Luftgeschw."] } }, - "FireDamper": { + "AirDuctRectangularFitting": { "default_ps": { - "flow_velocity": ["Calc", "[ve] Strömungsgeschwindigkeit"], "air_type": ["MyTab1", "[ANLAGE] Luftart"], - "drive_side": ["MyTab1", "[AS] Antriebseite"], - "remark": ["MyTab1", "[ATB] Bemerkung"], - "drive": ["MyTab1", "[ATR] Antrieb"], "outer_dimension_a": ["MyTab1", "[A] Aussenabmessung A"], "cross_section_a": ["MyTab1", "[a] Querschnitt a"], - "designation_is": ["MyTab1", "[BEZ] Bezeichnung (IS)"], "outer_dimension_b": ["MyTab1", "[B] Aussenabmessung B"], "cross_section_b": ["MyTab1", "[b] Querschnitt b"], "label_swap_ab": ["MyTab1", "[cab] Beschriftung a/b vertauschen"], "insert": ["MyTab1", "[e] Einschub"], - "pressure_loss_opening_1": ["MyTab1", "[DP] Druckverlust Öffnung 1"], "manufacturer": ["MyTab1", "[HST] Hersteller"], "isolation_type": ["MyTab1", "[ISOL-TYPE] Isolationstyp"], + "isolation": ["MyTab1", "[ISOL] Isolation"], "edge": ["MyTab1", "[KANTE] Kante"], "code": ["MyTab1", "[KBZ] KBZ"], "key": ["MyTab1", "[KEY] Schlüssel"], @@ -151,51 +167,37 @@ "connection_type": ["MyTab1", "[RAHMEN]"], "floor": ["MyTab1", "[STOCK] Stockwerk"], "type": ["MyTab1", "[TYP] Typ"], - "version": ["MyTab1", "[VERS] Version"], - "air_volume": ["MyTab1", "[VS] Luftvol"], - "zeta_opening_1": ["MyTab1", "[Z] Zeta Öffnung 1"] + "version": ["MyTab1", "[VERS] Version"] } }, - "VolumeFlowController_constant": { + "AirDuctRoundFitting": { "default_ps": { - "radius": ["Profile", "Radius"], - "flow_velocity": ["Calc", "[ve] Strömungsgeschwindigkeit"], "air_type": ["MyTab1", "[ANLAGE] Luftart"], - "designation_is": ["MyTab1", "[BEZ] Bezeichnung (IS)"], - "nominal_diameter_d1": ["MyTab1", "[d1] DN"], - "outer_diameter": ["MyTab1", "[D] Aussendurchmesser"], - "insert": ["MyTab1", "[e] Einschub"], "manufacturer": ["MyTab1", "[HST] Hersteller"], "isolation_type": ["MyTab1", "[ISOL-TYPE] Isolationstyp"], - "edge": ["MyTab1", "[KANTE] Kante"], "code": ["MyTab1", "[KBZ] KBZ"], "key": ["MyTab1", "[KEY] Schlüssel"], - "length": ["MyTab1", "[l] Länge"], - "material": ["MyTab1", "[MAT] Material"], "data_mask_number": ["MyTab1", "[MSKNR] Datenmaskennummer"], "on_list": ["MyTab1", "[ON_LST] Auf Liste"], - "pen": ["MyTab1", "[PEN] Schtift"], "system": ["MyTab1", "[PLANT] Anlage"], "position": ["MyTab1", "[POS] Position"], - "connection_type": ["MyTab1", "[RAHMEN]"], "floor": ["MyTab1", "[STOCK] Stockwerk"], "type": ["MyTab1", "[TYP] Typ"], "version": ["MyTab1", "[VERS] Version"], - "air_volume": ["MyTab1", "[VS] Luftvol"] + "length": ["MyTab1", "[l] Länge"], + "material": ["MyTab1", "[MAT] Material"], + "pen": ["MyTab1", "[PEN] Schtift"] } }, - "VolumeFlowController_dynamic": { + "AirDuctRoundTransition": { "default_ps": { - "flow_velocity": ["Calc", "[ve] Strömungsgeschwindigkeit"], "air_type": ["MyTab1", "[ANLAGE] Luftart"], - "outer_dimension_a": ["MyTab1", "[A] Aussenabmessung A"], - "cross_section_a": ["MyTab1", "[a] Querschnitt a"], - "designation": ["MyTab1", "[BEZ] Bezeichnung"], - "outer_dimension_b": ["MyTab1", "[B] Aussenabmessung B"], - "cross_section_b": ["MyTab1", "[b] Querschnitt b"], - "label_swap_ab": ["MyTab1", "[cab] Beschriftung a/b vertauschen"], - "insert": ["MyTab1", "[e] Einschub"], - "manufacturer": ["MyTab1", "[HST] Hersteller"], + "width_b": ["MyTab1", "[a] Breitendurchmesser B"], + "designation_is": ["MyTab1", "[BEZ] Bezeichnung (IS)"], + "height_h": ["MyTab1", "[b] Höhendurchmesser H"], + "cross_section_output": ["MyTab1", "[d1] Querschnitt Ausgang"], + "e_offset": ["MyTab1", "[c] e-Versatz"], + "f_offset": ["MyTab1", "[f] f-Versatz"], "isolation_type": ["MyTab1", "[ISOL-TYPE] Isolationstyp"], "isolation": ["MyTab1", "[ISOL] Isolation"], "edge": ["MyTab1", "[KANTE] Kante"], @@ -204,43 +206,53 @@ "length": ["MyTab1", "[l] Länge"], "material": ["MyTab1", "[MAT] Material"], "data_mask_number": ["MyTab1", "[MSKNR] Datenmaskennummer"], + "manufacturer": ["MyTab1", "[HST] Hersteller"], "on_list": ["MyTab1", "[ON_LST] Auf Liste"], "pen": ["MyTab1", "[PEN] Schtift"], "system": ["MyTab1", "[PLANT] Anlage"], "position": ["MyTab1", "[POS] Position"], "cross_section": ["MyTab1", "[QS] Querschnitt"], - "connection_type": ["MyTab1", "[RAHMEN]"], + "connection_type": ["MyTab1", "[RAHMEN] Rahmen"], "floor": ["MyTab1", "[STOCK] Stockwerk"], "type": ["MyTab1", "[TYP] Typ"], - "version": ["MyTab1", "[VERS] Version"], - "flow_rate_range": ["MyTab1", "[VS] Vm3/h"] + "version": ["MyTab1", "[VERS] Version"] } }, - "AirDuct_round_extra": { + "AirDuctRoundTransitionSymmetric": { "default_ps": { "air_type": ["MyTab1", "[ANLAGE] Luftart"], - "manufacturer": ["MyTab1", "[HST] Hersteller"], + "width_b": ["MyTab1", "[a] Breitendurchmesser B"], + "designation_is": ["MyTab1", "[BEZ] Bezeichnung (IS)"], + "height_h": ["MyTab1", "[b] Höhendurchmesser H"], + "cross_section_output": ["MyTab1", "[d1] Querschnitt Ausgang"], + "e_offset": ["MyTab1", "[e] Versatz e"], + "f_offset": ["MyTab1", "[f] Versatz f"], "isolation_type": ["MyTab1", "[ISOL-TYPE] Isolationstyp"], + "isolation": ["MyTab1", "[ISOL] Isolation"], + "edge": ["MyTab1", "[KANTE] Kante"], "code": ["MyTab1", "[KBZ] KBZ"], "key": ["MyTab1", "[KEY] Schlüssel"], + "length": ["MyTab1", "[l] Länge"], + "material": ["MyTab1", "[MAT] Material"], "data_mask_number": ["MyTab1", "[MSKNR] Datenmaskennummer"], + "manufacturer": ["MyTab1", "[HST] Hersteller"], "on_list": ["MyTab1", "[ON_LST] Auf Liste"], + "pen": ["MyTab1", "[PEN] Schtift"], "system": ["MyTab1", "[PLANT] Anlage"], "position": ["MyTab1", "[POS] Position"], + "cross_section": ["MyTab1", "[QS] Querschnitt"], + "connection_type": ["MyTab1", "[RAHMEN] Rahmen"], "floor": ["MyTab1", "[STOCK] Stockwerk"], - "type": ["MyTab1", "[TYP] Typ"], "version": ["MyTab1", "[VERS] Version"] } }, - "Silencer_round_flexible": { + "AirDuctOvalBow": { "default_ps": { - "flow_velocity": ["Calc", "[ve] Strömungsgeschwindigkeit"], + "radius": ["Profile", "Radius"], "air_type": ["MyTab1", "[ANLAGE] Luftart"], + "width_b": ["MyTab1", "[a] Breitendurchmesser B"], "designation_is": ["MyTab1", "[BEZ] Bezeichnung (IS)"], - "nominal_diameter_d1": ["MyTab1", "[d1] DN"], - "outer_diameter": ["MyTab1", "[D] Aussendurchmesser"], - "insert_end": ["MyTab1", "[e] Einschub Ende"], - "manufacturer": ["MyTab1", "[HST] Hersteller"], + "height_h": ["MyTab1", "[b] Höhendurchmesser H"], "isolation_type": ["MyTab1", "[ISOL-TYPE] Isolationstyp"], "edge": ["MyTab1", "[KANTE] Kante"], "code": ["MyTab1", "[KBZ] KBZ"], @@ -248,78 +260,116 @@ "length": ["MyTab1", "[l] Länge"], "material": ["MyTab1", "[MAT] Material"], "data_mask_number": ["MyTab1", "[MSKNR] Datenmaskennummer"], + "manufacturer": ["MyTab1", "[HST] Hersteller"], "on_list": ["MyTab1", "[ON_LST] Auf Liste"], "pen": ["MyTab1", "[PEN] Schtift"], "system": ["MyTab1", "[PLANT] Anlage"], "position": ["MyTab1", "[POS] Position"], - "connection_type": ["MyTab1", "[RAHMEN]"], + "cross_section": ["MyTab1", "[QS] Querschnitt"], + "connection_type": ["MyTab1", "[RAHMEN] Rahmen"], "floor": ["MyTab1", "[STOCK] Stockwerk"], - "type": ["MyTab1", "[TYP] Typ"], - "version": ["MyTab1", "[VERS] Version"] + "version": ["MyTab1", "[VERS] Version"], + "air_velocity": ["MyTab1", "[v] Luftgeschw."], + "angle": ["MyTab1", "[w] Winkel"] } }, - "AirTerminal": { + "AirDuctRoundBend": { "default_ps": { + "radius": ["Profile", "Radius"], "air_type": ["MyTab1", "[ANLAGE] Luftart"], - "designation": ["MyTab1", "[BEZ] Bezeichnung"], - "outlet_grille_graphic": ["MyTab1", "[CG] Auslass-Gitter-Grafik"], + "designation_is": ["MyTab1", "[BEZ] Bezeichnung (IS)"], "nominal_diameter_d1": ["MyTab1", "[d1] DN"], + "neutral_axis": ["MyTab1", "[l] Neutrale Fase"], + "insertion_length": ["MyTab1", "[le2] Einstedekaenge"], + "material": ["MyTab1", "[MAT] Material"], + "data_mask_number": ["MyTab1", "[MSKNR] Datenmaskennummer"], "manufacturer": ["MyTab1", "[HST] Hersteller"], + "on_list": ["MyTab1", "[ON_LST] Auf Liste"], + "pen": ["MyTab1", "[PEN] Schtift"], + "system": ["MyTab1", "[PLANT] Anlage"], + "position": ["MyTab1", "[POS] Position"], + "connection_type": ["MyTab1", "[RAHMEN] Verbindungsart"], + "floor": ["MyTab1", "[STOCK] Stockwerk"], + "version": ["MyTab1", "[VERS] Version"], + "air_velocity": ["MyTab1", "[v] Luftgeschw."], + "angle": ["MyTab1", "[w] Winkel"], + "code": ["MyTab1", "[KBZ] KBZ"], + "key": ["MyTab1", "[KEY] Schlüssel"], + "edge": ["MyTab1", "[KANTE] Kante"], + "isolation_type": ["MyTab1", "[ISOL-TYPE] Isolationstyp"] + } + }, + "AirDuctConcentricReduction": { + "default_ps": { + "air_type": ["MyTab1", "[ANLAGE] Luftart"], + "designation_is": ["MyTab1", "[BEZ] Bezeichnung (IS)"], + "nominal_diameter_d1": ["MyTab1", "[d1] DN1"], + "nominal_diameter_d2": ["MyTab1", "[d2] DN2"], "isolation_type": ["MyTab1", "[ISOL-TYPE] Isolationstyp"], "edge": ["MyTab1", "[KANTE] Kante"], "code": ["MyTab1", "[KBZ] KBZ"], "key": ["MyTab1", "[KEY] Schlüssel"], + "insertion_length": ["MyTab1", "[le2] Einstedekaenge"], + "length": ["MyTab1", "[l] Länge"], "material": ["MyTab1", "[MAT] Material"], "data_mask_number": ["MyTab1", "[MSKNR] Datenmaskennummer"], + "manufacturer": ["MyTab1", "[HST] Hersteller"], + "on_list": ["MyTab1", "[ON_LST] Auf Liste"], "pen": ["MyTab1", "[PEN] Schtift"], "system": ["MyTab1", "[PLANT] Anlage"], "position": ["MyTab1", "[POS] Position"], - "project": ["MyTab1", "[PRJ] Projekt"], - "connection_type": ["MyTab1", "[RAHMEN]"], - "series": ["MyTab1", "[SER] Serie"], + "connection_type": ["MyTab1", "[RAHMEN] Verbindungsart"], "floor": ["MyTab1", "[STOCK] Stockwerk"], "type": ["MyTab1", "[TYP] Typ"], "version": ["MyTab1", "[VERS] Version"], - "flow_rate": ["MyTab1", "[VOL] Vm3/h"], "air_velocity": ["MyTab1", "[v] Luftgeschw."] } }, - "Silencer_telephony ": { + "AirDuctRectangularBow": { "default_ps": { - "radius": ["Profile", "Radius"], "air_type": ["MyTab1", "[ANLAGE] Luftart"], + "height_a": ["MyTab1", "[a] Höhe (a)"], "designation": ["MyTab1", "[BEZ] Bezeichnung"], - "nominal_diameter_d1": ["MyTab1", "[d1] DN"], - "outer_diameter": ["MyTab1", "[D] Aussendurchmesser"], - "insert": ["MyTab1", "[e] Einschub"], - "manufacturer": ["MyTab1", "[HST] Hersteller"], + "width_start_b": ["MyTab1", "[b] Breite Anfang (b)"], + "width_end_d": ["MyTab1", "[d] Breite Ende (d)"], + "insert_end": ["MyTab1", "[e] Einschub Ende"], + "insert_start": ["MyTab1", "[f] Einschub Anfang"], "isolation_type": ["MyTab1", "[ISOL-TYPE] Isolationstyp"], + "isolation": ["MyTab1", "[ISOL] Isolation"], "edge": ["MyTab1", "[KANTE] Kante"], "code": ["MyTab1", "[KBZ] KBZ"], "key": ["MyTab1", "[KEY] Schlüssel"], - "list_round": ["MyTab1", "[LSTR] Auf Liste Rund"], - "length": ["MyTab1", "[l] Länge"], + "guide_vanes": ["MyTab1", "[LBT] Leitbleche"], + "number_guide_vanes": ["MyTab1", "[LB] Anzahl Leitbleche"], "material": ["MyTab1", "[MAT] Material"], "data_mask_number": ["MyTab1", "[MSKNR] Datenmaskennummer"], "on_list": ["MyTab1", "[ON_LST] Auf Liste"], + "fitting_length": ["MyTab1", "[PE] Passlänge e"], "pen": ["MyTab1", "[PEN] Schtift"], "system": ["MyTab1", "[PLANT] Anlage"], "position": ["MyTab1", "[POS] Position"], + "cross_section": ["MyTab1", "[QS] Querschnitt"], "connection_type": ["MyTab1", "[RAHMEN]"], - "rings": ["MyTab1", "[RI] Ringe"], + "pipe_connection": ["MyTab1", "[RAHMEN] Rohrverbindung"], + "inner_radius_is_bend": ["MyTab1", "[RK] Innerradius ist Abkantung"], + "radius": ["MyTab1", "[r] Radius"], "floor": ["MyTab1", "[STOCK] Stockwerk"], - "type": ["MyTab1", "[TYP] Typ"], - "version": ["MyTab1", "[VERS] Version"] + "version": ["MyTab1", "[VERS] Version"], + "air_velocity": ["MyTab1", "[v] Luftgeschw."], + "angle": ["MyTab1", "[w] Winkel"], + "absolute_roughness_factor": ["Pset_DuctFittingTypeCommon", "AbsoluteRoughnessFactor"], + "manufacturer": ["MyTab1", "[HST] Hersteller"], + "length": ["MyTab1", "[l] Länge"] } }, - "AirDuct_round_flexible": { + "AirDuctEndCap": { "default_ps": { - "radius": ["Profile", "Radius"], "air_type": ["MyTab1", "[ANLAGE] Luftart"], + "width_a": ["MyTab1", "[a] (a)"], "designation": ["MyTab1", "[BEZ] Bezeichnung"], - "nominal_diameter_d1": ["MyTab1", "[d1] DN"], - "cross_section_d": ["MyTab1", "[d] Querschnitt"], + "height_b": ["MyTab1", "[b] (b)"], "isolation_type": ["MyTab1", "[ISOL-TYPE] Isolationstyp"], + "isolation": ["MyTab1", "[ISOL] Isolation"], "edge": ["MyTab1", "[KANTE] Kante"], "code": ["MyTab1", "[KBZ] KBZ"], "key": ["MyTab1", "[KEY] Schlüssel"], @@ -330,19 +380,18 @@ "pen": ["MyTab1", "[PEN] Schtift"], "system": ["MyTab1", "[PLANT] Anlage"], "position": ["MyTab1", "[POS] Position"], - "pipe_connection": ["MyTab1", "[RAHMEN] Rohrverbindung"], - "rings": ["MyTab1", "[RI] Ringe"], + "cross_section": ["MyTab1", "[QS] Querschnitt"], + "connection_type": ["MyTab1", "[RAHMEN]"], "floor": ["MyTab1", "[STOCK] Stockwerk"], "version": ["MyTab1", "[VERS] Version"], - "air_velocity": ["MyTab1", "[v] Luftgeschw."] + "manufacturer": ["MyTab1", "[HST] Hersteller"] } }, - "AirDuct_T_piece": { + "AirDuctTPiece": { "default_ps": { "air_type": ["MyTab1", "[ANLAGE] Luftart"], "width_entrance_a": ["MyTab1", "[a] Breite Eingang (a)"], "designation": ["MyTab1", "[BEZ] Bezeichnung"], - "width_start_b": ["MyTab1", "[b] Breite Anfang (b)"], "height_entrance_b": ["MyTab1", "[b] Höhe Eingang (b)"], "width_outlet_d": ["MyTab1", "[d] Breite Ausgang (d)"], "width_branch_g": ["MyTab1", "[g] Breite Abzweig (g)"], @@ -369,10 +418,11 @@ "floor": ["MyTab1", "[STOCK] Stockwerk"], "version": ["MyTab1", "[VERS] Version"], "air_velocity": ["MyTab1", "[v] Luftgeschw."], - "absolute_roughness_factor": ["Pset_DuctFittingTypeCommon", "AbsoluteRoughnessFactor"] + "absolute_roughness_factor": ["Pset_DuctFittingTypeCommon", "AbsoluteRoughnessFactor"], + "manufacturer": ["MyTab1", "[HST] Hersteller"] } }, - "AirDuct_transition_rectangular": { + "AirDuctTransitionRectangularAsymmetric": { "default_ps": { "air_type": ["MyTab1", "[ANLAGE] Luftart"], "list_as_ua": ["MyTab1", "[AUA] Auf Liste als UA"], @@ -412,31 +462,7 @@ "absolute_roughness_factor": ["Pset_DuctFittingTypeCommon", "AbsoluteRoughnessFactor"] } }, - "AirDuct_end_rectangular": { - "default_ps": { - "air_type": ["MyTab1", "[ANLAGE] Luftart"], - "width_a": ["MyTab1", "[a] (a)"], - "designation": ["MyTab1", "[BEZ] Bezeichnung"], - "height_b": ["MyTab1", "[b] (b)"], - "isolation_type": ["MyTab1", "[ISOL-TYPE] Isolationstyp"], - "isolation": ["MyTab1", "[ISOL] Isolation"], - "edge": ["MyTab1", "[KANTE] Kante"], - "code": ["MyTab1", "[KBZ] KBZ"], - "key": ["MyTab1", "[KEY] Schlüssel"], - "length": ["MyTab1", "[l] Länge"], - "material": ["MyTab1", "[MAT] Material"], - "data_mask_number": ["MyTab1", "[MSKNR] Datenmaskennummer"], - "on_list": ["MyTab1", "[ON_LST] Auf Liste"], - "pen": ["MyTab1", "[PEN] Schtift"], - "system": ["MyTab1", "[PLANT] Anlage"], - "position": ["MyTab1", "[POS] Position"], - "cross_section": ["MyTab1", "[QS] Querschnitt"], - "connection_type": ["MyTab1", "[RAHMEN]"], - "floor": ["MyTab1", "[STOCK] Stockwerk"], - "version": ["MyTab1", "[VERS] Version"] - } - }, - "AirDuct_transition_rectangular_sym": { + "AirDuctTransitionSymmetrical": { "default_ps": { "air_type": ["MyTab1", "[ANLAGE] Luftart"], "height_a": ["MyTab1", "[a] Höhe (a)"], @@ -463,55 +489,64 @@ "inner_radius_is_bend": ["MyTab1", "[RK] Innerradius ist Abkantung"], "radius": ["MyTab1", "[r] Radius"], "floor": ["MyTab1", "[STOCK] Stockwerk"], + "strand": ["MyTab1", "[STR] Strang"], "version": ["MyTab1", "[VERS] Version"], "air_velocity": ["MyTab1", "[v] Luftgeschw."], "angle": ["MyTab1", "[w] Winkel"], - "absolute_roughness_factor": ["Pset_DuctFittingTypeCommon", "AbsoluteRoughnessFactor"] + "flow_rate": ["MyTab1", "[QS] Querschnitt"], + "absolute_roughness_factor": ["Pset_DuctFittingTypeCommon", "AbsoluteRoughnessFactor"], + "manufacturer": ["MyTab1", "[HST] Hersteller"], + "length": ["MyTab1", "[l] Länge"] } }, - "AirDuct_transition_rectangular": { + "AirDuctTransitionRectangularBow": { "default_ps": { "air_type": ["MyTab1", "[ANLAGE] Luftart"], - "height_a": ["MyTab1", "[a] Höhe (a)"], - "designation": ["MyTab1", "[BEZ] Bezeichnung"], - "width_start_b": ["MyTab1", "[b] Breite Anfang (b)"], - "width_end_d": ["MyTab1", "[d] Breite Ende (d)"], - "insert_end": ["MyTab1", "[e] Einschub Ende"], - "insert_start": ["MyTab1", "[f] Einschub Anfang"], + "width_b": ["MyTab1", "[a] Breitendurchmesser B"], + "designation_is": ["MyTab1", "[BEZ] Bezeichnung (IS)"], + "height_h": ["MyTab1", "[b] Höhendurchmesser H"], + "cross_section_output": ["MyTab1", "[d1] Querschnitt Ausgang"], + "e_offset": ["MyTab1", "[e] Versatz e"], + "f_offset": ["MyTab1", "[f] Versatz f"], "isolation_type": ["MyTab1", "[ISOL-TYPE] Isolationstyp"], + "isolation": ["MyTab1", "[ISOL] Isolation"], "edge": ["MyTab1", "[KANTE] Kante"], "code": ["MyTab1", "[KBZ] KBZ"], "key": ["MyTab1", "[KEY] Schlüssel"], - "guide_vanes": ["MyTab1", "[LBT] Leitbleche"], - "number_guide_vanes": ["MyTab1", "[LB] Anzahl Leitbleche"], + "length": ["MyTab1", "[l] Länge"], "material": ["MyTab1", "[MAT] Material"], "data_mask_number": ["MyTab1", "[MSKNR] Datenmaskennummer"], - "height_branch_m": ["MyTab1", "[m] Höhe Abzweig"], - "length_entrance_n": ["MyTab1", "[n] Länge Eingang"], - "second_branch_length_entrance_o": ["MyTab1", "[o] 2. Verzweigung. Länge Eingang"], + "manufacturer": ["MyTab1", "[HST] Hersteller"], "on_list": ["MyTab1", "[ON_LST] Auf Liste"], "pen": ["MyTab1", "[PEN] Schtift"], "system": ["MyTab1", "[PLANT] Anlage"], "position": ["MyTab1", "[POS] Position"], - "second_branch_height_p": ["MyTab1", "[p] 2. Verzweigung. Höhe"], "cross_section": ["MyTab1", "[QS] Querschnitt"], - "connection_type": ["MyTab1", "[RAHMEN]"], - "inner_radius_is_bend": ["MyTab1", "[RK] Innerradius ist Abkantung"], - "radius": ["MyTab1", "[r] Radius"], + "connection_type": ["MyTab1", "[RAHMEN] Rahmen"], "floor": ["MyTab1", "[STOCK] Stockwerk"], "strand": ["MyTab1", "[STR] Strang"], "version": ["MyTab1", "[VERS] Version"], "air_velocity": ["MyTab1", "[v] Luftgeschw."], - "angle": ["MyTab1", "[w] Winkel"], + "flow_rate": ["MyTab1", "[QS] Querschnitt"], "absolute_roughness_factor": ["Pset_DuctFittingTypeCommon", "AbsoluteRoughnessFactor"] } }, - "PipeDuct_round": { + "FireDamper": { "default_ps": { + "flow_velocity": ["Calc", "[ve] Strömungsgeschwindigkeit"], "air_type": ["MyTab1", "[ANLAGE] Luftart"], - "width_b": ["MyTab1", "[a] Breitendurchmesser B"], - "designation": ["MyTab1", "[BEZ] Bezeichnung (IS)"], - "nominal_diameter_d1": ["MyTab1", "[d1] DN"], + "drive_side": ["MyTab1", "[AS] Antriebseite"], + "remark": ["MyTab1", "[ATB] Bemerkung"], + "drive": ["MyTab1", "[ATR] Antrieb"], + "outer_dimension_a": ["MyTab1", "[A] Aussenabmessung A"], + "cross_section_a": ["MyTab1", "[a] Querschnitt a"], + "designation_is": ["MyTab1", "[BEZ] Bezeichnung (IS)"], + "outer_dimension_b": ["MyTab1", "[B] Aussenabmessung B"], + "cross_section_b": ["MyTab1", "[b] Querschnitt b"], + "label_swap_ab": ["MyTab1", "[cab] Beschriftung a/b vertauschen"], + "insert": ["MyTab1", "[e] Einschub"], + "pressure_loss_opening_1": ["MyTab1", "[DP] Druckverlust Öffnung 1"], + "manufacturer": ["MyTab1", "[HST] Hersteller"], "isolation_type": ["MyTab1", "[ISOL-TYPE] Isolationstyp"], "edge": ["MyTab1", "[KANTE] Kante"], "code": ["MyTab1", "[KBZ] KBZ"], @@ -524,23 +559,28 @@ "system": ["MyTab1", "[PLANT] Anlage"], "position": ["MyTab1", "[POS] Position"], "cross_section": ["MyTab1", "[QS] Querschnitt"], - "connection_type": ["MyTab1", "[RAHMEN] Rahmen"], + "connection_type": ["MyTab1", "[RAHMEN]"], "floor": ["MyTab1", "[STOCK] Stockwerk"], + "type": ["MyTab1", "[TYP] Typ"], "version": ["MyTab1", "[VERS] Version"], - "air_velocity": ["MyTab1", "[v] Luftgeschw."] + "air_volume": ["MyTab1", "[VS] Luftvol"], + "zeta_opening_1": ["MyTab1", "[Z] Zeta Öffnung 1"] } }, - "AirDuct_oval": { + "AirVolumeFlowControllerConstant": { "default_ps": { + "radius": ["Profile", "Radius"], + "flow_velocity": ["Calc", "[ve] Strömungsgeschwindigkeit"], "air_type": ["MyTab1", "[ANLAGE] Luftart"], - "width_b": ["MyTab1", "[a] Breitendurchmesser B"], - "designation": ["MyTab1", "[BEZ] Bezeichnung (IS)"], - "height_h": ["MyTab1", "[b] Höhendurchmesser H"], + "designation_is": ["MyTab1", "[BEZ] Bezeichnung (IS)"], + "nominal_diameter_d1": ["MyTab1", "[d1] DN"], + "outer_diameter": ["MyTab1", "[D] Aussendurchmesser"], + "insert": ["MyTab1", "[e] Einschub"], + "manufacturer": ["MyTab1", "[HST] Hersteller"], "isolation_type": ["MyTab1", "[ISOL-TYPE] Isolationstyp"], "edge": ["MyTab1", "[KANTE] Kante"], "code": ["MyTab1", "[KBZ] KBZ"], "key": ["MyTab1", "[KEY] Schlüssel"], - "reference_edge": ["MyTab1", "[K] Bezugskante"], "length": ["MyTab1", "[l] Länge"], "material": ["MyTab1", "[MAT] Material"], "data_mask_number": ["MyTab1", "[MSKNR] Datenmaskennummer"], @@ -548,46 +588,58 @@ "pen": ["MyTab1", "[PEN] Schtift"], "system": ["MyTab1", "[PLANT] Anlage"], "position": ["MyTab1", "[POS] Position"], - "cross_section": ["MyTab1", "[QS] Querschnitt"], - "connection_type": ["MyTab1", "[RAHMEN] Rahmen"], + "connection_type": ["MyTab1", "[RAHMEN]"], "floor": ["MyTab1", "[STOCK] Stockwerk"], + "type": ["MyTab1", "[TYP] Typ"], "version": ["MyTab1", "[VERS] Version"], - "air_velocity": ["MyTab1", "[v] Luftgeschw."] + "air_volume": ["MyTab1", "[VS] Luftvol"] } }, - "Bend_round": { + "AirVolumeFlowControllerDynamic": { "default_ps": { - "radius": ["Profile", "Radius"], + "flow_velocity": ["Calc", "[ve] Strömungsgeschwindigkeit"], "air_type": ["MyTab1", "[ANLAGE] Luftart"], - "designation": ["MyTab1", "[BEZ] Bezeichnung (IS)"], - "nominal_diameter_d1": ["MyTab1", "[d1] DN"], - "neutral_axis": ["MyTab1", "[l] Neutrale Fase"], - "insertion_length": ["MyTab1", "[le2] Einstedekaenge"], + "outer_dimension_a": ["MyTab1", "[A] Aussenabmessung A"], + "cross_section_a": ["MyTab1", "[a] Querschnitt a"], + "designation": ["MyTab1", "[BEZ] Bezeichnung"], + "outer_dimension_b": ["MyTab1", "[B] Aussenabmessung B"], + "cross_section_b": ["MyTab1", "[b] Querschnitt b"], + "label_swap_ab": ["MyTab1", "[cab] Beschriftung a/b vertauschen"], + "insert": ["MyTab1", "[e] Einschub"], + "manufacturer": ["MyTab1", "[HST] Hersteller"], + "isolation_type": ["MyTab1", "[ISOL-TYPE] Isolationstyp"], + "isolation": ["MyTab1", "[ISOL] Isolation"], + "edge": ["MyTab1", "[KANTE] Kante"], + "code": ["MyTab1", "[KBZ] KBZ"], + "key": ["MyTab1", "[KEY] Schlüssel"], + "length": ["MyTab1", "[l] Länge"], "material": ["MyTab1", "[MAT] Material"], "data_mask_number": ["MyTab1", "[MSKNR] Datenmaskennummer"], "on_list": ["MyTab1", "[ON_LST] Auf Liste"], "pen": ["MyTab1", "[PEN] Schtift"], "system": ["MyTab1", "[PLANT] Anlage"], "position": ["MyTab1", "[POS] Position"], - "connection_type": ["MyTab1", "[N] Bogen Radius"], + "cross_section": ["MyTab1", "[QS] Querschnitt"], + "connection_type": ["MyTab1", "[RAHMEN]"], "floor": ["MyTab1", "[STOCK] Stockwerk"], + "type": ["MyTab1", "[TYP] Typ"], "version": ["MyTab1", "[VERS] Version"], - "air_velocity": ["MyTab1", "[v] Luftgeschw."], - "angle": ["MyTab1", "[w] Winkel"], - "length": ["Pset_DuctSegmentTypeCommon", "Length"] + "flow_rate_range": ["MyTab1", "[VS] Vm3/h"] } }, - "Reduction_concentric": { + "AirSilencerRoundFlexible": { "default_ps": { + "flow_velocity": ["Calc", "[ve] Strömungsgeschwindigkeit"], "air_type": ["MyTab1", "[ANLAGE] Luftart"], - "designation": ["MyTab1", "[BEZ] Bezeichnung (IS)"], - "nominal_diameter_d1": ["MyTab1", "[d1] DN1"], - "nominal_diameter_d2": ["MyTab1", "[d2] DN2"], + "designation_is": ["MyTab1", "[BEZ] Bezeichnung (IS)"], + "nominal_diameter_d1": ["MyTab1", "[d1] DN"], + "outer_diameter": ["MyTab1", "[D] Aussendurchmesser"], + "insert_end": ["MyTab1", "[e] Einschub Ende"], + "manufacturer": ["MyTab1", "[HST] Hersteller"], "isolation_type": ["MyTab1", "[ISOL-TYPE] Isolationstyp"], "edge": ["MyTab1", "[KANTE] Kante"], "code": ["MyTab1", "[KBZ] KBZ"], "key": ["MyTab1", "[KEY] Schlüssel"], - "insertion_length": ["MyTab1", "[le2] Einstedekaenge"], "length": ["MyTab1", "[l] Länge"], "material": ["MyTab1", "[MAT] Material"], "data_mask_number": ["MyTab1", "[MSKNR] Datenmaskennummer"], @@ -595,20 +647,18 @@ "pen": ["MyTab1", "[PEN] Schtift"], "system": ["MyTab1", "[PLANT] Anlage"], "position": ["MyTab1", "[POS] Position"], - "connection_type": ["MyTab1", "[RAHMEN] Verbindungsart"], + "connection_type": ["MyTab1", "[RAHMEN]"], "floor": ["MyTab1", "[STOCK] Stockwerk"], "type": ["MyTab1", "[TYP] Typ"], - "version": ["MyTab1", "[VERS] Version"], - "air_velocity": ["MyTab1", "[v] Luftgeschw."] + "version": ["MyTab1", "[VERS] Version"] } }, - "AirValve": { + "AirTerminal": { "default_ps": { - "radius": ["Profile", "Radius"], "air_type": ["MyTab1", "[ANLAGE] Luftart"], "designation": ["MyTab1", "[BEZ] Bezeichnung"], "outlet_grille_graphic": ["MyTab1", "[CG] Auslass-Gitter-Grafik"], - "nominal_diameter_connection": ["MyTab1", "[d1] Anschluss DN"], + "nominal_diameter_d1": ["MyTab1", "[d1] Anschluss DN"], "cylinder_1": ["MyTab1", "[DL1] Zyl1: DN1 DN2 Länge"], "cylinder_2": ["MyTab1", "[DL2] Zyl2: DN1 DN2 Länge"], "cylinder_3": ["MyTab1", "[DL3] Zyl3: DN1 DN2 Länge"], @@ -617,15 +667,13 @@ "edge": ["MyTab1", "[KANTE] Kante"], "code": ["MyTab1", "[KBZ] KBZ"], "key": ["MyTab1", "[KEY] Schlüssel"], - "cross_tee": ["MyTab1", "[KR] Kreuzstück"], - "cost_group": ["MyTab1", "[KGR] Kostengruppe"], + "material": ["MyTab1", "[MAT] Material"], "data_mask_number": ["MyTab1", "[MSKNR] Datenmaskennummer"], - "outlet_type": ["MyTab1", "[OT] Typ"], "pen": ["MyTab1", "[PEN] Schtift"], "system": ["MyTab1", "[PLANT] Anlage"], + "position": ["MyTab1", "[POS] Position"], "project": ["MyTab1", "[PRJ] Projekt"], "connection_type": ["MyTab1", "[RAHMEN]"], - "rings": ["MyTab1", "[RI] Ringe"], "series": ["MyTab1", "[SER] Serie"], "floor": ["MyTab1", "[STOCK] Stockwerk"], "type": ["MyTab1", "[TYP] Typ"], @@ -634,44 +682,67 @@ "air_velocity": ["MyTab1", "[v] Luftgeschw."] } }, - "AirDuct_rectangular_turn": { + "AirSilencerTelephony": { "default_ps": { "radius": ["Profile", "Radius"], "air_type": ["MyTab1", "[ANLAGE] Luftart"], - "height_a": ["MyTab1", "[a] Höhe (a)"], "designation": ["MyTab1", "[BEZ] Bezeichnung"], - "width_start_b": ["MyTab1", "[b] Breite Anfang (b)"], - "width_end_d": ["MyTab1", "[d] Breite Ende (d)"], - "width_outlet_d": ["MyTab1", "[d] Breite Ausgang (d)"], - "insert_end": ["MyTab1", "[e] Einschub Ende"], - "insert_start": ["MyTab1", "[f] Einschub Anfang"], + "nominal_diameter_d1": ["MyTab1", "[d1] DN"], + "outer_diameter": ["MyTab1", "[D] Aussendurchmesser"], + "insert": ["MyTab1", "[e] Einschub"], + "manufacturer": ["MyTab1", "[HST] Hersteller"], "isolation_type": ["MyTab1", "[ISOL-TYPE] Isolationstyp"], - "isolation": ["MyTab1", "[ISOL] Isolation"], "edge": ["MyTab1", "[KANTE] Kante"], "code": ["MyTab1", "[KBZ] KBZ"], "key": ["MyTab1", "[KEY] Schlüssel"], - "guide_vanes": ["MyTab1", "[LBT] Leitbleche"], - "number_guide_vanes": ["MyTab1", "[LB] Anzahl Leitbleche"], + "list_round": ["MyTab1", "[LSTR] Auf Liste Rund"], + "length": ["MyTab1", "[l] Länge"], "material": ["MyTab1", "[MAT] Material"], "data_mask_number": ["MyTab1", "[MSKNR] Datenmaskennummer"], "on_list": ["MyTab1", "[ON_LST] Auf Liste"], - "fitting_length": ["MyTab1", "[PE] Passlänge e"], "pen": ["MyTab1", "[PEN] Schtift"], "system": ["MyTab1", "[PLANT] Anlage"], "position": ["MyTab1", "[POS] Position"], - "cross_section": ["MyTab1", "[QS] Querschnitt"], "connection_type": ["MyTab1", "[RAHMEN]"], - "pipe_connection": ["MyTab1", "[RAHMEN] Rohrverbindung"], - "inner_radius_is_bend": ["MyTab1", "[RK] Innerradius ist Abkantung"], - "radius": ["MyTab1", "[r] Radius"], + "rings": ["MyTab1", "[RI] Ringe"], + "floor": ["MyTab1", "[STOCK] Stockwerk"], + "type": ["MyTab1", "[TYP] Typ"], + "version": ["MyTab1", "[VERS] Version"] + } + }, + "AirValve": { + "default_ps": { + "radius": ["Profile", "Radius"], + "air_type": ["MyTab1", "[ANLAGE] Luftart"], + "designation": ["MyTab1", "[BEZ] Bezeichnung"], + "outlet_grille_graphic": ["MyTab1", "[CG] Auslass-Gitter-Grafik"], + "nominal_diameter_connection": ["MyTab1", "[d1] Anschluss DN"], + "cylinder_1": ["MyTab1", "[DL1] Zyl1: DN1 DN2 Länge"], + "cylinder_2": ["MyTab1", "[DL2] Zyl2: DN1 DN2 Länge"], + "cylinder_3": ["MyTab1", "[DL3] Zyl3: DN1 DN2 Länge"], + "manufacturer": ["MyTab1", "[HST] Hersteller"], + "isolation_type": ["MyTab1", "[ISOL-TYPE] Isolationstyp"], + "edge": ["MyTab1", "[KANTE] Kante"], + "code": ["MyTab1", "[KBZ] KBZ"], + "key": ["MyTab1", "[KEY] Schlüssel"], + "cross_tee": ["MyTab1", "[KR] Kreuzstück"], + "cost_group": ["MyTab1", "[KGR] Kostengruppe"], + "data_mask_number": ["MyTab1", "[MSKNR] Datenmaskennummer"], + "outlet_type": ["MyTab1", "[OT] Typ"], + "pen": ["MyTab1", "[PEN] Schtift"], + "system": ["MyTab1", "[PLANT] Anlage"], + "project": ["MyTab1", "[PRJ] Projekt"], + "connection_type": ["MyTab1", "[RAHMEN]"], + "rings": ["MyTab1", "[RI] Ringe"], + "series": ["MyTab1", "[SER] Serie"], "floor": ["MyTab1", "[STOCK] Stockwerk"], + "type": ["MyTab1", "[TYP] Typ"], "version": ["MyTab1", "[VERS] Version"], - "air_velocity": ["MyTab1", "[v] Luftgeschw."], - "angle": ["MyTab1", "[w] Winkel"], - "absolute_roughness_factor": ["Pset_DuctFittingTypeCommon", "AbsoluteRoughnessFactor"] + "flow_rate": ["MyTab1", "[VOL] Vm3/h"], + "air_velocity": ["MyTab1", "[v] Luftgeschw."] } }, - "AHU_Fan": { + "AHUFan": { "default_ps": { "air_type": ["MyTab1", "[ANLAGE] Luftart"], "outer_dimension_a": ["MyTab1", "[A] Aussenabmessung A"], @@ -699,7 +770,7 @@ "flow_rate": ["MyTab1", "[VS] Vm3/h"] } }, - "AHU_AirChamber": { + "AHUAirChamber": { "default_ps": { "air_type": ["MyTab1", "[ANLAGE] Luftart"], "outer_dimension_a": ["MyTab1", "[A] Aussenabmessung A"], @@ -727,7 +798,7 @@ "flow_rate": ["MyTab1", "[VS] Vm3/h"] } }, - "AHU_Cooler": { + "AHUCooler": { "default_ps": { "air_type": ["MyTab1", "[ANLAGE] Luftart"], "outer_dimension_a": ["MyTab1", "[A] Aussenabmessung A"], @@ -755,7 +826,7 @@ "flow_rate": ["MyTab1", "[VS] Vm3/h"] } }, - "AHU_Silencer": { + "AHUSilencer": { "default_ps": { "air_type": ["MyTab1", "[ANLAGE] Luftart"], "outer_dimension_a": ["MyTab1", "[A] Aussenabmessung A"], @@ -784,3 +855,5 @@ } } } + + diff --git a/bim2sim/elements/hvac_elements.py b/bim2sim/elements/hvac_elements.py index 93e5e44d96..71b53c2dc5 100644 --- a/bim2sim/elements/hvac_elements.py +++ b/bim2sim/elements/hvac_elements.py @@ -1429,6 +1429,7 @@ def is_consumer(self): return True """ + class VentilationElement(HVACProduct): """Common properties for all ventilation elements""" air_type = attribute.Attribute( @@ -1467,6 +1468,12 @@ class VentilationElement(HVACProduct): designation = attribute.Attribute( description='Designation', ) + code = attribute.Attribute( + description='Code/KBZ', + ) + key = attribute.Attribute( + description='Key', + ) # ============================================================================ @@ -1496,11 +1503,12 @@ class AirDuct(VentilationElement): ) -class RectangularDuct(AirDuct): +class AirDuctRectangular(AirDuct): """Rectangular air ducts""" pattern_ifc_type = [ - re.compile('Kanal', flags=re.IGNORECASE), + re.compile(r'Gerader\s+Kanal.*\(.*K.*:.*101.*\)', flags=re.IGNORECASE), + re.compile(r'^Kanal\s+\d+\s*x\s*\d+', flags=re.IGNORECASE), ] height_a = attribute.Attribute( @@ -1519,14 +1527,17 @@ class RectangularDuct(AirDuct): description='Insulation thickness', unit=ureg.millimeter, ) + connection_type = attribute.Attribute( + description='Connection type', + ) -class OvalDuct(AirDuct): + +class AirDuctOval(AirDuct): """Oval air ducts (Ovalrohr)""" pattern_ifc_type = [ - # re.compile(r'Ovalrohr\s+Bogen', flags=re.IGNORECASE), - # re.compile(r'Ovalrohr\s+Bogen(?:\s*\(\d+\))?', flags=re.IGNORECASE), - re.compile(r'Ovalrohr\s+Bogen.*\(171\)', flags=re.IGNORECASE), + re.compile(r'Ovalrohr.*\(.*170.*\)', flags=re.IGNORECASE), + re.compile(r'^Kanal\s+\d+\s*x\s*\d+', flags=re.IGNORECASE), ] width_b = attribute.Attribute( @@ -1537,12 +1548,17 @@ class OvalDuct(AirDuct): description='Height diameter H', unit=ureg.millimeter, ) + connection_type = attribute.Attribute( + description='Connection type', + ) -class RoundDuct(AirDuct): + +class AirDuctRound(AirDuct): """Round/circular air ducts""" pattern_ifc_type = [ - re.compile('Rohr', flags=re.IGNORECASE), + re.compile(r'Rohr.*\(.*R.*,.*115.*\)', flags=re.IGNORECASE), + re.compile(r'^Rohr\s+DN\s*\d+', flags=re.IGNORECASE), ] radius = attribute.Attribute( @@ -1565,32 +1581,6 @@ class RoundDuct(AirDuct): ) -class FlexibleRoundDuct(RoundDuct): - """Flexible round air ducts""" - - pattern_ifc_type = [ - re.compile('Flexibles_Rohr', flags=re.IGNORECASE), - ] - - rings = attribute.Attribute( - description='Number of rings', - ) - -class DuctRoundTransition(RoundDuct): - """Round transition ducts (Rohrübergang Asym.)""" - - pattern_ifc_type = [ - re.compile(r'Rohrübergang\s*\(\s*RS\s*,\s*RA\s*:\s*109\s*\)', flags=re.IGNORECASE), - ] - - width_b = attribute.Attribute( - description='Width diameter B', - unit=ureg.millimeter, - ) - connection_type = attribute.Attribute( - description='Connection type/frame', - ) - # ============================================================================ # AIR DUCT FITTINGS @@ -1613,14 +1603,14 @@ class AirDuctFitting(VentilationElement): type = attribute.Attribute( description='Type', ) - insert = attribute.Attribute( - description='Insert dimension', - unit=ureg.millimeter, - ) -class RectangularDuctFitting(AirDuctFitting): - """Base class for rectangular duct fittings""" +class AirDuctRectangularFitting(AirDuctFitting): + """Rectangular duct fittings (Komponente eckig)""" + + pattern_ifc_type = [ + re.compile(r'Komponente\s+eckig.*\(.*122.*\)', flags=re.IGNORECASE), + ] outer_dimension_a = attribute.Attribute( description='Outer dimension A', @@ -1641,6 +1631,10 @@ class RectangularDuctFitting(AirDuctFitting): label_swap_ab = attribute.Attribute( description='Swap label a/b', ) + insert = attribute.Attribute( + description='Insert dimension', + unit=ureg.millimeter, + ) isolation = attribute.Attribute( description='Insulation thickness', unit=ureg.millimeter, @@ -1648,56 +1642,169 @@ class RectangularDuctFitting(AirDuctFitting): cross_section = attribute.Attribute( description='Cross section', ) + connection_type = attribute.Attribute( + description='Connection type', + ) -class RectangularDuctExtra(RectangularDuctFitting): - """Rectangular duct components (Komponente eckig)""" +class AirDuctRoundFitting(AirDuctFitting): + """Round duct components (Komponente rund)""" pattern_ifc_type = [ - re.compile('Komponente eckig', flags=re.IGNORECASE), + re.compile(r'Komponente\s+rund.*\(.*123.*\)', flags=re.IGNORECASE), ] +class AirDuctRoundFlexible(AirDuctFitting): + """Flexible round air ducts""" -class RoundDuctFitting(AirDuctFitting): - """Base class for round duct fittings""" - - pass + pattern_ifc_type = [ + re.compile(r'Flexibles\s+Rohr.*\(.*RF.*,.*121.*\)', flags=re.IGNORECASE), + re.compile(r'Flexibles[_\s]Rohr', flags=re.IGNORECASE), + ] + radius = attribute.Attribute( + description='Radius', + unit=ureg.millimeter, + ) + nominal_diameter_d1 = attribute.Attribute( + description='Nominal diameter DN', + unit=ureg.millimeter, + ) + cross_section_d = attribute.Attribute( + description='Cross section diameter', + unit=ureg.millimeter, + ) + pipe_connection = attribute.Attribute( + description='Pipe connection type', + ) -class RoundDuctExtra(RoundDuctFitting): - """Round duct components (Komponente rund)""" +class AirDuctRoundTransition(AirDuctFitting): + """Round/Oval transition ducts (Rohrübergang Asym.)""" pattern_ifc_type = [ - re.compile('Komponente rund', flags=re.IGNORECASE), + re.compile(r'Übergang\s+Oval\s*-\s*Rund/Oval.*\(.*172.*\)', flags=re.IGNORECASE), + re.compile(r'Rohrübergang\s+Asym', flags=re.IGNORECASE), ] -class PipeDuctRound(RoundDuctFitting): - """Round pipe ducts (Rohrübergang Asym.)""" + width_b = attribute.Attribute( + description='Width diameter B', + unit=ureg.millimeter, + ) + height_h = attribute.Attribute( + description='Height diameter H', + unit=ureg.millimeter, + ) + connection_type = attribute.Attribute( + description='Connection type/frame', + ) + designation_is = attribute.Attribute( + description='Designation (IS)', + ) + cross_section_output = attribute.Attribute( + description='Cross section output', + unit=ureg.millimeter, + ) + e_offset = attribute.Attribute( + description='E-offset', + unit=ureg.millimeter, + ) + f_offset = attribute.Attribute( + description='F-offset', + unit=ureg.millimeter, + ) + isolation = attribute.Attribute( + description='Insulation thickness', + unit=ureg.millimeter, + ) + + +class AirDuctRoundTransitionSymmetric(AirDuctFitting): + """Round transition symmetric (Rohrübergang RS, RA)""" pattern_ifc_type = [ - re.compile('Rohrübergang', flags=re.IGNORECASE), + re.compile(r'Rohrübergang.*\(.*RS.*,.*RA.*:.*109.*\)', flags=re.IGNORECASE), ] width_b = attribute.Attribute( description='Width diameter B', unit=ureg.millimeter, ) + designation_is = attribute.Attribute( + description='Designation (IS)', + ) + height_h = attribute.Attribute( + description='Height diameter H', + unit=ureg.millimeter, + ) + cross_section_output = attribute.Attribute( + description='Cross section output', + unit=ureg.millimeter, + ) + e_offset = attribute.Attribute( + description='E-offset', + unit=ureg.millimeter, + ) + f_offset = attribute.Attribute( + description='F-offset', + unit=ureg.millimeter, + ) + isolation_type = attribute.Attribute( + description='Isolation type', + ) + isolation = attribute.Attribute( + description='Insulation', + unit=ureg.millimeter, + ) connection_type = attribute.Attribute( - description='Connection type/frame', + description='Connection type', + ) + + +class AirDuctOvalBow(AirDuctFitting): + """Oval duct bows/bends (Ovalrohr Bogen / Bogen Sym.)""" + + pattern_ifc_type = [ + re.compile(r'Ovalrohr\s+Bogen.*\(.*171.*\)', flags=re.IGNORECASE), + re.compile(r'Bogen\s+Sym.*0/G2R', flags=re.IGNORECASE), + ] + + radius = attribute.Attribute( + description='Radius', + unit=ureg.millimeter, + ) + width_b = attribute.Attribute( + description='Width diameter B', + unit=ureg.millimeter, ) designation_is = attribute.Attribute( description='Designation (IS)', ) - nominal_diameter_d1 = attribute.Attribute( - description='Nominal diameter DN', + height_h = attribute.Attribute( + description='Height diameter H', unit=ureg.millimeter, ) + angle = attribute.Attribute( + description='Angle', + unit=ureg.degree, + ) + air_velocity = attribute.Attribute( + description='Air velocity', + unit=ureg.meter / ureg.second, + ) + cross_section = attribute.Attribute( + description='Cross section', + ) + connection_type = attribute.Attribute( + description='Connection type', + ) + -class RoundBend(RoundDuctFitting): +class AirDuctRoundBend(AirDuctFitting): """Round duct bends (Bogen (rund))""" pattern_ifc_type = [ - re.compile('Bogen (rund, 116)', flags=re.IGNORECASE), + re.compile(r'Bogen.*\(.*rund.*,.*116.*\)', flags=re.IGNORECASE), + re.compile(r'^Bogen\s+\(rund', flags=re.IGNORECASE), ] radius = attribute.Attribute( @@ -1730,16 +1837,14 @@ class RoundBend(RoundDuctFitting): description='Air velocity', unit=ureg.meter / ureg.second, ) - length = attribute.Attribute( - description='Length', - unit=ureg.millimeter, - ) -class ConcentricReduction(RoundDuctFitting): + +class AirDuctConcentricReduction(AirDuctFitting): """Concentric reductions (Reduktion_konzentrisch)""" pattern_ifc_type = [ - re.compile('Reduktion.*konzentrisch', flags=re.IGNORECASE), + re.compile(r'Reduktion.*\(.*rund.*,.*117.*\)', flags=re.IGNORECASE), + re.compile(r'Reduktion[_\s]konzentrisch', flags=re.IGNORECASE), ] designation_is = attribute.Attribute( @@ -1757,113 +1862,192 @@ class ConcentricReduction(RoundDuctFitting): description='Insertion length', unit=ureg.millimeter, ) - length = attribute.Attribute( - description='Length', - unit=ureg.millimeter, - ) connection_type = attribute.Attribute( description='Connection type', ) - type = attribute.Attribute( - description='Type', - ) air_velocity = attribute.Attribute( description='Air velocity', unit=ureg.meter / ureg.second, ) -class DuctTransition(AirDuctFitting): - """Base class for duct transitions""" +class AirDuctRectangularBow(AirDuctFitting): + """Rectangular bows/bends (Winkel)""" + + pattern_ifc_type = [ + re.compile(r'Bogen.*\(.*eckig.*WS.*,.*WA.*,.*111.*\)', flags=re.IGNORECASE), + re.compile(r'^Winkel', flags=re.IGNORECASE), + ] + + radius = attribute.Attribute( + description='Radius', + unit=ureg.millimeter, + ) + height_a = attribute.Attribute( + description='Height a', + unit=ureg.millimeter, + ) + width_start_b = attribute.Attribute( + description='Width start b', + unit=ureg.millimeter, + ) + width_end_d = attribute.Attribute( + description='Width end d', + unit=ureg.millimeter, + ) + insert_end = attribute.Attribute( + description='Insert end', + unit=ureg.millimeter, + ) + insert_start = attribute.Attribute( + description='Insert start', + unit=ureg.millimeter, + ) + guide_vanes = attribute.Attribute( + description='Guide vanes', + ) + number_guide_vanes = attribute.Attribute( + description='Number of guide vanes', + ) + inner_radius_is_bend = attribute.Attribute( + description='Inner radius is bend', + ) + angle = attribute.Attribute( + description='Angle', + unit=ureg.degree, + ) + fitting_length = attribute.Attribute( + description='Fitting length', + unit=ureg.millimeter, + ) + pipe_connection = attribute.Attribute( + description='Pipe connection', + ) + isolation = attribute.Attribute( + description='Insulation thickness', + unit=ureg.millimeter, + ) + cross_section = attribute.Attribute( + description='Cross section', + ) air_velocity = attribute.Attribute( description='Air velocity', unit=ureg.meter / ureg.second, ) - flow_rate = attribute.Attribute( - description='Flow rate', - unit=ureg.meter ** 3 / ureg.hour, - ) - strand = attribute.Attribute( - description='Strand', + absolute_roughness_factor = attribute.Attribute( + description='Absolute roughness factor', + unit=ureg.millimeter, ) -class RectangularTransition(DuctTransition): - """Rectangular duct transitions (Übergang Asym.)""" +class AirDuctEndCap(AirDuctFitting): + """Duct end caps (Endboden)""" pattern_ifc_type = [ - re.compile('Übergang Asym', flags=re.IGNORECASE), - re.compile('Übergangsbogen', flags=re.IGNORECASE), - re.compile(r'Bogen \(eckig BS, BA, 107\)', flags=re.IGNORECASE), + re.compile(r'Endboden.*\(.*eckig.*,.*138.*\)', flags=re.IGNORECASE), + re.compile(r'^Endboden', flags=re.IGNORECASE), ] - height_a = attribute.Attribute( - description='Height a', + width_a = attribute.Attribute( + description='Width a', unit=ureg.millimeter, ) - width_start_b = attribute.Attribute( - description='Width start b', + height_b = attribute.Attribute( + description='Height b', unit=ureg.millimeter, ) - height_end_c = attribute.Attribute( - description='Height end c', + isolation = attribute.Attribute( + description='Insulation thickness', unit=ureg.millimeter, ) - width_end_d = attribute.Attribute( - description='Width end d', + cross_section = attribute.Attribute( + description='Cross section', + ) + + +class AirDuctTPiece(AirDuctFitting): + """T-pieces and branches (T-Stück)""" + + pattern_ifc_type = [ + re.compile(r'T-Stück.*\(.*TS.*,.*TA.*:.*114.*\)', flags=re.IGNORECASE), + re.compile(r'^T-Stück', flags=re.IGNORECASE), + ] + + width_entrance_a = attribute.Attribute( + description='Width entrance a', unit=ureg.millimeter, ) - e_offset = attribute.Attribute( - description='E-offset', + height_entrance_b = attribute.Attribute( + description='Height entrance b', unit=ureg.millimeter, ) - f_offset = attribute.Attribute( - description='F-offset', + width_outlet_d = attribute.Attribute( + description='Width outlet d', unit=ureg.millimeter, ) - insert_start = attribute.Attribute( - description='Insert start', + width_branch_g = attribute.Attribute( + description='Width branch g', unit=ureg.millimeter, ) - extension_m = attribute.Attribute( - description='Extension m', + height_branch_h = attribute.Attribute( + description='Height branch h', unit=ureg.millimeter, ) - extension_n = attribute.Attribute( - description='Extension n', + height_branch_m = attribute.Attribute( + description='Height branch m', unit=ureg.millimeter, ) - box_lbh = attribute.Attribute( - description='Box length-width-height', + length_entrance_n = attribute.Attribute( + description='Length entrance n', + unit=ureg.millimeter, ) - box_bottom_lbh = attribute.Attribute( - description='Box bottom length-width-height', + second_branch_length_o = attribute.Attribute( + description='Second branch length o', + unit=ureg.millimeter, ) - connection_length_height = attribute.Attribute( - description='Connection length height', + second_branch_height_p = attribute.Attribute( + description='Second branch height p', + unit=ureg.millimeter, ) - list_as_ua = attribute.Attribute( - description='List as UA', + cross_tee = attribute.Attribute( + description='Cross tee', ) - isolation = attribute.Attribute( - description='Insulation thickness', + radius = attribute.Attribute( + description='Radius', unit=ureg.millimeter, ) cross_section = attribute.Attribute( description='Cross section', ) + air_velocity = attribute.Attribute( + description='Air velocity', + unit=ureg.meter / ureg.second, + ) absolute_roughness_factor = attribute.Attribute( description='Absolute roughness factor', + unit=ureg.millimeter, ) -class SymmetricalTransition(DuctTransition): - """Symmetrical duct transitions (Bogen Sym.)""" +class AirDuctTransitionRectangularAsymmetric(AirDuctFitting): + """Rectangular duct transitions (Übergang Asym.)""" pattern_ifc_type = [ - re.compile('Bogen Sym', flags=re.IGNORECASE), + re.compile(r'Etage.*,.*Übergang.*\(.*ES.*,.*US.*,.*UA.*:.*105.*\)', flags=re.IGNORECASE), + re.compile(r'^Übergang\s+Asym', flags=re.IGNORECASE), ] + air_velocity = attribute.Attribute( + description='Air velocity', + unit=ureg.meter / ureg.second, + ) + flow_rate = attribute.Attribute( + description='Flow rate', + unit=ureg.meter ** 3 / ureg.hour, + ) + strand = attribute.Attribute( + description='Strand', + ) height_a = attribute.Attribute( description='Height a', unit=ureg.millimeter, @@ -1872,34 +2056,45 @@ class SymmetricalTransition(DuctTransition): description='Width start b', unit=ureg.millimeter, ) + height_end_c = attribute.Attribute( + description='Height end c', + unit=ureg.millimeter, + ) width_end_d = attribute.Attribute( description='Width end d', unit=ureg.millimeter, ) - insert_end = attribute.Attribute( - description='Insert end', + e_offset = attribute.Attribute( + description='E-offset', + unit=ureg.millimeter, + ) + f_offset = attribute.Attribute( + description='F-offset', unit=ureg.millimeter, ) insert_start = attribute.Attribute( description='Insert start', unit=ureg.millimeter, ) - guide_vanes = attribute.Attribute( - description='Guide vanes', - ) - number_guide_vanes = attribute.Attribute( - description='Number of guide vanes', + extension_m = attribute.Attribute( + description='Extension m', + unit=ureg.millimeter, ) - radius = attribute.Attribute( - description='Radius', + extension_n = attribute.Attribute( + description='Extension n', unit=ureg.millimeter, ) - inner_radius_is_bend = attribute.Attribute( - description='Inner radius is bend', + box_lbh = attribute.Attribute( + description='Box length-width-height', ) - angle = attribute.Attribute( - description='Angle', - unit=ureg.degree, + box_bottom_lbh = attribute.Attribute( + description='Box bottom length-width-height', + ) + connection_length_height = attribute.Attribute( + description='Connection length height', + ) + list_as_ua = attribute.Attribute( + description='List as UA', ) isolation = attribute.Attribute( description='Insulation thickness', @@ -1910,19 +2105,28 @@ class SymmetricalTransition(DuctTransition): ) absolute_roughness_factor = attribute.Attribute( description='Absolute roughness factor', + unit=ureg.millimeter, ) -class RectangularElbow(AirDuctFitting): - """Rectangular elbows/bends (Winkel)""" +class AirDuctTransitionSymmetrical(AirDuctFitting): + """Symmetrical duct transitions (Bogen Sym.)""" pattern_ifc_type = [ - re.compile('Winkel', flags=re.IGNORECASE), + re.compile(r'Bogen.*\(.*eckig.*,.*BS.*,.*BA.*,.*107.*\)', flags=re.IGNORECASE), + re.compile(r'^Bogen\s+Sym.*eckig', flags=re.IGNORECASE), ] - radius = attribute.Attribute( - description='Radius', - unit=ureg.millimeter, + air_velocity = attribute.Attribute( + description='Air velocity', + unit=ureg.meter / ureg.second, + ) + flow_rate = attribute.Attribute( + description='Flow rate', + unit=ureg.meter ** 3 / ureg.hour, + ) + strand = attribute.Attribute( + description='Strand', ) height_a = attribute.Attribute( description='Height a', @@ -1936,10 +2140,6 @@ class RectangularElbow(AirDuctFitting): description='Width end d', unit=ureg.millimeter, ) - width_outlet_d = attribute.Attribute( - description='Width outlet d', - unit=ureg.millimeter, - ) insert_end = attribute.Attribute( description='Insert end', unit=ureg.millimeter, @@ -1954,6 +2154,10 @@ class RectangularElbow(AirDuctFitting): number_guide_vanes = attribute.Attribute( description='Number of guide vanes', ) + radius = attribute.Attribute( + description='Radius', + unit=ureg.millimeter, + ) inner_radius_is_bend = attribute.Attribute( description='Inner radius is bend', ) @@ -1961,13 +2165,6 @@ class RectangularElbow(AirDuctFitting): description='Angle', unit=ureg.degree, ) - fitting_length = attribute.Attribute( - description='Fitting length', - unit=ureg.millimeter, - ) - pipe_connection = attribute.Attribute( - description='Pipe connection', - ) isolation = attribute.Attribute( description='Insulation thickness', unit=ureg.millimeter, @@ -1975,102 +2172,66 @@ class RectangularElbow(AirDuctFitting): cross_section = attribute.Attribute( description='Cross section', ) - air_velocity = attribute.Attribute( - description='Air velocity', - unit=ureg.meter / ureg.second, - ) absolute_roughness_factor = attribute.Attribute( description='Absolute roughness factor', - ) - - -class DuctEndCap(AirDuctFitting): - """Duct end caps (Endboden)""" - - pattern_ifc_type = [ - re.compile('Endboden', flags=re.IGNORECASE), - ] - - width_a = attribute.Attribute( - description='Width a', - unit=ureg.millimeter, - ) - height_b = attribute.Attribute( - description='Height b', - unit=ureg.millimeter, - ) - isolation = attribute.Attribute( - description='Insulation thickness', unit=ureg.millimeter, ) - cross_section = attribute.Attribute( - description='Cross section', - ) -class DuctTee(AirDuctFitting): - """T-pieces and branches (T-Stück)""" +class AirDuctTransitionRectangularBow(AirDuctFitting): + """Rectangular duct transition bows (Übergangsbogen)""" pattern_ifc_type = [ - re.compile('T-Stück (rund, 118)', flags=re.IGNORECASE), + re.compile(r'^Übergangsbogen', flags=re.IGNORECASE), ] - width_entrance_a = attribute.Attribute( - description='Width entrance a', - unit=ureg.millimeter, - ) - width_start_b = attribute.Attribute( - description='Width start b', - unit=ureg.millimeter, + air_velocity = attribute.Attribute( + description='Air velocity', + unit=ureg.meter / ureg.second, ) - height_entrance_b = attribute.Attribute( - description='Height entrance b', - unit=ureg.millimeter, + flow_rate = attribute.Attribute( + description='Flow rate', + unit=ureg.meter ** 3 / ureg.hour, ) - width_outlet_d = attribute.Attribute( - description='Width outlet d', - unit=ureg.millimeter, + strand = attribute.Attribute( + description='Strand', ) - width_branch_g = attribute.Attribute( - description='Width branch g', + width_b = attribute.Attribute( + description='Width diameter B', unit=ureg.millimeter, ) - height_branch_h = attribute.Attribute( - description='Height branch h', - unit=ureg.millimeter, + designation_is = attribute.Attribute( + description='Designation (IS)', ) - height_branch_m = attribute.Attribute( - description='Height branch m', + height_h = attribute.Attribute( + description='Height diameter H', unit=ureg.millimeter, ) - length_entrance_n = attribute.Attribute( - description='Length entrance n', + cross_section_output = attribute.Attribute( + description='Cross section output', unit=ureg.millimeter, ) - second_branch_length_o = attribute.Attribute( - description='Second branch length o', + e_offset = attribute.Attribute( + description='E-offset', unit=ureg.millimeter, ) - second_branch_height_p = attribute.Attribute( - description='Second branch height p', + f_offset = attribute.Attribute( + description='F-offset', unit=ureg.millimeter, ) - cross_tee = attribute.Attribute( - description='Cross tee', - ) - radius = attribute.Attribute( - description='Radius', + isolation = attribute.Attribute( + description='Insulation thickness', unit=ureg.millimeter, ) cross_section = attribute.Attribute( description='Cross section', ) - air_velocity = attribute.Attribute( - description='Air velocity', - unit=ureg.meter / ureg.second, + connection_type = attribute.Attribute( + description='Connection type', ) absolute_roughness_factor = attribute.Attribute( description='Absolute roughness factor', + unit=ureg.millimeter, ) @@ -2078,13 +2239,18 @@ class DuctTee(AirDuctFitting): # DAMPERS AND CONTROLLERS # ============================================================================ -class Damper(VentilationElement): - """Base class for dampers""" + +class FireDamper(VentilationElement): + """Fire dampers (Brandschutzklappe)""" ifc_types = { 'IfcDamper': ['*'] } + pattern_ifc_type = [ + re.compile(r'^Brandschutzklappe', flags=re.IGNORECASE), + ] + manufacturer = attribute.Attribute( description='Manufacturer', ) @@ -2098,15 +2264,6 @@ class Damper(VentilationElement): cross_section = attribute.Attribute( description='Cross section', ) - - -class FireDamper(Damper): - """Fire dampers (Brandschutzklappe)""" - - pattern_ifc_type = [ - re.compile('Brandschutzklappe', flags=re.IGNORECASE), - ] - flow_velocity = attribute.Attribute( description='Flow velocity', unit=ureg.meter / ureg.second, @@ -2159,7 +2316,7 @@ class FireDamper(Damper): ) -class VolumeFlowController(VentilationElement): +class AirVolumeFlowController(VentilationElement): """Base class for volume flow controllers""" flow_velocity = attribute.Attribute( @@ -2182,7 +2339,7 @@ class VolumeFlowController(VentilationElement): ) -class ConstantVolumeFlowController(VolumeFlowController): +class AirVolumeFlowControllerConstant(AirVolumeFlowController): """Constant volume flow controllers (Konstant VSR)""" ifc_types = { @@ -2190,7 +2347,7 @@ class ConstantVolumeFlowController(VolumeFlowController): } pattern_ifc_type = [ - re.compile('Konstant VSR', flags=re.IGNORECASE), + re.compile(r'^Konstant\s+VSR', flags=re.IGNORECASE), ] radius = attribute.Attribute( @@ -2214,7 +2371,7 @@ class ConstantVolumeFlowController(VolumeFlowController): ) -class DynamicVolumeFlowController(VolumeFlowController): +class AirVolumeFlowControllerDynamic(AirVolumeFlowController): """Dynamic/variable volume flow controllers (Variabel VSR)""" ifc_types = { @@ -2222,7 +2379,7 @@ class DynamicVolumeFlowController(VolumeFlowController): } pattern_ifc_type = [ - re.compile('Variabel VSR', flags=re.IGNORECASE), + re.compile(r'^Variabel\s+VSR', flags=re.IGNORECASE), ] outer_dimension_a = attribute.Attribute( @@ -2261,7 +2418,7 @@ class DynamicVolumeFlowController(VolumeFlowController): # SILENCERS # ============================================================================ -class Silencer(VentilationElement): +class AirSilencer(VentilationElement): """Base class for silencers/sound dampeners""" manufacturer = attribute.Attribute( @@ -2276,7 +2433,7 @@ class Silencer(VentilationElement): ) -class FlexibleRoundSilencer(Silencer): +class AirSilencerRoundFlexible(AirSilencer): """Flexible round silencers (Flexibler Rohrschalldämpfer)""" ifc_types = { @@ -2284,7 +2441,7 @@ class FlexibleRoundSilencer(Silencer): } pattern_ifc_type = [ - re.compile('Flexibler Rohrschalldämpfer', flags=re.IGNORECASE), + re.compile(r'^Flexibler\s+Rohrschalldämpfer', flags=re.IGNORECASE), ] flow_velocity = attribute.Attribute( @@ -2308,7 +2465,7 @@ class FlexibleRoundSilencer(Silencer): ) -class TelephonySilencer(Silencer): +class AirSilencerTelephony(AirSilencer): """Telephony silencers (Telefonie-Schalldämpfer)""" ifc_types = { @@ -2316,7 +2473,7 @@ class TelephonySilencer(Silencer): } pattern_ifc_type = [ - re.compile('Telefonie-Schalldämpfer', flags=re.IGNORECASE), + re.compile(r'^Telefonie-Schalld[aä]mpfer', flags=re.IGNORECASE), ] radius = attribute.Attribute( @@ -2355,7 +2512,9 @@ class AirTerminal(VentilationElement): } pattern_ifc_type = [ - re.compile('Drallauslass (M_20011)', flags=re.IGNORECASE), + re.compile(r'Drallauslass.*\(.*M[_\s]20011.*\)', flags=re.IGNORECASE), + re.compile(r'^Luftdurchl[aä]sse', flags=re.IGNORECASE), + re.compile(r'^Auslass.*\(.*M[_\s]20011.*\)', flags=re.IGNORECASE), ] outlet_grille_graphic = attribute.Attribute( @@ -2365,6 +2524,15 @@ class AirTerminal(VentilationElement): description='Nominal diameter DN', unit=ureg.millimeter, ) + cylinder_1 = attribute.Attribute( + description='Cylinder 1: DN1 DN2 Length', + ) + cylinder_2 = attribute.Attribute( + description='Cylinder 2: DN1 DN2 Length', + ) + cylinder_3 = attribute.Attribute( + description='Cylinder 3: DN1 DN2 Length', + ) manufacturer = attribute.Attribute( description='Manufacturer', ) @@ -2395,7 +2563,8 @@ class AirValve(VentilationElement): } pattern_ifc_type = [ - re.compile('Tellerventile', flags=re.IGNORECASE), + re.compile(r'Auslass.*\(.*M[_\s]20111.*\)', flags=re.IGNORECASE), + re.compile(r'^Tellerventile', flags=re.IGNORECASE), ] radius = attribute.Attribute( @@ -2506,43 +2675,45 @@ class AHUFan(AHUComponent): } pattern_ifc_type = [ - re.compile('AHU_Fan', flags=re.IGNORECASE), + re.compile(r'^Ventilator\s+allgemein', flags=re.IGNORECASE), ] class AHUAirChamber(AHUComponent): """Air handling unit air chamber""" + ifc_types = { 'IfcDistributionChamberElement': ['*'] } pattern_ifc_type = [ - re.compile('AHU_AirChamber', flags=re.IGNORECASE), + re.compile(r'^Leerkammer', flags=re.IGNORECASE), ] class AHUCooler(AHUComponent): """Air handling unit cooler""" + ifc_types = { 'IfcDuctFitting': ['*'] } pattern_ifc_type = [ - re.compile('AHU_Cooler', flags=re.IGNORECASE), + re.compile(r'^Luftk[uü]hler', flags=re.IGNORECASE), ] class AHUSilencer(AHUComponent): """Air handling unit silencer""" + ifc_types = { 'IfcDamper': ['*'] } pattern_ifc_type = [ - re.compile('AHU_Silencer', flags=re.IGNORECASE), + re.compile(r'^Schalld[aä]mpfer\s+Allgemein', flags=re.IGNORECASE), ] - # collect all domain classes items: Set[HVACProduct] = set() for name, cls in inspect.getmembers( @@ -2550,7 +2721,7 @@ class AHUSilencer(AHUComponent): lambda member: inspect.isclass(member) # class at all and issubclass(member, HVACProduct) # domain subclass and member is not HVACProduct # but not base class - and member not in (VentilationElement, Silencer, VolumeFlowController, AHUComponent) # but not sub base class + and member not in (VentilationElement, AirSilencer, AirVolumeFlowController, AHUComponent) # but not sub base class and member.__module__ == __name__): # declared here items.add(cls) @@ -2561,6 +2732,6 @@ class AHUSilencer(AHUComponent): lambda member: inspect.isclass(member) # class at all and issubclass(member, VentilationElement) # domain subclass and member is not VentilationElement # but not base class - and member not in (Silencer, VolumeFlowController, AHUComponent) # but not sub base class + and member not in (AirSilencer, AirVolumeFlowController, AHUComponent) # but not sub base class and member.__module__ == __name__): # declared here ventilation_items.add(cls) From dd20cef08820502420fe4b2f8f9628715346cd4f Mon Sep 17 00:00:00 2001 From: Hoepp J <155448650+HoeppJ@users.noreply.github.com> Date: Thu, 20 Nov 2025 15:32:41 +0100 Subject: [PATCH 113/125] delete duplicate, empty class --- bim2sim/elements/base_elements.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bim2sim/elements/base_elements.py b/bim2sim/elements/base_elements.py index 919cac63c8..256fd9e6fe 100644 --- a/bim2sim/elements/base_elements.py +++ b/bim2sim/elements/base_elements.py @@ -444,9 +444,9 @@ def __str__(self): return "%s" % self.__class__.__name__ -class RelationBased(IFCBased): - - pass +# class RelationBased(IFCBased): +# +# pass class ProductBased(IFCBased): From 3106dde71e77c9ae930b526254e18f6c2385632d Mon Sep 17 00:00:00 2001 From: Hoepp J <155448650+HoeppJ@users.noreply.github.com> Date: Thu, 20 Nov 2025 15:33:05 +0100 Subject: [PATCH 114/125] fix pattern bugs --- .../assets/finder/template_LuArtX_Carf.json | 205 ++++++++++++++---- bim2sim/elements/hvac_elements.py | 187 +++++++++++++++- 2 files changed, 348 insertions(+), 44 deletions(-) diff --git a/bim2sim/assets/finder/template_LuArtX_Carf.json b/bim2sim/assets/finder/template_LuArtX_Carf.json index 50cbce3b9c..3b664bd017 100644 --- a/bim2sim/assets/finder/template_LuArtX_Carf.json +++ b/bim2sim/assets/finder/template_LuArtX_Carf.json @@ -56,10 +56,10 @@ "on_list": ["MyTab1", "[ON_LST] Auf Liste"], "pen": ["MyTab1", "[PEN] Schtift"], "system": ["MyTab1", "[PLANT] Anlage"], - "position": ["MyTab1", "[POS] Position"], + "position_in_ifc": ["MyTab1", "[POS] Position"], "cross_section": ["MyTab1", "[QS] Querschnitt"], "cross_section_area": ["MyTab1", "[QUERSCHNITT] G"], - "connection_type": ["MyTab1", "[RAHMEN]"], + "connection_type": ["MyTab1", "[RAHMEN] Rahmen"], "floor": ["MyTab1", "[STOCK] Stockwerk"], "version": ["MyTab1", "[VERS] Version"], "air_velocity": ["MyTab1", "[v] Luftgeschw."] @@ -82,7 +82,7 @@ "on_list": ["MyTab1", "[ON_LST] Auf Liste"], "pen": ["MyTab1", "[PEN] Schtift"], "system": ["MyTab1", "[PLANT] Anlage"], - "position": ["MyTab1", "[POS] Position"], + "position_in_ifc": ["MyTab1", "[POS] Position"], "cross_section": ["MyTab1", "[QS] Querschnitt"], "connection_type": ["MyTab1", "[RAHMEN] Rahmen"], "floor": ["MyTab1", "[STOCK] Stockwerk"], @@ -109,7 +109,7 @@ "on_list": ["MyTab1", "[ON_LST] Auf Liste"], "pen": ["MyTab1", "[PEN] Schtift"], "system": ["MyTab1", "[PLANT] Anlage"], - "position": ["MyTab1", "[POS] Position"], + "position_in_ifc": ["MyTab1", "[POS] Position"], "pipe_connection": ["MyTab1", "[RAHMEN] Verbindungsart"], "floor": ["MyTab1", "[STOCK] Stockwerk"], "version": ["MyTab1", "[VERS] Version"], @@ -133,7 +133,7 @@ "on_list": ["MyTab1", "[ON_LST] Auf Liste"], "pen": ["MyTab1", "[PEN] Schtift"], "system": ["MyTab1", "[PLANT] Anlage"], - "position": ["MyTab1", "[POS] Position"], + "position_in_ifc": ["MyTab1", "[POS] Position"], "pipe_connection": ["MyTab1", "[RAHMEN] Rohrverbindung"], "rings": ["MyTab1", "[RI] Ringe"], "floor": ["MyTab1", "[STOCK] Stockwerk"], @@ -162,9 +162,9 @@ "on_list": ["MyTab1", "[ON_LST] Auf Liste"], "pen": ["MyTab1", "[PEN] Schtift"], "system": ["MyTab1", "[PLANT] Anlage"], - "position": ["MyTab1", "[POS] Position"], + "position_in_ifc": ["MyTab1", "[POS] Position"], "cross_section": ["MyTab1", "[QS] Querschnitt"], - "connection_type": ["MyTab1", "[RAHMEN]"], + "connection_type": ["MyTab1", "[RAHMEN] Rahmen"], "floor": ["MyTab1", "[STOCK] Stockwerk"], "type": ["MyTab1", "[TYP] Typ"], "version": ["MyTab1", "[VERS] Version"] @@ -180,7 +180,7 @@ "data_mask_number": ["MyTab1", "[MSKNR] Datenmaskennummer"], "on_list": ["MyTab1", "[ON_LST] Auf Liste"], "system": ["MyTab1", "[PLANT] Anlage"], - "position": ["MyTab1", "[POS] Position"], + "position_in_ifc": ["MyTab1", "[POS] Position"], "floor": ["MyTab1", "[STOCK] Stockwerk"], "type": ["MyTab1", "[TYP] Typ"], "version": ["MyTab1", "[VERS] Version"], @@ -210,7 +210,7 @@ "on_list": ["MyTab1", "[ON_LST] Auf Liste"], "pen": ["MyTab1", "[PEN] Schtift"], "system": ["MyTab1", "[PLANT] Anlage"], - "position": ["MyTab1", "[POS] Position"], + "position_in_ifc": ["MyTab1", "[POS] Position"], "cross_section": ["MyTab1", "[QS] Querschnitt"], "connection_type": ["MyTab1", "[RAHMEN] Rahmen"], "floor": ["MyTab1", "[STOCK] Stockwerk"], @@ -239,7 +239,7 @@ "on_list": ["MyTab1", "[ON_LST] Auf Liste"], "pen": ["MyTab1", "[PEN] Schtift"], "system": ["MyTab1", "[PLANT] Anlage"], - "position": ["MyTab1", "[POS] Position"], + "position_in_ifc": ["MyTab1", "[POS] Position"], "cross_section": ["MyTab1", "[QS] Querschnitt"], "connection_type": ["MyTab1", "[RAHMEN] Rahmen"], "floor": ["MyTab1", "[STOCK] Stockwerk"], @@ -264,7 +264,7 @@ "on_list": ["MyTab1", "[ON_LST] Auf Liste"], "pen": ["MyTab1", "[PEN] Schtift"], "system": ["MyTab1", "[PLANT] Anlage"], - "position": ["MyTab1", "[POS] Position"], + "position_in_ifc": ["MyTab1", "[POS] Position"], "cross_section": ["MyTab1", "[QS] Querschnitt"], "connection_type": ["MyTab1", "[RAHMEN] Rahmen"], "floor": ["MyTab1", "[STOCK] Stockwerk"], @@ -287,7 +287,7 @@ "on_list": ["MyTab1", "[ON_LST] Auf Liste"], "pen": ["MyTab1", "[PEN] Schtift"], "system": ["MyTab1", "[PLANT] Anlage"], - "position": ["MyTab1", "[POS] Position"], + "position_in_ifc": ["MyTab1", "[POS] Position"], "connection_type": ["MyTab1", "[RAHMEN] Verbindungsart"], "floor": ["MyTab1", "[STOCK] Stockwerk"], "version": ["MyTab1", "[VERS] Version"], @@ -317,7 +317,7 @@ "on_list": ["MyTab1", "[ON_LST] Auf Liste"], "pen": ["MyTab1", "[PEN] Schtift"], "system": ["MyTab1", "[PLANT] Anlage"], - "position": ["MyTab1", "[POS] Position"], + "position_in_ifc": ["MyTab1", "[POS] Position"], "connection_type": ["MyTab1", "[RAHMEN] Verbindungsart"], "floor": ["MyTab1", "[STOCK] Stockwerk"], "type": ["MyTab1", "[TYP] Typ"], @@ -347,9 +347,9 @@ "fitting_length": ["MyTab1", "[PE] Passlänge e"], "pen": ["MyTab1", "[PEN] Schtift"], "system": ["MyTab1", "[PLANT] Anlage"], - "position": ["MyTab1", "[POS] Position"], + "position_in_ifc": ["MyTab1", "[POS] Position"], "cross_section": ["MyTab1", "[QS] Querschnitt"], - "connection_type": ["MyTab1", "[RAHMEN]"], + "connection_type": ["MyTab1", "[RAHMEN] Rahmen"], "pipe_connection": ["MyTab1", "[RAHMEN] Rohrverbindung"], "inner_radius_is_bend": ["MyTab1", "[RK] Innerradius ist Abkantung"], "radius": ["MyTab1", "[r] Radius"], @@ -379,9 +379,9 @@ "on_list": ["MyTab1", "[ON_LST] Auf Liste"], "pen": ["MyTab1", "[PEN] Schtift"], "system": ["MyTab1", "[PLANT] Anlage"], - "position": ["MyTab1", "[POS] Position"], + "position_in_ifc": ["MyTab1", "[POS] Position"], "cross_section": ["MyTab1", "[QS] Querschnitt"], - "connection_type": ["MyTab1", "[RAHMEN]"], + "connection_type": ["MyTab1", "[RAHMEN] Rahmen"], "floor": ["MyTab1", "[STOCK] Stockwerk"], "version": ["MyTab1", "[VERS] Version"], "manufacturer": ["MyTab1", "[HST] Hersteller"] @@ -410,10 +410,10 @@ "second_branch_length_o": ["MyTab1", "[o] 2. Verzweigung. Länge Eingang"], "pen": ["MyTab1", "[PEN] Schtift"], "system": ["MyTab1", "[PLANT] Anlage"], - "position": ["MyTab1", "[POS] Position"], + "position_in_ifc": ["MyTab1", "[POS] Position"], "second_branch_height_p": ["MyTab1", "[p] 2. Verzweigung. Höhe"], "cross_section": ["MyTab1", "[QS] Querschnitt"], - "connection_type": ["MyTab1", "[RAHMEN]"], + "connection_type": ["MyTab1", "[RAHMEN] Rahmen"], "radius": ["MyTab1", "[r] Radius"], "floor": ["MyTab1", "[STOCK] Stockwerk"], "version": ["MyTab1", "[VERS] Version"], @@ -451,9 +451,9 @@ "on_list": ["MyTab1", "[ON_LST] Auf Liste"], "pen": ["MyTab1", "[PEN] Schtift"], "system": ["MyTab1", "[PLANT] Anlage"], - "position": ["MyTab1", "[POS] Position"], + "position_in_ifc": ["MyTab1", "[POS] Position"], "cross_section": ["MyTab1", "[QS] Querschnitt"], - "connection_type": ["MyTab1", "[RAHMEN]"], + "connection_type": ["MyTab1", "[RAHMEN] Rahmen"], "floor": ["MyTab1", "[STOCK] Stockwerk"], "strand": ["MyTab1", "[STR] Strang"], "version": ["MyTab1", "[VERS] Version"], @@ -483,9 +483,9 @@ "on_list": ["MyTab1", "[ON_LST] Auf Liste"], "pen": ["MyTab1", "[PEN] Schtift"], "system": ["MyTab1", "[PLANT] Anlage"], - "position": ["MyTab1", "[POS] Position"], + "position_in_ifc": ["MyTab1", "[POS] Position"], "cross_section": ["MyTab1", "[QS] Querschnitt"], - "connection_type": ["MyTab1", "[RAHMEN]"], + "connection_type": ["MyTab1", "[RAHMEN] Rahmen"], "inner_radius_is_bend": ["MyTab1", "[RK] Innerradius ist Abkantung"], "radius": ["MyTab1", "[r] Radius"], "floor": ["MyTab1", "[STOCK] Stockwerk"], @@ -520,7 +520,7 @@ "on_list": ["MyTab1", "[ON_LST] Auf Liste"], "pen": ["MyTab1", "[PEN] Schtift"], "system": ["MyTab1", "[PLANT] Anlage"], - "position": ["MyTab1", "[POS] Position"], + "position_in_ifc": ["MyTab1", "[POS] Position"], "cross_section": ["MyTab1", "[QS] Querschnitt"], "connection_type": ["MyTab1", "[RAHMEN] Rahmen"], "floor": ["MyTab1", "[STOCK] Stockwerk"], @@ -531,6 +531,129 @@ "absolute_roughness_factor": ["Pset_DuctFittingTypeCommon", "AbsoluteRoughnessFactor"] } }, + "AirDuctRectangularPants": { + "default_ps": { + "air_type": ["MyTab1", "[ANLAGE] Luftart"], + "cross_section_entrance_a": ["MyTab1", "[a] Querschnitt Eingang"], + "designation": ["MyTab1", "[BEZ] Bezeichnung"], + "cross_section_entrance_b": ["MyTab1", "[b] Querschnitt Eingang"], + "cross_section_output_c": ["MyTab1", "[c] Querschnitt Ausgang"], + "cross_section_output_d": ["MyTab1", "[d] Querschnitt Ausgang (d)"], + "e_offset": ["MyTab1", "[e] Versatz e"], + "f_offset": ["MyTab1", "[fF] Versatz f"], + "cross_section_output_h": ["MyTab1", "[h] Querschnitt Ausgang (h)"], + "manufacturer": ["MyTab1", "[HST] Hersteller"], + "isolation_type": ["MyTab1", "[ISOL-TYPE] Isolationstyp"], + "isolation": ["MyTab1", "[ISOL] Isolation"], + "edge": ["MyTab1", "[KANTE] Kante"], + "code": ["MyTab1", "[KBZ] KBZ"], + "key": ["MyTab1", "[KEY] Schlüssel"], + "length": ["MyTab1", "[l] Länge"], + "material": ["MyTab1", "[MAT] Material"], + "data_mask_number": ["MyTab1", "[MSKNR] Datenmaskennummer"], + "center_distance": ["MyTab1", "[m] Mittelstück-m"], + "on_list": ["MyTab1", "[ON_LST] Auf Liste"], + "pen": ["MyTab1", "[PEN] Schtift"], + "system": ["MyTab1", "[PLANT] Anlage"], + "position_in_ifc": ["MyTab1", "[POS] Position"], + "cross_section": ["MyTab1", "[QS] Querschnitt"], + "connection_type": ["MyTab1", "[RAHMEN] Rahmen"], + "floor": ["MyTab1", "[STOCK] Stockwerk"], + "version": ["MyTab1", "[VERS] Version"] + } + }, + "AirDuctRectangularInspectionCover": { + "default_ps": { + "air_type": ["MyTab1", "[ANLAGE] Luftart"], + "length_a": ["MyTab1", "[a] Länge"], + "remark": ["MyTab1", "[BEM] Bemerkung"], + "designation_is": ["MyTab1", "[BEZ] Bezeichnung (IS)"], + "width_b": ["MyTab1", "[b] Breite"], + "nominal_diameter_dn": ["MyTab1", "[DN] DN Rohr"], + "height_h": ["MyTab1", "[H] Höhe"], + "isolation_type": ["MyTab1", "[ISOL-TYPE] Isolationstyp"], + "edge": ["MyTab1", "[KANTE] Kante"], + "code": ["MyTab1", "[KBZ] KBZ"], + "key": ["MyTab1", "[KEY] Schlüssel"], + "material": ["MyTab1", "[MAT] Material"], + "data_mask_number": ["MyTab1", "[MSKNR] Datenmaskennummer"], + "on_list": ["MyTab1", "[ON_LST] Auf Liste"], + "pen": ["MyTab1", "[PEN] Schtift"], + "system": ["MyTab1", "[PLANT] Anlage"], + "position_in_ifc": ["MyTab1", "[POS] Position"], + "cross_section": ["MyTab1", "[QS] Querschnitt"], + "connection_type": ["MyTab1", "[RAHMEN] Rahmen"], + "radius": ["MyTab1", "[R] Radius"], + "floor": ["MyTab1", "[STOCK] Stockwerk"], + "version": ["MyTab1", "[VERS] Version"] + } + }, + "AirDuctRoundStub": { + "default_ps": { + "air_type": ["MyTab1", "[ANLAGE] Luftart"], + "designation_is": ["MyTab1", "[BEZ] Bezeichnung (IS)"], + "nominal_diameter_d1": ["MyTab1", "[d1] DN"], + "isolation_type": ["MyTab1", "[ISOL-TYPE] Isolationstyp"], + "edge": ["MyTab1", "[KANTE] Kante"], + "code": ["MyTab1", "[KBZ] KBZ"], + "key": ["MyTab1", "[KEY] Schlüssel"], + "material": ["MyTab1", "[MAT] Material"], + "data_mask_number": ["MyTab1", "[MSKNR] Datenmaskennummer"], + "on_list": ["MyTab1", "[ON_LST] Auf Liste"], + "pen": ["MyTab1", "[PEN] Schtift"], + "system": ["MyTab1", "[PLANT] Anlage"], + "position_in_ifc": ["MyTab1", "[POS] Position"], + "floor": ["MyTab1", "[STOCK] Stockwerk"], + "version": ["MyTab1", "[VERS] Version"], + "air_velocity": ["MyTab1", "[v] Luftgeschw."] + } + }, + "AirDuctRivetedEdge": { + "default_ps": { + "air_type": ["MyTab1", "[ANLAGE] Medium"], + "dimension_a": ["MyTab1", "[a] (a)"], + "designation": ["MyTab1", "[BEZ] Bezeichnung"], + "dimension_b": ["MyTab1", "[b] (b)"], + "isolation_type": ["MyTab1", "[ISOL-TYPE] Isolationstyp"], + "edge": ["MyTab1", "[KANTE] Kante"], + "code": ["MyTab1", "[KBZ] Kurzbezeichnung"], + "key": ["MyTab1", "[KEY] Schlüssel"], + "material": ["MyTab1", "[MAT] Material"], + "data_mask_number": ["MyTab1", "[MSKNR] Datenmaskennummer"], + "on_list": ["MyTab1", "[ON_LST] Auf Liste"], + "pen": ["MyTab1", "[PEN] Schtift"], + "system": ["MyTab1", "[PLANT] Anlage"], + "position_in_ifc": ["MyTab1", "[POS] Position"], + "cross_section": ["MyTab1", "[QS] Querschnitt"], + "floor": ["MyTab1", "[STOCK] Stockwerk"], + "type": ["MyTab1", "[TYP] Typ"], + "version": ["MyTab1", "[VERS] Version"] + } + }, + "AirDuctRoundCoupling": { + "default_ps": { + "air_type": ["MyTab1", "[ANLAGE] Luftart"], + "designation_is": ["MyTab1", "[BEZ] Bezeichnung (IS)"], + "cross_section_d1": ["MyTab1", "[d1] Querschnitt"], + "manufacturer": ["MyTab1", "[HST] Hersteller"], + "isolation_type": ["MyTab1", "[ISOL-TYPE] Isolationstyp"], + "edge": ["MyTab1", "[KANTE] Kante"], + "code": ["MyTab1", "[KBZ] KBZ"], + "key": ["MyTab1", "[KEY] Schlüssel"], + "length": ["MyTab1", "[l] Länge"], + "material": ["MyTab1", "[MAT] Material"], + "data_mask_number": ["MyTab1", "[MSKNR] Datenmaskennummer"], + "on_list": ["MyTab1", "[ON_LST] Auf Liste"], + "pen": ["MyTab1", "[PEN] Schtift"], + "system": ["MyTab1", "[PLANT] Anlage"], + "position_in_ifc": ["MyTab1", "[POS] Position"], + "connection_type": ["MyTab1", "[RAHMEN] Verbindungsart"], + "floor": ["MyTab1", "[STOCK] Stockwerk"], + "type": ["MyTab1", "[TYP] Typ"], + "version": ["MyTab1", "[VERS] Version"], + "air_velocity": ["MyTab1", "[v] Luftgeschw."] + } + }, "FireDamper": { "default_ps": { "flow_velocity": ["Calc", "[ve] Strömungsgeschwindigkeit"], @@ -557,9 +680,9 @@ "on_list": ["MyTab1", "[ON_LST] Auf Liste"], "pen": ["MyTab1", "[PEN] Schtift"], "system": ["MyTab1", "[PLANT] Anlage"], - "position": ["MyTab1", "[POS] Position"], + "position_in_ifc": ["MyTab1", "[POS] Position"], "cross_section": ["MyTab1", "[QS] Querschnitt"], - "connection_type": ["MyTab1", "[RAHMEN]"], + "connection_type": ["MyTab1", "[RAHMEN] Rahmen"], "floor": ["MyTab1", "[STOCK] Stockwerk"], "type": ["MyTab1", "[TYP] Typ"], "version": ["MyTab1", "[VERS] Version"], @@ -587,8 +710,8 @@ "on_list": ["MyTab1", "[ON_LST] Auf Liste"], "pen": ["MyTab1", "[PEN] Schtift"], "system": ["MyTab1", "[PLANT] Anlage"], - "position": ["MyTab1", "[POS] Position"], - "connection_type": ["MyTab1", "[RAHMEN]"], + "position_in_ifc": ["MyTab1", "[POS] Position"], + "connection_type": ["MyTab1", "[RAHMEN] Rahmen"], "floor": ["MyTab1", "[STOCK] Stockwerk"], "type": ["MyTab1", "[TYP] Typ"], "version": ["MyTab1", "[VERS] Version"], @@ -618,9 +741,9 @@ "on_list": ["MyTab1", "[ON_LST] Auf Liste"], "pen": ["MyTab1", "[PEN] Schtift"], "system": ["MyTab1", "[PLANT] Anlage"], - "position": ["MyTab1", "[POS] Position"], + "position_in_ifc": ["MyTab1", "[POS] Position"], "cross_section": ["MyTab1", "[QS] Querschnitt"], - "connection_type": ["MyTab1", "[RAHMEN]"], + "connection_type": ["MyTab1", "[RAHMEN] Rahmen"], "floor": ["MyTab1", "[STOCK] Stockwerk"], "type": ["MyTab1", "[TYP] Typ"], "version": ["MyTab1", "[VERS] Version"], @@ -646,8 +769,8 @@ "on_list": ["MyTab1", "[ON_LST] Auf Liste"], "pen": ["MyTab1", "[PEN] Schtift"], "system": ["MyTab1", "[PLANT] Anlage"], - "position": ["MyTab1", "[POS] Position"], - "connection_type": ["MyTab1", "[RAHMEN]"], + "position_in_ifc": ["MyTab1", "[POS] Position"], + "connection_type": ["MyTab1", "[RAHMEN] Rahmen"], "floor": ["MyTab1", "[STOCK] Stockwerk"], "type": ["MyTab1", "[TYP] Typ"], "version": ["MyTab1", "[VERS] Version"] @@ -671,9 +794,9 @@ "data_mask_number": ["MyTab1", "[MSKNR] Datenmaskennummer"], "pen": ["MyTab1", "[PEN] Schtift"], "system": ["MyTab1", "[PLANT] Anlage"], - "position": ["MyTab1", "[POS] Position"], + "position_in_ifc": ["MyTab1", "[POS] Position"], "project": ["MyTab1", "[PRJ] Projekt"], - "connection_type": ["MyTab1", "[RAHMEN]"], + "connection_type": ["MyTab1", "[RAHMEN] Rahmen"], "series": ["MyTab1", "[SER] Serie"], "floor": ["MyTab1", "[STOCK] Stockwerk"], "type": ["MyTab1", "[TYP] Typ"], @@ -702,8 +825,8 @@ "on_list": ["MyTab1", "[ON_LST] Auf Liste"], "pen": ["MyTab1", "[PEN] Schtift"], "system": ["MyTab1", "[PLANT] Anlage"], - "position": ["MyTab1", "[POS] Position"], - "connection_type": ["MyTab1", "[RAHMEN]"], + "position_in_ifc": ["MyTab1", "[POS] Position"], + "connection_type": ["MyTab1", "[RAHMEN] Rahmen"], "rings": ["MyTab1", "[RI] Ringe"], "floor": ["MyTab1", "[STOCK] Stockwerk"], "type": ["MyTab1", "[TYP] Typ"], @@ -732,7 +855,7 @@ "pen": ["MyTab1", "[PEN] Schtift"], "system": ["MyTab1", "[PLANT] Anlage"], "project": ["MyTab1", "[PRJ] Projekt"], - "connection_type": ["MyTab1", "[RAHMEN]"], + "connection_type": ["MyTab1", "[RAHMEN] Rahmen"], "rings": ["MyTab1", "[RI] Ringe"], "series": ["MyTab1", "[SER] Serie"], "floor": ["MyTab1", "[STOCK] Stockwerk"], @@ -762,7 +885,7 @@ "on_list": ["MyTab1", "[ON_LST] Auf Liste"], "pen": ["MyTab1", "[PEN] Schtift"], "system": ["MyTab1", "[PLANT] Anlage"], - "position": ["MyTab1", "[POS] Position"], + "position_in_ifc": ["MyTab1", "[POS] Position"], "cross_section": ["MyTab1", "[QS] Querschnitt"], "floor": ["MyTab1", "[STOCK] Stockwerk"], "strand": ["MyTab1", "[STR] Strang"], @@ -790,7 +913,7 @@ "on_list": ["MyTab1", "[ON_LST] Auf Liste"], "pen": ["MyTab1", "[PEN] Schtift"], "system": ["MyTab1", "[PLANT] Anlage"], - "position": ["MyTab1", "[POS] Position"], + "position_in_ifc": ["MyTab1", "[POS] Position"], "cross_section": ["MyTab1", "[QS] Querschnitt"], "floor": ["MyTab1", "[STOCK] Stockwerk"], "strand": ["MyTab1", "[STR] Strang"], @@ -818,7 +941,7 @@ "on_list": ["MyTab1", "[ON_LST] Auf Liste"], "pen": ["MyTab1", "[PEN] Schtift"], "system": ["MyTab1", "[PLANT] Anlage"], - "position": ["MyTab1", "[POS] Position"], + "position_in_ifc": ["MyTab1", "[POS] Position"], "cross_section": ["MyTab1", "[QS] Querschnitt"], "floor": ["MyTab1", "[STOCK] Stockwerk"], "strand": ["MyTab1", "[STR] Strang"], @@ -846,7 +969,7 @@ "on_list": ["MyTab1", "[ON_LST] Auf Liste"], "pen": ["MyTab1", "[PEN] Schtift"], "system": ["MyTab1", "[PLANT] Anlage"], - "position": ["MyTab1", "[POS] Position"], + "position_in_ifc": ["MyTab1", "[POS] Position"], "cross_section": ["MyTab1", "[QS] Querschnitt"], "floor": ["MyTab1", "[STOCK] Stockwerk"], "strand": ["MyTab1", "[STR] Strang"], diff --git a/bim2sim/elements/hvac_elements.py b/bim2sim/elements/hvac_elements.py index 71b53c2dc5..ef3c46dbcc 100644 --- a/bim2sim/elements/hvac_elements.py +++ b/bim2sim/elements/hvac_elements.py @@ -1432,14 +1432,19 @@ def is_consumer(self): class VentilationElement(HVACProduct): """Common properties for all ventilation elements""" + + @property + def expected_hvac_ports(self): + return (2, float('inf')) + air_type = attribute.Attribute( description='Air type', ) system = attribute.Attribute( description='System', ) - position = attribute.Attribute( - description='Position', + position_in_ifc = attribute.Attribute( + description='Position out of ifc entity', ) floor = attribute.Attribute( description='Floor', @@ -2112,8 +2117,12 @@ class AirDuctTransitionRectangularAsymmetric(AirDuctFitting): class AirDuctTransitionSymmetrical(AirDuctFitting): """Symmetrical duct transitions (Bogen Sym.)""" + # @property + # def expected_hvac_ports(self): + # return 0 + pattern_ifc_type = [ - re.compile(r'Bogen.*\(.*eckig.*,.*BS.*,.*BA.*,.*107.*\)', flags=re.IGNORECASE), + re.compile(r'Bogen.*\(.*eckig\s+BS.*,.*BA.*,.*107.*\)', flags=re.IGNORECASE), re.compile(r'^Bogen\s+Sym.*eckig', flags=re.IGNORECASE), ] @@ -2234,6 +2243,178 @@ class AirDuctTransitionRectangularBow(AirDuctFitting): unit=ureg.millimeter, ) +class AirDuctRectangularPants(AirDuctFitting): + """Rectangular pants/Y-piece (Hosenstück eckig)""" + + pattern_ifc_type = [ + re.compile(r'Hosenstück.*\(.*HSE.*:.*131.*\)', flags=re.IGNORECASE), + re.compile(r'^Hosenstück\s+eckig', flags=re.IGNORECASE), + re.compile(r'Hosenstück', flags=re.IGNORECASE), + ] + + cross_section_entrance_a = attribute.Attribute( + description='Cross section entrance a', + unit=ureg.millimeter, + ) + cross_section_entrance_b = attribute.Attribute( + description='Cross section entrance b', + unit=ureg.millimeter, + ) + cross_section_output_c = attribute.Attribute( + description='Cross section output c', + unit=ureg.millimeter, + ) + cross_section_output_d = attribute.Attribute( + description='Cross section output d', + unit=ureg.millimeter, + ) + e_offset = attribute.Attribute( + description='E-offset', + unit=ureg.millimeter, + ) + f_offset = attribute.Attribute( + description='F-offset', + unit=ureg.millimeter, + ) + cross_section_output_h = attribute.Attribute( + description='Cross section output h', + unit=ureg.millimeter, + ) + center_distance = attribute.Attribute( + description='Center distance m', + unit=ureg.millimeter, + ) + isolation = attribute.Attribute( + description='Insulation thickness', + unit=ureg.millimeter, + ) + cross_section = attribute.Attribute( + description='Cross section', + ) + connection_type = attribute.Attribute( + description='Connection type', + ) + + +# class AirDuctRectangularInspectionCover(AirDuctFitting): +# """Rectangular inspection cover (Revisionsdeckel)""" +# +# pattern_ifc_type = [ +# re.compile(r'Revisionsdeckel.*\(.*REV.*,.*163.*\)', flags=re.IGNORECASE), +# re.compile(r'^Revisionsdeckel.*Rohr', flags=re.IGNORECASE), +# ] +# +# length_a = attribute.Attribute( +# description='Length a', +# unit=ureg.millimeter, +# ) +# remark = attribute.Attribute( +# description='Remark', +# ) +# designation_is = attribute.Attribute( +# description='Designation (IS)', +# ) +# width_b = attribute.Attribute( +# description='Width b', +# unit=ureg.millimeter, +# ) +# nominal_diameter_dn = attribute.Attribute( +# description='Nominal diameter DN', +# unit=ureg.millimeter, +# ) +# height_h = attribute.Attribute( +# description='Height', +# unit=ureg.millimeter, +# ) +# radius = attribute.Attribute( +# description='Radius', +# unit=ureg.millimeter, +# ) +# cross_section = attribute.Attribute( +# description='Cross section', +# ) +# connection_type = attribute.Attribute( +# description='Connection type', +# ) + + +class AirDuctRoundStub(AirDuctFitting): + """Round stub/nozzle (Stutzen rund)""" + + pattern_ifc_type = [ + re.compile(r'Stutzen.*rund.*\(.*SR.*:.*124.*\)', flags=re.IGNORECASE), + re.compile(r'^Stutzen[_\s]rund', flags=re.IGNORECASE), + ] + + designation_is = attribute.Attribute( + description='Designation (IS)', + ) + nominal_diameter_d1 = attribute.Attribute( + description='Nominal diameter DN', + unit=ureg.millimeter, + ) + air_velocity = attribute.Attribute( + description='Air velocity', + unit=ureg.meter / ureg.second, + ) + + +# class AirDuctRivetedEdge(AirDuctFitting): +# """Riveted edge/flange (Nietbord)""" +# +# @property +# def expected_hvac_ports(self): +# return 0 +# +# pattern_ifc_type = [ +# re.compile(r'Nietbord.*\(.*K.*:.*125.*\)', flags=re.IGNORECASE), +# re.compile(r'^Nietbord', flags=re.IGNORECASE), +# ] +# +# dimension_a = attribute.Attribute( +# description='Dimension a', +# unit=ureg.millimeter, +# ) +# dimension_b = attribute.Attribute( +# description='Dimension b', +# unit=ureg.millimeter, +# ) +# cross_section = attribute.Attribute( +# description='Cross section', +# ) +# type = attribute.Attribute( +# description='Type (Round/Rectangular)', +# ) + +class AirDuctRoundCoupling(AirDuctFitting): + """Round duct coupling/sleeve (Muffe)""" + + pattern_ifc_type = [ + re.compile(r'Muffe.*\(.*R.*,.*145.*\)', flags=re.IGNORECASE), + re.compile(r'^MUFFE\s+DN\d+', flags=re.IGNORECASE), + re.compile(r'^Muffe.*rund', flags=re.IGNORECASE), + ] + + radius = attribute.Attribute( + description='Radius', + unit=ureg.millimeter, + ) + designation_is = attribute.Attribute( + description='Designation (IS)', + ) + cross_section_d1 = attribute.Attribute( + description='Cross section diameter', + unit=ureg.millimeter, + ) + connection_type = attribute.Attribute( + description='Connection type', + ) + air_velocity = attribute.Attribute( + description='Air velocity', + unit=ureg.meter / ureg.second, + ) + + # ============================================================================ # DAMPERS AND CONTROLLERS From f951ecd400b8ed9a06292130dd6aa4142cbd5e22 Mon Sep 17 00:00:00 2001 From: Hoepp J <155448650+HoeppJ@users.noreply.github.com> Date: Thu, 20 Nov 2025 16:33:42 +0100 Subject: [PATCH 115/125] bugfix enrich_flow_direction.py --- bim2sim/tasks/hvac/enrich_flow_direction.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/bim2sim/tasks/hvac/enrich_flow_direction.py b/bim2sim/tasks/hvac/enrich_flow_direction.py index 6676fed1a0..7ab264ff51 100644 --- a/bim2sim/tasks/hvac/enrich_flow_direction.py +++ b/bim2sim/tasks/hvac/enrich_flow_direction.py @@ -1,4 +1,5 @@ from bim2sim.elements.graphs.hvac_graph import HvacGraph +from bim2sim.elements.hvac_elements import HVACPort from bim2sim.tasks.base import ITask import logging from bim2sim.kernel.decision import BoolDecision, DecisionBunch @@ -39,13 +40,12 @@ def set_flow_sides(self, graph: HvacGraph): while True: unset_port = None for port in list(graph.nodes): - if port.flow_side == FlowSide.unknown and graph.graph[port] \ + if port.flow_side == FlowSide.unknown and graph[port] \ and port not in accepted: unset_port = port break if unset_port: - side, visited, masters = self.recurse_set_unknown_sides( - unset_port) + side, visited, masters = self.recurse_set_unknown_sides(graph, unset_port) if side in (-1, 1): # apply suggestions for port in visited: @@ -108,7 +108,10 @@ def recurse_set_side(self, port, side, known: dict = None, return known - def recurse_set_unknown_sides(self, port, visited: list = None, + def recurse_set_unknown_sides(self, + graph: HvacGraph, + port: HVACPort, + visited: list = None, masters: list = None): """Recursive checks neighbours flow_side. :returns tuple of @@ -131,17 +134,15 @@ def recurse_set_unknown_sides(self, port, visited: list = None, # call neighbours neighbour_sides = {} - for neigh in self.neighbors(port): + for neigh in graph.neighbors(port): if neigh not in visited: if (neigh.parent.is_consumer() or neigh.parent.is_generator()) \ and port.parent is neigh.parent: # switch flag over consumers / generators - side, _, _ = self.recurse_set_unknown_sides( - neigh, visited, masters) + side, _, _ = self.recurse_set_unknown_sides(graph, neigh, visited, masters) side = -side else: - side, _, _ = self.recurse_set_unknown_sides( - neigh, visited, masters) + side, _, _ = self.recurse_set_unknown_sides(graph, neigh, visited, masters) neighbour_sides[neigh] = side sides = set(neighbour_sides.values()) From 4a4f16884d1f4bc363bb12894fd43bc22fd5b522 Mon Sep 17 00:00:00 2001 From: Hoepp J <155448650+HoeppJ@users.noreply.github.com> Date: Thu, 20 Nov 2025 16:58:25 +0100 Subject: [PATCH 116/125] dead end parent output --- bim2sim/tasks/hvac/dead_ends.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bim2sim/tasks/hvac/dead_ends.py b/bim2sim/tasks/hvac/dead_ends.py index 3f14822c1b..d9674f0b4b 100644 --- a/bim2sim/tasks/hvac/dead_ends.py +++ b/bim2sim/tasks/hvac/dead_ends.py @@ -136,7 +136,7 @@ def decide_dead_ends(graph: HvacGraph, pot_dead_ends: list, cur_decision = BoolDecision( question="Found possible dead end at port %s in system, " "please check if it is a dead end" % dead_end, - console_identifier="GUID: %s" % dead_end.guid, + console_identifier="GUID: %s, Parent GUID: %s" % (dead_end.guid, related_guid), key=dead_end, global_key="deadEnd.%s" % dead_end.guid, allow_skip=False, From af0b393e44465e3a3683b26abdabdb3c0d8ba5b9 Mon Sep 17 00:00:00 2001 From: Hoepp J <155448650+HoeppJ@users.noreply.github.com> Date: Tue, 9 Dec 2025 16:54:14 +0100 Subject: [PATCH 117/125] filter hvac_element instances according to ifc entity description --- bim2sim/elements/base_elements.py | 17 ++++++ bim2sim/elements/hvac_elements.py | 56 +++++++++---------- bim2sim/elements/mapping/ifc2python.py | 6 ++ bim2sim/kernel/__init__.py | 1 + .../e2_complex_project_hvac_aixlib.py | 14 +++-- 5 files changed, 60 insertions(+), 34 deletions(-) diff --git a/bim2sim/elements/base_elements.py b/bim2sim/elements/base_elements.py index b6b007b5fd..fe6104f036 100644 --- a/bim2sim/elements/base_elements.py +++ b/bim2sim/elements/base_elements.py @@ -749,6 +749,8 @@ def __init__( ifc_domain: IFCDomain, finder: Union[TemplateFinder, None] = None, dummy=Dummy): + + self.relevant_elements = relevant_elements self.mapping, self.blacklist, self.defaults = self.create_ifc_mapping(relevant_elements) self.dummy_cls = dummy self.ifc_domain = ifc_domain @@ -795,6 +797,21 @@ def __call__(self, ifc_entity, *args, ifc_type: str = None, use_dummy=True, f" will only be created for IFC files of domain " f"{element_cls.from_ifc_domains}") + descriptions = ifc2python.get_descriptions(ifc_entity) + + if descriptions and not any( + any(p.search(desc) for p in element_cls.pattern_ifc_type) + for desc in descriptions): + ele_matches = [] + for ele in self.relevant_elements: + if any(any(p.search(desc) for p in ele.pattern_ifc_type) + for desc in descriptions): + ele_matches.append(ele) + if len(ele_matches) != 1: + raise LookupError(f"No element found for {ifc_entity}") + else: + element_cls = ele_matches[0] + element = self.create(element_cls, ifc_entity, *args, **kwargs) return element diff --git a/bim2sim/elements/hvac_elements.py b/bim2sim/elements/hvac_elements.py index ef3c46dbcc..c2f36148a5 100644 --- a/bim2sim/elements/hvac_elements.py +++ b/bim2sim/elements/hvac_elements.py @@ -1513,7 +1513,6 @@ class AirDuctRectangular(AirDuct): pattern_ifc_type = [ re.compile(r'Gerader\s+Kanal.*\(.*K.*:.*101.*\)', flags=re.IGNORECASE), - re.compile(r'^Kanal\s+\d+\s*x\s*\d+', flags=re.IGNORECASE), ] height_a = attribute.Attribute( @@ -1542,7 +1541,6 @@ class AirDuctOval(AirDuct): pattern_ifc_type = [ re.compile(r'Ovalrohr.*\(.*170.*\)', flags=re.IGNORECASE), - re.compile(r'^Kanal\s+\d+\s*x\s*\d+', flags=re.IGNORECASE), ] width_b = attribute.Attribute( @@ -2040,6 +2038,7 @@ class AirDuctTransitionRectangularAsymmetric(AirDuctFitting): pattern_ifc_type = [ re.compile(r'Etage.*,.*Übergang.*\(.*ES.*,.*US.*,.*UA.*:.*105.*\)', flags=re.IGNORECASE), re.compile(r'^Übergang\s+Asym', flags=re.IGNORECASE), + re.compile(r'^Übergang\s+Sym', flags=re.IGNORECASE), ] air_velocity = attribute.Attribute( @@ -2359,32 +2358,32 @@ class AirDuctRoundStub(AirDuctFitting): ) -# class AirDuctRivetedEdge(AirDuctFitting): -# """Riveted edge/flange (Nietbord)""" -# -# @property -# def expected_hvac_ports(self): -# return 0 -# -# pattern_ifc_type = [ -# re.compile(r'Nietbord.*\(.*K.*:.*125.*\)', flags=re.IGNORECASE), -# re.compile(r'^Nietbord', flags=re.IGNORECASE), -# ] -# -# dimension_a = attribute.Attribute( -# description='Dimension a', -# unit=ureg.millimeter, -# ) -# dimension_b = attribute.Attribute( -# description='Dimension b', -# unit=ureg.millimeter, -# ) -# cross_section = attribute.Attribute( -# description='Cross section', -# ) -# type = attribute.Attribute( -# description='Type (Round/Rectangular)', -# ) +class AirDuctRivetedEdge(AirDuctFitting): + """Riveted edge/flange (Nietbord)""" + + @property + def expected_hvac_ports(self): + return 0 + + pattern_ifc_type = [ + re.compile(r'Nietbord.*\(.*K.*:.*125.*\)', flags=re.IGNORECASE), + re.compile(r'^Nietbord', flags=re.IGNORECASE), + ] + + dimension_a = attribute.Attribute( + description='Dimension a', + unit=ureg.millimeter, + ) + dimension_b = attribute.Attribute( + description='Dimension b', + unit=ureg.millimeter, + ) + cross_section = attribute.Attribute( + description='Cross section', + ) + type = attribute.Attribute( + description='Type (Round/Rectangular)', + ) class AirDuctRoundCoupling(AirDuctFitting): """Round duct coupling/sleeve (Muffe)""" @@ -2893,6 +2892,7 @@ class AHUSilencer(AHUComponent): pattern_ifc_type = [ re.compile(r'^Schalld[aä]mpfer\s+Allgemein', flags=re.IGNORECASE), + re.compile(r'^Schalld[aä]mpfer', flags=re.IGNORECASE), ] # collect all domain classes diff --git a/bim2sim/elements/mapping/ifc2python.py b/bim2sim/elements/mapping/ifc2python.py index f212dc9947..aad247a5e6 100644 --- a/bim2sim/elements/mapping/ifc2python.py +++ b/bim2sim/elements/mapping/ifc2python.py @@ -414,6 +414,12 @@ def get_predefined_type(ifcElement) -> Union[str, None]: except AttributeError: return None +def get_descriptions(ifcElement): + """Return the object type of the IFC element""" + try: + return [ifcElement.ObjectType, ifcElement.Name, ifcElement.Description] + except TypeError: + pass def getElementType(ifcElement): """Return the ifctype of the IFC element""" diff --git a/bim2sim/kernel/__init__.py b/bim2sim/kernel/__init__.py index 6a3b5d61ad..26cbbf3659 100644 --- a/bim2sim/kernel/__init__.py +++ b/bim2sim/kernel/__init__.py @@ -6,3 +6,4 @@ class IFCDomainError(Exception): """Exception raised if IFCDomain of file and element do not fit.""" + diff --git a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/examples/e2_complex_project_hvac_aixlib.py b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/examples/e2_complex_project_hvac_aixlib.py index fac25b04f8..0e0c7396aa 100644 --- a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/examples/e2_complex_project_hvac_aixlib.py +++ b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/examples/e2_complex_project_hvac_aixlib.py @@ -4,6 +4,9 @@ import bim2sim from bim2sim import Project, run_project, ConsoleDecisionHandler from bim2sim.utilities.types import IFCDomain +from bim2sim.elements.base_elements import Material +from bim2sim.elements import bps_elements as bps_elements, \ + hvac_elements as hvac_elements def run_example_complex_hvac_aixlib(): @@ -19,16 +22,13 @@ def run_example_complex_hvac_aixlib(): # Create a temp directory for the project, feel free to use a "normal" # directory - project_path = Path( - tempfile.TemporaryDirectory( - prefix='bim2sim_example_complex_aixlib').name) + project_path = Path(r"D:\02_Daten\Testing\PluginAixLib\Test") # Set path of ifc for hydraulic domain with the fresh downloaded test models ifc_paths = { IFCDomain.hydraulic: - Path(bim2sim.__file__).parent.parent / - 'test/resources/hydraulic/ifc/' - 'DigitalHub_Gebaeudetechnik-HEIZUNG_v2.ifc', + Path( + r"D:\03_Cloud\Sciebo\BIM2Praxis\IFC-Modelle\EDGE\ueberarbeitet_2025-10\BIM2PRAXIS_RLT-2025-09-11_cleaned_jho.ifc") } # Create a project including the folder structure for the project with project = Project.create(project_path, ifc_paths, 'aixlib') @@ -38,6 +38,8 @@ def run_example_complex_hvac_aixlib(): Path(bim2sim.__file__).parent.parent / 'test/resources/weather_files/DEU_NW_Aachen.105010_TMYx.mos') + project.sim_settings.relevant_elements = {*hvac_elements.ventilation_items, Material} + # Set fuzzy threshold to 0.5 to reduce the number of decisions (this is # IFC-file specific and needs to be evaluated by the user project.sim_settings.fuzzy_threshold = 0.5 From 2c8b2a4c3bf90c975457e4d93cf60b1727b62e95 Mon Sep 17 00:00:00 2001 From: Hoepp J <155448650+HoeppJ@users.noreply.github.com> Date: Wed, 17 Dec 2025 16:38:06 +0100 Subject: [PATCH 118/125] bug fix IfcDerivedUnit --- .../assets/finder/template_LuArtX_Carf.json | 2 +- bim2sim/elements/base_elements.py | 23 +++- bim2sim/elements/hvac_elements.py | 128 +++++++++++------- bim2sim/elements/mapping/units.py | 2 +- .../e2_complex_project_hvac_aixlib.py | 2 +- bim2sim/tasks/common/create_elements.py | 5 +- 6 files changed, 106 insertions(+), 56 deletions(-) diff --git a/bim2sim/assets/finder/template_LuArtX_Carf.json b/bim2sim/assets/finder/template_LuArtX_Carf.json index 3b664bd017..23880bf00c 100644 --- a/bim2sim/assets/finder/template_LuArtX_Carf.json +++ b/bim2sim/assets/finder/template_LuArtX_Carf.json @@ -50,7 +50,7 @@ "code": ["MyTab1", "[KBZ] KBZ"], "key": ["MyTab1", "[KEY] Schlüssel"], "reference_edge": ["MyTab1", "[K] Bezugskante"], - "length": ["MyTab1", "[l] Länge"], + "length": ["MyTab1", "[l] Länge (l)"], "material": ["MyTab1", "[MAT] Material"], "data_mask_number": ["MyTab1", "[MSKNR] Datenmaskennummer"], "on_list": ["MyTab1", "[ON_LST] Auf Liste"], diff --git a/bim2sim/elements/base_elements.py b/bim2sim/elements/base_elements.py index fe6104f036..36e9d6a4ea 100644 --- a/bim2sim/elements/base_elements.py +++ b/bim2sim/elements/base_elements.py @@ -178,11 +178,12 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) + self.ifc = ifc self.predefined_type = ifc2python.get_predefined_type(ifc) self.ifc_domain = ifc_domain self.finder = finder - self.ifc_units = ifc_units + self.ifc_units = ifc_units # ToDo Attribute enrichment in here self.source_tool: SourceTool = None # TBD @@ -203,6 +204,7 @@ def from_ifc(cls, ifc, *args, **kwargs): """Factory method to create instance from ifc""" ifc_args, ifc_kwargs = cls.ifc2args(ifc) kwargs.update(ifc_kwargs) + test = cls(*(args + ifc_args), **kwargs) return cls(*(args + ifc_args), **kwargs) @property @@ -804,15 +806,24 @@ def __call__(self, ifc_entity, *args, ifc_type: str = None, use_dummy=True, for desc in descriptions): ele_matches = [] for ele in self.relevant_elements: - if any(any(p.search(desc) for p in ele.pattern_ifc_type) - for desc in descriptions): - ele_matches.append(ele) - if len(ele_matches) != 1: + match_count = 0 + for p in ele.pattern_ifc_type: + for desc in descriptions: + if p.search(desc): + match_count += 1 + if match_count > 0: + ele_matches.append((ele, match_count)) + if not ele_matches: raise LookupError(f"No element found for {ifc_entity}") + elif len(ele_matches) == 1: + element_cls = ele_matches[0][0] else: - element_cls = ele_matches[0] + best_ele, _ = max(ele_matches, key=lambda x: x[1]) + element_cls = best_ele element = self.create(element_cls, ifc_entity, *args, **kwargs) + if not element.air_type: + test = self.create(element_cls, ifc_entity, *args, **kwargs) return element def create(self, element_cls, ifc_entity, *args, **kwargs): diff --git a/bim2sim/elements/hvac_elements.py b/bim2sim/elements/hvac_elements.py index c2f36148a5..164df12322 100644 --- a/bim2sim/elements/hvac_elements.py +++ b/bim2sim/elements/hvac_elements.py @@ -1495,6 +1495,10 @@ class AirDuct(VentilationElement): length = attribute.Attribute( description='Length', unit=ureg.millimeter, + patterns=[ + re.compile(r'\[l\] Länge', flags=re.IGNORECASE), + re.compile(r'\[l\] Länge \(l\)', flags=re.IGNORECASE), + ], ) air_velocity = attribute.Attribute( description='Air velocity', @@ -1512,7 +1516,9 @@ class AirDuctRectangular(AirDuct): """Rectangular air ducts""" pattern_ifc_type = [ - re.compile(r'Gerader\s+Kanal.*\(.*K.*:.*101.*\)', flags=re.IGNORECASE), + re.compile(r'Gerader\s+Kanal\s*\(\s*K\s*:\s*\d+\s*\)', flags=re.IGNORECASE), + re.compile(r'Kanal\s+\d+\s*x\s*\d+', flags=re.IGNORECASE), + re.compile(r'Kanal-Teil\s+\d+\s*x\s*\d+', flags=re.IGNORECASE) ] height_a = attribute.Attribute( @@ -1599,6 +1605,10 @@ class AirDuctFitting(VentilationElement): length = attribute.Attribute( description='Length', unit=ureg.millimeter, + patterns=[ + re.compile(r'\[l\] Länge', flags=re.IGNORECASE), + re.compile(r'\[l\] Länge \(l\)', flags=re.IGNORECASE), + ], ) manufacturer = attribute.Attribute( description='Manufacturer', @@ -1613,6 +1623,7 @@ class AirDuctRectangularFitting(AirDuctFitting): pattern_ifc_type = [ re.compile(r'Komponente\s+eckig.*\(.*122.*\)', flags=re.IGNORECASE), + re.compile(r'\d+\s*x\s*\d+', flags=re.IGNORECASE), ] outer_dimension_a = attribute.Attribute( @@ -1655,6 +1666,7 @@ class AirDuctRoundFitting(AirDuctFitting): pattern_ifc_type = [ re.compile(r'Komponente\s+rund.*\(.*123.*\)', flags=re.IGNORECASE), + re.compile(r'BEZ', flags=re.IGNORECASE), ] class AirDuctRoundFlexible(AirDuctFitting): @@ -1879,7 +1891,7 @@ class AirDuctRectangularBow(AirDuctFitting): pattern_ifc_type = [ re.compile(r'Bogen.*\(.*eckig.*WS.*,.*WA.*,.*111.*\)', flags=re.IGNORECASE), - re.compile(r'^Winkel', flags=re.IGNORECASE), + re.compile(r'Winkel', flags=re.IGNORECASE), ] radius = attribute.Attribute( @@ -2039,6 +2051,7 @@ class AirDuctTransitionRectangularAsymmetric(AirDuctFitting): re.compile(r'Etage.*,.*Übergang.*\(.*ES.*,.*US.*,.*UA.*:.*105.*\)', flags=re.IGNORECASE), re.compile(r'^Übergang\s+Asym', flags=re.IGNORECASE), re.compile(r'^Übergang\s+Sym', flags=re.IGNORECASE), + re.compile(r'Etage', flags=re.IGNORECASE), ] air_velocity = attribute.Attribute( @@ -2123,6 +2136,7 @@ class AirDuctTransitionSymmetrical(AirDuctFitting): pattern_ifc_type = [ re.compile(r'Bogen.*\(.*eckig\s+BS.*,.*BA.*,.*107.*\)', flags=re.IGNORECASE), re.compile(r'^Bogen\s+Sym.*eckig', flags=re.IGNORECASE), + re.compile(r'Kanalbogen\s+\d+\s*x\s*\d+\s*x\s*\w+', flags=re.IGNORECASE) ] air_velocity = attribute.Attribute( @@ -2295,46 +2309,46 @@ class AirDuctRectangularPants(AirDuctFitting): ) -# class AirDuctRectangularInspectionCover(AirDuctFitting): -# """Rectangular inspection cover (Revisionsdeckel)""" -# -# pattern_ifc_type = [ -# re.compile(r'Revisionsdeckel.*\(.*REV.*,.*163.*\)', flags=re.IGNORECASE), -# re.compile(r'^Revisionsdeckel.*Rohr', flags=re.IGNORECASE), -# ] -# -# length_a = attribute.Attribute( -# description='Length a', -# unit=ureg.millimeter, -# ) -# remark = attribute.Attribute( -# description='Remark', -# ) -# designation_is = attribute.Attribute( -# description='Designation (IS)', -# ) -# width_b = attribute.Attribute( -# description='Width b', -# unit=ureg.millimeter, -# ) -# nominal_diameter_dn = attribute.Attribute( -# description='Nominal diameter DN', -# unit=ureg.millimeter, -# ) -# height_h = attribute.Attribute( -# description='Height', -# unit=ureg.millimeter, -# ) -# radius = attribute.Attribute( -# description='Radius', -# unit=ureg.millimeter, -# ) -# cross_section = attribute.Attribute( -# description='Cross section', -# ) -# connection_type = attribute.Attribute( -# description='Connection type', -# ) +class AirDuctRectangularInspectionCover(AirDuctFitting): + """Rectangular inspection cover (Revisionsdeckel)""" + + pattern_ifc_type = [ + re.compile(r'Revisionsdeckel.*\(.*REV.*,.*163.*\)', flags=re.IGNORECASE), + re.compile(r'^Revisionsdeckel.*Rohr', flags=re.IGNORECASE), + ] + + length_a = attribute.Attribute( + description='Length a', + unit=ureg.millimeter, + ) + remark = attribute.Attribute( + description='Remark', + ) + designation_is = attribute.Attribute( + description='Designation (IS)', + ) + width_b = attribute.Attribute( + description='Width b', + unit=ureg.millimeter, + ) + nominal_diameter_dn = attribute.Attribute( + description='Nominal diameter DN', + unit=ureg.millimeter, + ) + height_h = attribute.Attribute( + description='Height', + unit=ureg.millimeter, + ) + radius = attribute.Attribute( + description='Radius', + unit=ureg.millimeter, + ) + cross_section = attribute.Attribute( + description='Cross section', + ) + connection_type = attribute.Attribute( + description='Connection type', + ) class AirDuctRoundStub(AirDuctFitting): @@ -2428,7 +2442,8 @@ class FireDamper(VentilationElement): } pattern_ifc_type = [ - re.compile(r'^Brandschutzklappe', flags=re.IGNORECASE), + re.compile(r'Brandschutzklappe', flags=re.IGNORECASE), + re.compile(r'Komponente\s+eckig\s*\(\s*(\d+)\s*\)', flags=re.IGNORECASE), ] manufacturer = attribute.Attribute( @@ -2486,6 +2501,10 @@ class FireDamper(VentilationElement): length = attribute.Attribute( description='Length', unit=ureg.millimeter, + patterns=[ + re.compile(r'\[l\] Länge', flags=re.IGNORECASE), + re.compile(r'\[l\] Länge \(l\)', flags=re.IGNORECASE), + ], ) air_volume = attribute.Attribute( description='Air volume', @@ -2516,6 +2535,10 @@ class AirVolumeFlowController(VentilationElement): length = attribute.Attribute( description='Length', unit=ureg.millimeter, + patterns=[ + re.compile(r'\[l\] Länge', flags=re.IGNORECASE), + re.compile(r'\[l\] Länge \(l\)', flags=re.IGNORECASE), + ], ) @@ -2528,6 +2551,7 @@ class AirVolumeFlowControllerConstant(AirVolumeFlowController): pattern_ifc_type = [ re.compile(r'^Konstant\s+VSR', flags=re.IGNORECASE), + re.compile(r'Komponente\s+rund\s*\(\s*(\d+)\s*\)', flags=re.IGNORECASE), ] radius = attribute.Attribute( @@ -2560,6 +2584,7 @@ class AirVolumeFlowControllerDynamic(AirVolumeFlowController): pattern_ifc_type = [ re.compile(r'^Variabel\s+VSR', flags=re.IGNORECASE), + re.compile(r'Komponente\s+eckig\s*\(\s*(\d+)\s*\)', flags=re.IGNORECASE), ] outer_dimension_a = attribute.Attribute( @@ -2610,6 +2635,10 @@ class AirSilencer(VentilationElement): length = attribute.Attribute( description='Length', unit=ureg.millimeter, + patterns=[ + re.compile(r'\[l\] Länge', flags=re.IGNORECASE), + re.compile(r'\[l\] Länge \(l\)', flags=re.IGNORECASE), + ], ) @@ -2621,7 +2650,8 @@ class AirSilencerRoundFlexible(AirSilencer): } pattern_ifc_type = [ - re.compile(r'^Flexibler\s+Rohrschalldämpfer', flags=re.IGNORECASE), + re.compile(r'^Flexibler\s+Rohrschalld(?:a|ä|ae)mpfer', flags=re.IGNORECASE), + re.compile(r'Komponente\s+rund\s*\(\s*(\d+)\s*\)', flags=re.IGNORECASE), ] flow_velocity = attribute.Attribute( @@ -2654,6 +2684,8 @@ class AirSilencerTelephony(AirSilencer): pattern_ifc_type = [ re.compile(r'^Telefonie-Schalld[aä]mpfer', flags=re.IGNORECASE), + re.compile(r'^Telefonie-Schalld(?:a|ä|ae)mpfer', flags=re.IGNORECASE), + re.compile(r'Komponente\s+rund\s*\(\s*(\d+)\s*\)', flags=re.IGNORECASE), ] radius = attribute.Attribute( @@ -2834,6 +2866,10 @@ class AHUComponent(VentilationElement): length = attribute.Attribute( description='Length', unit=ureg.millimeter, + patterns=[ + re.compile(r'\[l\] Länge', flags=re.IGNORECASE), + re.compile(r'\[l\] Länge \(l\)', flags=re.IGNORECASE), + ], ) cross_section = attribute.Attribute( description='Cross section', @@ -2891,8 +2927,8 @@ class AHUSilencer(AHUComponent): } pattern_ifc_type = [ - re.compile(r'^Schalld[aä]mpfer\s+Allgemein', flags=re.IGNORECASE), - re.compile(r'^Schalld[aä]mpfer', flags=re.IGNORECASE), + re.compile(r'^Schalld(?:a|ä|ae)mpfer\s+Allgemein', flags=re.IGNORECASE), + re.compile(r'^Schalld(?:a|ä|ae)mpfer', flags=re.IGNORECASE), ] # collect all domain classes diff --git a/bim2sim/elements/mapping/units.py b/bim2sim/elements/mapping/units.py index 48aa69bc19..05104000b4 100644 --- a/bim2sim/elements/mapping/units.py +++ b/bim2sim/elements/mapping/units.py @@ -64,7 +64,7 @@ def parse_ifc(unit_entity): unit_part = ureg.parse_units('{}{}'.format(prefix_string, ifc_pint_unitmap[ element.Unit.Name])) - if element.Unit.Dimensions: + if hasattr(element, "Dimensions"): unit_part = unit_part ** element.Dimensions unit = unit * unit_part ** element.Exponent return unit diff --git a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/examples/e2_complex_project_hvac_aixlib.py b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/examples/e2_complex_project_hvac_aixlib.py index 0e0c7396aa..472fb8f3e1 100644 --- a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/examples/e2_complex_project_hvac_aixlib.py +++ b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/examples/e2_complex_project_hvac_aixlib.py @@ -26,7 +26,7 @@ def run_example_complex_hvac_aixlib(): # Set path of ifc for hydraulic domain with the fresh downloaded test models ifc_paths = { - IFCDomain.hydraulic: + IFCDomain.ventilation: Path( r"D:\03_Cloud\Sciebo\BIM2Praxis\IFC-Modelle\EDGE\ueberarbeitet_2025-10\BIM2PRAXIS_RLT-2025-09-11_cleaned_jho.ifc") } diff --git a/bim2sim/tasks/common/create_elements.py b/bim2sim/tasks/common/create_elements.py index 0116f86e78..362b817d3a 100644 --- a/bim2sim/tasks/common/create_elements.py +++ b/bim2sim/tasks/common/create_elements.py @@ -239,7 +239,10 @@ def create_with_validation(self, entities_dict: dict, warn=True, bps.LayerSet, bps.Layer, Material ] - for entity, ifc_type_or_element_cls in entities_dict.items(): + entities_dict = sorted(entities_dict) + + for entity in entities_dict: + ifc_type_or_element_cls = entity.is_a() try: if isinstance(ifc_type_or_element_cls, str): if ifc_type_or_element_cls in blacklist: From 811c5a6e769261d44ad1284de69f163c3ea1d216 Mon Sep 17 00:00:00 2001 From: Hoepp J <155448650+HoeppJ@users.noreply.github.com> Date: Tue, 3 Feb 2026 08:49:13 +0100 Subject: [PATCH 119/125] update Spawn simsettings for pydantic --- .../bim2sim_spawn/examples/e1_simple_project_bps_spawn.py | 4 ++-- bim2sim/plugins/PluginSpawn/bim2sim_spawn/sim_settings.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e1_simple_project_bps_spawn.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e1_simple_project_bps_spawn.py index 18181a6315..cf17f06474 100644 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e1_simple_project_bps_spawn.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e1_simple_project_bps_spawn.py @@ -27,7 +27,7 @@ def run_example_spawn_1(): # Create a temp directory for the project, feel free to use a "normal" # directory - project_path = Path(r"D:\02_Daten\Testing\Spawn\example1") + project_path = Path(r"D:\00_Temp\Testing\bim2sim\Spawn") # Set the ifc path to use and define which domain the IFC belongs to ifc_paths = { @@ -43,7 +43,7 @@ def run_example_spawn_1(): # Set the install path to your EnergyPlus installation according to your # system requirements - project.sim_settings.ep_install_path = Path("D:\99_Programme\EnergyPlus") + project.sim_settings.ep_install_path = Path("C:") project.sim_settings.ep_version = "9-4-0" project.sim_settings.weather_file_path_ep = ( Path(bim2sim.__file__).parent.parent / diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/sim_settings.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/sim_settings.py index e714cc5cf0..13859d46d9 100644 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/sim_settings.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/sim_settings.py @@ -14,7 +14,7 @@ def __init__(self): self.outer_heat_ports = True hvac_modelica_library = ChoiceSetting( - default='AixLib', + value='AixLib', choices={ 'AixLib': 'Using AixLib for HVAC simulation', 'HKESim': 'Using HKESim for HVAC simulation' From bd04b9309e3fc844727478ece192acc6d1ad34da Mon Sep 17 00:00:00 2001 From: Hoepp J <155448650+HoeppJ@users.noreply.github.com> Date: Tue, 3 Feb 2026 18:09:15 +0100 Subject: [PATCH 120/125] update spawn export & templates --- .../assets/templates/modelica/tmplModel.txt | 3 ++ .../templates/modelica/tmplSpawnBuilding.txt | 37 ++++++++++++++++--- .../modelica/tmplSpawnTotalModel.txt | 10 ++++- .../tasks/export_spawn_building.py | 33 +++++++++++++++-- .../bim2sim_spawn/tasks/export_spawn_total.py | 17 ++++++--- 5 files changed, 84 insertions(+), 16 deletions(-) diff --git a/bim2sim/assets/templates/modelica/tmplModel.txt b/bim2sim/assets/templates/modelica/tmplModel.txt index 211327bc8e..c3b4f1f51f 100644 --- a/bim2sim/assets/templates/modelica/tmplModel.txt +++ b/bim2sim/assets/templates/modelica/tmplModel.txt @@ -39,6 +39,9 @@ Modelica.Thermal.HeatTransfer.Interfaces.HeatPort_a heatPortOuterRad[${len(model annotation (Placement(transformation(extent={{-110,-22},{-90,-2}}))); % endif +Buildings.BoundaryConditions.WeatherData.Bus weaBus "Weather data bus" + annotation (Placement(transformation(extent={{-110,-110},{-90,-90}}))); + equation % for con1, con2, pos1, pos2 in model.connections: connect(${con1}, ${con2}) diff --git a/bim2sim/assets/templates/modelica/tmplSpawnBuilding.txt b/bim2sim/assets/templates/modelica/tmplSpawnBuilding.txt index 85fad2258e..85c01913df 100644 --- a/bim2sim/assets/templates/modelica/tmplSpawnBuilding.txt +++ b/bim2sim/assets/templates/modelica/tmplSpawnBuilding.txt @@ -8,11 +8,18 @@ model ${model_name} "${model_comment}" parameter Integer nZones = ${n_zones}; - final parameter Modelica.Units.SI.MassFlowRate mOut_flow[nZones]=0.3/3600 * fill(1, nZones) * 1.2 + final parameter Modelica.Units.SI.MassFlowRate mOut_flow_base[nZones]=0.1/3600 * fill(1, nZones) * 1.2 "Outside air infiltration for each exterior room"; + parameter String zoneNames[nZones] = {${', '.join('"' + z + '"' for z in ep_zone_lists.strip("{}").split(','))}} "Name of the thermal zone as specified in the EnergyPlus input"; + parameter Real baseInfiltration[nZones] = {${', '.join(z for z in base_infiltration.strip("{}").split(','))}} + "Base infiltration per thermal zone"; + + Modelica.Units.SI.MassFlowRate mOut_flow_infiltration[nZones] + "Outside air infiltration for each exterior room"; + inner Buildings.ThermalZones.EnergyPlus_9_6_0.Building building( idfName = ${idf_path}, epwName = ${weather_path_ep}, @@ -23,7 +30,7 @@ model ${model_name} "${model_comment}" Buildings.Fluid.Sources.MassFlowSource_WeatherData freshairsource[nZones]( redeclare package Medium = Buildings.Media.Air, - m_flow=mOut_flow, + use_m_flow_in=true, each nPorts=1) "" annotation (Placement(transformation(extent={{-43.1111,58},{-23.1111,78}}))); @@ -42,14 +49,14 @@ model ${model_name} "${model_comment}" Buildings.Fluid.FixedResistances.PressureDrop resOut[nZones]( redeclare each package Medium = Buildings.Media.Air, - each m_flow_nominal=sum(mOut_flow), + each m_flow_nominal=sum(mOut_flow_base), each dp_nominal=10, each linearized=true) "Small flow resistance for inlet" annotation (Placement(transformation(extent={{-6,58},{14,78}}))); Buildings.Fluid.FixedResistances.PressureDrop resIn[nZones]( redeclare package Medium = Buildings.Media.Air, - each m_flow_nominal=sum(mOut_flow), + each m_flow_nominal=sum(mOut_flow_base), each dp_nominal=10, each linearized=true) "Small flow resistance for outlet" annotation (Placement(transformation(extent={{-6,32},{14,52}}))); @@ -64,9 +71,19 @@ model ${model_name} "${model_comment}" Modelica.Blocks.Sources.Constant const[nZones,3](each k=0) "TODO" annotation (Placement(transformation(extent={{-100,16},{-80,36}}))); + + Buildings.BoundaryConditions.WeatherData.Bus weaBus "Weather data bus" + annotation (Placement(transformation(extent={{-110,90},{-90,110}}))); + + Modelica.Blocks.Sources.RealExpression Infiltration_m_flow[nZones](y= + mOut_flow_infiltration) + annotation (Placement(transformation(extent={{0,80},{-20,100}}))); + equation for i in 1:nZones loop + mOut_flow_infiltration[i] = baseInfiltration[i] * zon[i].V / 3600 * 1.2; + connect(building.weaBus, freshairsource[i].weaBus) annotation (Line( points={{-80,68},{-52,68},{-52,68.2},{-43.1111,68.2}}, color={255,204,51}, @@ -95,10 +112,18 @@ equation connect(const.y, zon.qGai_flow) annotation (Line(points={{-79,26},{-32,26},{-32, 10},{-22,10}}, color={0,0,127})); + + connect(building.weaBus, weaBus) annotation (Line( + points={{-80,70},{-52,70},{-52,100},{-100,100}}, + color={255,204,51})); + + connect(Infiltration_m_flow.y, freshairsource.m_flow_in) annotation (Line( + points={{-21,90},{-60,90},{-60,76},{-43.1111,76}}, color={0,0,127})); + annotation ( experiment(StopTime=36000), uses( Modelica(version="4.0.0"), - Buildings(version="10.0.0"), - AixLib(version="1.3.2"))); + Buildings(version="11.1.0"), + AixLib(version="3.0.0"))); end ${model_name}; diff --git a/bim2sim/assets/templates/modelica/tmplSpawnTotalModel.txt b/bim2sim/assets/templates/modelica/tmplSpawnTotalModel.txt index 9f2374c6f6..ecc4254d65 100644 --- a/bim2sim/assets/templates/modelica/tmplSpawnTotalModel.txt +++ b/bim2sim/assets/templates/modelica/tmplSpawnTotalModel.txt @@ -15,8 +15,16 @@ equation connect${str(cons).replace("'","")} annotation (Line(points={{0,0},{0,0},{0,0},{0,0},{0,0}}, color={191,0,0})); % endfor + connect(hydraulic.weaBus, buildingmodel.weaBus) annotation (Line( + points={{-14,-62},{-22,-62},{-22,48},{-14,48}}, + color={255,204,51}, + thickness=0.5)); annotation (Icon(coordinateSystem(preserveAspectRatio=false)), Diagram( - coordinateSystem(preserveAspectRatio=false))); + coordinateSystem(preserveAspectRatio=false)), + experiment( + StopTime=31536000, + Interval=3600, + __Dymola_Algorithm="Dassl")); end ${model_name}; diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_building.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_building.py index f5e6a91334..7955533b74 100644 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_building.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_building.py @@ -1,11 +1,13 @@ import codecs from pathlib import Path -from typing import List +from typing import List, Tuple, Optional, Dict +from collections import defaultdict from mako.template import Template import bim2sim from bim2sim.export.modelica import parse_to_modelica +from bim2sim.utilities.common_functions import filter_elements from bim2sim.tasks.base import ITask @@ -37,6 +39,9 @@ def run(self, elements: dict, weather_file_modelica: Path, self.logger.info("Export building of Spawn model to Modelica code") model_name_building = 'BuildingModel' + tz_elements = filter_elements(elements, 'ThermalZone') + base_infiltration = self._get_base_infiltration_per_zone(tz_elements, ep_zone_lists) + # Setup export paths export_package_path = self.paths.export / Path(package_name) @@ -46,7 +51,8 @@ def run(self, elements: dict, weather_file_modelica: Path, model_name=model_name_building, weather_file_ep=weather_file_ep, weather_file_modelica=weather_file_modelica, - ep_zone_lists=ep_zone_lists + ep_zone_lists=ep_zone_lists, + base_infiltration=base_infiltration ) # Write the generated Modelica code to file @@ -56,6 +62,25 @@ def run(self, elements: dict, weather_file_modelica: Path, return model_name_building, + @staticmethod + def _get_base_infiltration_per_zone(tz_elements: List, + ep_zone_lists: List) -> List[float]: + """Get base infiltration per zones according to the zone's UseConditions + + Args: + tz_elements: List of thermal zone elements. + ep_zone_lists: List of zones in energy plus idf file + + Returns: + base_infiltration: Dict mapping base infiltration to zones. + """ + base_infiltration = [] + for ep_zone in ep_zone_lists: + for tz in tz_elements: + if tz.guid == ep_zone: + base_infiltration.append(tz.base_infiltration) + return base_infiltration + @staticmethod def _load_template() -> Template: """Loads the building template for rendering. @@ -74,7 +99,8 @@ def _render_building_template(self, model_name: str, weather_file_ep: Path, weather_file_modelica: Path, - ep_zone_lists: list) -> str: + ep_zone_lists: list, + base_infiltration: list) -> str: """Render the building Modelica template using provided data. Args: @@ -99,6 +125,7 @@ def _render_building_template(self, weather_path_mos=parse_to_modelica( name=None, value=weather_file_modelica), ep_zone_lists=parse_to_modelica(name=None, value=ep_zone_lists), + base_infiltration=parse_to_modelica(name=None, value=base_infiltration), idf_path=parse_to_modelica(name=None, value=idf_path), n_zones=len(ep_zone_lists) ) diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_total.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_total.py index 07aaddccb2..f8f9c14860 100644 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_total.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/tasks/export_spawn_total.py @@ -142,12 +142,15 @@ def _group_space_heaters_by_zone(tz_elements: List, zone_to_heaters[tz.guid].append(space_heater.guid) return zone_to_heaters - def _save_total_modelica_model( - self, model_name_total: str, model_name_building: str, - model_name_hydraulic: str, - cons_heat_ports_conv_building_hvac: List[Tuple[str, str]], - cons_heat_ports_rad_building_hvac: List[Tuple[str, str]], - weather_path_mos: Path, package_path: Path): + def _save_total_modelica_model(self, + model_name_total: str, + model_name_building: str, + model_name_hydraulic: str, + cons_heat_ports_conv_building_hvac: List[Tuple[str, str]], + cons_heat_ports_rad_building_hvac: List[Tuple[str, str]], + weather_path_mos: Path, + package_path: Path): + """Render and save the total Modelica model file using a template. Args: @@ -180,6 +183,7 @@ def _save_total_modelica_model( with codecs.open(export_path, "w", "utf-8") as file: file.write(total_template_data) + @classmethod def get_port_mapping( cls, cons_heat_ports: List[Tuple[str, str]], port_type: str, @@ -214,6 +218,7 @@ def get_port_mapping( mapping.append((building_port, hydraulic_port)) return mapping + @staticmethod def get_building_index( zone_to_heaters: Dict[str, List[str]], From 3a9a2f711bfdbcd68af9af34d4f5479fa60da719 Mon Sep 17 00:00:00 2001 From: Hoepp J <155448650+HoeppJ@users.noreply.github.com> Date: Tue, 3 Feb 2026 18:14:29 +0100 Subject: [PATCH 121/125] bugfixed ifcopenshell update --- bim2sim/elements/base_elements.py | 46 +++++++++++++++---------------- bim2sim/elements/hvac_elements.py | 20 ++++++++++++-- bim2sim/tasks/common/load_ifc.py | 4 +-- 3 files changed, 42 insertions(+), 28 deletions(-) diff --git a/bim2sim/elements/base_elements.py b/bim2sim/elements/base_elements.py index 36e9d6a4ea..488c434a20 100644 --- a/bim2sim/elements/base_elements.py +++ b/bim2sim/elements/base_elements.py @@ -799,31 +799,31 @@ def __call__(self, ifc_entity, *args, ifc_type: str = None, use_dummy=True, f" will only be created for IFC files of domain " f"{element_cls.from_ifc_domains}") - descriptions = ifc2python.get_descriptions(ifc_entity) - - if descriptions and not any( - any(p.search(desc) for p in element_cls.pattern_ifc_type) - for desc in descriptions): - ele_matches = [] - for ele in self.relevant_elements: - match_count = 0 - for p in ele.pattern_ifc_type: - for desc in descriptions: - if p.search(desc): - match_count += 1 - if match_count > 0: - ele_matches.append((ele, match_count)) - if not ele_matches: - raise LookupError(f"No element found for {ifc_entity}") - elif len(ele_matches) == 1: - element_cls = ele_matches[0][0] - else: - best_ele, _ = max(ele_matches, key=lambda x: x[1]) - element_cls = best_ele + # descriptions = ifc2python.get_descriptions(ifc_entity) + # + # if descriptions and not any( + # any(p.search(desc) for p in element_cls.pattern_ifc_type) + # for desc in descriptions if desc): + # ele_matches = [] + # for ele in self.relevant_elements: + # match_count = 0 + # for p in ele.pattern_ifc_type: + # for desc in descriptions: + # if desc: + # if p.search(desc): + # match_count += 1 + # if match_count > 0: + # ele_matches.append((ele, match_count)) + # if not ele_matches: + # raise LookupError(f"No element found for {ifc_entity}") + # elif len(ele_matches) == 1: + # element_cls = ele_matches[0][0] + # else: + # best_ele, _ = max(ele_matches, key=lambda x: x[1]) + # element_cls = best_ele element = self.create(element_cls, ifc_entity, *args, **kwargs) - if not element.air_type: - test = self.create(element_cls, ifc_entity, *args, **kwargs) + return element def create(self, element_cls, ifc_entity, *args, **kwargs): diff --git a/bim2sim/elements/hvac_elements.py b/bim2sim/elements/hvac_elements.py index 164df12322..613307ecf1 100644 --- a/bim2sim/elements/hvac_elements.py +++ b/bim2sim/elements/hvac_elements.py @@ -949,11 +949,15 @@ def is_consumer(self): def _get_radiator_shape(self, name): """returns topods shape of the radiator""" - settings = ifcopenshell.geom.main.settings() + settings = ifcopenshell.geom.settings() settings.set(settings.USE_PYTHON_OPENCASCADE, True) settings.set(settings.USE_WORLD_COORDS, True) - settings.set(settings.EXCLUDE_SOLIDS_AND_SURFACES, False) - settings.set(settings.INCLUDE_CURVES, True) + settings.set(settings.PRECISION, 1e-6) + settings.set( + "dimensionality", + ifcopenshell.ifcopenshell_wrapper.CURVES_SURFACES_AND_SOLIDS) # 2 + # settings.set(settings.EXCLUDE_SOLIDS_AND_SURFACES, False) + # settings.set(settings.INCLUDE_CURVES, True) return ifcopenshell.geom.create_shape(settings, self.ifc).geometry shape = attribute.Attribute( @@ -2942,6 +2946,16 @@ class AHUSilencer(AHUComponent): and member.__module__ == __name__): # declared here items.add(cls) +hydraulic_items: Set[HVACProduct] = set() +for name, cls in inspect.getmembers( + sys.modules[__name__], + lambda member: inspect.isclass(member) # class at all + and issubclass(member, HVACProduct) # domain subclass + and member is not HVACProduct # but not base class + and not issubclass(member, VentilationElement) # but not ventilation element + and member.__module__ == __name__): # declared here + hydraulic_items.add(cls) + # collect all domain classes ventilation_items: Set[VentilationElement] = set() for name, cls in inspect.getmembers( diff --git a/bim2sim/tasks/common/load_ifc.py b/bim2sim/tasks/common/load_ifc.py index d401658b6a..b666ac7682 100644 --- a/bim2sim/tasks/common/load_ifc.py +++ b/bim2sim/tasks/common/load_ifc.py @@ -36,7 +36,7 @@ def load_ifc_files(self, base_path: Path): if not base_path.is_dir(): raise AssertionError(f"Given base_path {base_path} is not a" f" directory. Please provide a directory.") - ifc_files_dict = {k: [] for k in ['arch', 'hydraulic', 'ventilation']} + ifc_files_dict = {k: [] for k in ['arch', 'hydraulic', 'ventilation', 'mixed']} ifc_files_unsorted = [] ifc_files = [] ifc_files_paths = list(base_path.glob("**/*.ifc")) + list( @@ -64,7 +64,7 @@ def load_ifc_files(self, base_path: Path): f"This took {t_loading} seconds") for file in ifc_files_unsorted: ifc_files_dict[file.domain.name].append(file) - for domain in ('arch', 'hydraulic', 'ventilation'): + for domain in ('arch', 'hydraulic', 'ventilation', 'mixed'): ifc_files.extend(ifc_files_dict[domain]) if not ifc_files: self.logger.error("No ifc found in project folder.") From 2710e2207049e588f64d7c56ad2513190728fbc0 Mon Sep 17 00:00:00 2001 From: Hoepp J <155448650+HoeppJ@users.noreply.github.com> Date: Tue, 3 Feb 2026 18:14:50 +0100 Subject: [PATCH 122/125] update weather functions for spawn --- bim2sim/tasks/common/weather.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/bim2sim/tasks/common/weather.py b/bim2sim/tasks/common/weather.py index 4fbe94c718..2c8722c9e7 100644 --- a/bim2sim/tasks/common/weather.py +++ b/bim2sim/tasks/common/weather.py @@ -29,17 +29,8 @@ def run(self, elements: dict): "implement this") # lat, long = self.get_location_lat_long_from_ifc(elements) # weather_file = self.get_weatherfile_from_dwd(lat, long) - self.check_weather_file(weather_file_modelica, weather_file_ep) - if self.playground.sim_settings.weather_file_path_ep: - weather_file_ep = self.playground.sim_settings.weather_file_path_ep + # self.check_weather_file(weather_file_modelica, weather_file_ep) - # try to get TRY weather file for location of IFC - if not weather_file_ep and not weather_file_modelica: - raise NotImplementedError("Waiting for response from DWD if we can" - "implement this") - # lat, long = self.get_location_lat_long_from_ifc(elements) - # weather_file = self.get_weatherfile_from_dwd(lat, long) - self.check_weather_file(weather_file_modelica, weather_file_ep) if not weather_file_ep and not weather_file_modelica: raise ValueError("No weather file provided for the simulation, " "can't continue model generation.") From 6137d83e23348b1b52ac2c9e7390b1f012cfe0e9 Mon Sep 17 00:00:00 2001 From: Hoepp J <155448650+HoeppJ@users.noreply.github.com> Date: Tue, 3 Feb 2026 18:15:16 +0100 Subject: [PATCH 123/125] EP 9.6.0 update --- .../bim2sim_energyplus/task/ep_create_idf.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/bim2sim/plugins/PluginEnergyPlus/bim2sim_energyplus/task/ep_create_idf.py b/bim2sim/plugins/PluginEnergyPlus/bim2sim_energyplus/task/ep_create_idf.py index 03b1b6bab4..43666ae9b0 100644 --- a/bim2sim/plugins/PluginEnergyPlus/bim2sim_energyplus/task/ep_create_idf.py +++ b/bim2sim/plugins/PluginEnergyPlus/bim2sim_energyplus/task/ep_create_idf.py @@ -123,7 +123,7 @@ def run(self, elements: dict, weather_file_ep: Path) -> ( weather_file_sizing = ( self.playground.sim_settings.weather_file_for_sizing) else: - weather_file_sizing = str(weather_file) + weather_file_sizing = str(weather_file_ep) self.apply_system_sizing( idf, weather_file_sizing, sim_results_path) @@ -213,6 +213,8 @@ def init_idf(sim_settings: EnergyPlusSimSettings, paths: FolderStructure, # initialize the idf with a minimal idf setup idf = IDF(plugin_ep_path + '/data/Minimal.idf') # remove location and design days + idf.idfobjects['VERSION'][0].Version_Identifier = sim_settings.ep_version.replace("_", ".") + idf.removeallidfobjects('SIZINGPERIOD:DESIGNDAY') idf.removeallidfobjects('SITE:LOCATION') if sim_settings.system_weather_sizing != 'DesignDay': @@ -1024,7 +1026,7 @@ def set_infiltration(idf: IDF, idf.newidfobject( "ZONEINFILTRATION:DESIGNFLOWRATE", Name=name, - Zone_or_ZoneList_or_Space_or_SpaceList_Name=zone_name, + Zone_or_ZoneList_Name=zone_name, Schedule_Name="Continuous", Design_Flow_Rate_Calculation_Method="AirChanges/Hour", Air_Changes_per_Hour=space.base_infiltration @@ -1250,7 +1252,7 @@ def set_natural_ventilation(idf: IDF, name: str, zone_name: str, idf.newidfobject( "ZONEVENTILATION:DESIGNFLOWRATE", Name=name + '_winter', - Zone_or_ZoneList_or_Space_or_SpaceList_Name=zone_name, + Zone_or_ZoneList_Name=zone_name, Schedule_Name="Continuous", Ventilation_Type="Natural", Design_Flow_Rate_Calculation_Method="AirChanges/Hour", @@ -1264,7 +1266,7 @@ def set_natural_ventilation(idf: IDF, name: str, zone_name: str, idf.newidfobject( "ZONEVENTILATION:DESIGNFLOWRATE", Name=name + '_summer', - Zone_or_ZoneList_or_Space_or_SpaceList_Name=zone_name, + Zone_or_ZoneList_Name=zone_name, Schedule_Name="Continuous", Ventilation_Type="Natural", Design_Flow_Rate_Calculation_Method="AirChanges/Hour", @@ -1278,7 +1280,7 @@ def set_natural_ventilation(idf: IDF, name: str, zone_name: str, idf.newidfobject( "ZONEVENTILATION:DESIGNFLOWRATE", Name=name + '_overheating', - Zone_or_ZoneList_or_Space_or_SpaceList_Name=zone_name, + Zone_or_ZoneList_Name=zone_name, Schedule_Name="Continuous", Ventilation_Type="Natural", Design_Flow_Rate_Calculation_Method="AirChanges/Hour", From c18c170ae96e0c32f19f5588a469b4148b776d86 Mon Sep 17 00:00:00 2001 From: Hoepp J <155448650+HoeppJ@users.noreply.github.com> Date: Tue, 3 Feb 2026 18:15:30 +0100 Subject: [PATCH 124/125] Update spawn example --- .../PluginSpawn/bim2sim_spawn/__init__.py | 2 +- .../examples/e1_simple_project_bps_spawn.py | 41 ++++++++++++++++--- 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/__init__.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/__init__.py index e6d15c5736..dab0962caa 100644 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/__init__.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/__init__.py @@ -21,7 +21,7 @@ class PluginSpawnOfEP(Plugin): sim_settings = SpawnOfEnergyPlusSimSettings default_tasks = [ common.LoadIFC, - common.CheckIfc, + # common.CheckIfc, common.CreateElementsOnIfcTypes, bps.CreateSpaceBoundaries, diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e1_simple_project_bps_spawn.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e1_simple_project_bps_spawn.py index cf17f06474..0f09b62108 100644 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e1_simple_project_bps_spawn.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e1_simple_project_bps_spawn.py @@ -2,11 +2,13 @@ from pathlib import Path import bim2sim -from bim2sim import Project, ConsoleDecisionHandler +from bim2sim import Project, ConsoleDecisionHandler, run_project from bim2sim.kernel.decision.decisionhandler import DebugDecisionHandler # from bim2sim.kernel.log import default_logging_setup from bim2sim.utilities.types import IFCDomain - +from bim2sim.elements.base_elements import Material +from bim2sim.elements import bps_elements as bps_elements, \ + hvac_elements as hvac_elements def run_example_spawn_1(): """Export a SpawnOfEnergyPlus simulation model. @@ -27,7 +29,7 @@ def run_example_spawn_1(): # Create a temp directory for the project, feel free to use a "normal" # directory - project_path = Path(r"D:\00_Temp\Testing\bim2sim\Spawn") + project_path = Path(r"D:\00_Temp\Testing\bim2sim\Spawn1") # Set the ifc path to use and define which domain the IFC belongs to ifc_paths = { @@ -43,8 +45,8 @@ def run_example_spawn_1(): # Set the install path to your EnergyPlus installation according to your # system requirements - project.sim_settings.ep_install_path = Path("C:") - project.sim_settings.ep_version = "9-4-0" + project.sim_settings.ep_install_path = Path(r"C:\EnergyPlusV9-6-0") + project.sim_settings.ep_version = "9-6-0" project.sim_settings.weather_file_path_ep = ( Path(bim2sim.__file__).parent.parent / 'test/resources/weather_files/DEU_NW_Aachen.105010_TMYx.epw') @@ -56,6 +58,8 @@ def run_example_spawn_1(): project.sim_settings.hvac_modelica_library = "AixLib" + project.sim_settings.relevant_elements = {*bps_elements.items, *hvac_elements.hydraulic_items, Material} + # Set other simulation settings, otherwise all settings are set to default project.sim_settings.aggregations = [ 'PipeStrand', @@ -83,10 +87,35 @@ def run_example_spawn_1(): 8.5, # nominal power of boiler (in kW) 50, # nominal return temperature of boiler ) - # handler = ConsoleDecisionHandler() + + # answers = ( + # 'HVAC-PipeFitting', # Identify PipeFitting 03TbBCNszVXaBWMuR55Ezt + # 'HVAC-PipeFitting', # Identify PipeFitting 05GeK0Vqi$b4sUg10dylS4 + # 'HVAC-PipeFitting', # Identify PipeFitting 0LhPEcsRAfWKRlvc$odfB3 + # 'HVAC-Distributor', # Identify Distributor 1259naiEpIkasmH4NcC8DL + # 'HVAC-PipeFitting', # Identify PipeFitting 1fX98DWWkmb4_lxVNY2CYM + # 'HVAC-Pipe', # Identify PipeFitting 05lPOiNJdAe41k1zmv0NgS + # 'HVAC-ThreeWayValve', # Identify ThreeWayValve 05lPOiNJdAe41k1zmv0NgS + # 2010, # year of construction of building + # *(True,) * 7, # 7 real dead ends found + # *(0.001,) * 13, # volume of junctions + # 2000, 175, # rated_pressure_difference + rated_volume_flow pump of 1st storey (big) + # 4000, 200, # rated_pressure_difference + rated_volume_flow for 2nd storey + # *(70, 50,) * 7, # flow and return temp for 7 space heaters + # 0.056, # nominal_mass_flow_rate 2nd storey TRV (kg/s), + # 20, # dT water of boiler + # 70, # nominal flow temperature of boiler + # 0.3, # minimal part load range of boiler + # 8.5, # nominal power of boiler (in kW) + # 50, # nominal return temperature of boiler + # ) + + handler = ConsoleDecisionHandler() handler = DebugDecisionHandler(answers) handler.handle(project.run()) + # run_project(project, ConsoleDecisionHandler()) + if __name__ == '__main__': run_example_spawn_1() From 5f7eed094d736f72848895e44c815f37092f9f2e Mon Sep 17 00:00:00 2001 From: Hoepp J <155448650+HoeppJ@users.noreply.github.com> Date: Fri, 6 Feb 2026 10:09:21 +0100 Subject: [PATCH 125/125] save example --- .../bim2sim_spawn/examples/e1_simple_project_bps_spawn.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e1_simple_project_bps_spawn.py b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e1_simple_project_bps_spawn.py index 0f09b62108..e063c8be84 100644 --- a/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e1_simple_project_bps_spawn.py +++ b/bim2sim/plugins/PluginSpawn/bim2sim_spawn/examples/e1_simple_project_bps_spawn.py @@ -110,11 +110,11 @@ def run_example_spawn_1(): # 50, # nominal return temperature of boiler # ) - handler = ConsoleDecisionHandler() - handler = DebugDecisionHandler(answers) - handler.handle(project.run()) + # handler = ConsoleDecisionHandler() + # handler = DebugDecisionHandler(answers) + # handler.handle(project.run()) - # run_project(project, ConsoleDecisionHandler()) + run_project(project, ConsoleDecisionHandler()) if __name__ == '__main__':