# -*- coding: utf-8 -*-
# -*- Python Version: 2.7 -*-
"""Honeybee-PH-HVAC-Equipment: Ventilation (ERV) Devices."""
import sys
from copy import copy
try:
from typing import Any, Dict, List, Optional
except ImportError:
pass # IronPython
try:
from ladybug_geometry.geometry3d.pointvector import Point3D
except ImportError as e:
raise ImportError("Failed to import ladybug_geometry", e)
try:
from honeybee_phhvac import _base, ducting
except ImportError as e:
raise ImportError("\nFailed to import honeybee_phhvac:\n\t{}".format(e))
# -----------------------------------------------------------------------------
[docs]
class UnknownPhExhaustVentTypeError(Exception):
def __init__(self, _device_types, _received_type):
# type: (list[str], str) -> None
self.msg = 'Error: Unknown HBPH-Exhaust Ventilation type? Got: "{}"\
"but only types: {} are allowed?'.format(
_received_type, _device_types
)
super(UnknownPhExhaustVentTypeError, self).__init__(self.msg)
# -----------------------------------------------------------------------------
# -- ERV units
[docs]
class Ventilator(_base._PhHVACBase):
def __init__(self):
super(Ventilator, self).__init__()
self.display_name = "_unnamed_ventilator_" # type: str
self.id_num = 0 # type: int
self.quantity = 1 # type: int
self.sensible_heat_recovery = 0.0 # type:float
self.latent_heat_recovery = 0.0 # type float
self.electric_efficiency = 0.55 # type: float
self.frost_protection_reqd = True # type: bool
self.temperature_below_defrost_used = -5 # type: float
self.in_conditioned_space = True # type: bool
[docs]
def to_dict(self):
# type: () -> dict[str, Any]
d = super(Ventilator, self).to_dict()
d["quantity"] = self.quantity
d["sensible_heat_recovery"] = self.sensible_heat_recovery
d["latent_heat_recovery"] = self.latent_heat_recovery
d["electric_efficiency"] = self.electric_efficiency
d["frost_protection_reqd"] = self.frost_protection_reqd
d["temperature_below_defrost_used"] = self.temperature_below_defrost_used
d["in_conditioned_space"] = self.in_conditioned_space
return d
[docs]
@classmethod
def from_dict(cls, _input_dict):
# type: (dict[str, Any]) -> Ventilator
obj = cls()
obj.display_name = _input_dict["display_name"]
obj.identifier = _input_dict["identifier"]
obj.user_data = _input_dict.get("user_data", {})
obj.quantity = _input_dict["quantity"]
obj.sensible_heat_recovery = _input_dict["sensible_heat_recovery"]
obj.latent_heat_recovery = _input_dict["latent_heat_recovery"]
obj.electric_efficiency = _input_dict["electric_efficiency"]
obj.frost_protection_reqd = _input_dict["frost_protection_reqd"]
obj.temperature_below_defrost_used = _input_dict["temperature_below_defrost_used"]
obj.in_conditioned_space = _input_dict["in_conditioned_space"]
return obj
[docs]
def duplicate(self):
# type: () -> Ventilator
new_obj = Ventilator()
new_obj.display_name = self.display_name
new_obj.identifier = self.identifier
new_obj.user_data = copy(self.user_data)
new_obj.id_num = self.id_num
new_obj.quantity = self.quantity
new_obj.sensible_heat_recovery = self.sensible_heat_recovery
new_obj.latent_heat_recovery = self.latent_heat_recovery
new_obj.electric_efficiency = self.electric_efficiency
new_obj.frost_protection_reqd = self.frost_protection_reqd
new_obj.temperature_below_defrost_used = self.temperature_below_defrost_used
new_obj.in_conditioned_space = self.in_conditioned_space
return new_obj
def __lt__(self, other):
# type: (PhVentilationSystem) -> bool
return self.identifier < other.identifier
def __copy__(self):
# type: () -> Ventilator
return self.duplicate()
def __repr__(self):
return "{}(display_name={!r}, sensible_heat_recovery={:0.2f})".format(
self.__class__.__name__, self.display_name, self.sensible_heat_recovery
)
[docs]
def ToString(self):
return self.__repr__()
[docs]
class PhVentilationSystem(_base._PhHVACBase):
"""Passive House Fresh-Air Ventilation System."""
def __init__(self):
# type: () -> None
super(PhVentilationSystem, self).__init__()
self.display_name = "_unnamed_ph_vent_system_"
self.sys_type = 1 # '1-Balanced PH ventilation with HR'
self.supply_ducting = [] # type: List[ducting.PhDuctElement]
self.exhaust_ducting = [] # type: List[ducting.PhDuctElement]
self._ventilation_unit = None # type: Optional[Ventilator]
self.id_num = 0
@property
def ventilation_unit(self):
# type: () -> Optional[Ventilator]
return self._ventilation_unit
@ventilation_unit.setter
def ventilation_unit(self, _in):
# type: (Optional[Ventilator]) -> None
self._ventilation_unit = _in
if not self._ventilation_unit:
return None
if self._ventilation_unit.display_name == "_unnamed_ventilator_":
self._ventilation_unit.display_name = self.display_name
return None
[docs]
def add_supply_duct_element(self, _duct_element):
# type: (ducting.PhDuctElement) -> None
"""Add a supply-air duct element to the ventilation system."""
self.supply_ducting.append(_duct_element)
[docs]
def add_exhaust_duct_element(self, _duct_element):
# type: (ducting.PhDuctElement) -> None
"""Add an exhaust-air duct element to the ventilation system."""
self.exhaust_ducting.append(_duct_element)
@property
def supply_ducting_total_length(self):
# type: () -> float
"""Return the total length of all supply-air ducting in model-units."""
return sum(duct.length for duct in self.supply_ducting)
@property
def exhaust_ducting_total_length(self):
# type: () -> float
"""Return the total length of all exhaust-air ducting in model-units."""
return sum(duct.length for duct in self.exhaust_ducting)
@property
def supply_ducting_size_description(self):
# type: () -> Optional[str]
"""Return the size of the supply-air ducting."""
descriptions = {s.shape_type_description for s in self.supply_ducting}
if len(descriptions) == 0:
return None
elif len(descriptions) == 1:
return descriptions.pop()
else:
raise ValueError("Mixed shape-types in supply-air duct segments.")
@property
def exhaust_ducting_size_description(self):
# type: () -> Optional[str]
"""Return the size of the exhaust-air ducting."""
descriptions = {s.shape_type_description for s in self.exhaust_ducting}
if len(descriptions) == 0:
return None
elif len(descriptions) == 1:
return descriptions.pop()
else:
raise ValueError("Mixed shape-types in exhaust-air duct segments.")
[docs]
def to_dict(self):
# type: () -> dict[str, Any]
d = super(PhVentilationSystem, self).to_dict()
d["sys_type"] = self.sys_type
d["exhaust_ducting"] = [e_duct.to_dict() for e_duct in self.exhaust_ducting]
d["supply_ducting"] = [s_duct.to_dict() for s_duct in self.supply_ducting]
d["id_num"] = self.id_num
if self.ventilation_unit:
d["ventilation_unit"] = self.ventilation_unit.to_dict()
return d
[docs]
@classmethod
def from_dict(cls, _input_dict):
# type: (dict[str, Any]) -> PhVentilationSystem
obj = cls()
obj.identifier = _input_dict["identifier"]
obj.display_name = _input_dict["display_name"]
obj.user_data = _input_dict.get("user_data", {})
obj.sys_type = _input_dict["sys_type"]
obj.supply_ducting = [ducting.PhDuctElement.from_dict(s_duct) for s_duct in _input_dict["supply_ducting"]]
obj.exhaust_ducting = [ducting.PhDuctElement.from_dict(e_duct) for e_duct in _input_dict["exhaust_ducting"]]
obj.id_num = _input_dict.get("id_num", 0)
vent_unit_dict = _input_dict.get("ventilation_unit", None)
if vent_unit_dict:
obj.ventilation_unit = Ventilator.from_dict(vent_unit_dict)
return obj
[docs]
def duplicate(self):
# type: () -> PhVentilationSystem
new_obj = self.__class__()
new_obj.display_name = self.display_name
new_obj.identifier = self.identifier
new_obj.user_data = copy(self.user_data)
new_obj.sys_type = self.sys_type
new_obj.supply_ducting = [s_duct.duplicate() for s_duct in self.supply_ducting]
new_obj.exhaust_ducting = [e_duct.duplicate() for e_duct in self.exhaust_ducting]
new_obj.id_num = self.id_num
if self.ventilation_unit:
new_obj._ventilation_unit = self.ventilation_unit.duplicate()
return new_obj
def __copy__(self):
# type: () -> PhVentilationSystem
return self.duplicate()
def __lt__(self, other):
# type: (PhVentilationSystem) -> bool
return self.identifier < other.identifier
def __repr__(self):
return "{}(display_name={!r}, sys_type={!r})".format(self.__class__.__name__, self.display_name, self.sys_type)
[docs]
def ToString(self):
return self.__repr__()
[docs]
def move(self, moving_vec3D):
"""Move the System's ducts along a vector.
Args:
moving_vec3D: A Vector3D with the direction and distance to move the ray.
"""
new_system = self.duplicate()
new_system.identifier = self.identifier
new_system.supply_ducting = [duct_element.move(moving_vec3D) for duct_element in self.supply_ducting]
new_system.exhaust_ducting = [duct_element.move(moving_vec3D) for duct_element in self.exhaust_ducting]
return new_system
[docs]
def rotate(self, axis_vec3D, angle_degrees, origin_pt3D):
"""Rotate the System's ducts by a certain angle around an axis and origin.
Right hand rule applies:
If axis has a positive orientation, rotation will be clockwise.
If axis has a negative orientation, rotation will be counterclockwise.
Args:
axis_vec3D: A Vector3D axis representing the axis of rotation.
angle_degrees: An angle for rotation in degrees.
origin_pt3D: A Point3D for the origin around which the object will be rotated.
"""
new_system = self.duplicate()
new_system.identifier = self.identifier
new_system.supply_ducting = [
duct_element.rotate(axis_vec3D, angle_degrees, origin_pt3D) for duct_element in self.supply_ducting
]
new_system.exhaust_ducting = [
duct_element.rotate(axis_vec3D, angle_degrees, origin_pt3D) for duct_element in self.exhaust_ducting
]
return new_system
[docs]
def rotate_xy(self, angle_degrees, origin_pt3D):
"""Rotate the System's ducts counterclockwise in the XY plane by a certain angle.
Args:
angle_degrees: An angle in degrees.
origin_pt3D: A Point3D for the origin around which the object will be rotated.
"""
new_system = self.duplicate()
new_system.identifier = self.identifier
new_system.supply_ducting = [
duct_element.rotate_xy(angle_degrees, origin_pt3D) for duct_element in self.supply_ducting
]
new_system.exhaust_ducting = [
duct_element.rotate_xy(angle_degrees, origin_pt3D) for duct_element in self.exhaust_ducting
]
return new_system
[docs]
def reflect(self, normal_vec3D, origin_pt3D):
"""Reflected the System's ducts across a plane with the input normal vector and origin.
Args:
normal_vec3D: A Vector3D representing the normal vector for the plane across
which the line segment will be reflected. THIS VECTOR MUST BE NORMALIZED.
origin_pt3D: A Point3D representing the origin from which to reflect.
"""
new_system = self.duplicate()
new_system.identifier = self.identifier
new_system.supply_ducting = [
duct_element.reflect(normal_vec3D, origin_pt3D) for duct_element in self.supply_ducting
]
new_system.exhaust_ducting = [
duct_element.reflect(normal_vec3D, origin_pt3D) for duct_element in self.exhaust_ducting
]
return new_system
[docs]
def scale(self, scale_factor, origin_pt3D=None):
# type: (float, Optional[Point3D]) -> PhVentilationSystem
"""Scale the System's ducts by a factor from an origin point.
Args:
scale_factor: A number representing how much the line segment should be scaled.
origin_pt3D: A Point3D representing the origin from which to scale.
If None, it will be scaled from the World origin (0, 0, 0).
"""
new_system = self.duplicate()
new_system.identifier = self.identifier
new_system.supply_ducting = [
duct_element.scale(scale_factor, origin_pt3D) for duct_element in self.supply_ducting
]
new_system.exhaust_ducting = [
duct_element.scale(scale_factor, origin_pt3D) for duct_element in self.exhaust_ducting
]
return new_system
# -----------------------------------------------------------------------------
# -- Exhaust Ventilators are not part of the Ventilation System,
# -- but instead are treated more like appliances which get added to the Room.
class _ExhaustVentilatorBase(_base._PhHVACBase):
def __init__(self):
super(_ExhaustVentilatorBase, self).__init__()
self.device_class_name = self.__class__.__name__
self.display_name = "_unnamed_exhaust_ventilator_"
self.quantity = 1
self.annual_runtime_minutes = 0.0
self.exhaust_flow_rate_m3s = 0.0
def to_dict(self):
# type: () -> Dict[str, Any]
d = super(_ExhaustVentilatorBase, self).to_dict()
d["device_class_name"] = self.device_class_name
d["quantity"] = self.quantity
d["annual_runtime_minutes"] = self.annual_runtime_minutes
d["exhaust_flow_rate_m3s"] = self.exhaust_flow_rate_m3s
return d
@classmethod
def from_dict(cls, _input_dict):
# type: (Dict[str, Any]) -> _ExhaustVentilatorBase
new_obj = cls()
new_obj.display_name = _input_dict["display_name"]
new_obj.identifier = _input_dict["identifier"]
new_obj.user_data = _input_dict.get("user_data", {})
new_obj.device_class_name = _input_dict["device_class_name"]
new_obj.quantity = _input_dict["quantity"]
new_obj.annual_runtime_minutes = _input_dict["annual_runtime_minutes"]
new_obj.exhaust_flow_rate_m3s = _input_dict["exhaust_flow_rate_m3s"]
return new_obj
def __lt__(self, other):
# type: (_ExhaustVentilatorBase) -> bool
return self.identifier < other.identifier
def __str__(self):
return "{}(display_name={!r}, exhaust_flow_rate_m3s={:0.02f})".format(
self.__class__.__name__, self.display_name, self.exhaust_flow_rate_m3s
)
def __repr__(self):
return str(self)
def ToString(self):
return self.__repr__()
def move(self, moving_vec3D):
"""Move the device's elements along a vector.
Args:
moving_vec3D: A Vector3D with the direction and distance to move the ray.
"""
pass
def rotate(self, axis_vec3D, angle_degrees, origin_pt3D):
"""Rotate the device's elements by a certain angle around an axis_vec3D and origin_pt3D.
Right hand rule applies:
If axis_vec3D has a positive orientation, rotation will be clockwise.
If axis_vec3D has a negative orientation, rotation will be counterclockwise.
Args:
axis_vec3D: A Vector3D axis_vec3D representing the axis_vec3D of rotation.
angle_degrees: An angle for rotation in degrees.
origin_pt3D: A Point3D for the origin_pt3D around which the object will be rotated.
"""
pass
def rotate_xy(self, angle_degrees, origin_pt3D):
"""Rotate the device's elements counterclockwise in the XY plane by a certain angle.
Args:
angle_degrees: An angle in degrees.
origin_pt3D: A Point3D for the origin_pt3D around which the object will be rotated.
"""
pass
def reflect(self, normal_vec3D, origin_pt3D):
"""Reflected the device's elements across a plane with the input normal vector and origin_pt3D.
Args:
normal_vec3D: A Vector3D representing the normal vector for the plane across
which the line segment will be reflected. THIS VECTOR MUST BE NORMALIZED.
origin_pt3D: A Point3D representing the origin_pt3D from which to reflect.
"""
pass
def scale(self, scale_factor, origin_pt3D=None):
"""Scale the device's elements by a factor from an origin_pt3D point.
Args:
scale_factor: A number representing how much the line segment should be scaled.
origin_pt3D: A Point3D representing the origin_pt3D from which to scale.
If None, it will be scaled from the World origin_pt3D (0, 0, 0).
"""
pass
def __copy__(self):
# type: () -> ExhaustVentDryer
raise NotImplementedError("This method must be implemented by the subclass.")
def duplicate(self):
# type: () -> ExhaustVentDryer
raise NotImplementedError("This method must be implemented by the subclass.")
[docs]
class ExhaustVentDryer(_ExhaustVentilatorBase):
def __init__(self):
super(ExhaustVentDryer, self).__init__()
self.display_name = "_unnamed_dryer_exh_"
def __copy__(self):
# type: () -> ExhaustVentDryer
return self.duplicate()
[docs]
def duplicate(self):
# type: () -> ExhaustVentDryer
new_obj = self.__class__()
new_obj.identifier = self.identifier
new_obj.display_name = self.display_name
new_obj.user_data = copy(self.user_data)
new_obj.quantity = self.quantity
new_obj.annual_runtime_minutes = self.annual_runtime_minutes
new_obj.device_class_name = self.device_class_name
new_obj.exhaust_flow_rate_m3s = self.exhaust_flow_rate_m3s
return new_obj
[docs]
class ExhaustVentKitchenHood(_ExhaustVentilatorBase):
def __init__(self):
super(ExhaustVentKitchenHood, self).__init__()
self.display_name = "_unnamed_kitchen_hood_exh_"
def __copy__(self):
# type: () -> ExhaustVentKitchenHood
return self.duplicate()
[docs]
def duplicate(self):
# type: () -> ExhaustVentKitchenHood
new_obj = self.__class__()
new_obj.identifier = self.identifier
new_obj.display_name = self.display_name
new_obj.user_data = copy(self.user_data)
new_obj.quantity = self.quantity
new_obj.annual_runtime_minutes = self.annual_runtime_minutes
new_obj.device_class_name = self.device_class_name
new_obj.exhaust_flow_rate_m3s = self.exhaust_flow_rate_m3s
return new_obj
[docs]
class ExhaustVentUserDefined(_ExhaustVentilatorBase):
def __init__(self):
super(ExhaustVentUserDefined, self).__init__()
self.display_name = "_unnamed_user_defined_exh_"
def __copy__(self):
# type: () -> ExhaustVentUserDefined
return self.duplicate()
[docs]
def duplicate(self):
# type: () -> ExhaustVentUserDefined
new_obj = self.__class__()
new_obj.identifier = self.identifier
new_obj.display_name = self.display_name
new_obj.user_data = copy(self.user_data)
new_obj.quantity = self.quantity
new_obj.annual_runtime_minutes = self.annual_runtime_minutes
new_obj.device_class_name = self.device_class_name
new_obj.exhaust_flow_rate_m3s = self.exhaust_flow_rate_m3s
return new_obj
[docs]
class PhExhaustDeviceBuilder(object):
"""Constructor class for HBPH-Exhaust Ventilation Devices"""
[docs]
@classmethod
def from_dict(cls, _input_dict):
# type: (dict[str, Any]) -> _ExhaustVentilatorBase
"""Find the right device constructor class from the module based on the 'type' name."""
device_class_name = _input_dict["device_class_name"] # type: str
valid_class_types = [nm for nm in dir(sys.modules[__name__]) if nm.startswith("ExhaustVent")]
if device_class_name not in valid_class_types:
raise UnknownPhExhaustVentTypeError(valid_class_types, device_class_name)
device_class = getattr(sys.modules[__name__], device_class_name) # type: _ExhaustVentilatorBase
new_equipment = device_class.from_dict(_input_dict)
return new_equipment
def __str__(self):
return "{}()".format(self.__class__.__name__)
def __repr__(self):
return str(self)
[docs]
def ToString(self):
"""Overwrite .NET ToString."""
return repr(self)