Source code for dragonfly_trace.exp

"""Methods to generate EXP templates for Trane TRACE."""
from honeybee.typing import readable_short_name
from ladybug.datatype.area import Area
from ladybug.datatype.conductivity import Conductivity
from ladybug.datatype.density import Density
from ladybug.datatype.distance import Distance
from ladybug.datatype.power import Power
from ladybug.datatype.energyflux import EnergyFlux
from ladybug.datatype.volumeflowrate import VolumeFlowRate
from ladybug.datatype.temperature import Temperature
from ladybug.datatype.specificheatcapacity import SpecificHeatCapacity
from ladybug.datatype.rvalue import RValue
from ladybug.datatype.uvalue import UValue
from honeybee_energy.material.opaque import EnergyMaterialNoMass


area_dt = Area()
conductivity_dt = Conductivity()
density_dt = Density()
distance_dt = Distance()
power_dt = Power()
flux_dt = EnergyFlux()
flow_dt = VolumeFlowRate()
temp_dt = Temperature()
specific_heat_capacity_dt = SpecificHeatCapacity()
rvalue_dt = RValue()
uvalue_dt = UValue()


[docs] def people_to_trace700_people(people, si_units=False): """Get a TRACE 700 "T.LOAD_PEOPLE" entry string from a Honeybee People object. Args: people: A Honeybee People object. si_units: Boolean to note whether the units of the values in the resulting matrix are in SI (True) instead of IP (False). (Default: False). Returns: A text string for a TRACE 700 T.LOAD_PEOPLE library entry. """ description = readable_short_name(people.display_name, max_length=40) radiant_pct = people.radiant_fraction * 100.0 area_unit = 'm2' if si_units else 'ft2' power_unit = 'W' if si_units else 'Btu/h' amount_unit_code = 2 if si_units else 1 # 1 = sq ft/person, 2 = sq m/person load_unit_code = 8 if si_units else 7 # 7 = Btu/h, 8 = W people_amount = area_dt.to_unit([people.area_per_person], area_unit, 'm2')[0] sensible_ppl = power_dt.to_unit([people.activity_max_sensible], power_unit, 'W')[0] latent_ppl = power_dt.to_unit([people.activity_max_latent], power_unit, 'W')[0] # People; # Description; # 1; # People amount; # People amount unit; # Sensible load; # Sensible load unit; # Latent load; # Latent load unit; # Longwave radiant %; exp_line = ( f'People;{description};1;{people_amount:.6f};{amount_unit_code};' f'{sensible_ppl:.6f};{load_unit_code};{latent_ppl:.6f};{load_unit_code};{radiant_pct:.1f};' ) return exp_line
[docs] def lighting_to_trace700_lighting(lighting, fixture_type='SUSFLUOR'): """Get a TRACE 700 "T.LOAD_LIGHTS" entry string from a Honeybee Lighting object. Args: lighting: A Honeybee Lighting object. fixture_type: Optional text string for the lighting fixture type. (Default: 'SUSFLUOR'). Returns: A text string for a TRACE 700 T.LOAD_LIGHTS library entry. """ description = lighting.display_name plenum_pct = lighting.return_air_fraction * 100.0 radiant_pct = lighting.radiant_fraction * 100.0 visible_pct = lighting.visible_fraction * 100.0 # Lighting; # Description; # 1; # Fixture type; # Plenum %; # Ballast factor; # Longwave radiant %; # Shortwave radiant %; exp_line = ( f'Lighting;{description};1;{fixture_type};{plenum_pct:.1f};1;' f'{radiant_pct:.1f};{visible_pct:.1f};' ) return exp_line
[docs] def equipment_to_trace700_miscellaneous(equipment, si_units=False, watts_per_area=None): """Get a TRACE 700 "T.LOAD_MISEQUIP" entry string from a Honeybee Equipment object. Args: equipment: A Honeybee ElectricEquipment or GasEquipment object. si_units: Boolean to note whether the units of the values in the resulting matrix are in SI (True) instead of IP (False). (Default: False). watts_per_area: Optional float for total W/m2 override to represent a combined electric + gas equipment load density. If None, equipment.watts_per_area will be used. (Default: None). Returns: A text string for a TRACE 700 T.LOAD_MISEQUIP library entry. """ description = readable_short_name(equipment.display_name, max_length=40) sensible_pct = (1.0 - equipment.latent_fraction) * 100.0 radiant_pct = equipment.radiant_fraction * 100.0 lost_pct = equipment.lost_fraction * 100.0 room_pct = 100.0 - lost_pct plenum_pct = 0.0 meter_code = 2 if 'Gas' in equipment.__class__.__name__ else 1 air_path = 2 if lost_pct > 0 else 1 flux_unit = 'W/m2' if si_units else 'W/ft2' misc_unit_code = 9 if si_units else 8 # 8 = W/sq ft, 9 = W/sq m lpd_val = watts_per_area if watts_per_area is not None else equipment.watts_per_area energy_value = flux_dt.to_unit([lpd_val], flux_unit, 'W/m2')[0] # Miscellaneous; # Description; # 1; # Energy consumption; # Energy consumption unit; # Sensible %; # Room %; # Plenum %; # Radiant %; # Energy meter; # Air path; exp_line = ( f'Miscellaneous;{description};1;{energy_value:.6f};{misc_unit_code};{sensible_pct:.1f};' f'{room_pct:.1f};{plenum_pct:.1f};{radiant_pct:.1f};{meter_code};{air_path};' ) return exp_line
[docs] def program_to_trace700_internal_load_template(program, si_units=False): """Get a TRACE 700 "T.InternalLoadTemplate" entry string from a Honeybee ProgramType. Args: program: A Honeybee ProgramType object. si_units: Boolean to note whether the units of the values in the resulting matrix are in SI (True) instead of IP (False). (Default: False). Returns: A text string for a TRACE 700 T.InternalLoadTemplate entry. """ template_name = program.display_name area_unit = 'm2' if si_units else 'ft2' power_unit = 'W' if si_units else 'Btu/h' flux_unit = 'W/m2' if si_units else 'W/ft2' ppl_amount_unit = 2 if si_units else 1 ppl_load_unit = 8 if si_units else 7 ltg_unit = 4 if si_units else 3 misc_unit = 9 if si_units else 8 # Fields 2-9 (People) ppl = program.people if ppl is not None: ppl_type = readable_short_name(ppl.display_name, max_length=40) ppl_amount = area_dt.to_unit([ppl.area_per_person], area_unit, 'm2')[0] sensible_watts = power_dt.to_unit([ppl.activity_max_sensible], power_unit, 'W')[0] latent_watts = power_dt.to_unit([ppl.activity_max_latent], power_unit, 'W')[0] else: ppl_type = 'None' ppl_amount = 0 # default sensible and latent loads: 250 Btu/h (73.26775 W) # unit is hardcoded to IP since the sensible and latent values are converted # to Btu/h when importing the template sensible_watts = 250 latent_watts = 250 ppl_load_unit = 7 # Fields 10-13 (Lighting) ltg = program.lighting if ltg is not None: ltg_type = ltg.display_name ltg_lpd = flux_dt.to_unit([ltg.watts_per_area], flux_unit, 'W/m2')[0] if ltg else 0.0 else: ltg_type = 'Fluorescent, hung below ceiling, 100% load to space' ltg_lpd = 0.0 # Fields 14-18 (Miscellaneous Equipment) elec_eq = program.electric_equipment gas_eq = program.gas_equipment if elec_eq is not None or gas_eq is not None: elec_lpd = elec_eq.watts_per_area if elec_eq is not None else 0.0 gas_lpd = gas_eq.watts_per_area if gas_eq is not None else 0.0 combined_lpd = elec_lpd + gas_lpd if combined_lpd > 0: misc_energy = flux_dt.to_unit([combined_lpd], flux_unit, 'W/m2')[0] eq_obj = elec_eq if elec_lpd >= gas_lpd else gas_eq energy_meter = 1 if elec_lpd >= gas_lpd else 2 misc_type = readable_short_name(eq_obj.display_name, max_length=40) else: misc_type = 'None' misc_energy = 0 energy_meter = 0 else: misc_type = 'None' misc_energy = 0 energy_meter = 0 field_19, field_20 = 1, 2 # workstation, workstation unit exp_line = ( f"{template_name};{ppl_type};Cooling Only (Design);{ppl_amount:.6f};{ppl_amount_unit};" f"{sensible_watts:.4f};{ppl_load_unit};{latent_watts:.4f};{ppl_load_unit};" f"{ltg_type};Cooling Only (Design);{ltg_lpd:.6f};{ltg_unit};" f"{misc_type};Cooling Only (Design);{misc_energy:.6f};{misc_unit};" f"{energy_meter};{field_19};{field_20};" ) return exp_line
[docs] def program_to_trace700_airflow_template( program, si_units=False, ventilation_method='Sum of Outdoor Air' ): """Get a TRACE 700 "T.AirflowTemplate" entry string from a Honeybee ProgramType. Args: program: A Honeybee ProgramType object. si_units: Boolean to note whether the units of the values in the resulting matrix are in SI (True) instead of IP (False). (Default: False). ventilation_method: Optional text for the ventilation method to be used in the airflow template. Choose from the following. * Sum of Outdoor Air * ASHRAE 62.1 Returns: A text string for a TRACE 700 T.AirflowTemplate entry. """ blank_val = '9999.99' template_name = program.display_name # Fields 4-7 (Ventilation Layout) vent = program.ventilation vent_flow_unit = 'L/s' if si_units else 'cfm' vent_unit = 6 if si_units else 3 # 6 = L/s/sq m, 3 = cfm/sq ft ppl_unit = 7 if si_units else 2 # 7 = L/s/person m, 2 = cfm/person vent_val, ppl, ez_cool, ez_heat = 0, 0, 100, 100 if vent is not None and ventilation_method == 'ASHRAE 62.1': vent_val = flow_dt.to_unit([vent.flow_per_area], vent_flow_unit, 'm3/s')[0] vent_val = vent_val if si_units else vent_val * 10.7639104 # convert to cfm/ft2 ppl = flow_dt.to_unit([vent.flow_per_person], vent_flow_unit, 'm3/s')[0] ez_cool = int(vent.effectiveness_cooling * 100) ez_cool = 100 if ez_cool > 100 else ez_cool ez_heat = int(vent.effectiveness_heating * 100) ez_heat = 100 if ez_heat > 100 else ez_heat # Fields 8-13 (Infiltration Layout) inf = program.infiltration inf_unit = 5 if si_units else 3 # 5 = L/s/sq m, 3 = cfm/sq ft inf_val = 0 if inf is not None: inf_val = inf.flow_per_exterior_area_si if si_units else inf.flow_per_exterior_area_ip if ventilation_method == 'ASHRAE 62.1': exp_line = ( f"{template_name};Default Std62;Available (100%);{ppl:.6f};{ppl_unit};{vent_val:.6f};{vent_unit};" f"None;Available (100%);{inf_val:.6f};{inf_unit};{inf_val:.6f};{inf_unit};" f"{blank_val};0;Available (100%);{blank_val};8;{blank_val};9;{blank_val};10;{blank_val};10;0;2;" f"Available (100%);1;0;{ez_cool};0;{ez_heat};1;{blank_val};{blank_val};0;0;{blank_val};0;" ) else: exp_line = ( f"{template_name};None;Available (100%);{vent_val:.6f};{vent_unit};{vent_val:.6f};{vent_unit};" f"None;Available (100%);{inf_val:.6f};{inf_unit};{inf_val:.6f};{inf_unit};" f"{blank_val};0;Available (100%);{blank_val};8;{blank_val};9;{blank_val};10;{blank_val};10;0;2;" f"Available (100%);0;0;{blank_val};0;{blank_val};1;{blank_val};{blank_val};0;0;{blank_val};0;" ) return exp_line
[docs] def program_to_trace700_thermostat_template(program, si_units=False): """Get a TRACE 700 "T.ThermostatTemplate" entry string from a Honeybee ProgramType. Args: program: A Honeybee ProgramType object. si_units: Boolean to note whether the units of the values in the resulting matrix are in SI (True) instead of IP (False). (Default: False). Returns: A text string for a TRACE 700 T.ThermostatTemplate entry. """ set_pt = program.setpoint template_name = program.display_name temp_unit_label = 'C' if si_units else 'F' temp_unit_code = 0 if si_units else 1 # 0 = Celsius, 1 = Fahrenheit if set_pt is not None: clg_db = temp_dt.to_unit([set_pt.cooling_setpoint], temp_unit_label, 'C')[0] htg_db = temp_dt.to_unit([set_pt.heating_setpoint], temp_unit_label, 'C')[0] clg_drift = temp_dt.to_unit([set_pt.cooling_setback], temp_unit_label, 'C')[0] htg_drift = temp_dt.to_unit([set_pt.heating_setback], temp_unit_label, 'C')[0] rh_val = set_pt.dehumidifying_setpoint if set_pt.dehumidifying_setpoint is not None else 50.0 else: clg_db = temp_dt.to_unit([75.0], temp_unit_label, 'F')[0] htg_db = temp_dt.to_unit([68.0], temp_unit_label, 'F')[0] clg_drift = temp_dt.to_unit([90.0], temp_unit_label, 'F')[0] htg_drift = temp_dt.to_unit([55.0], temp_unit_label, 'F')[0] rh_val = 50.0 control_fields = '1;0;2;1;' exp_line = ( f"{template_name};{clg_db:.1f};{temp_unit_code};{htg_db:.1f};{temp_unit_code};" f"{rh_val:.0f};{clg_drift:.1f};None;{htg_drift:.1f};None;" f"{control_fields}" ) return exp_line
[docs] def program_to_trace700_room_template(program): """Get a TRACE 700 "T.RoomTemplate" entry string from a Honeybee ProgramType. Args: program: A Honeybee ProgramType object. Returns: A text string for a TRACE 700 T.RoomTemplate entry. """ template_name = program.display_name construction_template = 'Default' exp_line = f'{template_name};{template_name};{template_name};{template_name};{construction_template};' return exp_line
[docs] def internal_loads_to_exp(program, si_units=False): """Get an EXP string containing internal load library entries and templates for a ProgramType. Args: program: A Honeybee ProgramType object. si_units: Boolean to note whether the units of the values in the resulting matrix are in SI (True) instead of IP (False). (Default: False). Returns: Text string of an EXP file for TRACE 700 containing internal load definitions. """ newline = '\n' standalone_blocks = ['EDITORSv6.3.1'] if program.people: standalone_blocks.extend([ 'T.LOAD_PEOPLE', people_to_trace700_people(program.people, si_units) ]) if program.lighting: standalone_blocks.extend([ 'T.LOAD_LIGHTS', lighting_to_trace700_lighting(program.lighting) ]) elec_eq = program.electric_equipment gas_eq = program.gas_equipment if elec_eq is not None or gas_eq is not None: elec_lpd = elec_eq.watts_per_area if elec_eq is not None else 0.0 gas_lpd = gas_eq.watts_per_area if gas_eq is not None else 0.0 combined_lpd = elec_lpd + gas_lpd if elec_lpd + gas_lpd > 0: eq_obj = elec_eq if elec_lpd >= gas_lpd else gas_eq standalone_blocks.extend([ 'T.LOAD_MISEQUIP', equipment_to_trace700_miscellaneous(eq_obj, si_units, watts_per_area=combined_lpd) ]) internal_load_template = program_to_trace700_internal_load_template(program, si_units) standalone_blocks.extend([ 'T.InternalLoadTemplate', internal_load_template ]) file_data = newline.join(standalone_blocks) + newline return file_data
[docs] def airflow_to_exp(program, si_units=False, ventilation_method='Sum of Outdoor Air'): """Get an EXP string containing only the airflow template for a ProgramType. Args: program: A Honeybee ProgramType object. si_units: Boolean to note whether the units of the values in the resulting matrix are in SI (True) instead of IP (False). (Default: False). ventilation_method: Optional text for the ventilation method to be used in the airflow template. Choose from the following. * Sum of Outdoor Air * ASHRAE 62.1 Returns: Text string of an EXP file for TRACE 700 containing the airflow template. """ newline = '\n' airflow_template = program_to_trace700_airflow_template( program, si_units, ventilation_method ) standalone_blocks = [ 'EDITORSv6.3.1', 'T.AirflowTemplate', airflow_template ] file_data = newline.join([b for b in standalone_blocks if b != '']) + newline return file_data
[docs] def thermostat_to_exp(program, si_units=False): """Get an EXP string containing only the thermostat template for a ProgramType. Args: program: A Honeybee ProgramType object. si_units: Boolean to note whether the units of the values in the resulting matrix are in SI (True) instead of IP (False). (Default: False). Returns: Text string of an EXP file for TRACE 700 containing the thermostat template. """ newline = '\n' thermostat_template = program_to_trace700_thermostat_template(program, si_units) standalone_blocks = [ 'EDITORSv6.3.1', 'T.ThermostatTemplate', thermostat_template ] file_data = newline.join([b for b in standalone_blocks if b != '']) + newline return file_data
[docs] def program_to_exp(program, si_units=False, ventilation_method='Sum of Outdoor Air'): """Get a complete EXP string for a Honeybee ProgramType. Args: program: A Honeybee ProgramType object for which the TRACE 700 EXP string will be generated. si_units: Boolean to note whether the units of the values in the resulting matrix are in SI (True) instead of IP (False). (Default: False). ventilation_method: Optional text for the ventilation method to be used in the airflow template. Choose from the following. * Sum of Outdoor Air * ASHRAE 62.1 Returns: Text string of a complete EXP file for TRACE 700. """ newline = '\n' standalone_blocks = ['EDITORSv6.3.1'] if program.people: standalone_blocks.extend([ 'T.LOAD_PEOPLE', people_to_trace700_people(program.people, si_units) ]) if program.lighting: standalone_blocks.extend([ 'T.LOAD_LIGHTS', lighting_to_trace700_lighting(program.lighting) ]) elec_eq = program.electric_equipment gas_eq = program.gas_equipment if elec_eq is not None or gas_eq is not None: elec_lpd = elec_eq.watts_per_area if elec_eq is not None else 0.0 gas_lpd = gas_eq.watts_per_area if gas_eq is not None else 0.0 combined_lpd = elec_lpd + gas_lpd if elec_lpd + gas_lpd > 0: eq_obj = elec_eq if elec_lpd >= gas_lpd else gas_eq standalone_blocks.extend([ 'T.LOAD_MISEQUIP', equipment_to_trace700_miscellaneous(eq_obj, si_units, watts_per_area=combined_lpd) ]) internal_load_template = program_to_trace700_internal_load_template(program, si_units) airflow_template = program_to_trace700_airflow_template(program, si_units, ventilation_method) thermostat_template = program_to_trace700_thermostat_template(program, si_units) room_template = program_to_trace700_room_template(program) standalone_blocks.extend([ 'T.InternalLoadTemplate', internal_load_template, 'T.AirflowTemplate', airflow_template, 'T.ThermostatTemplate', thermostat_template, 'T.RoomTemplate', room_template ]) file_data = newline.join(standalone_blocks) + newline return file_data
[docs] def programs_to_exp(programs, si_units=False, ventilation_method='Sum of Outdoor Air'): """Get a single combined EXP string for a list of Honeybee ProgramTypes. Args: programs: A list of Honeybee ProgramType objects. si_units: Boolean to note whether the units of the values in the resulting matrix are in SI (True) instead of IP (False). (Default: False). ventilation_method: Optional text for the ventilation method to be used in the airflow template. Choose from the following. * Sum of Outdoor Air * ASHRAE 62.1 Returns: Text string of a combined EXP file for TRACE 700. """ newline = '\n' people_templates = [] lighting_templates = [] equip_templates = [] internal_load_templates = [] airflow_templates = [] thermostat_templates = [] room_templates = [] seen_people = set() seen_lighting = set() seen_equip = set() for program in programs: if program.people and program.people.identifier not in seen_people: people_templates.append(people_to_trace700_people(program.people, si_units)) seen_people.add(program.people.identifier) if program.lighting and program.lighting.identifier not in seen_lighting: lighting_templates.append(lighting_to_trace700_lighting(program.lighting)) seen_lighting.add(program.lighting.identifier) elec_eq = program.electric_equipment gas_eq = program.gas_equipment if elec_eq is not None or gas_eq is not None: elec_lpd = elec_eq.watts_per_area if elec_eq is not None else 0.0 gas_lpd = gas_eq.watts_per_area if gas_eq is not None else 0.0 combined_lpd = elec_lpd + gas_lpd if combined_lpd > 0: eq_obj = elec_eq if elec_lpd >= gas_lpd else gas_eq if eq_obj.identifier not in seen_equip: equip_templates.append( equipment_to_trace700_miscellaneous( eq_obj, si_units=si_units, watts_per_area=combined_lpd ) ) seen_equip.add(eq_obj.identifier) internal_load_templates.append( program_to_trace700_internal_load_template(program, si_units) ) airflow_templates.append( program_to_trace700_airflow_template(program, si_units, ventilation_method) ) thermostat_templates.append(program_to_trace700_thermostat_template(program, si_units)) room_templates.append(program_to_trace700_room_template(program)) standalone_blocks = ['EDITORSv6.3.1'] if people_templates: standalone_blocks.append('T.LOAD_PEOPLE') standalone_blocks.extend(people_templates) if lighting_templates: standalone_blocks.append('T.LOAD_LIGHTS') standalone_blocks.extend(lighting_templates) if equip_templates: standalone_blocks.append('T.LOAD_MISEQUIP') standalone_blocks.extend(equip_templates) if internal_load_templates: standalone_blocks.append('T.InternalLoadTemplate') standalone_blocks.extend(internal_load_templates) if airflow_templates: standalone_blocks.append('T.AirflowTemplate') standalone_blocks.extend(airflow_templates) if thermostat_templates: standalone_blocks.append('T.ThermostatTemplate') standalone_blocks.extend(thermostat_templates) if room_templates: standalone_blocks.append('T.RoomTemplate') standalone_blocks.extend(room_templates) file_data = newline.join(standalone_blocks) + newline return file_data
[docs] def energy_material_to_trace700_material(material, si_units=False, id=999): """Converts a Honeybee EnergyMaterial or EnergyMaterialNoMass to a T.MATR_MATERIAL entry.""" description = readable_short_name(material.display_name, max_length=40) material_id = f'M{id}' if not str(id).startswith('M') else str(id) thickness_unit = 'm' if si_units else 'in' thickness_code = 2 if si_units else 1 conductivity_unit = 'W/m-K' if si_units else 'Btu/h-ft-F' conductivity_code = 0 if si_units else 1 density_unit = 'kg/m3' if si_units else 'lb/ft3' density_code = 1 if si_units else 0 specific_heat_unit = 'J/kg-K' if si_units else 'Btu/lb-F' specific_heat_code = 0 if si_units else 1 resistance_unit = 'm2-K/W' if si_units else 'h-ft2-F/Btu' resistance_unit_code = 1 category = 'Undefined Material' if isinstance(material, EnergyMaterialNoMass): resistance = rvalue_dt.to_unit([material.r_value], resistance_unit, 'm2-K/W')[0] thickness = 0 conductivity = 0 density = 0 specific_heat = 0 else: thickness = distance_dt.to_unit([material.thickness], thickness_unit, 'm')[0] conductivity = conductivity_dt.to_unit([material.conductivity], conductivity_unit, 'W/m-K')[0] density = density_dt.to_unit([material.density], density_unit, 'kg/m3')[0] thickness = distance_dt.to_unit([material.thickness], thickness_unit, 'm')[0] specific_heat = specific_heat_capacity_dt.to_unit( [material.specific_heat], specific_heat_unit, 'J/kg-K' )[0] resistance = 0.0 # this is 0 because the other fields are used exp_line = ( f'0;{material_id};{category};0;{description};{thickness};{thickness_code};' f'{conductivity};{conductivity_code};{density};{density_code};' f'{specific_heat};{specific_heat_code};{resistance};{resistance_unit_code};' ) return exp_line
[docs] def opaque_construction_to_trace700_construction( construction, const_type='Wall', si_units=False, starting_material_id=500, material_id_map=None ): """Converts a Honeybee OpaqueConstruction to a TRACE 700 construction entry.""" description = readable_short_name(construction.display_name, max_length=40) film_map = { 'Wall': ('A0', 'E0'), 'Roof': ('A0', 'E0'), 'Floor': ('E0', 'E0'), 'Partition': ('A0', 'E0'), 'Exposed Floor': ('A0', 'E0') } out_film, in_film = film_map.get(const_type, ('A0', 'E0')) layers_ids = ['None'] * 10 layers_ids[0] = out_film material_lines = [] id_counter = starting_material_id for idx, mat in enumerate(construction.materials): if idx >= 8: break mat_key = mat.identifier if material_id_map is not None and mat_key in material_id_map: mat_tag = material_id_map[mat_key] else: mat_tag = f'M{id_counter}' mat_line = energy_material_to_trace700_material(mat, si_units=si_units, id=id_counter) material_lines.append(mat_line) if material_id_map is not None: material_id_map[mat_key] = mat_tag id_counter += 1 layers_ids[idx + 1] = mat_tag layers_ids[len(construction.materials) + 1] = in_film layers_str = ';'.join(layers_ids) b_coeffs = ';'.join([''] * 7) d_coeffs = ';'.join([''] * 7) u_factor_unit = 'W/m2-K' if si_units else 'Btu/h-ft2-F' u_factor = uvalue_dt.to_unit([construction.u_factor], u_factor_unit, 'W/m2-K')[0] u_code = 0 if si_units else 1 hc_si = construction.area_heat_capacity heat_capacity = hc_si if si_units else (hc_si * 0.000048919) hc_code = 0 if si_units else 1 wt_si = sum( m.density * m.thickness for m in construction.materials if hasattr(m, 'density') and hasattr(m, 'thickness') ) if si_units: area_density = wt_si ad_code = 0 else: area_density = wt_si * 0.20481614 ad_code = 1 out_mat = construction.outside_material in_mat = construction.inside_material sol_abs_out = getattr(out_mat, 'solar_absorptance', 0.9) therm_abs_out = getattr(out_mat, 'thermal_absorptance', 0.9) sol_abs_in = getattr(in_mat, 'solar_absorptance', 0.65) therm_abs_in = getattr(in_mat, 'thermal_absorptance', 0.9) vis_refl_in = getattr(in_mat, 'visible_reflectance', 0.20) roughness_map = { 'VeryRough': 0, 'Rough': 1, 'MediumRough': 2, 'MediumSmooth': 3, 'Smooth': 4, 'VerySmooth': 5 } roughness = roughness_map.get(getattr(out_mat, 'roughness', 'Rough'), 1) comment = '' zero_padding_block = ';'.join(['0'] * 10) exp_line = ( f'{const_type};{description};1;0;{layers_str};' f'{b_coeffs};{d_coeffs};' f'0;0;1;' f'{area_density};{ad_code};{heat_capacity};{hc_code};{u_factor};{u_code};' f'0;{comment};{sol_abs_out:.8f};' f'{zero_padding_block};' f'{therm_abs_out:.8f};{sol_abs_in:.8f};{therm_abs_in:.8f};{vis_refl_in:.8f};{roughness};1;' ) return exp_line, material_lines
[docs] def window_construction_to_trace700_glass(construction, si_units=False, is_door=False): """Converts a Honeybee WindowConstruction object to a T.GLAS_GLASS entry.""" description = construction.display_name if is_door and not description.lower().endswith('door'): description = f'{description} Door' description = readable_short_name(description, max_length=40) u_value_unit = 'W/m2-K' if si_units else 'Btu/h-ft2-F' u_value = uvalue_dt.to_unit([construction.u_factor], u_value_unit, 'W/m2-K')[0] u_value_code = 0 if si_units else 1 shgc = getattr(construction, 'shgc', 0.0) vt = getattr(construction, 'visible_transmittance', 0.5) panes = getattr(construction, 'glazing_count', 1) inside_vis_refl = construction.inside_solar_reflectance inside_sol_refl = construction.inside_solar_reflectance solar_transmissivity = getattr(construction, 'solar_transmittance', 0.0) outside_emissivity = construction.outside_emissivity inside_emissivity = construction.inside_emissivity # Field 179: 1 for standard glass, 2 for glass doors glass_type_code = 2 if is_door else 1 # TRACE 700 custom glass data (213 fields) # This is copied from an exported default glass material in TRACE 700 glass_tail = ( "0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;" "0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;" "0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;" "0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;" f"0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;{glass_type_code};0;0;" "0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;57.1500015258789063;" "25.3999996185302734;25.3999996185302734;4.59999990463256836;1.10099995136260986;" ".89999997615814209;.89999997615814209;.89999997615814209;0;0;0;0;0;0;0;0;0;0;0;0;" ) exp_line = ( f'{description};Glass;1;{shgc:.6f};{u_value:.6f};{u_value_code};{panes};0;' f'{vt:.4f};{inside_vis_refl:.4f};{solar_transmissivity:.4f};{inside_sol_refl:.4f};' f'{outside_emissivity:.4f};{inside_emissivity:.4f};{glass_tail}' ) return exp_line
[docs] def construction_set_to_trace700_template(construction_set, si_units=False): """Translates a Honeybee ConstructionSet into a TRACE 700 Construction Template line.""" template_name = readable_short_name(construction_set.display_name, max_length=40) u_code = 0 if si_units else 1 def _get_opaque_info(cnst): if cnst is None: return 'None', 0.0 u_value_unit = 'W/m2-K' if si_units else 'Btu/h-ft2-F' u_value = uvalue_dt.to_unit([cnst.u_value], u_value_unit, 'W/m2-K')[0] return readable_short_name(cnst.display_name, max_length=40), u_value def _get_window_info(cnst, is_door=False): if cnst is None: return 'None', 0.0, 0.0 u_value_unit = 'W/m2-K' if si_units else 'Btu/h-ft2-F' u_value = uvalue_dt.to_unit([cnst.u_value], u_value_unit, 'W/m2-K')[0] desc = cnst.display_name if is_door and not desc.lower().endswith('door'): desc = f'{desc} Door' return readable_short_name(desc, max_length=40), u_value, cnst.shgc def _get_door_info(cnst): if cnst is None: return 'None', 0.0, 0.0 is_glass = hasattr(cnst, 'shgc') if is_glass: return _get_window_info(cnst, is_door=True) else: desc, u_val = _get_opaque_info(cnst) return readable_short_name(desc, max_length=40), u_val, 0.0 roof_cnst = construction_set.roof_ceiling_set.exterior_construction roof_name, roof_u = _get_opaque_info(roof_cnst) floor_cnst = ( construction_set.floor_set.ground_construction or construction_set.floor_set.exterior_construction or construction_set.floor_set.interior_construction ) floor_name, floor_u = _get_opaque_info(floor_cnst) skylight_cnst = construction_set.aperture_set.skylight_construction skylight_name, skylight_u, skylight_shgc = _get_window_info(skylight_cnst) wall_cnst = construction_set.wall_set.exterior_construction wall_name, wall_u = _get_opaque_info(wall_cnst) window_cnst = construction_set.aperture_set.window_construction window_name, window_u, window_shgc = _get_window_info(window_cnst) partition_cnst = construction_set.wall_set.interior_construction partition_name, partition_u = _get_opaque_info(partition_cnst) # resolve door construction priority if getattr(construction_set.door_set, '_exterior_construction', None) is not None: door_cnst = construction_set.door_set.exterior_construction elif getattr(construction_set.door_set, '_exterior_glass_construction', None) is not None: door_cnst = construction_set.door_set.exterior_glass_construction else: door_cnst = ( construction_set.door_set.exterior_construction or construction_set.door_set.exterior_glass_construction ) door_name, door_u, door_shgc = _get_door_info(door_cnst) tail_fields = '0;0;' exp_line = ( f'{template_name};' f'{floor_name};{floor_u:.6f};{u_code};' f'{roof_name};{roof_u:.6f};{u_code};' f'{skylight_name};{skylight_u:.6f};{u_code};{skylight_shgc:.6f};' f'{wall_name};{wall_u:.6f};{u_code};' f'{window_name};{window_u:.6f};{u_code};{window_shgc:.6f};' f'{partition_name};{partition_u:.6f};{u_code};' f'10;0;2;0;10;0;' f'{door_name};{door_u:.6f};{u_code};{door_shgc:.6f};' f'{tail_fields}' ) return exp_line
[docs] def construction_sets_to_exp(construction_sets, si_units=False): """Get a single combined EXP string for a list of Honeybee ConstructionSets.""" newline = '\n' exfloor_templates = [] floor_templates = [] partition_templates = [] roof_templates = [] wall_templates = [] glass_templates = [] material_templates = [] construction_templates = [] seen_constructions = set() seen_materials = set() material_id_map = {} material_counter = 500 def _process_opaque(cnst, category): nonlocal material_counter if cnst is not None and cnst.identifier not in seen_constructions: line, mat_lines = opaque_construction_to_trace700_construction( cnst, const_type=category, si_units=si_units, starting_material_id=material_counter, material_id_map=material_id_map ) material_counter += len(mat_lines) seen_constructions.add(cnst.identifier) for ml in mat_lines: mat_id = ml.split(';')[1] if mat_id not in seen_materials: material_templates.append(ml) seen_materials.add(mat_id) return line return None def _process_glass(cnst, is_door=False): if cnst is not None: key = (cnst.identifier, is_door) if key not in seen_constructions: line = window_construction_to_trace700_glass(cnst, si_units=si_units, is_door=is_door) seen_constructions.add(key) return line return None for c_set in construction_sets: w_ext = _process_opaque(c_set.wall_set.exterior_construction, 'Wall') if w_ext: wall_templates.append(w_ext) w_grd = _process_opaque(c_set.wall_set.ground_construction, 'Wall') if w_grd: wall_templates.append(w_grd) w_int = _process_opaque(c_set.wall_set.interior_construction, 'Partition') if w_int: partition_templates.append(w_int) r_ext = _process_opaque(c_set.roof_ceiling_set.exterior_construction, 'Roof') if r_ext: roof_templates.append(r_ext) r_grd = _process_opaque(c_set.roof_ceiling_set.ground_construction, 'Roof') if r_grd: roof_templates.append(r_grd) r_int = _process_opaque(c_set.roof_ceiling_set.interior_construction, 'Floor') if r_int: floor_templates.append(r_int) f_ext = _process_opaque(c_set.floor_set.exterior_construction, 'Exposed Floor') if f_ext: exfloor_templates.append(f_ext) f_grd = _process_opaque(c_set.floor_set.ground_construction, 'Floor') if f_grd: floor_templates.append(f_grd) f_int = _process_opaque(c_set.floor_set.interior_construction, 'Floor') if f_int: floor_templates.append(f_int) for ap_cnst in ( c_set.aperture_set.window_construction, c_set.aperture_set.interior_construction, c_set.aperture_set.skylight_construction, c_set.aperture_set.operable_construction, ): g_line = _process_glass(ap_cnst, is_door=False) if g_line: glass_templates.append(g_line) d_ovh = _process_glass(c_set.door_set.overhead_construction, is_door=True) if d_ovh: glass_templates.append(d_ovh) d_int = _process_glass(c_set.door_set.interior_construction, is_door=True) if d_int: glass_templates.append(d_int) d_int_gls = _process_glass(c_set.door_set.interior_glass_construction, is_door=True) if d_int_gls: glass_templates.append(d_int_gls) d_ext_gls = _process_glass(c_set.door_set.exterior_glass_construction, is_door=True) if d_ext_gls: glass_templates.append(d_ext_gls) d_ext = _process_glass(c_set.door_set.exterior_construction, is_door=True) if d_ext: glass_templates.append(d_ext) construction_templates.append(construction_set_to_trace700_template(c_set, si_units=si_units)) standalone_blocks = ['EDITORSv6.3.1'] if exfloor_templates: standalone_blocks.append('T.CNST_EXFLOOR') standalone_blocks.extend(exfloor_templates) if floor_templates: standalone_blocks.append('T.CNST_FLOOR') standalone_blocks.extend(floor_templates) if partition_templates: standalone_blocks.append('T.CNST_PART') standalone_blocks.extend(partition_templates) if roof_templates: standalone_blocks.append('T.CNST_ROOF') standalone_blocks.extend(roof_templates) if wall_templates: standalone_blocks.append('T.CNST_WALL') standalone_blocks.extend(wall_templates) if glass_templates: standalone_blocks.append('T.GLAS_GLASS') standalone_blocks.extend(glass_templates) if material_templates: standalone_blocks.append('T.MATR_MATERIAL') standalone_blocks.extend(material_templates) if construction_templates: standalone_blocks.append('T.ConstructionTemplate') standalone_blocks.extend(construction_templates) file_data = newline.join(standalone_blocks) + newline return file_data