Skip to content

Commit

Permalink
feat(zone): Add a property for Room.zone
Browse files Browse the repository at this point in the history
This is just an initial commit so that we can get the ball rolling to implement zones in other repos.
  • Loading branch information
chriswmackey committed Dec 20, 2024
1 parent c374d3e commit 97ab1e0
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 7 deletions.
19 changes: 19 additions & 0 deletions honeybee/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -961,6 +961,25 @@ def top_level_dict(self):
base[sm.identifier] = sm
return base

@property
def has_zones(self):
"""Get a boolean for whether any Rooms in the model have zones assigned."""
return any(room._zone is not None for room in self._rooms)

@property
def zone_dict(self):
"""Get dictionary of Rooms with zone identifiers as the keys.
This is useful for grouping rooms by their Zone for export.
"""
zones = {}
for room in self.rooms:
try:
zones[room.zone].append(room)
except KeyError: # first room to be found in the zone
zones[room.zone] = [room]
return zones

def add_model(self, other_model):
"""Add another Model object to this model."""
assert isinstance(other_model, Model), \
Expand Down
48 changes: 42 additions & 6 deletions honeybee/room.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ class Room(_BaseWithShade):
* display_name
* faces
* multiplier
* zone
* story
* exclude_floor_area
* indoor_furniture
Expand Down Expand Up @@ -83,8 +84,9 @@ class Room(_BaseWithShade):
* user_data
"""
__slots__ = (
'_geometry', '_faces',
'_multiplier', '_story', '_exclude_floor_area', '_parent')
'_geometry', '_faces', '_multiplier', '_zone', '_story',
'_exclude_floor_area',
'_parent')

def __init__(self, identifier, faces, tolerance=0, angle_tolerance=0):
"""Initialize Room."""
Expand Down Expand Up @@ -124,6 +126,7 @@ def __init__(self, identifier, faces, tolerance=0, angle_tolerance=0):
self._geometry = room_polyface

self._multiplier = 1 # default value that can be overridden later
self._zone = None # default value that can be overridden later
self._story = None # default value that can be overridden later
self._exclude_floor_area = False # default value that can be overridden later
self._parent = None # completely hidden as it is only used by Dragonfly
Expand Down Expand Up @@ -164,6 +167,8 @@ def from_dict(cls, data, tolerance=0, angle_tolerance=0):
room.user_data = data['user_data']
if 'multiplier' in data and data['multiplier'] is not None:
room.multiplier = data['multiplier']
if 'zone' in data and data['zone'] is not None:
room.zone = data['zone']
if 'story' in data and data['story'] is not None:
room.story = data['story']
if 'exclude_floor_area' in data and data['exclude_floor_area'] is not None:
Expand Down Expand Up @@ -276,6 +281,31 @@ def multiplier(self):
def multiplier(self, value):
self._multiplier = int_in_range(value, 1, input_name='room multiplier')

@property
def zone(self):
"""Get or set text for the zone identifier to which this Room belongs.
Rooms sharing the same zone identifier are considered part of the same
zone in a Model. If the zone identifier has not been specified, it
will be the same as the Room identifier.
Note that the zone identifier has no character restrictions much
like display_name.
"""
if self._zone is None:
return self._identifier
return self._zone

@zone.setter
def zone(self, value):
if value is not None:
try:
self._zone = str(value)
except UnicodeEncodeError: # Python 2 machine lacking the character set
self._zone = value # keep it as unicode
else:
self._zone = value

@property
def story(self):
"""Get or set text for the story identifier to which this Room belongs.
Expand Down Expand Up @@ -1330,8 +1360,10 @@ def is_geo_equivalent(self, room, tolerance=0.01):
Returns:
True if geometrically equivalent. False if not geometrically equivalent.
"""
met_1 = (self.display_name, self.multiplier, self.story, self.exclude_floor_area)
met_2 = (room.display_name, room.multiplier, room.story, room.exclude_floor_area)
met_1 = (self.display_name, self.multiplier, self.zone, self.story,
self.exclude_floor_area)
met_2 = (room.display_name, room.multiplier, room.zone, room.story,
room.exclude_floor_area)
if met_1 != met_2:
return False
if len(self._faces) != len(room._faces):
Expand Down Expand Up @@ -2666,7 +2698,8 @@ def to_extrusion(self, tolerance=0.01, angle_tolerance=1.0):
ext_room._display_name = self._display_name
ext_room._user_data = None if self.user_data is None else self.user_data.copy()
ext_room._multiplier = self.multiplier
ext_room._story = self.story
ext_room._zone = self._zone
ext_room._story = self._story
ext_room._exclude_floor_area = self.exclude_floor_area
ext_room._properties._duplicate_extension_attr(self._properties)
return ext_room
Expand Down Expand Up @@ -2719,6 +2752,8 @@ def to_dict(self, abridged=False, included_prop=None, include_plane=True):
self._add_shades_to_dict(base, abridged, included_prop, include_plane)
if self.multiplier != 1:
base['multiplier'] = self.multiplier
if self._zone is not None:
base['zone'] = self.zone
if self.story is not None:
base['story'] = self.story
if self.exclude_floor_area:
Expand Down Expand Up @@ -2905,7 +2940,8 @@ def __copy__(self):
new_r._display_name = self._display_name
new_r._user_data = None if self.user_data is None else self.user_data.copy()
new_r._multiplier = self.multiplier
new_r._story = self.story
new_r._zone = self._zone
new_r._story = self._story
new_r._exclude_floor_area = self.exclude_floor_area
self._duplicate_child_shades(new_r)
new_r._geometry = self._geometry
Expand Down
9 changes: 8 additions & 1 deletion tests/model_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ def test_model_properties_setability():
assert model.angle_tolerance == 0.01
model.tolerance = None
assert model.tolerance == 0.01
assert not model.has_zones


def test_model_init_orphaned_objects():
Expand Down Expand Up @@ -146,15 +147,17 @@ def test_model_init_orphaned_objects():
assert len(model.orphaned_shades) == 2
assert len(model.orphaned_apertures) == 1
assert len(model.orphaned_doors) == 1
assert not model.has_zones


def test_adjacent_zone_model():
"""Test the solve adjacency method with an interior aperture."""
room_south = Room.from_box('SouthZone', 5, 5, 3, origin=Point3D(0, 0, 0))
room_north = Room.from_box('NorthZone', 5, 5, 3, origin=Point3D(0, 5, 0))
room_south.zone = 'FullHouse'
room_north.zone = 'FullHouse'
room_south[1].apertures_by_ratio(0.4, 0.01)
room_north[3].apertures_by_ratio(0.4, 0.01)

room_south[3].apertures_by_ratio(0.4, 0.01)
room_south[3].apertures[0].overhang(0.5, indoor=False)
room_south[3].apertures[0].overhang(0.5, indoor=True)
Expand All @@ -176,6 +179,8 @@ def test_adjacent_zone_model():
assert len(model.shades) == 2
assert len(model.apertures) == 4
assert len(model.doors) == 1
assert model.has_zones
assert len(model.zone_dict) == 1

model_dict = model.to_dict()
new_model = Model.from_dict(model_dict)
Expand All @@ -186,6 +191,8 @@ def test_adjacent_zone_model():
new_model.rooms[1][3].apertures[0].identifier
assert new_model.rooms[1][3].apertures[0].boundary_condition.boundary_condition_object == \
new_model.rooms[0][1].apertures[0].identifier
assert model.has_zones
assert len(model.zone_dict) == 1


def test_model_init_from_objects():
Expand Down

0 comments on commit 97ab1e0

Please sign in to comment.