honeybee-core 1.63.0__py3-none-any.whl → 1.64.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.
- honeybee/model.py +266 -46
- honeybee/room.py +192 -84
- honeybee/shademesh.py +17 -0
- {honeybee_core-1.63.0.dist-info → honeybee_core-1.64.1.dist-info}/METADATA +1 -1
- {honeybee_core-1.63.0.dist-info → honeybee_core-1.64.1.dist-info}/RECORD +9 -9
- {honeybee_core-1.63.0.dist-info → honeybee_core-1.64.1.dist-info}/WHEEL +0 -0
- {honeybee_core-1.63.0.dist-info → honeybee_core-1.64.1.dist-info}/entry_points.txt +0 -0
- {honeybee_core-1.63.0.dist-info → honeybee_core-1.64.1.dist-info}/licenses/LICENSE +0 -0
- {honeybee_core-1.63.0.dist-info → honeybee_core-1.64.1.dist-info}/top_level.txt +0 -0
honeybee/model.py
CHANGED
|
@@ -107,6 +107,18 @@ class Model(_Base):
|
|
|
107
107
|
* exterior_skylight_aperture_area
|
|
108
108
|
* min
|
|
109
109
|
* max
|
|
110
|
+
* roof_to_exterior_edges
|
|
111
|
+
* slab_to_exterior_edges
|
|
112
|
+
* exposed_floor_to_exterior_wall_edges
|
|
113
|
+
* exterior_wall_to_wall_edges
|
|
114
|
+
* roof_ridge_edges
|
|
115
|
+
* exposed_floor_to_floor_edges
|
|
116
|
+
* underground_edges
|
|
117
|
+
* interior_edges
|
|
118
|
+
* exterior_aperture_edges
|
|
119
|
+
* exterior_door_edges
|
|
120
|
+
* exterior_aperture_edges
|
|
121
|
+
* exterior_door_edges
|
|
110
122
|
* top_level_dict
|
|
111
123
|
* user_data
|
|
112
124
|
"""
|
|
@@ -1014,6 +1026,92 @@ class Model(_Base):
|
|
|
1014
1026
|
"""Get a Point3D for the max bounding box vertex in the XY plane."""
|
|
1015
1027
|
return self._calculate_max(self._all_objects())
|
|
1016
1028
|
|
|
1029
|
+
@property
|
|
1030
|
+
def roof_to_exterior_edges(self):
|
|
1031
|
+
"""Get LineSegment3Ds where roofs meet exterior walls (or floors).
|
|
1032
|
+
|
|
1033
|
+
Note that both the roof Face and the wall/floor Face must be next to one
|
|
1034
|
+
another in the model's outer envelope and have outdoor boundary conditions for
|
|
1035
|
+
the edge to show up in this list.
|
|
1036
|
+
"""
|
|
1037
|
+
return self.classified_envelope_edges()[0]
|
|
1038
|
+
|
|
1039
|
+
@property
|
|
1040
|
+
def slab_to_exterior_edges(self):
|
|
1041
|
+
"""Get LineSegment3Ds where ground floor slabs meet exterior walls or roofs.
|
|
1042
|
+
|
|
1043
|
+
Note that the floor Face must have a ground boundary condition and the wall or
|
|
1044
|
+
roof Face must have an outdoor boundary condition for the edge between the
|
|
1045
|
+
two Faces to show up in this list.
|
|
1046
|
+
"""
|
|
1047
|
+
return self.classified_envelope_edges()[1]
|
|
1048
|
+
|
|
1049
|
+
@property
|
|
1050
|
+
def exposed_floor_to_exterior_wall_edges(self):
|
|
1051
|
+
"""Get LineSegment3Ds where exposed floors meet exterior walls.
|
|
1052
|
+
|
|
1053
|
+
Note that both the wall Face and the floor Face must be next to one
|
|
1054
|
+
another in the model's outer envelope and have outdoor boundary conditions for
|
|
1055
|
+
the edge to show up in this list.
|
|
1056
|
+
"""
|
|
1057
|
+
return self.classified_envelope_edges()[2]
|
|
1058
|
+
|
|
1059
|
+
@property
|
|
1060
|
+
def exterior_wall_to_wall_edges(self):
|
|
1061
|
+
"""Get LineSegment3Ds where exterior walls meet one another.
|
|
1062
|
+
|
|
1063
|
+
Note that both wall Faces must be next to one another in the model's
|
|
1064
|
+
outer envelope and have outdoor boundary conditions for the edge to
|
|
1065
|
+
show up in this list.
|
|
1066
|
+
"""
|
|
1067
|
+
return self.classified_envelope_edges()[3]
|
|
1068
|
+
|
|
1069
|
+
@property
|
|
1070
|
+
def roof_ridge_edges(self):
|
|
1071
|
+
"""Get a list of LineSegment3D where exterior roofs meet one another.
|
|
1072
|
+
|
|
1073
|
+
Note that both roof Faces must be next to one another in the model's
|
|
1074
|
+
outer envelope and have outdoor boundary conditions for the edge to
|
|
1075
|
+
show up in this list.
|
|
1076
|
+
"""
|
|
1077
|
+
return self.classified_envelope_edges()[4]
|
|
1078
|
+
|
|
1079
|
+
@property
|
|
1080
|
+
def exposed_floor_to_floor_edges(self):
|
|
1081
|
+
"""Get LineSegment3Ds where exposed floors meet one another.
|
|
1082
|
+
|
|
1083
|
+
Note that both floor Faces must be next to one another in the model's
|
|
1084
|
+
outer envelope and have outdoor boundary conditions for the edge to
|
|
1085
|
+
show up in this list.
|
|
1086
|
+
"""
|
|
1087
|
+
return self.classified_envelope_edges()[5]
|
|
1088
|
+
|
|
1089
|
+
@property
|
|
1090
|
+
def underground_edges(self):
|
|
1091
|
+
"""Get a list of LineSegment3D where underground Faces meet one another.
|
|
1092
|
+
|
|
1093
|
+
Note that both Faces must be next to one another in the model's outer envelope
|
|
1094
|
+
and have ground boundary conditions for the edge to show up in this list.
|
|
1095
|
+
"""
|
|
1096
|
+
return self.classified_envelope_edges()[6]
|
|
1097
|
+
|
|
1098
|
+
@property
|
|
1099
|
+
def exterior_aperture_edges(self):
|
|
1100
|
+
"""Get a list of LineSegment3D for the borders around room exterior apertures.
|
|
1101
|
+
"""
|
|
1102
|
+
edges = []
|
|
1103
|
+
for room in self.rooms:
|
|
1104
|
+
edges.extend(room.exterior_aperture_edges)
|
|
1105
|
+
return edges
|
|
1106
|
+
|
|
1107
|
+
@property
|
|
1108
|
+
def exterior_door_edges(self):
|
|
1109
|
+
"""Get a list of LineSegment3D for the borders around room exterior doors."""
|
|
1110
|
+
edges = []
|
|
1111
|
+
for room in self.rooms:
|
|
1112
|
+
edges.extend(room.exterior_door_edges)
|
|
1113
|
+
return edges
|
|
1114
|
+
|
|
1017
1115
|
@property
|
|
1018
1116
|
def top_level_dict(self):
|
|
1019
1117
|
"""Get dictionary of top-level model objects with identifiers as the keys.
|
|
@@ -1378,6 +1476,63 @@ class Model(_Base):
|
|
|
1378
1476
|
)
|
|
1379
1477
|
return shades
|
|
1380
1478
|
|
|
1479
|
+
def classified_envelope_edges(self, tolerance=None, exclude_coplanar=False):
|
|
1480
|
+
"""Get classified edges of this Model's envelope based on Faces they adjoin.
|
|
1481
|
+
|
|
1482
|
+
The edges returned by this method will only exist along the exterior
|
|
1483
|
+
envelope of the Model's Rooms as defined by the contiguous volume across
|
|
1484
|
+
all Room interior adjacencies.
|
|
1485
|
+
|
|
1486
|
+
Args:
|
|
1487
|
+
tolerance: The maximum difference between point values for them to be
|
|
1488
|
+
considered equivalent. If None, the Model's tolerance will be used.
|
|
1489
|
+
exclude_coplanar: Boolean to note whether edges falling between two
|
|
1490
|
+
coplanar Faces in the building envelope should be included
|
|
1491
|
+
in the result (False) or excluded from it (True). (Default: False).
|
|
1492
|
+
|
|
1493
|
+
Returns:
|
|
1494
|
+
A tuple with eight items where each item is a list containing
|
|
1495
|
+
LineSegment3D adjoining different types of Faces.
|
|
1496
|
+
|
|
1497
|
+
- roof_to_exterior - Roofs meet exterior walls or floors.
|
|
1498
|
+
|
|
1499
|
+
- slab_to_exterior - Ground floor slabs meet exterior walls or roofs.
|
|
1500
|
+
|
|
1501
|
+
- exposed_floor_to_exterior_wall - Exposed floors meet exterior walls.
|
|
1502
|
+
|
|
1503
|
+
- exterior_wall_to_wall - Exterior walls meet.
|
|
1504
|
+
|
|
1505
|
+
- roof_ridge - Exterior roofs meet.
|
|
1506
|
+
|
|
1507
|
+
- exposed_floor_to_floor - Exposed floors meet.
|
|
1508
|
+
|
|
1509
|
+
- underground - Underground faces meet.
|
|
1510
|
+
"""
|
|
1511
|
+
# set up lists to be populated
|
|
1512
|
+
roof_to_exterior, slab_to_exterior, exposed_floor_to_exterior_wall = [], [], []
|
|
1513
|
+
exterior_wall_to_wall, roof_ridge, exposed_floor_to_floor = [], [], []
|
|
1514
|
+
underground, interior = [], []
|
|
1515
|
+
tol = tolerance if tolerance else self.tolerance
|
|
1516
|
+
ang_tol = self.angle_tolerance if exclude_coplanar else None
|
|
1517
|
+
|
|
1518
|
+
# join all of the rooms in the model across their adjacencies
|
|
1519
|
+
merged_rooms = Room.join_adjacent_rooms(self.rooms, tol)
|
|
1520
|
+
for room in merged_rooms:
|
|
1521
|
+
rf_to_ext, slb_to_ext, ex_flr_to_ext, ext_wl_to_wl, rf_ridge, \
|
|
1522
|
+
ex_flr_to_flr, under_gnd, inter = room.classified_edges(tol, ang_tol)
|
|
1523
|
+
roof_to_exterior.extend(rf_to_ext)
|
|
1524
|
+
slab_to_exterior.extend(slb_to_ext)
|
|
1525
|
+
exposed_floor_to_exterior_wall.extend(ex_flr_to_ext)
|
|
1526
|
+
exterior_wall_to_wall.extend(ext_wl_to_wl)
|
|
1527
|
+
roof_ridge.extend(rf_ridge)
|
|
1528
|
+
exposed_floor_to_floor.extend(ex_flr_to_flr)
|
|
1529
|
+
underground.extend(under_gnd)
|
|
1530
|
+
interior.extend(inter)
|
|
1531
|
+
|
|
1532
|
+
# return the classified edges
|
|
1533
|
+
return roof_to_exterior, slab_to_exterior, exposed_floor_to_exterior_wall, \
|
|
1534
|
+
exterior_wall_to_wall, roof_ridge, exposed_floor_to_floor, underground
|
|
1535
|
+
|
|
1381
1536
|
def add_prefix(self, prefix):
|
|
1382
1537
|
"""Change the identifier of this object and child objects by inserting a prefix.
|
|
1383
1538
|
|
|
@@ -1404,6 +1559,25 @@ class Model(_Base):
|
|
|
1404
1559
|
for shade_mesh in self._shade_meshes:
|
|
1405
1560
|
shade_mesh.add_prefix(prefix)
|
|
1406
1561
|
|
|
1562
|
+
def reset_room_ids(self):
|
|
1563
|
+
"""Reset the identifiers of the Model Rooms to be derived from display_names.
|
|
1564
|
+
|
|
1565
|
+
In the event that duplicate Room identifiers are found, an integer will
|
|
1566
|
+
be automatically appended to the new Room ID to make it unique.
|
|
1567
|
+
|
|
1568
|
+
Returns:
|
|
1569
|
+
A dictionary that relates the old identifiers (keys) to the new
|
|
1570
|
+
identifiers (values). This can be used to map between old and new
|
|
1571
|
+
objects and update things like Surface boundary conditions.
|
|
1572
|
+
"""
|
|
1573
|
+
room_dict, room_map = {}, {}
|
|
1574
|
+
for room in self.rooms:
|
|
1575
|
+
new_id = clean_and_number_string(
|
|
1576
|
+
room.display_name, room_dict, 'Room identifier')
|
|
1577
|
+
room_map[room.identifier] = new_id
|
|
1578
|
+
room.identifier = new_id
|
|
1579
|
+
return room_map
|
|
1580
|
+
|
|
1407
1581
|
def reset_ids(self, repair_surface_bcs=True):
|
|
1408
1582
|
"""Reset the identifiers of all Model objects to be derived from display_names.
|
|
1409
1583
|
|
|
@@ -1458,39 +1632,7 @@ class Model(_Base):
|
|
|
1458
1632
|
shade_mesh.display_name, sm_dict, 'ShadeMesh identifier')
|
|
1459
1633
|
# reset all of the Surface boundary conditions if requested
|
|
1460
1634
|
if repair_surface_bcs:
|
|
1461
|
-
|
|
1462
|
-
for face in room.faces:
|
|
1463
|
-
if isinstance(face.boundary_condition, Surface):
|
|
1464
|
-
old_objs = face.boundary_condition.boundary_condition_objects
|
|
1465
|
-
try:
|
|
1466
|
-
new_objs = (face_map[old_objs[0]], room_map[old_objs[1]])
|
|
1467
|
-
except KeyError: # missing adjacency
|
|
1468
|
-
try: # see if maybe the room reference is still there
|
|
1469
|
-
new_objs = (old_objs[0], room_map[old_objs[1]])
|
|
1470
|
-
except KeyError: # just let the invalid adjacency pass
|
|
1471
|
-
continue
|
|
1472
|
-
new_bc = Surface(new_objs)
|
|
1473
|
-
face.boundary_condition = new_bc
|
|
1474
|
-
for ap in face.apertures:
|
|
1475
|
-
old_objs = ap.boundary_condition.boundary_condition_objects
|
|
1476
|
-
try:
|
|
1477
|
-
new_objs = (ap_map[old_objs[0]], face_map[old_objs[1]],
|
|
1478
|
-
room_map[old_objs[2]])
|
|
1479
|
-
except KeyError: # missing adjacency
|
|
1480
|
-
new_objs = (old_objs[0], old_objs[1],
|
|
1481
|
-
room_map[old_objs[2]])
|
|
1482
|
-
new_bc = Surface(new_objs, True)
|
|
1483
|
-
ap.boundary_condition = new_bc
|
|
1484
|
-
for dr in face.doors:
|
|
1485
|
-
old_objs = dr.boundary_condition.boundary_condition_objects
|
|
1486
|
-
try:
|
|
1487
|
-
new_objs = (dr_map[old_objs[0]], face_map[old_objs[1]],
|
|
1488
|
-
room_map[old_objs[2]])
|
|
1489
|
-
except KeyError: # missing adjacency
|
|
1490
|
-
new_objs = (old_objs[0], old_objs[1],
|
|
1491
|
-
room_map[old_objs[2]])
|
|
1492
|
-
new_bc = Surface(new_objs, True)
|
|
1493
|
-
dr.boundary_condition = new_bc
|
|
1635
|
+
self._repair_surface_bcs(room_map, face_map, ap_map, dr_map)
|
|
1494
1636
|
# return a dictionary that maps between old and new IDs
|
|
1495
1637
|
return {
|
|
1496
1638
|
'rooms': room_map,
|
|
@@ -1499,24 +1641,90 @@ class Model(_Base):
|
|
|
1499
1641
|
'doors': dr_map
|
|
1500
1642
|
}
|
|
1501
1643
|
|
|
1502
|
-
def
|
|
1503
|
-
"""Reset the identifiers of
|
|
1644
|
+
def reset_ids_to_integers(self, start_integer=0, repair_surface_bcs=True):
|
|
1645
|
+
"""Reset the identifiers of all Model geometry objects to be a unique integer.
|
|
1504
1646
|
|
|
1505
|
-
|
|
1506
|
-
|
|
1647
|
+
Integers are simply incremented from the start_integer, assigning integers
|
|
1648
|
+
first to Rooms, then to Faces, then to Apertures/Doors and lastly to
|
|
1649
|
+
Shades/ShadeMeshes.
|
|
1650
|
+
|
|
1651
|
+
Args:
|
|
1652
|
+
start_integer: The starting integer that will be used to set a lower
|
|
1653
|
+
limit on the
|
|
1654
|
+
repair_surface_bcs: A Boolean to note whether all Surface boundary
|
|
1655
|
+
conditions across the model should be updated with the new
|
|
1656
|
+
identifiers that were generated from the display names. (Default: True).
|
|
1507
1657
|
|
|
1508
1658
|
Returns:
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1659
|
+
An integer for the last value assigned to the model geometry objects.
|
|
1660
|
+
This can be used to ensure that any future IDs assigned after running
|
|
1661
|
+
this method do not have IDs that collide with the model objects.
|
|
1512
1662
|
"""
|
|
1513
|
-
|
|
1663
|
+
# set up dictionaries to hold various pieces of information
|
|
1664
|
+
room_map, face_map, ap_map, dr_map = {}, {}, {}, {}
|
|
1665
|
+
# loop through the objects and change their identifiers
|
|
1514
1666
|
for room in self.rooms:
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1667
|
+
room_map[room.identifier] = str(start_integer)
|
|
1668
|
+
room.identifier = str(start_integer)
|
|
1669
|
+
start_integer += 1
|
|
1670
|
+
for face in self.faces:
|
|
1671
|
+
face_map[face.identifier] = str(start_integer)
|
|
1672
|
+
face.identifier = str(start_integer)
|
|
1673
|
+
start_integer += 1
|
|
1674
|
+
for ap in self.apertures:
|
|
1675
|
+
ap_map[ap.identifier] = str(start_integer)
|
|
1676
|
+
ap.identifier = str(start_integer)
|
|
1677
|
+
start_integer += 1
|
|
1678
|
+
for dr in self.doors:
|
|
1679
|
+
dr_map[dr.identifier] = str(start_integer)
|
|
1680
|
+
dr.identifier = str(start_integer)
|
|
1681
|
+
start_integer += 1
|
|
1682
|
+
for shade in self.shades:
|
|
1683
|
+
shade.identifier = str(start_integer)
|
|
1684
|
+
start_integer += 1
|
|
1685
|
+
for shade_mesh in self.shade_meshes:
|
|
1686
|
+
shade_mesh.identifier = str(start_integer)
|
|
1687
|
+
start_integer += 1
|
|
1688
|
+
# reset all of the Surface boundary conditions if requested
|
|
1689
|
+
if repair_surface_bcs:
|
|
1690
|
+
self._repair_surface_bcs(room_map, face_map, ap_map, dr_map)
|
|
1691
|
+
return start_integer
|
|
1692
|
+
|
|
1693
|
+
def _repair_surface_bcs(self, room_map, face_map, ap_map, dr_map):
|
|
1694
|
+
"""Repair Surface boundary conditions across the model using dict maps."""
|
|
1695
|
+
for room in self.rooms:
|
|
1696
|
+
for face in room.faces:
|
|
1697
|
+
if isinstance(face.boundary_condition, Surface):
|
|
1698
|
+
old_objs = face.boundary_condition.boundary_condition_objects
|
|
1699
|
+
try:
|
|
1700
|
+
new_objs = (face_map[old_objs[0]], room_map[old_objs[1]])
|
|
1701
|
+
except KeyError: # missing adjacency
|
|
1702
|
+
try: # see if maybe the room reference is still there
|
|
1703
|
+
new_objs = (old_objs[0], room_map[old_objs[1]])
|
|
1704
|
+
except KeyError: # just let the invalid adjacency pass
|
|
1705
|
+
continue
|
|
1706
|
+
new_bc = Surface(new_objs)
|
|
1707
|
+
face.boundary_condition = new_bc
|
|
1708
|
+
for ap in face.apertures:
|
|
1709
|
+
old_objs = ap.boundary_condition.boundary_condition_objects
|
|
1710
|
+
try:
|
|
1711
|
+
new_objs = (ap_map[old_objs[0]], face_map[old_objs[1]],
|
|
1712
|
+
room_map[old_objs[2]])
|
|
1713
|
+
except KeyError: # missing adjacency
|
|
1714
|
+
new_objs = (old_objs[0], old_objs[1],
|
|
1715
|
+
room_map[old_objs[2]])
|
|
1716
|
+
new_bc = Surface(new_objs, True)
|
|
1717
|
+
ap.boundary_condition = new_bc
|
|
1718
|
+
for dr in face.doors:
|
|
1719
|
+
old_objs = dr.boundary_condition.boundary_condition_objects
|
|
1720
|
+
try:
|
|
1721
|
+
new_objs = (dr_map[old_objs[0]], face_map[old_objs[1]],
|
|
1722
|
+
room_map[old_objs[2]])
|
|
1723
|
+
except KeyError: # missing adjacency
|
|
1724
|
+
new_objs = (old_objs[0], old_objs[1],
|
|
1725
|
+
room_map[old_objs[2]])
|
|
1726
|
+
new_bc = Surface(new_objs, True)
|
|
1727
|
+
dr.boundary_condition = new_bc
|
|
1520
1728
|
|
|
1521
1729
|
def solve_adjacency(
|
|
1522
1730
|
self, merge_coplanar=False, intersect=False, overwrite=False,
|
|
@@ -2026,6 +2234,18 @@ class Model(_Base):
|
|
|
2026
2234
|
extrusion_rooms.append(room.to_extrusion(tol, a_tol))
|
|
2027
2235
|
self._rooms = extrusion_rooms
|
|
2028
2236
|
|
|
2237
|
+
def shade_meshes_to_shades(self):
|
|
2238
|
+
"""Convert all ShadeMesh objects on the Model to planar Shades."""
|
|
2239
|
+
new_shades = []
|
|
2240
|
+
for shade_mesh in self.shade_meshes:
|
|
2241
|
+
try:
|
|
2242
|
+
shade_mesh.triangulate_and_remove_degenerate_faces(self.tolerance)
|
|
2243
|
+
new_shades.extend(shade_mesh.to_shades())
|
|
2244
|
+
except AssertionError:
|
|
2245
|
+
pass # completely degenerate ShadeMesh to ignore
|
|
2246
|
+
self._orphaned_shades.extend(new_shades)
|
|
2247
|
+
self._shade_meshes = []
|
|
2248
|
+
|
|
2029
2249
|
def convert_to_units(self, units='Meters'):
|
|
2030
2250
|
"""Convert all of the geometry in this model to certain units.
|
|
2031
2251
|
|
honeybee/room.py
CHANGED
|
@@ -8,6 +8,7 @@ import uuid
|
|
|
8
8
|
from ladybug_geometry.geometry2d import Point2D, Vector2D, Polygon2D
|
|
9
9
|
from ladybug_geometry.geometry3d import Point3D, Vector3D, Ray3D, Plane, Face3D, \
|
|
10
10
|
Mesh3D, Polyface3D
|
|
11
|
+
from ladybug_geometry.bounding import overlapping_bounding_boxes
|
|
11
12
|
from ladybug_geometry_polyskel.polysplit import perimeter_core_subpolygons
|
|
12
13
|
|
|
13
14
|
import honeybee.writer.room as writer
|
|
@@ -71,6 +72,8 @@ class Room(_BaseWithShade):
|
|
|
71
72
|
* center
|
|
72
73
|
* min
|
|
73
74
|
* max
|
|
75
|
+
* exterior_aperture_edges
|
|
76
|
+
* exterior_door_edges
|
|
74
77
|
* volume
|
|
75
78
|
* floor_area
|
|
76
79
|
* exposed_area
|
|
@@ -430,6 +433,24 @@ class Room(_BaseWithShade):
|
|
|
430
433
|
all_geo.extend(self._faces)
|
|
431
434
|
return self._calculate_max(all_geo)
|
|
432
435
|
|
|
436
|
+
@property
|
|
437
|
+
def exterior_aperture_edges(self):
|
|
438
|
+
"""Get a list of LineSegment3D for the borders around exterior apertures."""
|
|
439
|
+
edges = []
|
|
440
|
+
for ap in self.apertures:
|
|
441
|
+
if isinstance(ap.boundary_condition, Outdoors):
|
|
442
|
+
edges.extend(ap.geometry.segments)
|
|
443
|
+
return edges
|
|
444
|
+
|
|
445
|
+
@property
|
|
446
|
+
def exterior_door_edges(self):
|
|
447
|
+
"""Get a list of LineSegment3D for the borders around exterior doors."""
|
|
448
|
+
edges = []
|
|
449
|
+
for dr in self.doors:
|
|
450
|
+
if isinstance(dr.boundary_condition, Outdoors):
|
|
451
|
+
edges.extend(dr.geometry.segments)
|
|
452
|
+
return edges
|
|
453
|
+
|
|
433
454
|
@property
|
|
434
455
|
def volume(self):
|
|
435
456
|
"""Get the volume of the room.
|
|
@@ -584,101 +605,92 @@ class Room(_BaseWithShade):
|
|
|
584
605
|
areas += face.area
|
|
585
606
|
return orientations / areas if areas != 0 else None
|
|
586
607
|
|
|
587
|
-
def
|
|
588
|
-
"""
|
|
589
|
-
|
|
590
|
-
This is particularly useful in workflows where you duplicate and edit
|
|
591
|
-
a starting object and then want to combine it with the original object
|
|
592
|
-
into one Model (like making a model of repeated rooms) since all objects
|
|
593
|
-
within a Model must have unique identifiers.
|
|
608
|
+
def classified_edges(self, tolerance=0.01, angle_tolerance=None):
|
|
609
|
+
"""Get classified edges of this Room's Polyface3D based on Faces they adjoin.
|
|
594
610
|
|
|
595
611
|
Args:
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
self._identifier = clean_string('{}_{}'.format(prefix, self.identifier))
|
|
602
|
-
self.display_name = '{}_{}'.format(prefix, self.display_name)
|
|
603
|
-
self.properties.add_prefix(prefix)
|
|
604
|
-
for face in self._faces:
|
|
605
|
-
face.add_prefix(prefix)
|
|
606
|
-
self._add_prefix_shades(prefix)
|
|
612
|
+
tolerance: The maximum difference between point values for them to be
|
|
613
|
+
considered equivalent. (Default: 0.01, suitable for objects in meters).
|
|
614
|
+
angle_tolerance: An optional value in degrees, which can be used to
|
|
615
|
+
exclude edges falling between coplanar Faces. If None, edges falling
|
|
616
|
+
between coplanar Faces will be included. (Default: None).
|
|
607
617
|
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
"""Set the display name of this Room using a format string with Room attributes.
|
|
618
|
+
Returns:
|
|
619
|
+
A tuple with eight items where each item is a list containing
|
|
620
|
+
LineSegment3D adjoining different types of Faces.
|
|
612
621
|
|
|
613
|
-
|
|
614
|
-
format_str: Text string for the pattern with which the Room will be
|
|
615
|
-
renamed. Any property on this class may be used (eg. story)
|
|
616
|
-
and each property should be put in curly brackets. Nested
|
|
617
|
-
properties can be specified by using "." to denote nesting levels
|
|
618
|
-
(eg. properties.energy.program_type.display_name). Functions that
|
|
619
|
-
return string outputs can also be passed here as long as these
|
|
620
|
-
functions defaults specified for all arguments.
|
|
621
|
-
"""
|
|
622
|
-
matches = re.findall(r'{([^}]*)}', format_str)
|
|
623
|
-
attributes = [get_attr_nested(self, m) for m in matches]
|
|
624
|
-
for attr_name, attr_val in zip(matches, attributes):
|
|
625
|
-
format_str = format_str.replace('{{{}}}'.format(attr_name), attr_val)
|
|
626
|
-
self.display_name = format_str
|
|
627
|
-
return format_str
|
|
622
|
+
- roof_to_exterior - Roofs meet exterior walls or floors.
|
|
628
623
|
|
|
629
|
-
|
|
630
|
-
self,
|
|
631
|
-
format_str='{parent.display_name} - {gbxml_type} - {cardinal_direction}'
|
|
632
|
-
):
|
|
633
|
-
"""Set the display name for all of this Room's faces using a format string.
|
|
624
|
+
- slab_to_exterior - Ground floor slabs meet exterior walls or roofs.
|
|
634
625
|
|
|
635
|
-
|
|
636
|
-
format_str: Text string for the pattern with which the faces will be
|
|
637
|
-
renamed. Any property of the Face class may be used (eg. gbxml_str)
|
|
638
|
-
and each property should be put in curly brackets. Nested
|
|
639
|
-
properties can be specified by using "." to denote nesting levels
|
|
640
|
-
(eg. properties.energy.construction.display_name). Functions that
|
|
641
|
-
return string outputs can also be passed here as long as these
|
|
642
|
-
functions defaults specified for all arguments.
|
|
643
|
-
"""
|
|
644
|
-
for face in self.faces:
|
|
645
|
-
face.rename_by_attribute(format_str)
|
|
626
|
+
- exposed_floor_to_exterior_wall - Exposed floors meet exterior walls.
|
|
646
627
|
|
|
647
|
-
|
|
648
|
-
self,
|
|
649
|
-
format_str='{parent.parent.display_name} - {gbxml_type} - {cardinal_direction}'
|
|
650
|
-
):
|
|
651
|
-
"""Set the display name for all of this Room's apertures using a format string.
|
|
628
|
+
- exterior_wall_to_wall - Exterior walls meet.
|
|
652
629
|
|
|
653
|
-
|
|
654
|
-
format_str: Text string for the pattern with which the apertures will be
|
|
655
|
-
renamed. Any property on the Aperture class may be used (eg. gbxml_str)
|
|
656
|
-
and each property should be put in curly brackets. Nested
|
|
657
|
-
properties can be specified by using "." to denote nesting levels
|
|
658
|
-
(eg. properties.energy.construction.display_name). Functions that
|
|
659
|
-
return string outputs can also be passed here as long as these
|
|
660
|
-
functions defaults specified for all arguments.
|
|
661
|
-
"""
|
|
662
|
-
for ap in self.apertures:
|
|
663
|
-
ap.rename_by_attribute(format_str)
|
|
630
|
+
- roof_ridge - Exterior roofs meet.
|
|
664
631
|
|
|
665
|
-
|
|
666
|
-
self,
|
|
667
|
-
format_str='{parent.parent.display_name} - {energyplus_type} - {cardinal_direction}'
|
|
668
|
-
):
|
|
669
|
-
"""Set the display name for all of this Room's doors using a format string.
|
|
632
|
+
- exposed_floor_to_floor - Exposed floors meet.
|
|
670
633
|
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
and each property should be put in curly brackets. Nested
|
|
675
|
-
properties can be specified by using "." to denote nesting levels
|
|
676
|
-
(eg. properties.energy.construction.display_name). Functions that
|
|
677
|
-
return string outputs can also be passed here as long as these
|
|
678
|
-
functions defaults specified for all arguments.
|
|
634
|
+
- underground - Underground faces meet.
|
|
635
|
+
|
|
636
|
+
- interior - Interior faces meet.
|
|
679
637
|
"""
|
|
680
|
-
|
|
681
|
-
|
|
638
|
+
# set up lists to be populated
|
|
639
|
+
roof_to_exterior, slab_to_exterior, exposed_floor_to_exterior_wall = [], [], []
|
|
640
|
+
exterior_wall_to_wall, roof_ridge, exposed_floor_to_floor = [], [], []
|
|
641
|
+
underground, interior = [], []
|
|
642
|
+
|
|
643
|
+
# get all of the edges and map them to room faces
|
|
644
|
+
edges = self.geometry.internal_edges
|
|
645
|
+
edge_faces = [[] for _ in edges]
|
|
646
|
+
for i, edge in enumerate(edges):
|
|
647
|
+
for face in self.faces:
|
|
648
|
+
if overlapping_bounding_boxes(face.geometry, edge, tolerance):
|
|
649
|
+
for f_edge in face.geometry.segments:
|
|
650
|
+
if edge.distance_to_point(f_edge.p1) < tolerance and \
|
|
651
|
+
edge.distance_to_point(f_edge.p2) < tolerance:
|
|
652
|
+
edge_faces[i].append(face)
|
|
653
|
+
break
|
|
654
|
+
|
|
655
|
+
# classify the edges by analyzing the faces they adjoin
|
|
656
|
+
for edge, faces in zip(edges, edge_faces):
|
|
657
|
+
# first check for cases where the edge should be excluded
|
|
658
|
+
if len(edge_faces) <= 1: # not an edge between two faces
|
|
659
|
+
continue
|
|
660
|
+
if angle_tolerance is not None:
|
|
661
|
+
ang_tol = math.radians(angle_tolerance)
|
|
662
|
+
base_normal = faces[0].normal
|
|
663
|
+
if all(f.normal.angle(base_normal) < ang_tol for f in faces[1:]):
|
|
664
|
+
continue
|
|
665
|
+
|
|
666
|
+
# then check for which category the edge should go into
|
|
667
|
+
ext_faces = [f for f in faces if isinstance(f.boundary_condition, Outdoors)
|
|
668
|
+
and not isinstance(f.type, AirBoundary)]
|
|
669
|
+
if len(ext_faces) >= 2: # some type of exterior edge
|
|
670
|
+
if all(isinstance(f.type, Wall) for f in ext_faces):
|
|
671
|
+
exterior_wall_to_wall.append(edge)
|
|
672
|
+
elif all(isinstance(f.type, RoofCeiling) for f in ext_faces):
|
|
673
|
+
roof_ridge.append(edge)
|
|
674
|
+
elif all(isinstance(f.type, Floor) for f in ext_faces):
|
|
675
|
+
exposed_floor_to_floor.append(edge)
|
|
676
|
+
elif any(isinstance(f.type, RoofCeiling) for f in ext_faces):
|
|
677
|
+
roof_to_exterior.append(edge)
|
|
678
|
+
elif any(isinstance(f.type, Floor) for f in ext_faces):
|
|
679
|
+
exposed_floor_to_exterior_wall.append(edge)
|
|
680
|
+
else:
|
|
681
|
+
gnd_faces = [f for f in faces if isinstance(f.boundary_condition, Ground)
|
|
682
|
+
and not isinstance(f.type, AirBoundary)]
|
|
683
|
+
if len(ext_faces) >= 1 and len(gnd_faces) >= 1:
|
|
684
|
+
slab_to_exterior.append(edge)
|
|
685
|
+
elif len(gnd_faces) >= 2: # some type of underground edge
|
|
686
|
+
underground.append(edge)
|
|
687
|
+
else: # some type of interior edge
|
|
688
|
+
interior.append(edge)
|
|
689
|
+
|
|
690
|
+
# return the classified edges
|
|
691
|
+
return roof_to_exterior, slab_to_exterior, exposed_floor_to_exterior_wall, \
|
|
692
|
+
exterior_wall_to_wall, roof_ridge, exposed_floor_to_floor, \
|
|
693
|
+
underground, interior
|
|
682
694
|
|
|
683
695
|
def horizontal_boundary(self, match_walls=False, tolerance=0.01):
|
|
684
696
|
"""Get a Face3D representing the horizontal boundary around the Room.
|
|
@@ -774,6 +786,102 @@ class Room(_BaseWithShade):
|
|
|
774
786
|
return self._match_walls_to_horizontal_faces(horiz_bound, tolerance)
|
|
775
787
|
return horiz_bound
|
|
776
788
|
|
|
789
|
+
def add_prefix(self, prefix):
|
|
790
|
+
"""Change the identifier of this object and child objects by inserting a prefix.
|
|
791
|
+
|
|
792
|
+
This is particularly useful in workflows where you duplicate and edit
|
|
793
|
+
a starting object and then want to combine it with the original object
|
|
794
|
+
into one Model (like making a model of repeated rooms) since all objects
|
|
795
|
+
within a Model must have unique identifiers.
|
|
796
|
+
|
|
797
|
+
Args:
|
|
798
|
+
prefix: Text that will be inserted at the start of this object's
|
|
799
|
+
(and child objects') identifier and display_name. It is recommended
|
|
800
|
+
that this prefix be short to avoid maxing out the 100 allowable
|
|
801
|
+
characters for honeybee identifiers.
|
|
802
|
+
"""
|
|
803
|
+
self._identifier = clean_string('{}_{}'.format(prefix, self.identifier))
|
|
804
|
+
self.display_name = '{}_{}'.format(prefix, self.display_name)
|
|
805
|
+
self.properties.add_prefix(prefix)
|
|
806
|
+
for face in self._faces:
|
|
807
|
+
face.add_prefix(prefix)
|
|
808
|
+
self._add_prefix_shades(prefix)
|
|
809
|
+
|
|
810
|
+
def rename_by_attribute(
|
|
811
|
+
self, format_str='{story} - {display_name}'
|
|
812
|
+
):
|
|
813
|
+
"""Set the display name of this Room using a format string with Room attributes.
|
|
814
|
+
|
|
815
|
+
Args:
|
|
816
|
+
format_str: Text string for the pattern with which the Room will be
|
|
817
|
+
renamed. Any property on this class may be used (eg. story)
|
|
818
|
+
and each property should be put in curly brackets. Nested
|
|
819
|
+
properties can be specified by using "." to denote nesting levels
|
|
820
|
+
(eg. properties.energy.program_type.display_name). Functions that
|
|
821
|
+
return string outputs can also be passed here as long as these
|
|
822
|
+
functions defaults specified for all arguments.
|
|
823
|
+
"""
|
|
824
|
+
matches = re.findall(r'{([^}]*)}', format_str)
|
|
825
|
+
attributes = [get_attr_nested(self, m) for m in matches]
|
|
826
|
+
for attr_name, attr_val in zip(matches, attributes):
|
|
827
|
+
format_str = format_str.replace('{{{}}}'.format(attr_name), attr_val)
|
|
828
|
+
self.display_name = format_str
|
|
829
|
+
return format_str
|
|
830
|
+
|
|
831
|
+
def rename_faces_by_attribute(
|
|
832
|
+
self,
|
|
833
|
+
format_str='{parent.display_name} - {gbxml_type} - {cardinal_direction}'
|
|
834
|
+
):
|
|
835
|
+
"""Set the display name for all of this Room's faces using a format string.
|
|
836
|
+
|
|
837
|
+
Args:
|
|
838
|
+
format_str: Text string for the pattern with which the faces will be
|
|
839
|
+
renamed. Any property of the Face class may be used (eg. gbxml_str)
|
|
840
|
+
and each property should be put in curly brackets. Nested
|
|
841
|
+
properties can be specified by using "." to denote nesting levels
|
|
842
|
+
(eg. properties.energy.construction.display_name). Functions that
|
|
843
|
+
return string outputs can also be passed here as long as these
|
|
844
|
+
functions defaults specified for all arguments.
|
|
845
|
+
"""
|
|
846
|
+
for face in self.faces:
|
|
847
|
+
face.rename_by_attribute(format_str)
|
|
848
|
+
|
|
849
|
+
def rename_apertures_by_attribute(
|
|
850
|
+
self,
|
|
851
|
+
format_str='{parent.parent.display_name} - {gbxml_type} - {cardinal_direction}'
|
|
852
|
+
):
|
|
853
|
+
"""Set the display name for all of this Room's apertures using a format string.
|
|
854
|
+
|
|
855
|
+
Args:
|
|
856
|
+
format_str: Text string for the pattern with which the apertures will be
|
|
857
|
+
renamed. Any property on the Aperture class may be used (eg. gbxml_str)
|
|
858
|
+
and each property should be put in curly brackets. Nested
|
|
859
|
+
properties can be specified by using "." to denote nesting levels
|
|
860
|
+
(eg. properties.energy.construction.display_name). Functions that
|
|
861
|
+
return string outputs can also be passed here as long as these
|
|
862
|
+
functions defaults specified for all arguments.
|
|
863
|
+
"""
|
|
864
|
+
for ap in self.apertures:
|
|
865
|
+
ap.rename_by_attribute(format_str)
|
|
866
|
+
|
|
867
|
+
def rename_doors_by_attribute(
|
|
868
|
+
self,
|
|
869
|
+
format_str='{parent.parent.display_name} - {energyplus_type} - {cardinal_direction}'
|
|
870
|
+
):
|
|
871
|
+
"""Set the display name for all of this Room's doors using a format string.
|
|
872
|
+
|
|
873
|
+
Args:
|
|
874
|
+
format_str: Text string for the pattern with which the doors will be
|
|
875
|
+
renamed. Any property on the Door class may be used (eg. gbxml_str)
|
|
876
|
+
and each property should be put in curly brackets. Nested
|
|
877
|
+
properties can be specified by using "." to denote nesting levels
|
|
878
|
+
(eg. properties.energy.construction.display_name). Functions that
|
|
879
|
+
return string outputs can also be passed here as long as these
|
|
880
|
+
functions defaults specified for all arguments.
|
|
881
|
+
"""
|
|
882
|
+
for dr in self.doors:
|
|
883
|
+
dr.rename_by_attribute(format_str)
|
|
884
|
+
|
|
777
885
|
def remove_indoor_furniture(self):
|
|
778
886
|
"""Remove all indoor furniture assigned to this Room.
|
|
779
887
|
|
honeybee/shademesh.py
CHANGED
|
@@ -324,6 +324,23 @@ class ShadeMesh(_Base):
|
|
|
324
324
|
base['user_data'] = self.user_data
|
|
325
325
|
return base
|
|
326
326
|
|
|
327
|
+
def to_shades(self):
|
|
328
|
+
"""Return a list of Honeybee Shade objects derived from this ShadeMesh.
|
|
329
|
+
|
|
330
|
+
Note that the resulting Shades may be degenerate or non-planar so it
|
|
331
|
+
may be useful to call triangulate_and_remove_degenerate_faces on this
|
|
332
|
+
object before converting to Shades.
|
|
333
|
+
"""
|
|
334
|
+
from honeybee.shade import Shade # imported here to avoid circular import
|
|
335
|
+
shades = []
|
|
336
|
+
for i, shade_geo in enumerate(self.geometry.face_vertices):
|
|
337
|
+
shade_id = '{}_{}'.format(self.identifier, i)
|
|
338
|
+
shade = Shade(shade_id, Face3D(shade_geo), self.is_detached)
|
|
339
|
+
if self._display_name is not None:
|
|
340
|
+
shade.display_name = '{} {}'.format(self.display_name, i)
|
|
341
|
+
shades.append(shade)
|
|
342
|
+
return shades
|
|
343
|
+
|
|
327
344
|
@staticmethod
|
|
328
345
|
def _display_mesh(mesh3d, color):
|
|
329
346
|
"""Create a DisplayMesh3D dictionary from a Mesh3D and color."""
|
|
@@ -16,13 +16,13 @@ honeybee/extensionutil.py,sha256=DDQYhM7tFD3avRSCOjwTzLqX9ldUxl5GzY79V3_sATE,964
|
|
|
16
16
|
honeybee/face.py,sha256=BOL2tlFLErxY27euixVWFzL1z-xtABtzvMmLIpzMqcE,111961
|
|
17
17
|
honeybee/facetype.py,sha256=vCtWZKHp21RH-Yzs8zsHJHuFhJvczNh0yFl8wDe_RWY,4489
|
|
18
18
|
honeybee/logutil.py,sha256=2gn-6RcWqFLvwdFzBHPqUwFqTj_R3iwHKALrl-2eL7M,2564
|
|
19
|
-
honeybee/model.py,sha256=
|
|
19
|
+
honeybee/model.py,sha256=ajsXa4uAwU7Srml4QJ_aKcf-_hPSaQ2wx7_3U5HBJp0,198311
|
|
20
20
|
honeybee/orientation.py,sha256=GogGblASW9OU-fobfDaQ7w5yRbEAFdJJuHwg2fadhKI,5046
|
|
21
21
|
honeybee/properties.py,sha256=ok976fbUdXzLDR2XiPNf8Q5ejfOM-PArqm5CVUbFiCc,34316
|
|
22
|
-
honeybee/room.py,sha256=
|
|
22
|
+
honeybee/room.py,sha256=ZrjABLErBTdTHcDRvPdt4W8-0REyPjQLtnu0rnrML0U,168284
|
|
23
23
|
honeybee/search.py,sha256=KOIeQjYdj0yhRWPmF5kiFiH8Ei0WbzuiU-capnwYVeM,5060
|
|
24
24
|
honeybee/shade.py,sha256=2zuW1lR5wfe-UyAosuCX-3tII6CKONq7wt8Ac_jXU6c,20698
|
|
25
|
-
honeybee/shademesh.py,sha256=
|
|
25
|
+
honeybee/shademesh.py,sha256=WnpYkt04zuB8w-6OETNP7ZfzJarykEkQig6YdNkuVOw,14069
|
|
26
26
|
honeybee/typing.py,sha256=E8-HrCB9cSoqhFR6zcFXhrAlQRAFw_sxHGdobB8Lre8,20051
|
|
27
27
|
honeybee/units.py,sha256=_qG_G5b9hdqjpyVOpGdIYCB6k8VKYjcxSJn1St-7Xjc,3204
|
|
28
28
|
honeybee/cli/__init__.py,sha256=nYyTV_HapGo-a1XZLZps9__Bqp50YJYzHZ1LzHp4TJU,3675
|
|
@@ -40,9 +40,9 @@ honeybee/writer/model.py,sha256=N7F_jksf-5TrdVecuxTaFWxnPVFLmQs7k8g27TsdB7Q,177
|
|
|
40
40
|
honeybee/writer/room.py,sha256=kFghgStTU1SEJSLigXB0VjOWhZtgs4uXuAqdwd4yRQo,174
|
|
41
41
|
honeybee/writer/shade.py,sha256=EpgX-vMc-s21TnMvNWvWTKyT8iAnxu1nFVXzjY1oyF8,177
|
|
42
42
|
honeybee/writer/shademesh.py,sha256=Y41bLogJ7dwpvMe5cAWVRDRVqJEwo9e5hFJQjlt6UX8,189
|
|
43
|
-
honeybee_core-1.
|
|
44
|
-
honeybee_core-1.
|
|
45
|
-
honeybee_core-1.
|
|
46
|
-
honeybee_core-1.
|
|
47
|
-
honeybee_core-1.
|
|
48
|
-
honeybee_core-1.
|
|
43
|
+
honeybee_core-1.64.1.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
|
|
44
|
+
honeybee_core-1.64.1.dist-info/METADATA,sha256=iLC7XbnZvDuIgb927jQbB2jHpv6_jBuN8SiCeeYVXsI,3729
|
|
45
|
+
honeybee_core-1.64.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
46
|
+
honeybee_core-1.64.1.dist-info/entry_points.txt,sha256=r3YqOm40goBroH3ccUhpwQjvTwu10JWLd0HIRHI1J8E,47
|
|
47
|
+
honeybee_core-1.64.1.dist-info/top_level.txt,sha256=8ve7puCRLUA9XDEGc1Mcs-UX9sFjpPV8MeTaIMwQ_Tg,9
|
|
48
|
+
honeybee_core-1.64.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|