diff --git a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/sim_settings.py b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/sim_settings.py index fa848a033f..08be8035d5 100644 --- a/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/sim_settings.py +++ b/bim2sim/plugins/PluginAixLib/bim2sim_aixlib/sim_settings.py @@ -10,7 +10,7 @@ class AixLibSimSettings(PlantSimSettings): """ path_aixlib = PathSetting( - default=None, + value=None, description='Path to the local AixLib`s repository. This needs to ' 'point to the root level package.mo file. If not' ' provided, the version for regression testing will be ' diff --git a/bim2sim/plugins/PluginComfort/bim2sim_comfort/sim_settings.py b/bim2sim/plugins/PluginComfort/bim2sim_comfort/sim_settings.py index 907d744170..0859a3b055 100644 --- a/bim2sim/plugins/PluginComfort/bim2sim_comfort/sim_settings.py +++ b/bim2sim/plugins/PluginComfort/bim2sim_comfort/sim_settings.py @@ -10,35 +10,35 @@ def __init__(self): super().__init__() prj_use_conditions = PathSetting( - default=Path(__file__).parent / 'assets/UseConditionsComfort.json', + value=Path(__file__).parent / 'assets/UseConditionsComfort.json', description="Path to a custom UseConditions.json for the specific " "comfort application. These use conditions have " "comfort-based use conditions as a default.", for_frontend=True ) use_dynamic_clothing = BooleanSetting( - default=False, + value=False, description='Use dynamic clothing according to ASHRAE 55 standard.', for_frontend=True ) rename_plot_keys = BooleanSetting( - default=False, + value=False, description='Rename room names for plot results', for_frontend=True ) rename_plot_keys_path = PathSetting( - default=Path(__file__).parent / 'assets/rename_plot_keys.json', + value=Path(__file__).parent / 'assets/rename_plot_keys.json', description="Path for renaming the zone keys for plot results. Path " "to a json file with pairs of current keys and new keys. ", for_frontend=True ) comfort_occupancy_weighting = BooleanSetting( - default=False, description='Weight the comfort rating by occupancy ' + value=False, description='Weight the comfort rating by occupancy ' 'schedules.' ) plot_zone_usages = ChoiceSetting( - default=[], - # default=['office', 'meeting', 'canteen', 'sanitary', 'kitchen'], + value=[], + # value=['office', 'meeting', 'canteen', 'sanitary', 'kitchen'], choices={'': 'Choose empty string to plot all zones.'}, description='Choose string patterns of zone usages for ' 'evaluation of comfort results (multiple choice). Use ' diff --git a/bim2sim/plugins/PluginEnergyPlus/bim2sim_energyplus/sim_settings.py b/bim2sim/plugins/PluginEnergyPlus/bim2sim_energyplus/sim_settings.py index 00300d1bbf..c178e65a30 100644 --- a/bim2sim/plugins/PluginEnergyPlus/bim2sim_energyplus/sim_settings.py +++ b/bim2sim/plugins/PluginEnergyPlus/bim2sim_energyplus/sim_settings.py @@ -13,40 +13,40 @@ class EnergyPlusSimSettings(BuildingSimSettings): and export settings. """ cfd_export = BooleanSetting( - default=False, + value=False, description='Whether to use CFD export for this simulation or not.', for_frontend=True ) split_bounds = BooleanSetting( - default=False, + value=False, description='Whether to convert up non-convex space boundaries or ' 'not.', for_frontend=True ) add_shadings = BooleanSetting( - default=True, + value=True, description='Whether to add shading surfaces if available or not.', for_frontend=True ) add_hash = BooleanSetting( - default=False, + value=False, description='Whether to add a hash as a comment at the first line of' 'IDF file for IFC-to-IDF tracking or not.', for_frontend=False ) split_shadings = BooleanSetting( - default=False, + value=False, description='Whether to convert up non-convex shading boundaries or ' 'not.', for_frontend=True ) run_full_simulation = BooleanSetting( - default=False, + value=False, description='Choose simulation period.', for_frontend=True ) ep_version = ChoiceSetting( - default='9-4-0', + value='9-4-0', choices={ '9-2-0': 'EnergyPlus Version 9-2-0', '9-4-0': 'EnergyPlus Version 9-4-0', @@ -57,30 +57,30 @@ class EnergyPlusSimSettings(BuildingSimSettings): any_string=True ) ep_install_path = PathSetting( - default=Path('/usr/local/EnergyPlus-9-4-0/'), + value=Path('/usr/local/EnergyPlus-9-4-0/'), description='Choose EnergyPlus Installation Path', for_frontend=False, ) system_sizing = BooleanSetting( - default=True, + value=True, description='Whether to do system sizing calculations in EnergyPlus ' 'or not.', for_frontend=True ) run_for_sizing_periods = BooleanSetting( - default=False, + value=False, description='Whether to run the EnergyPlus simulation for sizing ' 'periods or not.', for_frontend=True ) run_for_weather_period = BooleanSetting( - default=True, + value=True, description='Whether to run the EnergyPlus simulation for weather ' 'file period or not.', for_frontend=True ) system_weather_sizing = ChoiceSetting( - default='Typical', + value='Typical', choices={'Typical': 'SummerTypical and WinterTypical for system ' 'sizing.', 'Extreme': 'SummerExtreme and WinterExtreme for system ' @@ -91,25 +91,25 @@ class EnergyPlusSimSettings(BuildingSimSettings): 'file.'}, description='Choose whether to perform the system sizing for ' 'DesignDays, extreme weather periods, typical weather ' - 'periods. Default=Typical (i.e., apply system sizing for ' + 'periods. value=Typical (i.e., apply system sizing for ' 'typical summer/winter days). ' ) weather_file_for_sizing = PathSetting( - default=None, + value=None, description='Path to the weather file that should be used for system ' 'sizing in EnergyPlus', for_frontend=True, mandatory=False ) enforce_system_sizing = BooleanSetting( - default=False, + value=False, description='Choose True if you want to enforce HVAC Sizing to sizing ' 'period settings (limit heating and cooling capacity) ' 'instead of autosizing.', for_frontend=True ) solar_distribution = ChoiceSetting( - default='FullExterior', + value='FullExterior', choices={ 'FullExterior': 'Full exterior solar distribution', 'FullInteriorAndExterior': 'Full interior and exterior solar ' @@ -119,7 +119,7 @@ class EnergyPlusSimSettings(BuildingSimSettings): for_frontend=True ) add_window_shading = ChoiceSetting( - default=None, + value=None, choices={ None: 'Do not add window shading', 'Interior': 'Add an interior shade in EnergyPlus', @@ -129,7 +129,7 @@ class EnergyPlusSimSettings(BuildingSimSettings): for_frontend=True, ) output_format = ChoiceSetting( - default='CommaAndHTML', + value='CommaAndHTML', choices={ 'Comma': 'Output format Comma (.csv)', 'Tab': 'Output format Tab (.tab)', @@ -145,7 +145,7 @@ class EnergyPlusSimSettings(BuildingSimSettings): for_frontend=True ) unit_conversion = ChoiceSetting( - default='JtoKWH', + value='JtoKWH', choices={ 'None': 'No unit conversions', 'JtoKWH': 'Convert Joule into kWh (1/3600000)', @@ -158,7 +158,7 @@ class EnergyPlusSimSettings(BuildingSimSettings): for_frontend=True ) output_keys = ChoiceSetting( - default=['output_outdoor_conditions', 'output_zone_temperature', + value=['output_outdoor_conditions', 'output_zone_temperature', 'output_zone', 'output_infiltration', 'output_meters'], choices={ 'output_outdoor_conditions': 'Add outputs for outdoor conditions.', @@ -176,32 +176,32 @@ class EnergyPlusSimSettings(BuildingSimSettings): for_frontend=True ) correct_space_boundaries = BooleanSetting( - default=True, + value=True, description='Apply geometric correction to space boundaries.', for_frontend=True ) close_space_boundary_gaps = BooleanSetting( - default=True, + value=True, description='Close gaps in the set of space boundaries by adding ' 'additional 2b space boundaries.', for_frontend=True ) add_natural_ventilation = BooleanSetting( - default=True, + value=True, description='Add natural ventilation to the building. Natural ' 'ventilation is not available when cooling is activated.', for_frontend=True ) hvac_off_at_night = BooleanSetting( - default=False, description='Disable all HVAC systems at night from ' + value=False, description='Disable all HVAC systems at night from ' '10pm to 6am.' ) control_operative_temperature = BooleanSetting( - default=False, description='Use operative temperature instead of air ' + value=False, description='Use operative temperature instead of air ' 'temperature for zonal temperature control.' ) ventilation_demand_control = ChoiceSetting( - default=None, + value=None, choices={None: 'No demand control for mechanical ventilation.', 'OccupancySchedule': 'Demand control based on occupancy ' 'schedule.'}, @@ -209,7 +209,7 @@ class EnergyPlusSimSettings(BuildingSimSettings): 'controlled. Default is None. ' ) outdoor_air_economizer = ChoiceSetting( - default='NoEconomizer', + value='NoEconomizer', choices={'NoEconomizer': 'No outdoor air economizer is applied.', 'DifferentialDryBulb': 'The outdoor air economizer is ' 'applied based on the differential ' @@ -223,7 +223,7 @@ class EnergyPlusSimSettings(BuildingSimSettings): '"NoEconomizer".' ) heat_recovery_type = ChoiceSetting( - default='Enthalpy', + value='Enthalpy', choices={'Enthalpy': 'Use Enthalpy Heat Recovery.', 'Sensible': 'Use Sensible Heat Recovery.', 'None': 'No Heat Recovery'}, @@ -231,34 +231,34 @@ class EnergyPlusSimSettings(BuildingSimSettings): 'mechanical ventilation.' ) heat_recovery_sensible = NumberSetting( - default=0.8, min_value=0, max_value=1, + value=0.8, min_value=0, max_value=1, description='Choose the sensible heat recovery effectiveness. ' 'Default: 0.8.' ) heat_recovery_latent = NumberSetting( - default=0.7, min_value=0, max_value=1, + value=0.7, min_value=0, max_value=1, description='Choose the latent heat recovery effectiveness. Only ' 'applicable if heat_recovery_type="Enthalpy". Default: 0.7.' ) outdoor_air_per_person = NumberSetting( - default=7, + value=7, min_value=0, max_value=25, description='Outdoor air per person in l/s. Defaults to 7 l/s ' 'according to DIN EN 16798-1, Category II.' ) outdoor_air_per_area = NumberSetting( - default=0.7, min_value=0, max_value=10, + value=0.7, min_value=0, max_value=10, description='Outdoor air per floor area in l/s. Defaults to 0.7 l/(s ' 'm2) according to DIN EN 16798-1, Category II for low ' 'emission buildings.' ) residential = BooleanSetting( - default=False, description='Choose True to use residential settings ' + value=False, description='Choose True to use residential settings ' 'for natural ventilation (DIN4108-2), ' 'False for non-residential houses.' ) natural_ventilation_approach = ChoiceSetting( - default="Simple", + value="Simple", description='Choose calculation approach for natural ventilation.', choices={ "Simple": "use simplified ventilation based on TEASER templates.", diff --git a/bim2sim/plugins/PluginOpenFOAM/bim2sim_openfoam/sim_settings.py b/bim2sim/plugins/PluginOpenFOAM/bim2sim_openfoam/sim_settings.py index 2e055825bf..a0cf032af5 100644 --- a/bim2sim/plugins/PluginOpenFOAM/bim2sim_openfoam/sim_settings.py +++ b/bim2sim/plugins/PluginOpenFOAM/bim2sim_openfoam/sim_settings.py @@ -14,27 +14,27 @@ def __init__(self): Material} add_heating = BooleanSetting( - default=True, + value=True, description='Whether to add heating devices or not.', for_frontend=True ) add_floorheating = BooleanSetting( - default=False, + value=False, description='Whether to add floorheating instead of usual radiators.', for_frontend=True ) add_airterminals = BooleanSetting( - default=True, + value=True, description='Whether to add air terminals or not.', for_frontend=True ) ignore_heatloss = BooleanSetting( - default=False, + value=False, description='Ignores heat loss through walls if set to True.', for_frontend=True ) inlet_type = ChoiceSetting( - default='Plate', + value='Plate', choices={ 'Original': 'Simplified IFC shape for inlet', 'Plate': 'Simplified plate for inlet', @@ -48,7 +48,7 @@ def __init__(self): for_frontend=True ) outlet_type = ChoiceSetting( - default='Plate', + value='Plate', choices={ 'Original': 'Simplified IFC shape for outlet', 'Plate': 'Simplified plate for outlet', @@ -63,7 +63,7 @@ def __init__(self): for_frontend=True ) outflow_direction = ChoiceSetting( - default='down', + value='down', choices={ 'down': 'Outflow facing downward. Only applicable for original ' 'shapes.', @@ -77,7 +77,7 @@ def __init__(self): 'downward facing flows.' ) select_space_guid = ChoiceSetting( - default='', + value='', choices={ '': 'No guid selected, first space will be selected.' }, @@ -87,7 +87,7 @@ def __init__(self): any_string=True, ) simulation_date = ChoiceSetting( - default='12/21', + value='12/21', description='Select date of simulation according to simulated ' 'timeframe in the PluginEnergyPlus. Insert as string in ' 'format MM/DD.', @@ -99,7 +99,7 @@ def __init__(self): any_string=True ) simulation_time = NumberSetting( - default=11, + value=11, description='Select time of simulation according to simulated timeframe ' 'in the PluginEnergyPlus. Insert as number (time) ' 'for the full hour ranging 1 to 24.', @@ -108,7 +108,7 @@ def __init__(self): for_frontend=True, ) simulation_type = ChoiceSetting( - default='steady', + value='steady', choices={ 'steady': 'steady-state simulation', 'combined': 'preconditioned transient simulation', @@ -119,7 +119,7 @@ def __init__(self): for_frontend=True, ) mesh_size = NumberSetting( - default=0.1, + value=0.1, description='Set the mesh size of the blockMesh in [m]. Insert a ' 'number between 0.001 and 0.2.', min_value=0.001, @@ -127,7 +127,7 @@ def __init__(self): for_frontend=True, ) mesh_max_global_cells = NumberSetting( - default=3000000, + value=3000000, description='Set the maximum number of global cells for meshing in ' 'snappyHexMesh.', min_value=2000000, @@ -135,21 +135,21 @@ def __init__(self): for_frontend=True, ) adjust_refinements = BooleanSetting( - default=False, + value=False, description='Whether surface and region refinements of airterminals ' 'and interior elements should be automatically ' 'recomputed or not.', for_frontend=True ) total_iterations = NumberSetting( - default=20000, + value=20000, min_value=20, max_value=50000, description='Total number of iterations for the simulation.', for_frontend=True ) steady_iterations = NumberSetting( - default=2500, + value=2500, min_value=20, max_value=15000, for_frontend=True, @@ -157,26 +157,26 @@ def __init__(self): 'a transient simulation.', ) n_procs = NumberSetting( - default=12, + value=12, min_value=1, description='Set the number of processors for decomposition and ' 'simulation.', for_frontend=True ) run_meshing = BooleanSetting( - default=False, + value=False, description='Whether to run OpenFOAM meshing or not. Only available for' ' linux systems.', for_frontend=True ) run_cfd_simulation = BooleanSetting( - default=False, + value=False, description='Whether to run the OpenFOAM CFD simulation or not. Only ' 'available for linux systems.', for_frontend=True ) heater_radiation = NumberSetting( - default=0.3, + value=0.3, description='Select the radiative portion of the heating heat transfer.' ' Choose between 0 and 1.', min_value=0, @@ -184,22 +184,22 @@ def __init__(self): for_frontend=True, ) add_comfort = BooleanSetting( - default=True, + value=True, description='Whether to add thermal comfort settings to OpenFOAM', for_frontend=True ) add_furniture = BooleanSetting( - default=False, + value=False, description='Whether to add furniture to OpenFOAM', for_frontend=True ) level_heat_balance = BooleanSetting( - default=True, + value=True, description='Whether to level heat balance: reduce heating towards ' 'leveled heat balance considering internal gains.' ) furniture_setting = ChoiceSetting( - default='Office', + value='Office', choices={ 'Office': 'Office setup, chair and desk', 'Concert': 'Concert setup, chairs in rows', @@ -212,13 +212,13 @@ def __init__(self): for_frontend=True ) furniture_amount = NumberSetting( - default=1, + value=1, min_value=0, max_value=300, for_frontend=True, ) furniture_orientation = ChoiceSetting( - default='short_side', + value='short_side', choices={ 'long_side': 'Long side of a rectangular space', 'short_side': 'short side of a rectangular space', @@ -231,12 +231,12 @@ def __init__(self): } ) add_people = BooleanSetting( - default=False, + value=False, for_frontend=True, description='Choose if people should be added.' ) use_energyplus_people_amount = BooleanSetting( - default=False, description='Choose if number of people should be ' + value=False, description='Choose if number of people should be ' 'assigned as defined in the EnergyPlus ' 'simulation. If true, people_amount is not ' 'considered but overwritten with the ' @@ -244,7 +244,7 @@ def __init__(self): 'EnergyPlus simulation. ' ) people_setting = ChoiceSetting( - default='Seated', + value='Seated', choices={ 'Seated': 'Seated, furniture needs to be provided in sufficient amount. ', 'Standing': 'Standing, no furniture required.' @@ -253,13 +253,13 @@ def __init__(self): for_frontend=True ) people_amount = NumberSetting( - default=1, + value=1, min_value=0, max_value=300, for_frontend=True, ) radiation_model = ChoiceSetting( - default='P1', + value='P1', choices={ 'none': 'No radiation', 'P1': 'Use P1 Radiation Model', @@ -270,7 +270,7 @@ def __init__(self): for_frontend=True ) radiation_precondition_time = NumberSetting( - default=1000, + value=1000, min_value=10, max_value=5000, description='Choose number of preconditioning iterations using P1 ' @@ -278,12 +278,12 @@ def __init__(self): for_frontend=True ) add_solar_radiation=BooleanSetting( - default=True, + value=True, description='Add solar radiation. Requires fvDOM as radiation model.', for_frontend=True ) add_air_volume_evaluation = BooleanSetting( - default=False, + value=False, description='Add an air volume evaluation. Removes voids of all ' 'people, furniture elements, and heaters, to enhance the ' 'evaluation of the air volume itself. This is ' @@ -291,20 +291,20 @@ def __init__(self): for_frontend=True ) scale_person_for_eval = NumberSetting( - default=0.05, + value=0.05, min_value=0.001, max_value=0.2, description='Scale shape of person for evaluation in paraview.', for_frontend=True, ) mesh_feature_snapping = BooleanSetting( - default=False, + value=False, description='Choose if explicit surface feature snapping should be ' 'applied in snappyHexMesh. ', for_frontend=True ) cluster_jobname = ChoiceSetting( - default="newJob", + value="newJob", choices={"fullRun": "Specify content", "Job1234": "Enumerate"}, description='Jobname that is used when running the simulation via a ' 'batch script on the RWTH Compute Cluster.', @@ -312,7 +312,7 @@ def __init__(self): any_string=True ) cluster_compute_account = ChoiceSetting( - default='', + value='', choices={ '': 'Skip defining compute cluster account.', "thes1234": "Define thesis account", @@ -325,7 +325,7 @@ def __init__(self): any_string=True ) cluster_max_runtime_simulation = ChoiceSetting( - default="23:59:00", + value="23:59:00", choices={"11:59:00": "12 hours", "06:00:00": "6 hours"}, description=r'Max runtime for a full simulation in the format of ' r'"D:HH:MM:SS".', @@ -333,7 +333,7 @@ def __init__(self): any_string=True ) cluster_max_runtime_meshing = ChoiceSetting( - default="00:59:00", + value="00:59:00", choices={"00:30:00": "30 minutes", "00:15:00": "15 minutes"}, description='Max runtime for meshing in the format of ' 'D:HH:MM:SS .', @@ -341,7 +341,7 @@ def __init__(self): any_string=True ) cluster_cpu_per_node = NumberSetting( - default=96, + value=96, min_value=1, max_value=1024, description='Number of physical cores per node.', diff --git a/bim2sim/plugins/PluginTEASER/bim2sim_teaser/sim_settings.py b/bim2sim/plugins/PluginTEASER/bim2sim_teaser/sim_settings.py index b2d18cc9b3..3ac8b27e3e 100644 --- a/bim2sim/plugins/PluginTEASER/bim2sim_teaser/sim_settings.py +++ b/bim2sim/plugins/PluginTEASER/bim2sim_teaser/sim_settings.py @@ -11,7 +11,7 @@ class TEASERSimSettings(BuildingSimSettings): specific settings are added here. """ sim_results = ChoiceSetting( - default=[ + value=[ "heat_demand_total", "cool_demand_total", "heat_demand_rooms", "cool_demand_rooms", "heat_energy_total", "cool_energy_total", @@ -68,7 +68,7 @@ class TEASERSimSettings(BuildingSimSettings): ) zoning_criteria = ChoiceSetting( - default=ZoningCriteria.individual_spaces, + value=ZoningCriteria.individual_spaces, choices={ ZoningCriteria.external: 'Group all thermal zones that have contact to the exterior' @@ -96,7 +96,7 @@ class TEASERSimSettings(BuildingSimSettings): ) path_aixlib = PathSetting( - default=None, + value=None, description='Path to the local AixLib`s repository. This needs to ' 'point to the root level package.mo file. If not' ' provided, the version for regression testing will be ' diff --git a/bim2sim/project.py b/bim2sim/project.py index e214ceb89d..96b423d83e 100644 --- a/bim2sim/project.py +++ b/bim2sim/project.py @@ -49,7 +49,7 @@ def add_config_section( if not callable(getattr(sim_settings, attr)) and not attr.startswith('__')] for attr in attributes: - default_value = getattr(sim_settings, attr).default + default_value = getattr(sim_settings, attr).value if isinstance(default_value, Enum): default_value = str(default_value) if not attr in config[name]: @@ -325,10 +325,8 @@ def __init__( "Use Project.create() to create a new Project") self._made_decisions = DecisionBunch() self.loaded_decisions = load(self.paths.decisions) - self.plugin_cls = self._get_plugin(plugin) self.playground = Playground(self) - # link sim_settings to project to make set of settings easier self.sim_settings = self.playground.sim_settings def _get_plugin(self, plugin): diff --git a/bim2sim/sim_settings.py b/bim2sim/sim_settings.py index dd4bf3a946..9c5a5a67e8 100644 --- a/bim2sim/sim_settings.py +++ b/bim2sim/sim_settings.py @@ -2,13 +2,16 @@ This targets both, settings to set for the later simulation and settings for the model generation process in bim2sim. """ + import logging import ast import os.path -from pathlib import Path -from typing import Union +from typing import Union, Optional, List import sys - +from pydantic import BaseModel, Field, model_validator, field_validator, FilePath, DirectoryPath +from pydantic_core import PydanticCustomError +from typing_extensions import Self +from enum import Enum from bim2sim.utilities import types from bim2sim.utilities.types import LOD from bim2sim.elements.base_elements import Material @@ -21,48 +24,15 @@ class AutoSettingNameMeta(type): - """Adds the name to every SimulationSetting attribute based on its instance + """Sets the name to every SimulationSetting attribute based on its instance name. - - This makes the definition of an extra attribute 'name' obsolete, as the - attributes 'name' is automatic defined based on the instance name. - - - Example: - >>> # create new simulation settings for your awesome simulation - >>> class MyAwesomeSimulationSettings(BaseSimSettings): - ... def __init__(self): - ... super().__init__() - - >>> # create a new simulation setting, name will be taken automatic - from - >>> # instance name - >>> make_simulation_extra_fast = Setting( - ... default=True, - ... choices={ - ... True: 'This simulation will be incredible fast.', - ... False: 'This simulation will be increbdile slow.' - ... }, - ... description='Run the simulation in extra fast mode?', - ... for_frontend=True - ... ) - - >>> # create a SimulationSettings instance and get the value - >>> my_awesome_settings = MyAwesomeSimulationSettings() - >>> # get initial value which is always none - >>> print(my_awesome_settings.make_simulation_extra_fast) - None - >>> # set default values and get the value - >>> my_awesome_settings.load_default_settings() - >>> print(my_awesome_settings.make_simulation_extra_fast) - True -""" + """ def __init__(cls, name, bases, namespace): super(AutoSettingNameMeta, cls).__init__(name, bases, namespace) # get all namespace objects for name, obj in namespace.items(): - # filter for settings of simulaiton + # filter for settings of simulation if isinstance(obj, Setting): # provide name of the setting as attribute obj.name = name @@ -81,6 +51,7 @@ class SettingsManager(dict): bound_simulation_settings: instance of sim_settings this manager is bound to. E.g. BuildingSimSettings. """ + defaults = {} def __init__(self, bound_simulation_settings): super().__init__() @@ -90,361 +61,184 @@ def __init__(self, bound_simulation_settings): def _create_settings(self): """Add all listed settings from the simulation in its attributes.""" for name in self.names: + # Loads setting by name setting = getattr(type(self.bound_simulation_settings), name) - setting.initialize(self) + self[name] = setting + + # Store predefined default values in the defaults dict, + # so they can be set back to their defaults later + self.defaults[setting.name] = setting.value @property def names(self): - """Returns a generator object with all settings that the - bound_simulation_settings owns.""" - return (name for name in dir(type(self.bound_simulation_settings)) - if - isinstance(getattr(type(self.bound_simulation_settings), name), - Setting)) + """Returns a generator object with all settings that the bound_simulation_settings owns.""" + bound_simulation_settings_class = type(self.bound_simulation_settings) + + for attribute_name in dir(bound_simulation_settings_class): + attribute = getattr(bound_simulation_settings_class, attribute_name) + + if isinstance(attribute, Setting): + yield attribute_name + + +class Setting(BaseModel, validate_assignment=True, validate_default=True): + """Base class for all simulation settings. + The attribute "value", which contains the payload of the simulation setting, is added in + the derived classes for the different data types (e.g. NumberSetting(Setting)). -class Setting: - """Define specific settings regarding model creation and simulation. + The value, which is assigned to the attribute "value", when instancing the setting, serves + as a default value and can be changed according to the use case, if necessary. Args: - default: default value that will be applied when calling load_default() - choices: dict of possible choice for this setting as key and a - description per choice as value - description: description of what the settings does as Str + name: Name of the setting. Is set automatically by AutoSettingNameMeta(type) + description: description the setting for_frontend: should this setting be shown in the frontend - multiple_choice: allows multiple choice any_string: any string is allowed instead of a given choice mandatory: whether a setting needs to be set """ - def __init__( - self, - default=None, - description: Union[str, None] = None, - for_frontend: bool = False, - any_string: bool = False, - mandatory=False - ): - self.name = None # set by AutoSettingNameMeta - self.default = default - self.value = None - self.description = description - self.for_webapp = for_frontend - self.any_string = any_string - self.mandatory = mandatory - self.manager = None - - def initialize(self, manager): - """Link between manager stored setting and direct setting of simulation - """ - if not self.name: - raise AttributeError("Attribute.name not set!") - self.check_setting_config() - self.manager = manager - self.manager[self.name] = self - self.manager[self.name].value = None - - def check_setting_config(self): - """Checks if the setting is configured correctly""" - return True + name: str = Field(default="set automatically") + description: Optional[str] = None + for_frontend: bool = Field(default=False) + any_string: bool = Field(default=False) + mandatory: bool = Field(default=False) - def load_default(self): - if not self.value: - self.value = self.default + def __set__(self, bound_simulation_settings, value): + """Creates a new attribute with the name and value of the simulation setting instance, + stored in sim_settings. This makes it easier to access the setting's payload. + + Example: + + sim_settings = {e.g. BuildingSimSettings} + ... + example_setting_name = {bool}True + manager {SettingsManager} + 'example_setting_name' = {BooleanSetting}(name='ahu_heating_overwrite',value=True, ...) + ... + ... + """ + bound_simulation_settings.manager[self.name].value = value def __get__(self, bound_simulation_settings, owner): - """This is the get function that provides the value of the - simulation setting when calling sim_settings.""" + """Allows direct access to the setting's value""" if bound_simulation_settings is None: return self - return self._inner_get(bound_simulation_settings) - - def _inner_get(self, bound_simulation_settings): - """Gets the value for the setting from the manager.""" return bound_simulation_settings.manager[self.name].value - def _inner_set(self, bound_simulation_settings, value): - """Sets the value for the setting inside the manager.""" - bound_simulation_settings.manager[self.name].value = value - - def check_value(self, bound_simulation_settings, value): - """Checks the value that should be set for correctness - - Args: - bound_simulation_settings: the sim setting belonging to the value - value: value that should be checked for correctness - Returns: - True: if check was successful - Raises: - ValueError: if check was not successful - """ - return True - - def __set__(self, bound_simulation_settings, value): - """This is the set function that sets the value in the simulation - setting when calling sim_settings. = """ - if self.check_value(bound_simulation_settings, value): - self._inner_set(bound_simulation_settings, value) - class NumberSetting(Setting): - def __init__( - self, - default=None, - description: Union[str, None] = None, - for_frontend: bool = False, - any_string: bool = False, - min_value: float = None, - max_value: float = None - ): - super().__init__(default, description, for_frontend, any_string) - self.min_value = min_value - self.max_value = max_value - - def check_setting_config(self): - """Make sure min and max values are reasonable""" - if not self.min_value: + value: Optional[Union[float, int]] + min_value: Optional[Union[float, int]] = None + max_value: Optional[Union[float, int]] = None + + @model_validator(mode='after') + def check_setting_config(self) -> Self: + if self.min_value is None: self.min_value = sys.float_info.epsilon logger.info(f'No min_value given for sim_setting {self}, assuming' f'smallest float epsilon.') - if not self.max_value: + + if self.max_value is None: self.max_value = float('inf') logger.info(f'No max_value given for sim_setting {self}, assuming' f'biggest float inf.') - if self.default: - if self.default > self.max_value or self.default < self.min_value: - raise AttributeError( - f"The specified limits for min_value, max_value and" - f"default are contradictory min: {self.min_value} " - f"max: {self.max_value}") + if self.min_value > self.max_value: - raise AttributeError( - f"The specified limits for min_value and max_value are " - f"contradictory min: {self.min_value} max: {self.max_value}") - else: - return True - - def check_value(self, bound_simulation_settings, value): - """Checks the value that should be set for correctness - - Checks if value is in limits. - Args: - bound_simulation_settings: the sim setting belonging to the value - value: value that should be checked for correctness - Returns: - True: if check was successful - Raises: - ValueError: if check was not successful - """ - # None is allowed for settings that should not be used at all but have - # number values if used - if value is None: - return True - if not isinstance(value, (float, int)): - raise ValueError("The provided value is not a number.") - if self.min_value <= value <= self.max_value: - return True - else: - raise ValueError( - f"The provided value is not inside the limits: min: " - f"{self.min_value}, max: {self.max_value}, value: {value}") + raise PydanticCustomError("contradictory_limits", + f"The specified limits for min_value and max_value are " # type: ignore[misc] + f"contradictory min: {self.min_value} max: {self.max_value}") + + return self + + @model_validator(mode='after') + def check_limits(self) -> Self: + if self.value is not None: + if not (self.min_value <= self.value <= self.max_value): + raise PydanticCustomError( + "value_out_of_range", + f"value ({self.value}) must be between {self.min_value} and {self.max_value}" # type: ignore[misc] + ) + return self class ChoiceSetting(Setting): - def __init__( - self, - default=None, - description: Union[str, None] = None, - for_frontend: bool = False, - any_string: bool = False, - choices: dict = None, - multiple_choice: bool = False - ): - super().__init__(default, description, for_frontend, any_string) - self.choices = choices - self.multiple_choice = multiple_choice - - def check_setting_config(self): - """make sure str choices don't hold '.' as this is seperator for enums. - """ - for choice in self.choices: - if isinstance(choice, str) and '.' in choice: - if '.' in choice: - raise AttributeError( - f"Provided setting {choice} has a choice with " - f"character" - f" '.', this is prohibited.") - return True - - def check_value(self, bound_simulation_settings, value): - """Checks the value that should be set for correctness - - Checks if the selected value is in choices. - Args: - bound_simulation_settings: the sim setting belonging to the value - value: value that should be checked for correctness - Returns: - True: if check was successful - Raises: - ValueError: if check was not successful - """ - choices = bound_simulation_settings.manager[self.name].choices - if isinstance(value, list): + value: Union[str, List[str], Enum, None] + choices: dict + multiple_choice: bool = False + + def _check_for_value_in_choices(self, value): + if value not in self.choices: + if not self.any_string: + raise PydanticCustomError( + "value_not_in_choices", + f'{value} is no valid value for setting {self.name}, select one of {self.choices}.' # type: ignore[misc] + ) + + @field_validator('choices', mode='after') + @classmethod + def check_setting_config(cls, choices): + for choice in choices: + # Check for string type, to exclude enums + if isinstance(choice, str) and "." in choice: + raise PydanticCustomError("illegal_character", + f"Provided setting {choice} contains character '.', this is prohibited.") # type: ignore[misc] + return choices + + @model_validator(mode='after') + def check_content(self): + if isinstance(self.value, list): if not self.multiple_choice: - raise ValueError(f'Only one choice is allowed for setting' - f' {self.name}, but {len(value)} choices ' - f'are given.') - for val in value: - self.check_value(bound_simulation_settings, val) - return True - else: - if self.any_string and not isinstance(value, str): - raise ValueError(f'{value} is no valid value for setting ' - f'{self.name}, please enter a string.') - elif value not in choices and not self.any_string: - raise ValueError(f'{value} is no valid value for setting ' - f'{self.name}, select one of {choices}.') + raise PydanticCustomError("one_choice_allowed", f'Only one choice is allowed for setting' # type: ignore[misc] + f' {self.name}, but {len(self.value)} choices are given.') # type: ignore[misc] else: - return True + for val in self.value: + self._check_for_value_in_choices(val) + else: + self._check_for_value_in_choices(self.value) + return self -class PathSetting(Setting): - def check_value(self, bound_simulation_settings, value): - """Checks the value that should be set for correctness - - Checks if the value is a valid path - Args: - bound_simulation_settings: the sim setting belonging to the value - value: value that should be checked for correctness - Returns: - True: if check was successful - Raises: - ValueError: if check was not successful - """ - # check for existence - # TODO #556 Do not check default path for existence because this might - # not exist on system. This is a hack and should be solved when - # improving communication between config and settings - if not value == self.default: - if not value.exists(): - raise FileNotFoundError( - f"The path provided for '{self.name}' does not exist." - f" Please check the provided setting path which is: " - f"{str(value)}") - return True - def __set__(self, bound_simulation_settings, value): - """This is the set function that sets the value in the simulation - setting - when calling sim_settings. = """ - if not isinstance(value, Path): - if value is not None: - try: - value = Path(value) - except TypeError: - raise TypeError( - f"Could not convert the simulation setting for " - f"{self.name} into a path, please check the path.") - # if default value is None this is ok - elif value == self.default: - pass - else: - raise ValueError(f"No Path provided for setting {self.name}.") - if self.check_value(bound_simulation_settings, value): - self._inner_set(bound_simulation_settings, value) +class PathSetting(Setting): + value: Optional[Union[DirectoryPath, FilePath]] class BooleanSetting(Setting): - def check_value(self, bound_simulation_settings, value): - if not isinstance(value, bool) and value is not None: - raise ValueError(f"The provided value {value} for sim_setting " - f"{self.name} is not a Boolean") - else: - return True + value: Optional[bool] class GuidListSetting(Setting): - """Define a setting that accepts a list of IFC GUIDs. + value: Optional[List[str]] = None - This setting type is used for storing collections of IFC GUIDs, - where each GUID is validated to ensure it follows IFC GUID format. - - Args: - default: default list of GUIDs (if any) that will be applied when calling load_default() - description: description of what the settings does as Str - for_frontend: should this setting be shown in the frontend - mandatory: whether a setting needs to be set - """ - - def __init__( - self, - default=None, - description: Union[str, None] = None, - for_frontend: bool = False, - mandatory=False - ): - super().__init__(default, description, for_frontend, False, mandatory) - # Initialize with empty list if default is None - if default is None: - self.default = [] - - def check_value(self, bound_simulation_settings, value): - """Checks if each GUID in the list is valid according to IFC standards. - - Args: - bound_simulation_settings: the sim setting belonging to the value - value: list of GUIDs that should be checked for correctness - - Returns: - True: if all GUIDs in the list are valid - - Raises: - ValueError: if any GUID in the list is invalid or value is not a list - """ - # None is allowed for optional settings - if value is None: - return True - - # Check if value is a list - if not isinstance(value, list): - raise ValueError( - f"The value for {self.name} must be a list of GUIDs, but got {type(value).__name__}") - - # Empty list is valid - if len(value) == 0: - return True - - # Check each GUID in the list - for i, guid in enumerate(value): - if not isinstance(guid, str): - raise ValueError( - f"GUID at index {i} must be a string, but got {type(guid).__name__}") - - # Use the existing check_guid function to validate the GUID format - if not check_guid(guid): - raise ValueError( - f"Invalid IFC GUID format at index {i}: '{guid}'") - - return True + @field_validator('value', mode='after') + @classmethod + def check_value(cls, value): + if value is not None: + for i, guid in enumerate(value): + if not check_guid(guid): + raise PydanticCustomError("invalid_guid", + f"Invalid IFC GUID format at index {i}: '{guid}'") # type: ignore[misc] + return value class BaseSimSettings(metaclass=AutoSettingNameMeta): """Specification of basic bim2sim simulation settings which are common for all simulations""" - def __init__(self, - filters: list = None): + def __init__(self, filters: list = None): self.manager = SettingsManager(bound_simulation_settings=self) self.relevant_elements = {} self.simulated = False - self.load_default_settings() def load_default_settings(self): """loads default values for all settings""" + for setting in self.manager.values(): - setting.load_default() + default = self.manager.defaults[setting.name] + setting.value = default def update_from_config(self, config): """Updates the simulation settings specification from the config @@ -517,18 +311,21 @@ def check_mandatory(self): f"but is marked as mandatory. Please configure " f"{setting.name} before running your project.") + dymola_simulation = BooleanSetting( - default=False, - description='Run a Simulation with Dymola after model export?', - for_frontend=True + value=False, + description="Run a Simulation with Dymola after model export?", + for_frontend=True, ) + create_external_elements = BooleanSetting( - default=False, + value=False, description='Create external elements?', for_frontend=True ) + max_wall_thickness = NumberSetting( - default=0.3, + value=0.3, max_value=0.60, min_value=1e-3, description='Choose maximum wall thickness as a tolerance for mapping ' @@ -538,7 +335,25 @@ def check_mandatory(self): ) group_unidentified = ChoiceSetting( - default='fuzzy', + value='fuzzy', + choices={ + 'fuzzy': 'Use fuzzy search to find ifc name similarities', + 'name': 'Only group elements with exact same ifc name', + 'name_and_description': 'Only group elements with the same ifc' + ' name and ifc description' + }, + description='To reduce the number of decisions by user to identify ' + 'elements which can not be identified automatically by ' + 'the ' + 'system, you can either use simple grouping by same name ' + 'of' + ' IFC element or fuzzy search to group based on' + ' similarities in name.', + for_frontend=True, + ) + + group_unidentified_ = ChoiceSetting( + value='fuzzy', choices={ 'fuzzy': 'Use fuzzy search to find ifc name similarities', 'name': 'Only group elements with exact same ifc name', @@ -554,8 +369,9 @@ def check_mandatory(self): ' similarities in name.', for_frontend=True ) + fuzzy_threshold = NumberSetting( - default=0.7, + value=0.7, min_value=0.5, max_value=0.9, description='If you want to use fuzzy search in the ' @@ -569,7 +385,7 @@ def check_mandatory(self): ) reset_guids = BooleanSetting( - default=False, + value=False, description='Reset GlobalIDs from imported IFC if duplicate ' 'GlobalIDs occur in the IFC. As EnergyPlus evaluates all' 'GlobalIDs upper case only, this might also be ' @@ -579,7 +395,7 @@ def check_mandatory(self): ) weather_file_path = PathSetting( - default=None, + value=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' @@ -592,33 +408,34 @@ def check_mandatory(self): ) building_rotation_overwrite = NumberSetting( - default=0, - description='Overwrite the (clockwise) building rotation angle in ' - 'degrees.', + value=0, min_value=0, max_value=359, + description='Overwrite the (clockwise) building rotation angle in ' + 'degrees.', for_frontend=True ) add_space_boundaries = BooleanSetting( - default=False, + value=False, description='Add space boundaries. Only required for building ' 'performance simulation and co-simulations.', for_frontend=True ) correct_space_boundaries = BooleanSetting( - default=False, + value=False, description='Apply geometric correction to space boundaries.', for_frontend=True ) close_space_boundary_gaps = BooleanSetting( - default=False, + value=False, description='Close gaps in the set of space boundaries by adding ' 'additional 2b space boundaries.', for_frontend=True ) + stories_to_load_guids = GuidListSetting( - default=[], + value=[], description='List of IFC GUIDs for the specific stories that should ' 'be loaded. If empty, all stories will be considered ' 'for loading. This setting is useful for large buildings ' @@ -642,7 +459,7 @@ def __init__(self): # Todo maybe make every aggregation its own setting with LOD in the future, # but currently we have no usage for this afaik. aggregations = ChoiceSetting( - default=[ + value=[ 'UnderfloorHeating', 'PipeStrand', 'Consumer', @@ -667,7 +484,7 @@ def __init__(self): ) tolerance_connect_by_position = NumberSetting( - default=10, + value=10, description="Tolerance for distance for which ports should be " "connected. Based on there position in IFC.", for_frontend=True, @@ -675,10 +492,10 @@ def __init__(self): ) verify_connection_by_position = BooleanSetting( + value=True, description="Choose if connection of elements via IfcDistributionPorts" " should be validated by the geometric position of the " - "ports.", - default=True + "ports." ) @@ -690,7 +507,7 @@ def __init__(self): Material} layers_and_materials = ChoiceSetting( - default=LOD.low, + value=LOD.low, choices={ LOD.low: 'Override materials with predefined setups', # LOD.full: 'Get all information from IFC and enrich if needed' @@ -700,7 +517,7 @@ def __init__(self): for_frontend=True ) year_of_construction_overwrite = NumberSetting( - default=None, + value=None, min_value=0, max_value=2015, description="Force an overwrite of the year of construction as a " @@ -708,7 +525,7 @@ def __init__(self): for_frontend=True, ) construction_class_walls = ChoiceSetting( - default='iwu_heavy', + value='iwu_heavy', choices={ 'iwu_heavy': 'Wall structures according to iwu heavy standard', 'iwu_light': 'Wall structures according to iwu light standard', @@ -828,8 +645,9 @@ def __init__(self): "kfw_* the year of construction is required.", for_frontend=True ) + construction_class_windows = ChoiceSetting( - default='Alu- oder Stahlfenster, Waermeschutzverglasung, zweifach', + value='Alu- oder Stahlfenster, Waermeschutzverglasung, zweifach', choices={ 'Holzfenster, zweifach': 'Zeifachverglasung mit Holzfenstern', @@ -938,7 +756,7 @@ def __init__(self): " the windows of the selected building.", ) construction_class_doors = ChoiceSetting( - default='iwu_typical', + value='iwu_typical', choices={ 'iwu_typical': 'Typical door data based', 'kfw_40': 'Doors according to kfw 40 standard', @@ -986,7 +804,7 @@ def __init__(self): " the windows of the selected building.", ) heating_tz_overwrite = BooleanSetting( - default=None, + value=None, description='If True, all thermal zones will be provided with heating,' 'if False no heating for thermal zones is provided, ' 'regardless of information in the IFC or in the use ' @@ -994,7 +812,7 @@ def __init__(self): for_frontend=True ) cooling_tz_overwrite = BooleanSetting( - default=None, + value=None, description='If True, all thermal zones will be provided with cooling,' 'if False no cooling for thermal zones is provided, ' 'regardless of information in the IFC or in the use ' @@ -1002,7 +820,7 @@ def __init__(self): for_frontend=True ) ahu_tz_overwrite = BooleanSetting( - default=None, + value=None, description='If True, all thermal zones will be provided with AHU,' 'if False no AHU for thermal zones is provided, ' 'regardless of information in the IFC or in the use ' @@ -1010,7 +828,7 @@ def __init__(self): for_frontend=True ) prj_use_conditions = PathSetting( - default=None, + value=None, description="Path to a custom UseConditions.json for the specific " "project, that holds custom usage conditions for this " "project. If this is used, this use_conditions file have " @@ -1019,7 +837,7 @@ def __init__(self): for_frontend=True ) prj_custom_usages = PathSetting( - default=None, + value=None, description="Path to a custom customUsages.json for the specific " "project, that holds mappings between space names from " "IFC " @@ -1027,7 +845,7 @@ def __init__(self): for_frontend=True ) setpoints_from_template = BooleanSetting( - default=False, + value=False, description="Use template heating and cooling profiles instead of " "setpoints from IFC. Defaults to False, i.e., " "use original data source. Set to True, " @@ -1035,14 +853,14 @@ def __init__(self): for_frontend=True ) use_maintained_illuminance = BooleanSetting( - default=True, + value=True, description="Use maintained illuminance required per zone based on " "DIN V EN 18599 information to calculate internal loads" "through lighting.", for_frontend=True ) sim_results = ChoiceSetting( - default=[ + value=[ "heat_demand_total", "cool_demand_total", "heat_demand_rooms", "cool_demand_rooms", "heat_energy_total", "cool_energy_total", @@ -1097,53 +915,53 @@ def __init__(self): multiple_choice=True, ) add_space_boundaries = BooleanSetting( - default=True, + value=True, description='Add space boundaries. Only required for building ' 'performance simulation and co-simulations.', for_frontend=True ) correct_space_boundaries = BooleanSetting( - default=False, + value=False, description='Apply geometric correction to space boundaries.', for_frontend=True ) split_bounds = BooleanSetting( - default=False, + value=False, description='Whether to convert up non-convex space boundaries or ' 'not.', for_frontend=True ) add_shadings = BooleanSetting( - default=False, + value=False, description='Whether to add shading surfaces if available or not.', for_frontend=True ) split_shadings = BooleanSetting( - default=False, + value=False, description='Whether to convert up non-convex shading boundaries or ' 'not.', for_frontend=True ) close_space_boundary_gaps = BooleanSetting( - default=False, + value=False, description='Close gaps in the set of space boundaries by adding ' 'additional 2b space boundaries.', for_frontend=True ) create_plots = BooleanSetting( - default=False, + value=False, description='Create plots for simulation results after the simulation ' 'finished.', for_frontend=True ) set_run_period = BooleanSetting( - default=False, + value=False, description="Choose whether run period for simulation execution " "should be set manually instead of running annual " "simulation." ) run_period_start_month = NumberSetting( - default=1, + value=1, min_value=1, max_value=12, description="Choose start month of run period. Requires " @@ -1151,7 +969,7 @@ def __init__(self): for_frontend=True ) run_period_start_day = NumberSetting( - default=1, + value=1, min_value=1, max_value=31, description="Choose start day of run period. Requires " @@ -1159,7 +977,7 @@ def __init__(self): for_frontend=True ) run_period_end_month = NumberSetting( - default=12, + value=12, min_value=1, max_value=12, description="Choose end month of run period. Requires " @@ -1167,7 +985,7 @@ def __init__(self): for_frontend=True ) run_period_end_day = NumberSetting( - default=31, + value=31, min_value=1, max_value=31, description="Choose end day of run period. Requires " @@ -1175,47 +993,47 @@ def __init__(self): for_frontend=True ) plot_singe_zone_guid = ChoiceSetting( - default='', + value='', choices={'': "Skip"}, description="Choose the GlobalId of the IfcSpace for which results " "should be plotted.", any_string=True ) ahu_heating_overwrite = BooleanSetting( - default=None, + value=None, description="Choose if the central AHU should provide heating. " ) ahu_cooling_overwrite = BooleanSetting( - default=None, + value=None, description="Choose if the central AHU should provide cooling." ) ahu_dehumidification_overwrite = BooleanSetting( - default=None, + value=None, description="Choose if the central AHU should provide " "dehumidification." ) ahu_humidification_overwrite = BooleanSetting( - default=None, + value=None, description="Choose if the central AHU should provide humidification." "otherwise this has no effect. " ) ahu_heat_recovery_overwrite = BooleanSetting( - default=None, + value=None, description="Choose if the central AHU should zuse heat recovery." ) ahu_heat_recovery_efficiency_overwrite = NumberSetting( - default=None, + value=None, min_value=0.5, max_value=0.99, description="Choose the heat recovery efficiency of the central AHU." ) use_constant_infiltration_overwrite = BooleanSetting( - default=None, + value=None, description="If only constant base infiltration should be used and no " "dynamic ventilation through e.g. windows." ) base_infiltration_rate_overwrite = NumberSetting( - default=None, + value=None, min_value=0.001, max_value=5, description="Overwrite base value for the natural infiltration in 1/h " diff --git a/pyproject.toml b/pyproject.toml index 7ff4f4113a..54de65aaf5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,6 +37,7 @@ classifiers = [ "Programming Language :: Python :: 3.11", ] dependencies = [ + "pydantic==2.11.7", "ifcopenshell==0.7.0.240627", "docopt==0.6.2", "numpy==1.26.0", diff --git a/test/unit/test_sim_settings.py b/test/unit/test_sim_settings.py index 32991380f0..8c223a4202 100644 --- a/test/unit/test_sim_settings.py +++ b/test/unit/test_sim_settings.py @@ -15,7 +15,7 @@ def __init__(self): super().__init__( ) new_setting_lod = sim_settings.ChoiceSetting( - default=LOD.low, + value=LOD.low, choices={ LOD.low: 'not so detailed setting', LOD.full: 'awesome detailed setting' @@ -24,12 +24,12 @@ def __init__(self): for_frontend=True ) new_setting_bool = sim_settings.BooleanSetting( - default=False, + value=False, description='A new sim_settings bool setting to be created.', for_frontend=True ) new_setting_str = sim_settings.ChoiceSetting( - default='Perfect', + value='Perfect', choices={ 'Perfect': 'A perfect setting', 'Awesome': 'An awesome setting' @@ -38,7 +38,7 @@ def __init__(self): for_frontend=True ) new_setting_list = sim_settings.ChoiceSetting( - default=[ + value=[ 'a', 'b', 'c'], choices={ 'a': 'option a', @@ -50,7 +50,7 @@ def __init__(self): for_frontend=True ) new_setting_path = sim_settings.PathSetting( - default=Path(__file__), + value=Path(__file__), description='Setting to get a path.' ) @@ -140,7 +140,7 @@ def __init__(self): super().__init__() guid_list = sim_settings.GuidListSetting( - default=['0rB_VAJfDAowPYhJGd9wjZ', '3V8lRtj8n5AxfePCnKtF31'], + value=['0rB_VAJfDAowPYhJGd9wjZ', '3V8lRtj8n5AxfePCnKtF31'], description='Test GUID list setting', for_frontend=True ) @@ -163,7 +163,9 @@ def __init__(self): test_settings.guid_list, ['0rB_VAJfDAowPYhJGd9wjZ', '3V8lRtj8n5AxfePCnKtF31'] ) - self.assertEqual(test_settings.empty_guid_list, []) + dings = test_settings.empty_guid_list + + self.assertEqual(test_settings.empty_guid_list, None) # Test valid GUID updates valid_guids = ['1HBLcH3L5AgRg8QXUjHQ2T', '2MBmFkSRv59wfBR9XN_dIe'] diff --git a/test/unit/utilities/test_common_functions.py b/test/unit/utilities/test_common_functions.py index 20947c7519..bb595a2e0e 100644 --- a/test/unit/utilities/test_common_functions.py +++ b/test/unit/utilities/test_common_functions.py @@ -232,11 +232,11 @@ def test_construction_class_doors_choices(self): def test_default_values(self): """Test that default values are set and valid""" walls_default = self.settings_manager[ - 'construction_class_walls'].default + 'construction_class_walls'].value windows_default = self.settings_manager[ - 'construction_class_windows'].default + 'construction_class_windows'].value doors_default = self.settings_manager[ - 'construction_class_doors'].default + 'construction_class_doors'].value self.assertEqual(walls_default, 'iwu_heavy') self.assertEqual(windows_default,