fairyfly-therm 0.3.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,38 @@
1
+ """Establish the default materials within the fairyfly_therm library."""
2
+ from ._loadmaterials import _solid_materials, _cavity_materials
3
+
4
+
5
+ # establish variables for the default materials used across the library
6
+ concrete = _solid_materials['Generic HW Concrete']
7
+ air_cavity = _cavity_materials['Frame Cavity - CEN Simplified']
8
+
9
+
10
+ # make lists of material identifiers to look up items in the library
11
+ SOLID_MATERIALS = tuple(_solid_materials.keys())
12
+ CAVITY_MATERIALS = tuple(_cavity_materials.keys())
13
+
14
+
15
+ def solid_material_by_name(material_name):
16
+ """Get a solid material from the library given the material name.
17
+
18
+ Args:
19
+ material_name: A text string for the display_name of the material.
20
+ """
21
+ try: # first check the default data
22
+ return _solid_materials[material_name]
23
+ except KeyError:
24
+ raise ValueError(
25
+ '"{}" was not found in the solid material library.'.format(material_name))
26
+
27
+
28
+ def cavity_material_by_name(material_name):
29
+ """Get a cavity material from the library given the material name.
30
+
31
+ Args:
32
+ material_name: A text string for the display_name of the material.
33
+ """
34
+ try: # first check the default data
35
+ return _cavity_materials[material_name]
36
+ except KeyError:
37
+ raise ValueError(
38
+ '"{}" was not found in the cavity material library.'.format(material_name))
@@ -0,0 +1 @@
1
+ """fairyfly-therm materials."""
@@ -0,0 +1,182 @@
1
+ # coding=utf-8
2
+ """Base therm material."""
3
+ from __future__ import division
4
+ import uuid
5
+ import random
6
+
7
+ from ladybug.color import Color
8
+ from fairyfly._lockable import lockable
9
+ from fairyfly.typing import valid_uuid
10
+
11
+
12
+ @lockable
13
+ class _ResourceObjectBase(object):
14
+ """Base class for resources.
15
+
16
+ Args:
17
+ identifier: Text string for a unique Material ID. Must be < 100 characters
18
+ and not contain any thermPlus special characters. This will be used to
19
+ identify the object across a model and in the exported IDF.
20
+
21
+ Properties:
22
+ * identifier
23
+ * display_name
24
+ * protected
25
+ * user_data
26
+ """
27
+ __slots__ = ('_identifier', '_display_name', '_protected', '_user_data', '_locked')
28
+
29
+ def __init__(self, identifier):
30
+ """Initialize resource object base."""
31
+ self._locked = False
32
+ self.identifier = identifier
33
+ self._display_name = None
34
+ self.protected = False
35
+ self._user_data = None
36
+
37
+ @property
38
+ def identifier(self):
39
+ """Get or set a text string for the unique object identifier.
40
+
41
+ This must be a UUID in the format xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
42
+ and it remains constant as the object is mutated, copied, and
43
+ serialized to different formats (eg. therm XML). As such, this
44
+ property is used to reference the object across a Model.
45
+ """
46
+ return self._identifier
47
+
48
+ @identifier.setter
49
+ def identifier(self, value):
50
+ if value is None:
51
+ self._identifier = str(uuid.uuid4())
52
+ else:
53
+ self._identifier = valid_uuid(value, 'therm material identifier')
54
+
55
+ @property
56
+ def display_name(self):
57
+ """Get or set a string for the object name without any character restrictions.
58
+
59
+ If not set, this will be equal to the identifier.
60
+ """
61
+ if self._display_name is None:
62
+ return self._identifier
63
+ return self._display_name
64
+
65
+ @display_name.setter
66
+ def display_name(self, value):
67
+ if value is not None:
68
+ try:
69
+ value = str(value)
70
+ except UnicodeEncodeError: # Python 2 machine lacking the character set
71
+ pass # keep it as unicode
72
+ self._display_name = value
73
+
74
+ @property
75
+ def protected(self):
76
+ """Get or set a boolean for whether the material is protected in THERM."""
77
+ return self._protected
78
+
79
+ @protected.setter
80
+ def protected(self, value):
81
+ try:
82
+ self._protected = bool(value)
83
+ except TypeError:
84
+ raise TypeError(
85
+ 'Expected boolean for Material.protected. Got {}.'.format(value))
86
+
87
+ @property
88
+ def user_data(self):
89
+ """Get or set an optional dictionary for additional meta data for this object.
90
+
91
+ This will be None until it has been set. All keys and values of this
92
+ dictionary should be of a standard Python type to ensure correct
93
+ serialization of the object to/from JSON (eg. str, float, int, list, dict)
94
+ """
95
+ if self._user_data is not None:
96
+ return self._user_data
97
+
98
+ @user_data.setter
99
+ def user_data(self, value):
100
+ if value is not None:
101
+ assert isinstance(value, dict), 'Expected dictionary for fairyfly_therm' \
102
+ 'object user_data. Got {}.'.format(type(value))
103
+ self._user_data = value
104
+
105
+ def duplicate(self):
106
+ """Get a copy of this object."""
107
+ return self.__copy__()
108
+
109
+ def __copy__(self):
110
+ new_obj = self.__class__(self.identifier)
111
+ new_obj._display_name = self._display_name
112
+ new_obj._protected = self._protected
113
+ new_obj._user_data = None if self._user_data is None else self._user_data.copy()
114
+ return new_obj
115
+
116
+ def ToString(self):
117
+ """Overwrite .NET ToString."""
118
+ return self.__repr__()
119
+
120
+ def __repr__(self):
121
+ return 'Base THERM Resource:\n{}'.format(self.display_name)
122
+
123
+
124
+ @lockable
125
+ class _ThermMaterialBase(_ResourceObjectBase):
126
+ """Base therm material.
127
+
128
+ Args:
129
+ identifier: Text string for a unique Material ID. Must be < 100 characters
130
+ and not contain any thermPlus special characters. This will be used to
131
+ identify the object across a model and in the exported IDF.
132
+
133
+ Properties:
134
+ * identifier
135
+ * display_name
136
+ * color
137
+ * protected
138
+ * user_data
139
+ """
140
+ __slots__ = ('_color',)
141
+
142
+ def __init__(self, identifier):
143
+ """Initialize therm material base."""
144
+ _ResourceObjectBase.__init__(self, identifier)
145
+ self.color = None
146
+
147
+ @property
148
+ def color(self):
149
+ """Get or set an optional color for the material as it displays in THERM.
150
+
151
+ This will always be a Ladybug Color object when getting this property
152
+ but the setter supports specifying hex codes. If unspecified, a radom
153
+ color will automatically be assigned.
154
+ """
155
+ return self._color
156
+
157
+ @color.setter
158
+ def color(self, value):
159
+ if value is None:
160
+ self._color = Color(
161
+ random.randint(0, 255),
162
+ random.randint(0, 255),
163
+ random.randint(0, 255)
164
+ )
165
+ elif isinstance(value, str):
166
+ value = value.replace('0x', '')
167
+ self._color = Color.from_hex(value)
168
+ else:
169
+ assert isinstance(value, Color), 'Expected ladybug Color object for ' \
170
+ 'material color. Got {}.'.format(type(value))
171
+ self._color = value
172
+
173
+ def __copy__(self):
174
+ new_obj = self.__class__(self.identifier)
175
+ new_obj._display_name = self._display_name
176
+ new_obj._color = self._color
177
+ new_obj._protected = self._protected
178
+ new_obj._user_data = None if self._user_data is None else self._user_data.copy()
179
+ return new_obj
180
+
181
+ def __repr__(self):
182
+ return 'Base THERM Material:\n{}'.format(self.display_name)
@@ -0,0 +1,377 @@
1
+ # coding=utf-8
2
+ """Cavity THERM material."""
3
+ from __future__ import division
4
+ import xml.etree.ElementTree as ET
5
+
6
+ from fairyfly._lockable import lockable
7
+ from fairyfly.typing import float_in_range, therm_id_from_uuid, uuid_from_therm_id
8
+
9
+ from ._base import _ThermMaterialBase
10
+ from .gas import Gas
11
+ from ..lib.gases import air
12
+
13
+
14
+ @lockable
15
+ class CavityMaterial(_ThermMaterialBase):
16
+ """Typical cavity material.
17
+
18
+ Args:
19
+ gas: A Gas material object for the gas that fills the cavity. (Default: air).
20
+ cavity_model: Text for the type of cavity model to be used to determine
21
+ the thermal resistance of the material. Choose from the following:
22
+
23
+ * CEN
24
+ * NFRC
25
+ * ISO15099
26
+ * ISO15099Ventilated
27
+
28
+ emissivity: Number between 0 and 1 for the infrared hemispherical
29
+ emissivity of the front side of the material. (Default: 0.9).
30
+ emissivity_back: Number between 0 and 1 for the infrared hemispherical
31
+ emissivity of the back side of the material. If None, this will
32
+ default to the same value specified for emissivity. (Default: None)
33
+ identifier: Text string for a unique object ID. Must be a UUID in the
34
+ format xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx. If None, a UUID will
35
+ automatically be generated. (Default: None).
36
+
37
+ Properties:
38
+ * identifier
39
+ * display_name
40
+ * therm_uuid
41
+ * gas
42
+ * cavity_model
43
+ * emissivity
44
+ * emissivity_back
45
+ * color
46
+ * protected
47
+ * user_data
48
+ """
49
+ __slots__ = ('_gas', '_cavity_model', '_emissivity', '_emissivity_back')
50
+ CAVITY_MODELS = ('CEN', 'NFRC', 'ISO15099', 'ISO15099Ventilated')
51
+
52
+ def __init__(
53
+ self, gas=air, cavity_model='CEN', emissivity=0.9, emissivity_back=None,
54
+ identifier=None
55
+ ):
56
+ """Initialize therm material."""
57
+ _ThermMaterialBase.__init__(self, identifier)
58
+ self.gas = gas
59
+ self.cavity_model = cavity_model
60
+ self.emissivity = emissivity
61
+ self.emissivity_back = emissivity_back
62
+
63
+ @property
64
+ def gas(self):
65
+ """Get or set a Gas object used to denote the gas in the cavity."""
66
+ return self._gas
67
+
68
+ @gas.setter
69
+ def gas(self, value):
70
+ if value is not None:
71
+ assert isinstance(value, Gas), 'Expected Gas object for CavityMaterial ' \
72
+ 'gas. Got {}.'.format(type(value))
73
+ else:
74
+ value = air
75
+ self._gas = value
76
+
77
+ @property
78
+ def cavity_model(self):
79
+ """Get or set text for the convection model to be used in the cavity."""
80
+ return self._cavity_model
81
+
82
+ @cavity_model.setter
83
+ def cavity_model(self, value):
84
+ if value is not None:
85
+ clean_input = str(value).lower()
86
+ for key in self.CAVITY_MODELS:
87
+ if key.lower() == clean_input:
88
+ value = key
89
+ break
90
+ else:
91
+ raise ValueError(
92
+ 'Material cavity_model "{}" is not supported.\n'
93
+ 'Choose from the following:\n{}'.format(
94
+ value, '\n'.join(self.CAVITY_MODELS)))
95
+ self._cavity_model = value
96
+ else:
97
+ self._cavity_model = self.CAVITY_MODELS[0]
98
+
99
+ @property
100
+ def emissivity(self):
101
+ """Get or set the hemispherical emissivity of the front side of the material."""
102
+ return self._emissivity
103
+
104
+ @emissivity.setter
105
+ def emissivity(self, ir_e):
106
+ ir_e = float_in_range(ir_e, 0.0, 1.0, 'material emissivity')
107
+ self._emissivity = ir_e
108
+
109
+ @property
110
+ def emissivity_back(self):
111
+ """Get or set the hemispherical emissivity of the back side of the material."""
112
+ return self._emissivity_back if self._emissivity_back is not None \
113
+ else self._emissivity
114
+
115
+ @emissivity_back.setter
116
+ def emissivity_back(self, ir_e):
117
+ if ir_e is not None:
118
+ ir_e = float_in_range(ir_e, 0.0, 1.0, 'material emissivity')
119
+ self._emissivity_back = ir_e
120
+
121
+ @property
122
+ def therm_uuid(self):
123
+ """Get the UUID of this object as it would appear in a THERM XML or thmz file.
124
+
125
+ This is always derived from the object identifier but this is slightly
126
+ different than standard UUIDs, which have 4 more values in a 8-4-4-4-12
127
+ structure instead of a 8-4-4-12 structure used by THERM.
128
+ """
129
+ return therm_id_from_uuid(self._identifier)
130
+
131
+ @classmethod
132
+ def from_therm_xml(cls, xml_element, gases):
133
+ """Create a CavityMaterial from an XML element of a THERM Material.
134
+
135
+ Args:
136
+ xml_element: An XML element of a THERM material.
137
+ gases: A dictionary with gas names as keys and Gas object instances
138
+ as values. These will be used to reassign the gas that fills
139
+ this cavity.
140
+ """
141
+ # create the base material from the UUID and conductivity
142
+ xml_uuid = xml_element.find('UUID')
143
+ identifier = xml_uuid.text
144
+ if len(identifier) == 31:
145
+ identifier = uuid_from_therm_id(identifier)
146
+ xml_cavity = xml_element.find('Cavity')
147
+ xml_c_model = xml_cavity.find('CavityStandard')
148
+ cavity_model = xml_c_model.text
149
+ xml_gas = xml_cavity.find('Gas')
150
+ try:
151
+ gas = gases[xml_gas.text]
152
+ except KeyError as e:
153
+ raise ValueError('Failed to find {} in gases.'.format(e))
154
+ mat = CavityMaterial(gas, cavity_model, identifier=identifier)
155
+ # assign the other attributes if specified
156
+ xml_emiss1 = xml_cavity.find('EmissivitySide1')
157
+ if xml_emiss1 is not None:
158
+ mat.emissivity = xml_emiss1.text
159
+ xml_emiss2 = xml_cavity.find('EmissivitySide2')
160
+ if xml_emiss2 is not None:
161
+ mat.emissivity_back = xml_emiss2.text
162
+ # assign the name and color if they are specified
163
+ xml_name = xml_element.find('Name')
164
+ if xml_name is not None:
165
+ mat.display_name = xml_name.text
166
+ xml_col = xml_element.find('Color')
167
+ if xml_col is not None:
168
+ mat.color = xml_col.text
169
+ xml_protect = xml_element.find('Protected')
170
+ if xml_protect is not None:
171
+ mat.protected = True if xml_protect.text == 'true' else False
172
+ return mat
173
+
174
+ @classmethod
175
+ def from_therm_xml_str(cls, xml_str, gases):
176
+ """Create a CavityMaterial from an XML text string of a THERM Material.
177
+
178
+ Args:
179
+ xml_str: An XML text string of a THERM material.
180
+ gases: A dictionary with gas names as keys and Gas object instances
181
+ as values. These will be used to reassign the gas that fills
182
+ this cavity.
183
+ """
184
+ root = ET.fromstring(xml_str)
185
+ return cls.from_therm_xml(root, gases)
186
+
187
+ @classmethod
188
+ def from_dict(cls, data):
189
+ """Create a CavityMaterial from a dictionary.
190
+
191
+ Args:
192
+ data: A python dictionary in the following format
193
+
194
+ .. code-block:: python
195
+
196
+ {
197
+ "type": 'CavityMaterial',
198
+ "identifier": '0b46bbd7-0dbc-c148-3afe87431bf0',
199
+ "display_name": 'Frame Cavity - CEN Simplified',
200
+ "gas": {}, # dictionary definition of a gas
201
+ "cavity_model": "CEN",
202
+ "emissivity": 0.9,
203
+ "emissivity_back": 0.9
204
+ }
205
+ """
206
+ assert data['type'] == 'CavityMaterial', \
207
+ 'Expected CavityMaterial. Got {}.'.format(data['type'])
208
+
209
+ emiss = data['emissivity'] if 'emissivity' in data and \
210
+ data['emissivity'] is not None else 0.9
211
+ emiss_b = data['emissivity_back'] if 'emissivity_back' in data else None
212
+ new_mat = cls(
213
+ Gas.from_dict(data['gas']), data['cavity_model'], emiss, emiss_b,
214
+ data['identifier'])
215
+ cls._assign_optional_from_dict(new_mat, data)
216
+ return new_mat
217
+
218
+ @classmethod
219
+ def from_dict_abridged(cls, data, gases):
220
+ """Create a Gas from an abridged dictionary.
221
+
222
+ Args:
223
+ data: An GasAbridged dictionary.
224
+ gases: A dictionary with Gas identifiers as keys and Gas object instances
225
+ as values. These will be used to reassign the gas that fills
226
+ this cavity.
227
+
228
+ .. code-block:: python
229
+
230
+ {
231
+ "type": 'CavityMaterial',
232
+ "identifier": '0b46bbd7-0dbc-c148-3afe87431bf0',
233
+ "display_name": 'Frame Cavity - CEN Simplified',
234
+ "gas": '6c2409e9-5296-46c1-be11-9029b59a549b',
235
+ "cavity_model": "CEN",
236
+ "emissivity": 0.9,
237
+ "emissivity_back": 0.9
238
+ }
239
+ """
240
+ assert data['type'] == 'CavityMaterialAbridged', \
241
+ 'Expected CavityMaterialAbridged. Got {}.'.format(data['type'])
242
+ try:
243
+ gas_obj = gases[data['gas']]
244
+ except KeyError as e:
245
+ raise ValueError('Failed to find {} in gases.'.format(e))
246
+ emiss = data['emissivity'] if 'emissivity' in data and \
247
+ data['emissivity'] is not None else 0.9
248
+ emiss_b = data['emissivity_back'] if 'emissivity_back' in data else None
249
+ new_mat = cls(gas_obj, data['cavity_model'], emiss, emiss_b, data['identifier'])
250
+ cls._assign_optional_from_dict(new_mat, data)
251
+ return new_mat
252
+
253
+ @staticmethod
254
+ def _assign_optional_from_dict(new_obj, data):
255
+ """Assign optional attributes when serializing from dict."""
256
+ if 'display_name' in data and data['display_name'] is not None:
257
+ new_obj.display_name = data['display_name']
258
+ if 'color' in data and data['color'] is not None:
259
+ new_obj.color = data['color']
260
+ if 'protected' in data and data['protected'] is not None:
261
+ new_obj.protected = data['protected']
262
+ if 'user_data' in data and data['user_data'] is not None:
263
+ new_obj.user_data = data['user_data']
264
+
265
+ def to_therm_xml(self, materials_element=None):
266
+ """Get an THERM XML element of the material.
267
+
268
+ Args:
269
+ materials_element: An optional XML Element for the Materials to
270
+ which the generated objects will be added. If None, a new XML
271
+ Element will be generated.
272
+
273
+ .. code-block:: xml
274
+
275
+ <Material>
276
+ <UUID>0b46bbd7-0dbc-c148-3afe87431bf0</UUID>
277
+ <Name>Frame Cavity - CEN Simplified</Name>
278
+ <Protected>false</Protected>
279
+ <Color>0xB3FFB3</Color>
280
+ <Cavity>
281
+ <CavityStandard>CEN</CavityStandard>
282
+ <Gas>Air</Gas>
283
+ <EmissivitySide1>0.9</EmissivitySide1>
284
+ <EmissivitySide2>0.9</EmissivitySide2>
285
+ </Cavity>
286
+ </Material>
287
+ """
288
+ # create a new Materials element if one is not specified
289
+ if materials_element is not None:
290
+ xml_mat = ET.SubElement(materials_element, 'Material')
291
+ else:
292
+ xml_mat = ET.Element('Material')
293
+ # add all of the required basic attributes
294
+ xml_id = ET.SubElement(xml_mat, 'UUID')
295
+ xml_id.text = self.therm_uuid
296
+ xml_name = ET.SubElement(xml_mat, 'Name')
297
+ xml_name.text = self.display_name
298
+ xml_protect = ET.SubElement(xml_mat, 'Protected')
299
+ xml_protect.text = 'true' if self.protected else 'false'
300
+ xml_color = ET.SubElement(xml_mat, 'Color')
301
+ xml_color.text = self.color.to_hex().replace('#', '0x')
302
+ xml_cavity = ET.SubElement(xml_mat, 'Cavity')
303
+ # add all of the required cavity attributes
304
+ xml_model = ET.SubElement(xml_cavity, 'CavityStandard')
305
+ xml_model.text = self.cavity_model
306
+ xml_gas = ET.SubElement(xml_cavity, 'Gas')
307
+ xml_gas.text = self.gas.display_name
308
+ xml_emiss = ET.SubElement(xml_cavity, 'EmissivitySide1')
309
+ xml_emiss.text = str(self.emissivity)
310
+ xml_emiss_b = ET.SubElement(xml_cavity, 'EmissivitySide2')
311
+ xml_emiss_b.text = str(self.emissivity_back)
312
+ return xml_mat
313
+
314
+ def to_therm_xml_str(self):
315
+ """Get an THERM XML string of the material."""
316
+ xml_root = self.to_therm_xml()
317
+ try: # try to indent the XML to make it read-able
318
+ ET.indent(xml_root)
319
+ return ET.tostring(xml_root, encoding='unicode')
320
+ except AttributeError: # we are in Python 2 and no indent is available
321
+ return ET.tostring(xml_root)
322
+
323
+ def to_dict(self, abridged=False):
324
+ """CavityMaterial dictionary representation."""
325
+ base = {'type': 'CavityMaterial'} if not abridged \
326
+ else {'type': 'CavityMaterialAbridged'}
327
+ base['identifier'] = self.identifier
328
+ base['gas'] = self.gas.identifier if abridged else self.gas.to_dict()
329
+ base['cavity_model'] = self.cavity_model
330
+ base['emissivity'] = self.emissivity
331
+ if self._emissivity_back is not None:
332
+ base['emissivity_back'] = self.emissivity_back
333
+ if self._display_name is not None:
334
+ base['display_name'] = self.display_name
335
+ base['protected'] = self._protected
336
+ base['color'] = self.color.to_hex()
337
+ if self._user_data is not None:
338
+ base['user_data'] = self.user_data
339
+ return base
340
+
341
+ def lock(self):
342
+ """The lock() method will also lock the gas."""
343
+ self._locked = True
344
+ self.gas.lock()
345
+
346
+ def unlock(self):
347
+ """The unlock() method will also unlock the gas."""
348
+ self._locked = False
349
+ self.gas.unlock()
350
+
351
+ def __key(self):
352
+ """A tuple based on the object properties, useful for hashing."""
353
+ return (self.therm_uuid, hash(self.gas), self.cavity_model,
354
+ self.emissivity, self.emissivity_back)
355
+
356
+ def __hash__(self):
357
+ return hash(self.__key())
358
+
359
+ def __eq__(self, other):
360
+ return isinstance(other, CavityMaterial) and self.__key() == other.__key()
361
+
362
+ def __ne__(self, other):
363
+ return not self.__eq__(other)
364
+
365
+ def __copy__(self):
366
+ new_material = self.__class__(
367
+ self.gas.duplicate(), self.cavity_model,
368
+ self._emissivity, self._emissivity_back, self.identifier)
369
+ new_material._display_name = self._display_name
370
+ new_material._color = self._color
371
+ new_material._protected = self._protected
372
+ new_material._user_data = None if self._user_data is None \
373
+ else self._user_data.copy()
374
+ return new_material
375
+
376
+ def __repr__(self):
377
+ return 'Cavity THERM Material: {}'.format(self.display_name)