# coding=utf-8
"""Cavity THERM material."""
from __future__ import division
import xml.etree.ElementTree as ET
from fairyfly._lockable import lockable
from fairyfly.typing import float_in_range, therm_id_from_uuid, uuid_from_therm_id
from ._base import _ThermMaterialBase
from .gas import Gas
from ..lib.gases import air
[docs]
@lockable
class CavityMaterial(_ThermMaterialBase):
"""Typical cavity material.
Args:
gas: A Gas material object for the gas that fills the cavity. (Default: air).
cavity_model: Text for the type of cavity model to be used to determine
the thermal resistance of the material. Choose from the following:
* CEN
* NFRC
* ISO15099
* ISO15099Ventilated
emissivity: Number between 0 and 1 for the infrared hemispherical
emissivity of the front side of the material. (Default: 0.9).
emissivity_back: Number between 0 and 1 for the infrared hemispherical
emissivity of the back side of the material. If None, this will
default to the same value specified for emissivity. (Default: None)
identifier: Text string for a unique object ID. Must be a UUID in the
format xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx. If None, a UUID will
automatically be generated. (Default: None).
Properties:
* identifier
* display_name
* therm_uuid
* gas
* cavity_model
* emissivity
* emissivity_back
* color
* protected
* user_data
"""
__slots__ = ('_gas', '_cavity_model', '_emissivity', '_emissivity_back')
CAVITY_MODELS = ('CEN', 'NFRC', 'ISO15099', 'ISO15099Ventilated')
def __init__(
self, gas=air, cavity_model='CEN', emissivity=0.9, emissivity_back=None,
identifier=None
):
"""Initialize therm material."""
_ThermMaterialBase.__init__(self, identifier)
self.gas = gas
self.cavity_model = cavity_model
self.emissivity = emissivity
self.emissivity_back = emissivity_back
@property
def gas(self):
"""Get or set a Gas object used to denote the gas in the cavity."""
return self._gas
@gas.setter
def gas(self, value):
if value is not None:
assert isinstance(value, Gas), 'Expected Gas object for CavityMaterial ' \
'gas. Got {}.'.format(type(value))
else:
value = air
self._gas = value
@property
def cavity_model(self):
"""Get or set text for the convection model to be used in the cavity."""
return self._cavity_model
@cavity_model.setter
def cavity_model(self, value):
if value is not None:
clean_input = str(value).lower()
for key in self.CAVITY_MODELS:
if key.lower() == clean_input:
value = key
break
else:
raise ValueError(
'Material cavity_model "{}" is not supported.\n'
'Choose from the following:\n{}'.format(
value, '\n'.join(self.CAVITY_MODELS)))
self._cavity_model = value
else:
self._cavity_model = self.CAVITY_MODELS[0]
@property
def emissivity(self):
"""Get or set the hemispherical emissivity of the front side of the material."""
return self._emissivity
@emissivity.setter
def emissivity(self, ir_e):
ir_e = float_in_range(ir_e, 0.0, 1.0, 'material emissivity')
self._emissivity = ir_e
@property
def emissivity_back(self):
"""Get or set the hemispherical emissivity of the back side of the material."""
return self._emissivity_back if self._emissivity_back is not None \
else self._emissivity
@emissivity_back.setter
def emissivity_back(self, ir_e):
if ir_e is not None:
ir_e = float_in_range(ir_e, 0.0, 1.0, 'material emissivity')
self._emissivity_back = ir_e
@property
def therm_uuid(self):
"""Get the UUID of this object as it would appear in a THERM XML or thmz file.
This is always derived from the object identifier but this is slightly
different than standard UUIDs, which have 4 more values in a 8-4-4-4-12
structure instead of a 8-4-4-12 structure used by THERM.
"""
return therm_id_from_uuid(self._identifier)
[docs]
@classmethod
def from_therm_xml(cls, xml_element, gases):
"""Create a CavityMaterial from an XML element of a THERM Material.
Args:
xml_element: An XML element of a THERM material.
gases: A dictionary with gas names as keys and Gas object instances
as values. These will be used to reassign the gas that fills
this cavity.
"""
# create the base material from the UUID and conductivity
xml_uuid = xml_element.find('UUID')
identifier = xml_uuid.text
if len(identifier) == 31:
identifier = uuid_from_therm_id(identifier)
xml_cavity = xml_element.find('Cavity')
xml_c_model = xml_cavity.find('CavityStandard')
cavity_model = xml_c_model.text
xml_gas = xml_cavity.find('Gas')
try:
gas = gases[xml_gas.text]
except KeyError as e:
raise ValueError('Failed to find {} in gases.'.format(e))
mat = CavityMaterial(gas, cavity_model, identifier=identifier)
# assign the other attributes if specified
xml_emiss1 = xml_cavity.find('EmissivitySide1')
if xml_emiss1 is not None:
mat.emissivity = xml_emiss1.text
xml_emiss2 = xml_cavity.find('EmissivitySide2')
if xml_emiss2 is not None:
mat.emissivity_back = xml_emiss2.text
# assign the name and color if they are specified
xml_name = xml_element.find('Name')
if xml_name is not None:
mat.display_name = xml_name.text
xml_col = xml_element.find('Color')
if xml_col is not None:
mat.color = xml_col.text
xml_protect = xml_element.find('Protected')
if xml_protect is not None:
mat.protected = True if xml_protect.text == 'true' else False
return mat
[docs]
@classmethod
def from_therm_xml_str(cls, xml_str, gases):
"""Create a CavityMaterial from an XML text string of a THERM Material.
Args:
xml_str: An XML text string of a THERM material.
gases: A dictionary with gas names as keys and Gas object instances
as values. These will be used to reassign the gas that fills
this cavity.
"""
root = ET.fromstring(xml_str)
return cls.from_therm_xml(root, gases)
[docs]
@classmethod
def from_dict(cls, data):
"""Create a CavityMaterial from a dictionary.
Args:
data: A python dictionary in the following format
.. code-block:: python
{
"type": 'CavityMaterial',
"identifier": '0b46bbd7-0dbc-c148-3afe87431bf0',
"display_name": 'Frame Cavity - CEN Simplified',
"gas": {}, # dictionary definition of a gas
"cavity_model": "CEN",
"emissivity": 0.9,
"emissivity_back": 0.9
}
"""
assert data['type'] == 'CavityMaterial', \
'Expected CavityMaterial. Got {}.'.format(data['type'])
emiss = data['emissivity'] if 'emissivity' in data and \
data['emissivity'] is not None else 0.9
emiss_b = data['emissivity_back'] if 'emissivity_back' in data else None
new_mat = cls(
Gas.from_dict(data['gas']), data['cavity_model'], emiss, emiss_b,
data['identifier'])
cls._assign_optional_from_dict(new_mat, data)
return new_mat
[docs]
@classmethod
def from_dict_abridged(cls, data, gases):
"""Create a Gas from an abridged dictionary.
Args:
data: An GasAbridged dictionary.
gases: A dictionary with Gas identifiers as keys and Gas object instances
as values. These will be used to reassign the gas that fills
this cavity.
.. code-block:: python
{
"type": 'CavityMaterial',
"identifier": '0b46bbd7-0dbc-c148-3afe87431bf0',
"display_name": 'Frame Cavity - CEN Simplified',
"gas": '6c2409e9-5296-46c1-be11-9029b59a549b',
"cavity_model": "CEN",
"emissivity": 0.9,
"emissivity_back": 0.9
}
"""
assert data['type'] == 'CavityMaterialAbridged', \
'Expected CavityMaterialAbridged. Got {}.'.format(data['type'])
try:
gas_obj = gases[data['gas']]
except KeyError as e:
raise ValueError('Failed to find {} in gases.'.format(e))
emiss = data['emissivity'] if 'emissivity' in data and \
data['emissivity'] is not None else 0.9
emiss_b = data['emissivity_back'] if 'emissivity_back' in data else None
new_mat = cls(gas_obj, data['cavity_model'], emiss, emiss_b, data['identifier'])
cls._assign_optional_from_dict(new_mat, data)
return new_mat
@staticmethod
def _assign_optional_from_dict(new_obj, data):
"""Assign optional attributes when serializing from dict."""
if 'display_name' in data and data['display_name'] is not None:
new_obj.display_name = data['display_name']
if 'color' in data and data['color'] is not None:
new_obj.color = data['color']
if 'protected' in data and data['protected'] is not None:
new_obj.protected = data['protected']
if 'user_data' in data and data['user_data'] is not None:
new_obj.user_data = data['user_data']
[docs]
def to_therm_xml(self, materials_element=None):
"""Get an THERM XML element of the material.
Args:
materials_element: An optional XML Element for the Materials to
which the generated objects will be added. If None, a new XML
Element will be generated.
.. code-block:: xml
<Material>
<UUID>0b46bbd7-0dbc-c148-3afe87431bf0</UUID>
<Name>Frame Cavity - CEN Simplified</Name>
<Protected>false</Protected>
<Color>0xB3FFB3</Color>
<Cavity>
<CavityStandard>CEN</CavityStandard>
<Gas>Air</Gas>
<EmissivitySide1>0.9</EmissivitySide1>
<EmissivitySide2>0.9</EmissivitySide2>
</Cavity>
</Material>
"""
# create a new Materials element if one is not specified
if materials_element is not None:
xml_mat = ET.SubElement(materials_element, 'Material')
else:
xml_mat = ET.Element('Material')
# add all of the required basic attributes
xml_id = ET.SubElement(xml_mat, 'UUID')
xml_id.text = self.therm_uuid
xml_name = ET.SubElement(xml_mat, 'Name')
xml_name.text = self.display_name
xml_protect = ET.SubElement(xml_mat, 'Protected')
xml_protect.text = 'true' if self.protected else 'false'
xml_color = ET.SubElement(xml_mat, 'Color')
xml_color.text = self.color.to_hex().replace('#', '0x')
xml_cavity = ET.SubElement(xml_mat, 'Cavity')
# add all of the required cavity attributes
xml_model = ET.SubElement(xml_cavity, 'CavityStandard')
xml_model.text = self.cavity_model
xml_gas = ET.SubElement(xml_cavity, 'Gas')
xml_gas.text = self.gas.display_name
xml_emiss = ET.SubElement(xml_cavity, 'EmissivitySide1')
xml_emiss.text = str(self.emissivity)
xml_emiss_b = ET.SubElement(xml_cavity, 'EmissivitySide2')
xml_emiss_b.text = str(self.emissivity_back)
return xml_mat
[docs]
def to_therm_xml_str(self):
"""Get an THERM XML string of the material."""
xml_root = self.to_therm_xml()
try: # try to indent the XML to make it read-able
ET.indent(xml_root)
return ET.tostring(xml_root, encoding='unicode')
except AttributeError: # we are in Python 2 and no indent is available
return ET.tostring(xml_root)
[docs]
def to_dict(self, abridged=False):
"""CavityMaterial dictionary representation."""
base = {'type': 'CavityMaterial'} if not abridged \
else {'type': 'CavityMaterialAbridged'}
base['identifier'] = self.identifier
base['gas'] = self.gas.identifier if abridged else self.gas.to_dict()
base['cavity_model'] = self.cavity_model
base['emissivity'] = self.emissivity
if self._emissivity_back is not None:
base['emissivity_back'] = self.emissivity_back
if self._display_name is not None:
base['display_name'] = self.display_name
base['protected'] = self._protected
base['color'] = self.color.to_hex()
if self._user_data is not None:
base['user_data'] = self.user_data
return base
[docs]
def lock(self):
"""The lock() method will also lock the gas."""
self._locked = True
self.gas.lock()
[docs]
def unlock(self):
"""The unlock() method will also unlock the gas."""
self._locked = False
self.gas.unlock()
def __key(self):
"""A tuple based on the object properties, useful for hashing."""
return (self.therm_uuid, hash(self.gas), self.cavity_model,
self.emissivity, self.emissivity_back)
def __hash__(self):
return hash(self.__key())
def __eq__(self, other):
return isinstance(other, CavityMaterial) and self.__key() == other.__key()
def __ne__(self, other):
return not self.__eq__(other)
def __copy__(self):
new_material = self.__class__(
self.gas.duplicate(), self.cavity_model,
self._emissivity, self._emissivity_back, self.identifier)
new_material._display_name = self._display_name
new_material._color = self._color
new_material._protected = self._protected
new_material._user_data = None if self._user_data is None \
else self._user_data.copy()
return new_material
def __repr__(self):
return 'Cavity THERM Material: {}'.format(self.display_name)