honeybee-core 1.64.12__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.
Files changed (48) hide show
  1. honeybee/__init__.py +23 -0
  2. honeybee/__main__.py +4 -0
  3. honeybee/_base.py +331 -0
  4. honeybee/_basewithshade.py +310 -0
  5. honeybee/_lockable.py +99 -0
  6. honeybee/altnumber.py +47 -0
  7. honeybee/aperture.py +997 -0
  8. honeybee/boundarycondition.py +358 -0
  9. honeybee/checkdup.py +173 -0
  10. honeybee/cli/__init__.py +118 -0
  11. honeybee/cli/compare.py +132 -0
  12. honeybee/cli/create.py +265 -0
  13. honeybee/cli/edit.py +559 -0
  14. honeybee/cli/lib.py +103 -0
  15. honeybee/cli/setconfig.py +43 -0
  16. honeybee/cli/validate.py +224 -0
  17. honeybee/colorobj.py +363 -0
  18. honeybee/config.json +5 -0
  19. honeybee/config.py +347 -0
  20. honeybee/dictutil.py +54 -0
  21. honeybee/door.py +746 -0
  22. honeybee/extensionutil.py +208 -0
  23. honeybee/face.py +2360 -0
  24. honeybee/facetype.py +153 -0
  25. honeybee/logutil.py +79 -0
  26. honeybee/model.py +4272 -0
  27. honeybee/orientation.py +132 -0
  28. honeybee/properties.py +845 -0
  29. honeybee/room.py +3485 -0
  30. honeybee/search.py +107 -0
  31. honeybee/shade.py +514 -0
  32. honeybee/shademesh.py +362 -0
  33. honeybee/typing.py +498 -0
  34. honeybee/units.py +88 -0
  35. honeybee/writer/__init__.py +7 -0
  36. honeybee/writer/aperture.py +6 -0
  37. honeybee/writer/door.py +6 -0
  38. honeybee/writer/face.py +6 -0
  39. honeybee/writer/model.py +6 -0
  40. honeybee/writer/room.py +6 -0
  41. honeybee/writer/shade.py +6 -0
  42. honeybee/writer/shademesh.py +6 -0
  43. honeybee_core-1.64.12.dist-info/METADATA +94 -0
  44. honeybee_core-1.64.12.dist-info/RECORD +48 -0
  45. honeybee_core-1.64.12.dist-info/WHEEL +5 -0
  46. honeybee_core-1.64.12.dist-info/entry_points.txt +2 -0
  47. honeybee_core-1.64.12.dist-info/licenses/LICENSE +661 -0
  48. honeybee_core-1.64.12.dist-info/top_level.txt +1 -0
