Skip to content

Commit

Permalink
fix(typing): Add methods for IDing with unique integer
Browse files Browse the repository at this point in the history
We'll make use of this in the export process to OpenStudio to give people the option of using display names.
  • Loading branch information
chriswmackey authored and Chris Mackey committed Sep 26, 2023
1 parent 7c1c9ce commit 29bf4d4
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 1 deletion.
16 changes: 15 additions & 1 deletion honeybee/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
from .shade import Shade
from .aperture import Aperture
from .door import Door
from .typing import float_positive, invalid_dict_error, clean_string
from .typing import float_positive, invalid_dict_error, clean_string, \
clean_and_number_string
from .config import folders
from .boundarycondition import Outdoors, Surface
from .facetype import AirBoundary, Wall, Floor, RoofCeiling, face_types
Expand Down Expand Up @@ -1203,6 +1204,19 @@ def add_prefix(self, prefix):
for door in self._orphaned_doors:
door.add_prefix(prefix)

def reset_room_ids(self):
"""Reset the identifiers of the Model Rooms to be derived from display_names.
In the event that duplicate Room identifiers are found in the Model, an
integer will be automatically appended to the new Room ID to make it
unique. This is similar to the routines that automatically assign unique
names to OpenStudio SDK objects.
"""
room_dict = {}
for room in self.rooms:
room.identifier = clean_and_number_string(
room.display_name, room_dict, 'Room identifier')

def solve_adjacency(
self, merge_coplanar=False, intersect=False, overwrite=False,
air_boundary=False, adiabatic=False,
Expand Down
79 changes: 79 additions & 0 deletions honeybee/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,85 @@ def clean_and_id_ep_string(value, input_name=''):
return val + '_' + str(uuid.uuid4())[:8]


def clean_and_number_string(value, existing_dict, input_name=''):
"""Clean a string and add an integer to it if it is found in the existing_dict.
The resulting string will be valid for both Radiance and EnergyPlus.
Args:
value: The text string to be cleaned and possibly given a unique integer.
existing_dict: A dictionary where the keys are text strings of existing items
and the values are the number of times that the item has appeared in
the model already.
"""
try:
val = re.sub(r'[^.A-Za-z0-9_-]', '_', value)
except TypeError:
raise TypeError('Input {} must be a text string. Got {}: {}.'.format(
input_name, type(value), value))
if len(val) > 95:
val = val[:95]
if val in existing_dict:
existing_dict[val] += 1
return val + '_' + str(existing_dict[val])
else:
existing_dict[val] = 1
return val


def clean_and_number_rad_string(value, existing_dict, input_name=''):
"""Clean a string for Radiance and add an integer if found in the existing_dict.
This includes stripping out illegal characters and white spaces.
Args:
value: The text string to be cleaned and possibly given a unique integer.
existing_dict: A dictionary where the keys are text strings of existing items
and the values are the number of times that the item has appeared in
the model already.
"""
try:
val = re.sub(r'[^.A-Za-z0-9_-]', '_', value)
except TypeError:
raise TypeError('Input {} must be a text string. Got {}: {}.'.format(
input_name, type(value), value))
if val in existing_dict:
existing_dict[val] += 1
return val + '_' + str(existing_dict[val])
else:
existing_dict[val] = 1
return val


def clean_and_number_ep_string(value, existing_dict, input_name=''):
"""Clean a string for EnergyPlus and add an integer if found in the existing_dict.
This includes stripping out all illegal characters and removing trailing white spaces.
Strings longer than 95 characters will be truncated before adding the integer.
Args:
value: The text string to be cleaned and possibly given a unique integer.
existing_dict: A dictionary where the keys are text strings of existing items
and the values are the number of times that the item has appeared in
the model already.
"""
try:
val = ''.join(i for i in value if ord(i) < 128) # strip out non-ascii
val = re.sub(r'[,;!\n\t]', '', val) # strip out E+ special characters
except TypeError:
raise TypeError('Input {} must be a text string. Got {}: {}.'.format(
input_name, type(value), value))
val = val.strip()
if len(val) > 95:
val = val[:95]
if val in existing_dict:
existing_dict[val] += 1
return val + ' ' + str(existing_dict[val])
else:
existing_dict[val] = 1
return val


def truncate_and_id_string(value, truncate_len=32, uuid_len=0, input_name=''):
"""Truncate a string to a length with an option to add unique characters at the end.
Expand Down
11 changes: 11 additions & 0 deletions tests/model_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,17 @@ def test_model_add_prefix():
assert shd.identifier.startswith(prefix)


def test_reset_room_ids():
"""Test the reset_room_ids method."""
model_json = './tests/json/model_with_adiabatic.hbjson'
parsed_model = Model.from_hbjson(model_json)

new_model = parsed_model.duplicate()
new_model.reset_room_ids()

assert new_model.rooms[0].identifier != parsed_model.rooms[0].identifier


def test_move():
"""Test the Model move method."""
room = Room.from_box('TinyHouseZone', 5, 10, 3)
Expand Down

0 comments on commit 29bf4d4

Please sign in to comment.