honeybee/typing.py ADDED
@@ -0,0 +1,498 @@
1
+ """Collection of methods for type input checking."""
2
+ import re
3
+ import os
4
+ import math
5
+ import uuid
6
+ import hashlib
7
+
8
+ try:
9
+ INFPOS = math.inf
10
+ INFNEG = -1 * math.inf
11
+ except AttributeError:
12
+ # python 2
13
+ INFPOS = float('inf')
14
+ INFNEG = float('-inf')
15
+
16
+
17
+ def valid_string(value, input_name=''):
18
+ """Check that a string is valid for both Radiance and EnergyPlus.
19
+
20
+ This is used for honeybee geometry object names.
21
+ """
22
+ try:
23
+ illegal_match = re.search(r'[^.A-Za-z0-9_-]', value)
24
+ except TypeError:
25
+ raise TypeError('Input {} must be a text string. Got {}: {}.'.format(
26
+ input_name, type(value), value))
27
+ assert illegal_match is None, 'Illegal character "{}" found in {}'.format(
28
+ illegal_match.group(0), input_name)
29
+ assert len(value) > 0, 'Input {} "{}" contains no characters.'.format(
30
+ input_name, value)
31
+ assert len(value) <= 100, 'Input {} "{}" must be less than 100 characters.'.format(
32
+ input_name, value)
33
+ return value
34
+
35
+
36
+ def valid_rad_string(value, input_name=''):
37
+ """Check that a string is valid for Radiance.
38
+
39
+ This is used for radiance modifier names, etc.
40
+ """
41
+ try:
42
+ illegal_match = re.search(r'[^.A-Za-z0-9_-]', value)
43
+ except TypeError:
44
+ raise TypeError('Input {} must be a text string. Got {}: {}.'.format(
45
+ input_name, type(value), value))
46
+ assert illegal_match is None, 'Illegal character "{}" found in {}'.format(
47
+ illegal_match.group(0), input_name)
48
+ assert len(value) > 0, 'Input {} "{}" contains no characters.'.format(
49
+ input_name, value)
50
+ return value
51
+
52
+
53
+ def valid_ep_string(value, input_name=''):
54
+ """Check that a string is valid for EnergyPlus.
55
+
56
+ This is used for energy material names, schedule names, etc.
57
+ """
58
+ try:
59
+ non_ascii = tuple(i for i in value if ord(i) >= 128)
60
+ except TypeError:
61
+ raise TypeError('Input {} must be a text string. Got {}: {}.'.format(
62
+ input_name, type(value), value))
63
+ assert non_ascii == (), 'Illegal characters {} found in {}'.format(
64
+ non_ascii, input_name)
65
+ illegal_match = re.search(r'[,;!\n\t]', value)
66
+ assert illegal_match is None, 'Illegal character "{}" found in {}'.format(
67
+ illegal_match.group(0), input_name)
68
+ assert len(value) > 0, 'Input {} "{}" contains no characters.'.format(
69
+ input_name, value)
70
+ assert len(value) <= 100, 'Input {} "{}" must be less than 100 characters.'.format(
71
+ input_name, value)
72
+ return value
73
+
74
+
75
+ def _number_check(value, input_name):
76
+ """Check if value is a number."""
77
+ try:
78
+ number = float(value)
79
+ except (ValueError, TypeError):
80
+ raise TypeError('Input {} must be a number. Got {}: {}.'.format(
81
+ input_name, type(value), value))
82
+ return number
83
+
84
+
85
+ def float_in_range(value, mi=INFNEG, ma=INFPOS, input_name=''):
86
+ """Check a float value to be between minimum and maximum."""
87
+ number = _number_check(value, input_name)
88
+ assert mi <= number <= ma, 'Input number {} must be between {} and {}. ' \
89
+ 'Got {}'.format(input_name, mi, ma, value)
90
+ return number
91
+
92
+
93
+ def float_in_range_excl(value, mi=INFNEG, ma=INFPOS, input_name=''):
94
+ """Check a float value to be greater than minimum and less than maximum."""
95
+ number = _number_check(value, input_name)
96
+ assert mi < number < ma, 'Input number {} must be greater than {} ' \
97
+ 'and less than {}. Got {}'.format(input_name, mi, ma, value)
98
+ return number
99
+
100
+
101
+ def float_in_range_excl_incl(value, mi=INFNEG, ma=INFPOS, input_name=''):
102
+ """Check a float value to be greater than minimum and less than/equal to maximum."""
103
+ number = _number_check(value, input_name)
104
+ assert mi < number <= ma, 'Input number {} must be greater than {} and less than ' \
105
+ 'or equal to {}. Got {}'.format(input_name, mi, ma, value)
106
+ return number
107
+
108
+
109
+ def float_in_range_incl_excl(value, mi=INFNEG, ma=INFPOS, input_name=''):
110
+ """Check a float value to be greater than/equal to minimum and less than maximum."""
111
+ number = _number_check(value, input_name)
112
+ assert mi <= number < ma, 'Input number {} must be greater than or equal to {} ' \
113
+ 'and less than {}. Got {}'.format(input_name, mi, ma, value)
114
+ return number
115
+
116
+
117
+ def int_in_range(value, mi=INFNEG, ma=INFPOS, input_name=''):
118
+ """Check an integer value to be between minimum and maximum."""
119
+ try:
120
+ number = int(value)
121
+ except ValueError:
122
+ # try to convert to float and then digit if possible
123
+ try:
124
+ number = int(float(value))
125
+ except (ValueError, TypeError):
126
+ raise TypeError('Input {} must be an integer. Got {}: {}.'.format(
127
+ input_name, type(value), value))
128
+ except (ValueError, TypeError):
129
+ raise TypeError('Input {} must be an integer. Got {}: {}.'.format(
130
+ input_name, type(value), value))
131
+ assert mi <= number <= ma, 'Input integer {} must be between {} and {}. ' \
132
+ 'Got {}.'.format(input_name, mi, ma, value)
133
+ return number
134
+
135
+
136
+ def float_positive(value, input_name=''):
137
+ """Check a float value to be positive."""
138
+ return float_in_range(value, 0, INFPOS, input_name)
139
+
140
+
141
+ def int_positive(value, input_name=''):
142
+ """Check if an integer value is positive."""
143
+ return int_in_range(value, 0, INFPOS, input_name)
144
+
145
+
146
+ def tuple_with_length(value, length=3, item_type=float, input_name=''):
147
+ """Try to create a tuple with a certain value."""
148
+ try:
149
+ value = tuple(item_type(v) for v in value)
150
+ except (ValueError, TypeError):
151
+ raise TypeError('Input {} must be a {}.'.format(
152
+ input_name, item_type))
153
+ assert len(value) == length, 'Input {} length must be {} not {}'.format(
154
+ input_name, length, len(value))
155
+ return value
156
+
157
+
158
+ def list_with_length(value, length=3, item_type=float, input_name=''):
159
+ """Try to create a list with a certain value."""
160
+ try:
161
+ value = [item_type(v) for v in value]
162
+ except (ValueError, TypeError):
163
+ raise TypeError('Input {} must be a {}.'.format(
164
+ input_name, item_type))
165
+ assert len(value) == length, 'Input {} length must be {} not {}'.format(
166
+ input_name, length, len(value))
167
+ return value
168
+
169
+
170
+ def clean_string(value, input_name=''):
171
+ """Clean a string so that it is valid for both Radiance and EnergyPlus.
172
+
173
+ This will strip out spaces and special characters and raise an error if the
174
+ string is has more than 100 characters. If the input has no valid characters
175
+ after stripping out illegal ones, a randomly-generated UUID will be returned.
176
+ """
177
+ try:
178
+ value = value.replace(' ', '_') # spaces > underscores for readability
179
+ val = re.sub(r'[^.A-Za-z0-9_-]', '', value)
180
+ except TypeError:
181
+ raise TypeError('Input {} must be a text string. Got {}: {}.'.format(
182
+ input_name, type(value), value))
183
+ if len(val) == 0: # generate a unique but consistent ID from the input
184
+ sha256_hash = hashlib.sha256(value.encode('utf-8'))
185
+ hash_str = str(sha256_hash.hexdigest())
186
+ return hash_str[:8] if len(hash_str) > 8 else hash_str
187
+ if len(val) > 100:
188
+ val = val[:100]
189
+ return val
190
+
191
+
192
+ def clean_rad_string(value, input_name=''):
193
+ """Clean a string for Radiance that can be used for rad material names.
194
+
195
+ This includes stripping out illegal characters and white spaces. If the input
196
+ has no valid characters after stripping out illegal ones, a randomly-generated
197
+ UUID will be returned.
198
+ """
199
+ try:
200
+ value = value.replace(' ', '_') # spaces > underscores for readability
201
+ val = re.sub(r'[^.A-Za-z0-9_-]', '', value)
202
+ except TypeError:
203
+ raise TypeError('Input {} must be a text string. Got {}: {}.'.format(
204
+ input_name, type(value), value))
205
+ if len(val) == 0: # generate a unique but consistent ID from the input
206
+ sha256_hash = hashlib.sha256(value.encode('utf-8'))
207
+ hash_str = str(sha256_hash.hexdigest())
208
+ return hash_str[:8] if len(hash_str) > 8 else hash_str
209
+ return val
210
+
211
+
212
+ def clean_ep_string(value, input_name=''):
213
+ """Clean a string for EnergyPlus that can be used for energy material names.
214
+
215
+ This includes stripping out all illegal characters, removing trailing spaces,
216
+ and rasing an error if the name is not longer than 100 characters. If the input
217
+ has no valid characters after stripping out illegal ones, a randomly-generated
218
+ UUID will be returned.
219
+ """
220
+ try:
221
+ val = ''.join(i for i in value if ord(i) < 128) # strip out non-ascii
222
+ val = re.sub(r'[,;!\n\t]', '', val) # strip out E+ special characters
223
+ except TypeError:
224
+ raise TypeError('Input {} must be a text string. Got {}: {}.'.format(
225
+ input_name, type(value), value))
226
+ val = val.strip()
227
+ if len(val) == 0: # generate a unique but consistent ID from the input
228
+ sha256_hash = hashlib.sha256(value.encode('utf-8'))
229
+ hash_str = str(sha256_hash.hexdigest())
230
+ return hash_str[:8] if len(hash_str) > 8 else hash_str
231
+ if len(val) > 100:
232
+ val = val[:100]
233
+ return val
234
+
235
+
236
+ def clean_and_id_string(value, input_name=''):
237
+ """Clean a string and add 8 unique characters to it to make it unique.
238
+
239
+ Strings longer than 50 characters will be truncated before adding the ID.
240
+ The resulting string will be valid for both Radiance and EnergyPlus.
241
+ """
242
+ try:
243
+ value = value.replace(' ', '_') # spaces > underscores for readability
244
+ val = re.sub(r'[^.A-Za-z0-9_-]', '', value)
245
+ except TypeError:
246
+ raise TypeError('Input {} must be a text string. Got {}: {}.'.format(
247
+ input_name, type(value), value))
248
+ if len(val) > 50:
249
+ val = val[:50]
250
+ return val + '_' + str(uuid.uuid4())[:8]
251
+
252
+
253
+ def clean_and_id_rad_string(value, input_name=''):
254
+ """Clean a string and add 8 unique characters to it to make it unique for Radiance.
255
+
256
+ This includes stripping out illegal characters and white spaces.
257
+ """
258
+ try:
259
+ value = value.replace(' ', '_') # spaces > underscores for readability
260
+ val = re.sub(r'[^.A-Za-z0-9_-]', '', value)
261
+ except TypeError:
262
+ raise TypeError('Input {} must be a text string. Got {}: {}.'.format(
263
+ input_name, type(value), value))
264
+ return val + '_' + str(uuid.uuid4())[:8]
265
+
266
+
267
+ def clean_and_id_ep_string(value, input_name=''):
268
+ """Clean a string and add 8 unique characters to it to make it unique for EnergyPlus.
269
+
270
+ This includes stripping out all illegal characters and removing trailing white spaces.
271
+ Strings longer than 50 characters will be truncated before adding the ID.
272
+ """
273
+ try:
274
+ val = ''.join(i for i in value if ord(i) < 128) # strip out non-ascii
275
+ val = re.sub(r'[,;!\n\t]', '', val) # strip out E+ special characters
276
+ except TypeError:
277
+ raise TypeError('Input {} must be a text string. Got {}: {}.'.format(
278
+ input_name, type(value), value))
279
+ val = val.strip()
280
+ if len(val) > 50:
281
+ val = val[:50]
282
+ return val + '_' + str(uuid.uuid4())[:8]
283
+
284
+
285
+ def clean_and_number_string(value, existing_dict, input_name=''):
286
+ """Clean a string and add an integer to it if it is found in the existing_dict.
287
+
288
+ The resulting string will be valid for both Radiance and EnergyPlus.
289
+
290
+ Args:
291
+ value: The text string to be cleaned and possibly given a unique integer.
292
+ existing_dict: A dictionary where the keys are text strings of existing items
293
+ and the values are the number of times that the item has appeared in
294
+ the model already.
295
+ """
296
+ try:
297
+ value = value.replace(' ', '_') # spaces > underscores for readability
298
+ val = re.sub(r'[^.A-Za-z0-9_-]', '_', value)
299
+ except TypeError:
300
+ raise TypeError('Input {} must be a text string. Got {}: {}.'.format(
301
+ input_name, type(value), value))
302
+ if len(val) > 95:
303
+ val = val[:95]
304
+ if val in existing_dict:
305
+ existing_dict[val] += 1
306
+ return val + '_' + str(existing_dict[val])
307
+ else:
308
+ existing_dict[val] = 1
309
+ return val
310
+
311
+
312
+ def clean_and_number_rad_string(value, existing_dict, input_name=''):
313
+ """Clean a string for Radiance and add an integer if found in the existing_dict.
314
+
315
+ This includes stripping out illegal characters and white spaces.
316
+
317
+ Args:
318
+ value: The text string to be cleaned and possibly given a unique integer.
319
+ existing_dict: A dictionary where the keys are text strings of existing items
320
+ and the values are the number of times that the item has appeared in
321
+ the model already.
322
+ """
323
+ try:
324
+ value = value.replace(' ', '_') # spaces > underscores for readability
325
+ val = re.sub(r'[^.A-Za-z0-9_-]', '_', value)
326
+ except TypeError:
327
+ raise TypeError('Input {} must be a text string. Got {}: {}.'.format(
328
+ input_name, type(value), value))
329
+ if val in existing_dict:
330
+ existing_dict[val] += 1
331
+ return val + '_' + str(existing_dict[val])
332
+ else:
333
+ existing_dict[val] = 1
334
+ return val
335
+
336
+
337
+ def clean_and_number_ep_string(value, existing_dict, input_name=''):
338
+ """Clean a string for EnergyPlus and add an integer if found in the existing_dict.
339
+
340
+ This includes stripping out all illegal characters and removing trailing white spaces.
341
+ Strings longer than 95 characters will be truncated before adding the integer.
342
+
343
+ Args:
344
+ value: The text string to be cleaned and possibly given a unique integer.
345
+ existing_dict: A dictionary where the keys are text strings of existing items
346
+ and the values are the number of times that the item has appeared in
347
+ the model already.
348
+ """
349
+ try:
350
+ val = ''.join(i for i in value if ord(i) < 128) # strip out non-ascii
351
+ val = re.sub(r'[,;!\n\t]', '', val) # strip out E+ special characters
352
+ except TypeError:
353
+ raise TypeError('Input {} must be a text string. Got {}: {}.'.format(
354
+ input_name, type(value), value))
355
+ val = val.strip()
356
+ if len(val) > 95:
357
+ val = val[:95]
358
+ if val in existing_dict:
359
+ existing_dict[val] += 1
360
+ return val + ' ' + str(existing_dict[val])
361
+ else:
362
+ existing_dict[val] = 1
363
+ return val
364
+
365
+
366
+ def truncate_and_id_string(value, truncate_len=32, uuid_len=0, input_name=''):
367
+ """Truncate a string to a length with an option to add unique characters at the end.
368
+
369
+ Note that all outputs will always be the truncate_len or less and the uuid_len
370
+ just specifies the number of characters to replace at the end with unique ones.
371
+
372
+ The result will be valid for EnergyPlus, Radiance, and likely many more engines
373
+ with different types of character restrictions.
374
+ """
375
+ try:
376
+ value = value.replace(' ', '_') # spaces > underscores for readability
377
+ val = re.sub(r'[^.A-Za-z0-9_-]', '', value)
378
+ except TypeError:
379
+ raise TypeError('Input {} must be a text string. Got {}: {}.'.format(
380
+ input_name, type(value), value))
381
+ final_len = truncate_len - uuid_len
382
+ if len(val) > final_len:
383
+ val = val[:final_len]
384
+ if uuid_len > 0:
385
+ return val + str(uuid.uuid4())[:uuid_len]
386
+ return val
387
+
388
+
389
+ def fixed_string_length(value, target_len=32):
390
+ """Truncate a string or add trailing spaces to hit a target character length.
391
+
392
+ This is useful when trying to construct human-readable tables of text.
393
+ """
394
+ if len(value) > target_len:
395
+ return value[:target_len]
396
+ elif len(value) < target_len:
397
+ return value + ' ' * (target_len - len(value))
398
+ else:
399
+ return value
400
+
401
+
402
+ def readable_short_name(value, max_length=24):
403
+ """Convert a string into a shorter but readable version of itself.
404
+
405
+ This is useful when dealing with interfaces of file formats that have very
406
+ strict character limits on names or identifiers (like in DOE-2/eQuest).
407
+
408
+ When ths input is less than or equal to the max length, the string will be
409
+ left as-is. If not, then the lower-case vowels will be removed from the name,
410
+ making the result abbreviated but still readable/recognizable. If the result
411
+ is still not shorter than the max length, then spaces will be removed. Lastly,
412
+ if all else fails to meet the max length, the middle characters will be,
413
+ removed leaving the beginning and end as they are, which should typically
414
+ help preserve the uniqueness of the name.
415
+
416
+ Note that this method does not do any check for illegal characters and presumes
417
+ that the input is already composed of legal characters.
418
+ """
419
+ # perform an initial check to see if it passes
420
+ if len(value) <= max_length:
421
+ return value
422
+ # strip out lowercase vowels and special characters like dashes
423
+ try:
424
+ value = re.sub(r'[aeiouy_\-]', '', value)
425
+ except TypeError:
426
+ raise TypeError('Input must be a text string. Got {}: {}.'.format(
427
+ type(value), value))
428
+ if len(value) <= max_length:
429
+ return value
430
+ # remove spaces from the string to see if it gets short enough
431
+ value = value.replace(' ', '')
432
+ if len(value) <= max_length:
433
+ return value
434
+ # lastly, remove some characters from the middle to get it to fit
435
+ mid_ind = int(max_length * 0.5)
436
+ assert mid_ind > 3, \
437
+ 'Max character length of {} is too restrictive.'.format(max_length)
438
+ end_length = max_length - mid_ind - 1
439
+ value = '{}_{}'.format(value[:mid_ind], value[-end_length:])
440
+ return value
441
+
442
+
443
+ def clean_doe2_string(value, max_length=24):
444
+ """Clean and shorten a string for DOE-2 so that it can be a U-name.
445
+
446
+ This includes stripping out all illegal characters (including both non-ASCII
447
+ and DOE-2 specific characters), removing trailing spaces, and passing the
448
+ result through the readable_short_name function to hit the target max_length.
449
+ Note that white spaces can be in the result with the assumption that
450
+ the name will be enclosed in double quotes.
451
+
452
+ Note that this method does not do any check to ensure the string is unique.
453
+ """
454
+ try:
455
+ val = ''.join(i for i in value if ord(i) < 128) # strip out non-ascii
456
+ val = re.sub(r'["\(\)\[\]\,\=\n\t]', '', val) # remove DOE-2 special characters
457
+ val = val.replace('_', ' ') # put back white spaces
458
+ except TypeError:
459
+ raise TypeError('Input must be a text string. Got {}: {}.'.format(
460
+ type(value), value))
461
+ val = val.strip()
462
+ if len(val) == 0: # generate a unique but consistent ID from the input
463
+ sha256_hash = hashlib.sha256(value.encode('utf-8'))
464
+ hash_str = str(sha256_hash.hexdigest())
465
+ return hash_str[:8] if len(hash_str) > 8 else hash_str
466
+ return readable_short_name(val, max_length)
467
+
468
+
469
+ def invalid_dict_error(invalid_dict, error):
470
+ """Raise a ValueError for an invalid dictionary that failed to serialize.
471
+
472
+ This error message will include the identifier (and display_name) if they are
473
+ present within the invalid_dict, making it easier for ens users to find the
474
+ invalid object within large objects like Models.
475
+
476
+ Args:
477
+ invalid_dict: A dictionary of an invalid honeybee object that failed
478
+ to serialize.
479
+ error:
480
+ """
481
+ obj_type = invalid_dict['type'].replace('Abridged', '') \
482
+ if 'type' in invalid_dict else 'Honeybee Object'
483
+ obj_id = invalid_dict['identifier'] if 'identifier' in invalid_dict else ''
484
+ full_id = '{}[{}]'.format(invalid_dict['display_name'], obj_id) \
485
+ if 'display_name' in invalid_dict else obj_id
486
+ raise ValueError('{} "{}" is invalid:\n{}'.format(obj_type, full_id, error))
487
+
488
+
489
+ wrapper = '"' if os.name == 'nt' else '\''
490
+ """String wrapper."""
491
+
492
+
493
+ def normpath(value):
494
+ """Normalize path eliminating double slashes, etc and put it in quotes if needed."""
495
+ value = os.path.normpath(value)
496
+ if ' ' in value:
497
+ value = '{0}{1}{0}'.format(wrapper, value)
498
+ return value
honeybee/units.py ADDED
@@ -0,0 +1,88 @@
1
+ """Utility functions for converting and parsing units of length."""
2
+
3
+ # global properties to set all supported units
4
+ UNITS = ('Meters', 'Millimeters', 'Feet', 'Inches', 'Centimeters')
5
+ UNITS_ABBREVIATIONS = ('m', 'mm', 'ft', 'in', 'cm')
6
+ UNITS_TOLERANCES = {
7
+ 'Meters': 0.01,
8
+ 'Millimeters': 1.0,
9
+ 'Feet': 0.01,
10
+ 'Inches': 0.1,
11
+ 'Centimeters': 1.0
12
+ }
13
+
14
+
15
+ def conversion_factor_to_meters(units):
16
+ """Get the conversion factor to meters based on input units.
17
+
18
+ Args:
19
+ units: Text for the units. Choose from the following:
20
+
21
+ * Meters
22
+ * Millimeters
23
+ * Feet
24
+ * Inches
25
+ * Centimeters
26
+
27
+ Returns:
28
+ A number for the conversion factor, which should be multiplied by
29
+ all distance units taken from Rhino geometry in order to convert
30
+ them to meters.
31
+ """
32
+ if units == 'Meters':
33
+ return 1.0
34
+ elif units == 'Millimeters':
35
+ return 0.001
36
+ elif units == 'Feet':
37
+ return 0.3048
38
+ elif units == 'Inches':
39
+ return 0.0254
40
+ elif units == 'Centimeters':
41
+ return 0.01
42
+ else:
43
+ raise ValueError(
44
+ 'You are kidding me! What units are you using? {}?\n'
45
+ 'Please use one of the following: {}'.format(units, ' '.join(UNITS))
46
+ )
47
+
48
+
49
+ def parse_distance_string(distance_string, destination_units='Meters'):
50
+ """Parse a string of a distance value into a destination units system.
51
+
52
+ Args:
53
+ distance_string: Text for a distance value to be parsed into the
54
+ destination units. This can have the units at the end of
55
+ it (eg. "3ft"). If no units are included, the number will be
56
+ assumed to be in the destination units system.
57
+ destination_units: The destination units system to which the distance
58
+ string will be computed. (Default: Meters).
59
+
60
+ Returns:
61
+ A number for the distance in the destination_units.
62
+ """
63
+ # separate the distance string into a number and a unit abbreviation
64
+ distance_string = distance_string.strip().replace(',', '.')
65
+ try: # check if the distance string is just a number
66
+ return float(distance_string)
67
+ except ValueError: # it must have some units attached to it
68
+ for i, ua in enumerate(UNITS_ABBREVIATIONS):
69
+ try: # see if replacing the units yields a float
70
+ distance = float(distance_string.replace(ua, '', 1))
71
+ u_sys = UNITS[i]
72
+ break
73
+ except ValueError: # not the right type of units
74
+ pass
75
+ else: # we could not match the units system
76
+ raise ValueError(
77
+ 'Text string "{}" could not be decoded into a distance and a unit.\n'
78
+ 'Make sure your units are one of the following: {}'.format(
79
+ distance_string, ' '.join(UNITS_ABBREVIATIONS))
80
+ )
81
+
82
+ # process the number into the destination units system
83
+ if u_sys == destination_units:
84
+ return distance
85
+ con_factor = 1 / conversion_factor_to_meters(destination_units)
86
+ if u_sys != 'Meters':
87
+ con_factor = con_factor * conversion_factor_to_meters(u_sys)
88
+ return distance * con_factor
@@ -0,0 +1,7 @@
1
+ """Geometry Writers.
2
+
3
+ Note to developers:
4
+ Use this package to extend honeybee's geometry writers for new extensions.
5
+ Functions added to the respective module of a given geometry object
6
+ will show up under the `to` method of the given object.
7
+ """
@@ -0,0 +1,6 @@
1
+ """Aperture Writer.
2
+
3
+ Note to developers:
4
+ Use this module to extend honeybee's Aperture writer for new extensions.
5
+ (eg. adding `idf` to this module adds the method `Aperture.to.idf`)
6
+ """
@@ -0,0 +1,6 @@
1
+ """Door Writer.
2
+
3
+ Note to developers:
4
+ Use this module to extend honeybee's Door writer for new extensions.
5
+ (eg. adding `idf` to this module adds the method `Door.to.idf`)
6
+ """
@@ -0,0 +1,6 @@
1
+ """Face Writer.
2
+
3
+ Note to developers:
4
+ Use this module to extend honeybee's Face writer for new extensions.
5
+ (eg. adding `idf` to this module adds the method `Face.to.idf`)
6
+ """
@@ -0,0 +1,6 @@
1
+ """Model Writer.
2
+
3
+ Note to developers:
4
+ Use this module to extend honeybee's Model writer for new extensions.
5
+ (eg. adding `idf` to this module adds the method `Model.to.idf`)
6
+ """
@@ -0,0 +1,6 @@
1
+ """Room Writer.
2
+
3
+ Note to developers:
4
+ Use this module to extend honeybee's Room writer for new extensions.
5
+ (eg. adding `idf` to this module adds the method `Room.to.idf`)
6
+ """
@@ -0,0 +1,6 @@
1
+ """Shade Writer.
2
+
3
+ Note to developers:
4
+ Use this module to extend honeybee's Shade writer for new extensions.
5
+ (eg. adding `idf` to this module adds the method `Shade.to.idf`)
6
+ """
@@ -0,0 +1,6 @@
1
+ """ShadeMesh Writer.
2
+
3
+ Note to developers:
4
+ Use this module to extend honeybee's ShadeMesh writer for new extensions.
5
+ (eg. adding `idf` to this module adds the method `ShadeMesh.to.idf`)
6
+ """