topologicpy 0.8.52__py3-none-any.whl → 0.8.55__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.
topologicpy/Cell.py CHANGED
@@ -1552,7 +1552,6 @@ class Cell():
1552
1552
  cluster = Cluster.ByTopologies(pentagons)
1553
1553
 
1554
1554
  cluster2 = Topology.Rotate(cluster, origin=Vertex.Origin(), axis=[1, 0, 0], angle=180)
1555
- #cluster2 = Topology.Rotate(cluster2, origin=Vertex.Origin(), axis=[0, 0, 1], angle=36)
1556
1555
  vertices = Topology.Vertices(cluster)
1557
1556
  zList = [Vertex.Z(v) for v in vertices]
1558
1557
  zList = list(set(zList))
@@ -1566,9 +1565,8 @@ class Cell():
1566
1565
  dodecahedron = Topology.Translate(dodecahedron, -Vertex.X(centroid), -Vertex.Y(centroid), -Vertex.Z(centroid))
1567
1566
  vertices = Topology.Vertices(dodecahedron)
1568
1567
  d = Vertex.Distance(Vertex.Origin(), vertices[0])
1568
+ # Make sure the distance from the origin to the vertices is equal to the radius.
1569
1569
  dodecahedron = Topology.Scale(dodecahedron, origin=Vertex.Origin(), x=radius/d, y=radius/d, z=radius/d)
1570
- verts = Topology.Vertices(dodecahedron)
1571
- print("Dodec: Distance", Vertex.Distance(Vertex.Origin(), verts[0]))
1572
1570
  if placement == "bottom":
1573
1571
  dodecahedron = Topology.Translate(dodecahedron, 0, 0, radius)
1574
1572
  elif placement == "lowerleft":
@@ -201,6 +201,51 @@ class EnergyModel:
201
201
  warnings.warn("EnergyModel.ByTopology - Error: Could not import openstudio.Please try to install openstudio manually. Returning None.")
202
202
  return None
203
203
 
204
+
205
+ def load_openstudio_model(osm_path: str, allow_newer: bool = True):
206
+ """
207
+ Load an OpenStudio .osm file and return an openstudio.model.Model.
208
+ Uses loadModelFromString to avoid SWIG path overload issues.
209
+ """
210
+ import os
211
+
212
+ if not os.path.exists(osm_path):
213
+ raise FileNotFoundError(osm_path)
214
+
215
+ vt = openstudio.osversion.VersionTranslator()
216
+ if allow_newer:
217
+ vt.setAllowNewerVersions(True)
218
+
219
+ # 1) Robust path-agnostic route: read text and load from string
220
+ with open(osm_path, "r", encoding="utf-8") as f:
221
+ txt = f.read()
222
+ model_opt = vt.loadModelFromString(txt) # <- avoids path type mismatches
223
+
224
+ # 2) Fallback: try the filesystem-path overloads if needed
225
+ if (not model_opt) or (not model_opt.is_initialized()):
226
+ os_path = None
227
+ for maker in (
228
+ lambda s: getattr(openstudio, "path")(s),
229
+ lambda s: getattr(openstudio, "toPath")(s),
230
+ lambda s: getattr(openstudio.openstudioutilitiescore, "toPath")(s),
231
+ ):
232
+ try:
233
+ os_path = maker(osm_path)
234
+ break
235
+ except Exception:
236
+ pass
237
+ if os_path is not None:
238
+ try:
239
+ model_opt = vt.loadModel(os_path)
240
+ except TypeError:
241
+ model_opt = openstudio.model.Model.load(os_path)
242
+
243
+ if (not model_opt) or (not model_opt.is_initialized()):
244
+ raise RuntimeError(f"Failed to load OpenStudio model from: {osm_path}")
245
+
246
+ model = model_opt.get()
247
+ return model
248
+
204
249
  def getKeyName(d, keyName):
205
250
  keys = d.Keys()
206
251
  for key in keys:
@@ -243,21 +288,23 @@ class EnergyModel:
243
288
 
244
289
  if not osModelPath:
245
290
  import os
246
- osModelPath = os.path.join(os.path.dirname(os.path.realpath(__file__)), "assets", "EnergyModel", "OSMTemplate-OfficeBuilding-3.5.0.osm")
291
+ osModelPath = os.path.join(os.path.dirname(os.path.realpath(__file__)), "assets", "EnergyModel", "OSMTemplate-OfficeBuilding-3.10.0.osm")
247
292
  if not weatherFilePath or not designDayFilePath:
248
293
  import os
249
294
  weatherFilePath = os.path.join(os.path.dirname(os.path.realpath(__file__)), "assets", "EnergyModel", "GBR_London.Gatwick.037760_IWEC.epw")
250
295
  designDayFilePath = os.path.join(os.path.dirname(os.path.realpath(__file__)), "assets", "EnergyModel", "GBR_London.Gatwick.037760_IWEC.ddy")
251
- translator = openstudio.osversion.VersionTranslator()
296
+
297
+ #translator = openstudio.osversion.VersionTranslator()
252
298
  # DEBUGGING
253
299
  #osmFile = openstudio.openstudioutilitiescore.toPath(osModelPath)
254
300
  #osModel = translator.loadModel(osmFile)
255
- osModel = translator.loadModel(osModelPath)
256
- if osModel.isNull():
257
- print("EnergyModel.ByTopology - Error: The openstudio model is null. Returning None.")
258
- return None
259
- else:
260
- osModel = osModel.get()
301
+ #osModel = translator.loadModel(osModelPath)
302
+ osModel = load_openstudio_model(osModelPath, allow_newer=True)
303
+ # if osModel.isNull():
304
+ # print("EnergyModel.ByTopology - Error: The openstudio model is null. Returning None.")
305
+ # return None
306
+ # else:
307
+ # osModel = osModel.get()
261
308
  # DEBUGGING
262
309
  #osEPWFile = openstudio.openstudioutilitiesfiletypes.EpwFile.load(openstudio.toPath(weatherFilePath))
263
310
  osEPWFile = openstudio.openstudioutilitiesfiletypes.EpwFile.load(weatherFilePath)
@@ -265,8 +312,8 @@ class EnergyModel:
265
312
  osEPWFile = osEPWFile.get()
266
313
  openstudio.model.WeatherFile.setWeatherFile(osModel, osEPWFile)
267
314
  # DEBUGGING
268
- #ddyModel = openstudio.openstudioenergyplus.loadAndTranslateIdf(openstudio.toPath(designDayFilePath))
269
- ddyModel = openstudio.openstudioenergyplus.loadAndTranslateIdf(designDayFilePath)
315
+ ddyModel = openstudio.openstudioenergyplus.loadAndTranslateIdf(openstudio.toPath(designDayFilePath))
316
+ #ddyModel = openstudio.openstudioenergyplus.loadAndTranslateIdf(designDayFilePath)
270
317
  if ddyModel.is_initialized():
271
318
  ddyModel = ddyModel.get()
272
319
  for ddy in ddyModel.getObjectsByType(openstudio.IddObjectType("OS:SizingPeriod:DesignDay")):
@@ -1195,3 +1242,57 @@ class EnergyModel:
1195
1242
  return None
1196
1243
  return units
1197
1244
 
1245
+ @staticmethod
1246
+ def Version(check: bool = True, silent: bool = False):
1247
+ """
1248
+ Returns the OpenStudio SDK version number.
1249
+
1250
+ Parameters
1251
+ ----------
1252
+ check : bool , optional
1253
+ if set to True, the version number is checked with the latest version on PyPi. Default is True.
1254
+
1255
+ silent : bool , optional
1256
+ If set to True, error and warning messages are suppressed. Default is False.
1257
+
1258
+ Returns
1259
+ -------
1260
+ str
1261
+ The OpenStudio SDK version number.
1262
+
1263
+ """
1264
+ from topologicpy.Helper import Helper
1265
+ try:
1266
+ import openstudio
1267
+ openstudio.Logger.instance().standardOutLogger().setLogLevel(openstudio.Fatal)
1268
+ except:
1269
+ if not silent:
1270
+ print("EnergyModel.Version - Information: Installing required openstudio library.")
1271
+ try:
1272
+ os.system("pip install openstudio")
1273
+ except:
1274
+ os.system("pip install openstudio --user")
1275
+ try:
1276
+ import openstudio
1277
+ openstudio.Logger.instance().standardOutLogger().setLogLevel(openstudio.Fatal)
1278
+ if not silent:
1279
+ print("EnergyModel.Version - Information: openstudio library installed correctly.")
1280
+ except:
1281
+ if not silent:
1282
+ print("EnergyModel.Version - Error: Could not import openstudio.Please try to install openstudio manually. Returning None.")
1283
+ return None
1284
+ import requests
1285
+ from packaging import version
1286
+
1287
+ result = getattr(openstudio, "openStudioVersion", None)
1288
+ if callable(result):
1289
+ result = result()
1290
+ else:
1291
+ if not silent:
1292
+ print("EnergyModel.Version - Error: Could not retrieve the openstudio SDK version number. Returning None.")
1293
+ return None
1294
+ if check == True:
1295
+ result = Helper.CheckVersion("openstudio", result, silent=silent)
1296
+ return result
1297
+
1298
+
topologicpy/Graph.py CHANGED
@@ -784,7 +784,291 @@ class Graph:
784
784
  return csv_buffer.getvalue()
785
785
  except Exception as e:
786
786
  return ""
787
-
787
+
788
+ @staticmethod
789
+ def AdjacencyMatrixFigure(graph,
790
+ vertexKey: str = None,
791
+ showZero: bool = False,
792
+ zeroChar: str = "·",
793
+ zeroColor: str = 'rgba(0,0,0,0)',
794
+ valueColor: str = 'rgba(0,0,0,0.05)',
795
+ diagonalHighlight: bool = True,
796
+ diagonalColor: str = 'rgba(0,0,0,0)',
797
+ title: str = None,
798
+ cellSize: int = 24,
799
+ fontFamily: str = "Arial",
800
+ fontSize: int = 12,
801
+ fontColor: str = 'rgba(0,0,0,0)',
802
+ backgroundColor: str = 'rgba(0,0,0,0)',
803
+ headerColor: str = 'rgba(0,0,0,0)',
804
+ reverse=False,
805
+ edgeKeyFwd=None,
806
+ edgeKeyBwd=None,
807
+ bidirKey=None,
808
+ bidirectional=True,
809
+ useEdgeIndex=False,
810
+ useEdgeLength=False,
811
+ mantissa: int = 6,
812
+ tolerance=0.0001,
813
+ silent: bool = False):
814
+ """
815
+ Returns a Plotly table figure visualizing the adjacency matrix of a Graph.
816
+
817
+ Parameters
818
+ ----------
819
+ graph : topologic_core.Graph
820
+ The input graph.
821
+ vertexKey : str , optional
822
+ If set, the returned list of vertices is sorted according to the dictionary values stored under this key. Default is None.
823
+ showZero : bool, optional
824
+ If True, show zeros as "0"; if False, show a subtle glyph (zero_char) or blank. Default is False.
825
+ zeroChar : str, optional
826
+ Character to display for zero entries when show_zero is False. Default is "·".
827
+ zeroColor : list or str , optional
828
+ The desired color to display for zero-valued cells. This can be any color list or plotly color string and may be specified as:
829
+ - An rgb list (e.g. [255,0,0])
830
+ - A cmyk list (e.g. [0.5, 0, 0.25, 0.2])
831
+ - A hex string (e.g. '#ff0000')
832
+ - An rgb/rgba string (e.g. 'rgb(255,0,0)')
833
+ - An hsl/hsla string (e.g. 'hsl(0,100%,50%)')
834
+ - An hsv/hsva string (e.g. 'hsv(0,100%,100%)')
835
+ - A named CSS color.
836
+ The default is 'rgba(0,0,0,0)' (transparent).
837
+ valueColor : list or str , optional
838
+ The desired color to display for non-zero-valued cells. This can be any color list or plotly color string and may be specified as:
839
+ - An rgb list (e.g. [255,0,0])
840
+ - A cmyk list (e.g. [0.5, 0, 0.25, 0.2])
841
+ - A hex string (e.g. '#ff0000')
842
+ - An rgb/rgba string (e.g. 'rgb(255,0,0)')
843
+ - An hsl/hsla string (e.g. 'hsl(0,100%,50%)')
844
+ - An hsv/hsva string (e.g. 'hsv(0,100%,100%)')
845
+ - A named CSS color.
846
+ The default is 'rgba(0,0,0,0.35)' (slight highlight).
847
+ diagonalHighlight : bool, optional
848
+ If True, lightly highlight diagonal cells. Default is True.
849
+ diagonalColor : list or str , optional
850
+ The desired diagonal highlight color. This can be any color list or plotly color string and may be specified as:
851
+ - An rgb list (e.g. [255,0,0])
852
+ - A cmyk list (e.g. [0.5, 0, 0.25, 0.2])
853
+ - A hex string (e.g. '#ff0000')
854
+ - An rgb/rgba string (e.g. 'rgb(255,0,0)')
855
+ - An hsl/hsla string (e.g. 'hsl(0,100%,50%)')
856
+ - An hsv/hsva string (e.g. 'hsv(0,100%,100%)')
857
+ - A named CSS color.
858
+ The default is 'rgba(0,0,0,0)' (transparent).
859
+ title : str, optional
860
+ Optional figure title.
861
+ cellSize : int, optional
862
+ Approximate pixel height of each table row. Default is 24.
863
+ fontFamily : str, optional
864
+ Font family for table text. Default is "Arial".
865
+ fontSize : int, optional
866
+ Font size for table text. Default is 12.
867
+ fontColor : list or str , optional
868
+ The desired font color. This can be any color list or plotly color string and may be specified as:
869
+ - An rgb list (e.g. [255,0,0])
870
+ - A cmyk list (e.g. [0.5, 0, 0.25, 0.2])
871
+ - A hex string (e.g. '#ff0000')
872
+ - An rgb/rgba string (e.g. 'rgb(255,0,0)')
873
+ - An hsl/hsla string (e.g. 'hsl(0,100%,50%)')
874
+ - An hsv/hsva string (e.g. 'hsv(0,100%,100%)')
875
+ - A named CSS color.
876
+ The default is 'rgba(0,0,0,0)' (transparent).
877
+ backgroundColor : list or str , optional
878
+ The desired background color. This can be any color list or plotly color string and may be specified as:
879
+ - An rgb list (e.g. [255,0,0])
880
+ - A cmyk list (e.g. [0.5, 0, 0.25, 0.2])
881
+ - A hex string (e.g. '#ff0000')
882
+ - An rgb/rgba string (e.g. 'rgb(255,0,0)')
883
+ - An hsl/hsla string (e.g. 'hsl(0,100%,50%)')
884
+ - An hsv/hsva string (e.g. 'hsv(0,100%,100%)')
885
+ - A named CSS color.
886
+ The default is 'rgba(0,0,0,0)' (transparent).
887
+ headerColor : list or str , optional
888
+ The desired header color. This can be any color list or plotly color string and may be specified as:
889
+ - An rgb list (e.g. [255,0,0])
890
+ - A cmyk list (e.g. [0.5, 0, 0.25, 0.2])
891
+ - A hex string (e.g. '#ff0000')
892
+ - An rgb/rgba string (e.g. 'rgb(255,0,0)')
893
+ - An hsl/hsla string (e.g. 'hsl(0,100%,50%)')
894
+ - An hsv/hsva string (e.g. 'hsv(0,100%,100%)')
895
+ - A named CSS color.
896
+ The default is 'rgba(0,0,0,0)' (transparent).
897
+ reverse : bool , optional
898
+ If set to True, the vertices are sorted in reverse order (only if vertexKey is set). Otherwise, they are not. Default is False.
899
+ edgeKeyFwd : str , optional
900
+ If set, the value at this key in the connecting edge from start vertex to end vertex (forward) will be used instead of the value 1. Default is None. useEdgeIndex and useEdgeLength override this setting.
901
+ edgeKeyBwd : str , optional
902
+ If set, the value at this key in the connecting edge from end vertex to start vertex (backward) will be used instead of the value 1. Default is None. useEdgeIndex and useEdgeLength override this setting.
903
+ bidirKey : bool , optional
904
+ If set to True or False, this key in the connecting edge will be used to determine is the edge is supposed to be bidirectional or not. If set to None, the input variable bidrectional will be used instead. Default is None
905
+ bidirectional : bool , optional
906
+ If set to True, the edges in the graph that do not have a bidireKey in their dictionaries will be treated as being bidirectional. Otherwise, the start vertex and end vertex of the connecting edge will determine the direction. Default is True.
907
+ useEdgeIndex : bool , optional
908
+ If set to True, the adjacency matrix values will the index of the edge in Graph.Edges(graph). Default is False. Both useEdgeIndex, useEdgeLength should not be True at the same time. If they are, useEdgeLength will be used.
909
+ useEdgeLength : bool , optional
910
+ If set to True, the adjacency matrix values will the length of the edge in Graph.Edges(graph). Default is False. Both useEdgeIndex, useEdgeLength should not be True at the same time. If they are, useEdgeLength will be used.
911
+ mantissa : int , optional
912
+ The number of decimal places to round the result to. Default is 6.
913
+ tolerance : float , optional
914
+ The desired tolerance. Default is 0.0001.
915
+ silent : bool, optional
916
+ If True, suppresses warning messages. Default is False.
917
+
918
+ Returns
919
+ -------
920
+ plotly.graph_objs._figure.Figure
921
+ A Plotly table figure containing the adjacency matrix table.
922
+ """
923
+
924
+ from topologicpy.Topology import Topology
925
+ from topologicpy.Graph import Graph
926
+ from topologicpy.Dictionary import Dictionary
927
+ import plotly.graph_objects as go
928
+
929
+ if not Topology.IsInstance(graph, "Graph"):
930
+ if not silent:
931
+ print("Plotly.AdjacencyMatrixTable - Error: The input is not a valid Graph. Returning None.")
932
+ return None
933
+
934
+ # Build adjacency matrix
935
+ adj = Graph.AdjacencyMatrix(graph,
936
+ vertexKey=vertexKey,
937
+ reverse=reverse,
938
+ edgeKeyFwd=edgeKeyFwd,
939
+ edgeKeyBwd=edgeKeyBwd,
940
+ bidirKey=bidirKey,
941
+ bidirectional=bidirectional,
942
+ useEdgeIndex=useEdgeIndex,
943
+ useEdgeLength=useEdgeLength,
944
+ mantissa=mantissa,
945
+ tolerance=tolerance)
946
+
947
+ if adj is None or not isinstance(adj, list) or len(adj) == 0:
948
+ if not silent:
949
+ print("Plotly.AdjacencyMatrixTable - Warning: Empty adjacency matrix. Returning None.")
950
+ return None
951
+
952
+ n = len(adj)
953
+ # Validate squareness
954
+ if any((not isinstance(row, list) or len(row) != n) for row in adj):
955
+ if not silent:
956
+ print("Plotly.AdjacencyMatrixTable - Error: Adjacency matrix must be square. Returning None.")
957
+ return None
958
+
959
+ # Derive labels
960
+ verts = Graph.Vertices(graph)
961
+ labels = [Dictionary.ValueAtKey(Topology.Dictionary(v), vertexKey, str(i)) for i, v in enumerate(verts)]
962
+ if len(labels) > 0 and not vertexKey == None:
963
+ labels.sort()
964
+ if reverse == True:
965
+ labels.reverse()
966
+ # Build display matrix (strings) while keeping a parallel style mask for diagonal
967
+ display_matrix = []
968
+ diag_mask = []
969
+ for r in range(n):
970
+ row_vals = []
971
+ row_diag = []
972
+ for c in range(n):
973
+ v = adj[r][c]
974
+ if v == 0:
975
+ row_vals.append("0" if showZero else (zeroChar if zeroChar else ""))
976
+ else:
977
+ # Keep integers unpadded for clarity; cast others nicely
978
+ row_vals.append(str(int(v)) if isinstance(v, (int, float)) and float(v).is_integer() else str(v))
979
+ row_diag.append(r == c)
980
+ display_matrix.append(row_vals)
981
+ diag_mask.append(row_diag)
982
+
983
+ # Construct header and cells for Plotly Table
984
+ # Header: blank corner + column labels
985
+ header_values = [""] + labels
986
+
987
+ # Body: first column is row labels, then matrix cells as strings
988
+ # Plotly Table expects columns as lists; we need to transpose
989
+ columns = []
990
+ # Column 0: row labels
991
+ columns.append(labels)
992
+ # Subsequent columns: for each c, collect display_matrix[r][c]
993
+ for c in range(n):
994
+ columns.append([display_matrix[r][c] for r in range(n)])
995
+
996
+ # Flatten cell fill_colors to highlight diagonal subtly.
997
+ # Plotly Table allows per-cell fillcolor via a 2D list matching the table shape for 'cells'.
998
+ # Our cells shape is n rows x (n+1) cols (including row label column).
999
+
1000
+ fill_colors = []
1001
+ # Column 0: row labels (no highlight)
1002
+ fill_colors.append([headerColor] * n)
1003
+
1004
+ # Columns 1..n: highlight diagonal where row index r == (column_index-1)
1005
+ for c in range(1, n + 1):
1006
+ col_colors = []
1007
+ for r in range(n):
1008
+ if diagonalHighlight and r == (c - 1):
1009
+ col_colors.append(diagonalColor)
1010
+ elif columns[c][r] == "0" or columns[c][r] == zeroChar:
1011
+ col_colors.append(zeroColor)
1012
+ else:
1013
+ col_colors.append(valueColor)
1014
+ fill_colors.append(col_colors)
1015
+
1016
+ # Minimal line style
1017
+ line_color = "rgba(0,0,0,0.12)"
1018
+ # --- Sizing to prevent cropped text ---
1019
+ # Heuristic widths (pixels)
1020
+ max_label_len = max(len(str(x)) for x in labels) if labels else 1
1021
+ row_label_px = max(120, min(320, 8 * max_label_len)) # scale with label length
1022
+ cell_px = 36 if n <= 30 else (30 if n <= 50 else 24) # narrower cells for very wide matrices
1023
+
1024
+ # Adaptive cell font size for many columns
1025
+ fontSize = max(fontSize, 3)
1026
+ cell_font_size = fontSize if n <= 35 else (fontSize-1 if n <= 60 else fontSize-2)
1027
+
1028
+ # Figure width: row label column + all matrix columns
1029
+ fig_width = row_label_px + n * cell_px
1030
+ fig_width = max(600, min(2400, fig_width)) # clamp to reasonable bounds
1031
+
1032
+ # Increase row height a bit for readability
1033
+ cellSize = max(cellSize, 26)
1034
+
1035
+ # Column widths in px (Plotly Table accepts pixel widths)
1036
+ columnwidth = [row_label_px] + [cell_px] * n
1037
+ fig = go.Figure(
1038
+ data=[
1039
+ go.Table(
1040
+ header=dict(
1041
+ values=header_values,
1042
+ align="center",
1043
+ font=dict(family=fontFamily, size=cell_font_size, color=fontColor),
1044
+ fill_color=headerColor,
1045
+ line_color=line_color,
1046
+ height=cellSize + 4 # a touch taller for the header
1047
+ ),
1048
+ cells=dict(
1049
+ values=columns,
1050
+ align=["right"] + ["center"] * n,
1051
+ font=dict(family=fontFamily, size=cell_font_size, color=fontColor),
1052
+ fill_color=fill_colors,
1053
+ line_color=line_color,
1054
+ height=cellSize
1055
+ ),
1056
+ columnorder=list(range(n + 1)),
1057
+ columnwidth=columnwidth
1058
+ )
1059
+ ]
1060
+ )
1061
+
1062
+ # Layout: generous margins, white background, optional title
1063
+ fig.update_layout(
1064
+ title=dict(text=title, x=0.5, xanchor="center") if title else None,
1065
+ paper_bgcolor="white",
1066
+ plot_bgcolor=backgroundColor,
1067
+ margin=dict(l=20, r=20, t=40 if title else 10, b=20)
1068
+ )
1069
+
1070
+ return fig
1071
+
788
1072
  @staticmethod
789
1073
  def AdjacencyList(graph, vertexKey=None, reverse=True, tolerance=0.0001):
790
1074
  """
topologicpy/Helper.py CHANGED
@@ -90,6 +90,53 @@ class Helper:
90
90
 
91
91
  return sorted(bin_averages)
92
92
 
93
+ def CheckVersion(library: str = None, version: str = None, silent: bool = False):
94
+ """
95
+ Compare an input version with the latest version of a Python library on PyPI.
96
+
97
+ Parameters
98
+ ----------
99
+ library : str
100
+ The input software library name. Default is None.
101
+ version : str
102
+ The input software version number to compare. Default is None.
103
+ silent : bool , optional
104
+ If set to True, error and warning messages are suppressed. Default is False.
105
+
106
+ Returns:
107
+ str: A message indicating whether the input version is less than,
108
+ equal to, or greater than the latest version on PyPI.
109
+ """
110
+ import requests
111
+ from packaging import version as ver
112
+
113
+ try:
114
+ # Fetch library data from PyPI
115
+ url = f"https://pypi.org/pypi/{library}/json"
116
+ response = requests.get(url)
117
+ response.raise_for_status()
118
+
119
+ # Extract the latest version from the JSON response
120
+ data = response.json()
121
+ latest_version = data['info']['version']
122
+
123
+ # Compare versions using the packaging library
124
+ if ver.parse(version) < ver.parse(latest_version):
125
+ return (f"The version that you are using ({version}) is OLDER than the latest version ({latest_version}) from PyPI. Please consider upgrading to the latest version.")
126
+ elif ver.parse(version) == ver.parse(latest_version):
127
+ return (f"The version that you are using ({version}) is EQUAL TO the latest version available on PyPI.")
128
+ else:
129
+ return (f"The version that you are using ({version}) is NEWER than the latest version ({latest_version}) available from PyPI.")
130
+
131
+ except requests.exceptions.RequestException as e:
132
+ if not silent:
133
+ print("Helper.CheckVersion - Error: Could not fetch data from PyPI. Returning None")
134
+ return None
135
+ except KeyError:
136
+ if not silent:
137
+ print("Helper.CheckVersion - Error: Could not fetch data from PyPI. Returning None")
138
+ return None
139
+
93
140
  @staticmethod
94
141
  def ClosestMatch(item, listA):
95
142
  """
@@ -788,58 +835,25 @@ class Helper:
788
835
  return returnList
789
836
 
790
837
  @staticmethod
791
- def Version():
838
+ def Version(check: bool = True, silent: bool = False):
792
839
  """
793
840
  Returns the current version of the software.
794
841
 
795
842
  Parameters
796
843
  ----------
797
-
844
+ check : bool , optional
845
+ if set to True, the version number is checked with the latest version on PyPi. Default is True.
846
+ silent : bool , optional
847
+ If set to True, error and warning messages are suppressed. Default is False.
848
+
798
849
  Returns
799
850
  -------
800
851
  str
801
- The current version of the software.
852
+ The current version of the software. Optionally, includes a check with PyPi.
802
853
 
803
854
  """
804
855
 
805
- import requests
806
- from packaging import version
807
-
808
- def compare_version_with_pypi(library_name, input_version):
809
- """
810
- Compare an input version with the latest version of a Python library on PyPI.
811
-
812
- Args:
813
- library_name (str): The name of the Python library on PyPI.
814
- input_version (str): The version number to compare (e.g., "0.7.58").
815
-
816
- Returns:
817
- str: A message indicating whether the input version is less than,
818
- equal to, or greater than the latest version on PyPI.
819
- """
820
- try:
821
- # Fetch library data from PyPI
822
- url = f"https://pypi.org/pypi/{library_name}/json"
823
- response = requests.get(url)
824
- response.raise_for_status()
825
-
826
- # Extract the latest version from the JSON response
827
- data = response.json()
828
- latest_version = data['info']['version']
829
-
830
- # Compare versions using the packaging library
831
- if version.parse(input_version) < version.parse(latest_version):
832
- return (f"The version that you are using ({input_version}) is OLDER than the latest version {latest_version} from PyPI. Please consider upgrading to the latest version.")
833
- elif version.parse(input_version) == version.parse(latest_version):
834
- return (f"The version that you are using ({input_version}) is the latest version available on PyPI.")
835
- else:
836
- return (f"The version that you are using ({input_version}) is NEWER than the latest version ({latest_version}) available from PyPI.")
837
-
838
- except requests.exceptions.RequestException as e:
839
- return f"Error fetching data from PyPI: {e}"
840
- except KeyError:
841
- return "Error: Unable to find the latest version in the PyPI response."
842
-
843
- current_version = topologicpy.__version__
844
- result = compare_version_with_pypi("topologicpy", current_version)
856
+ result = topologicpy.__version__
857
+ if check == True:
858
+ result = Helper.CheckVersion("topologicpy", result, silent=silent)
845
859
  return result
topologicpy/Honeybee.py CHANGED
@@ -14,6 +14,7 @@
14
14
  # You should have received a copy of the GNU Affero General Public License along with
15
15
  # this program. If not, see <https://www.gnu.org/licenses/>.
16
16
 
17
+ from __future__ import annotations
17
18
  import os
18
19
  import warnings
19
20
 
@@ -112,6 +113,530 @@ import json
112
113
  import topologic_core as topologic
113
114
 
114
115
  class Honeybee:
116
+ @staticmethod
117
+ def ByHBJSONDictionary(
118
+ dictionary,
119
+ includeRooms: bool = True,
120
+ includeFaces: bool = True,
121
+ includeShades: bool = True,
122
+ includeApertures: bool = True,
123
+ includeDoors: bool = True,
124
+ includeOrphanedRooms: bool = True,
125
+ includeOrphanedFaces: bool = True,
126
+ includeOrphanedShades: bool = True,
127
+ includeOrphanedApertures: bool = True,
128
+ includeOrphanedDoors: bool = True,
129
+ tolerance: float = 0.0001,
130
+ silent: bool = False):
131
+ """
132
+ Import an HBJSON model from a python dictionary and return a python dictionary. See: https://github.com/ladybug-tools/honeybee-schema/wiki/1.1-Model-Schema
133
+
134
+ Parameters
135
+ ----------
136
+ dictionary : dict
137
+ The HBJSON model as a Python dictionary (e.g., loaded via ``json.load``).
138
+ includeRooms : bool, optional
139
+ If True, parse rooms and attempt to create one ``Cell`` per room. Default is True.
140
+ includeFaces : bool, optional
141
+ If True, include top-level planar faces found outside rooms (e.g., at root "faces"). Default is True.
142
+ includeShades : bool, optional
143
+ If True, include context/standalone shades (e.g., ``context_geometry.shades``). Default is True.
144
+ includeApertures : bool, optional
145
+ If True, include **room** apertures (e.g., windows) as separate ``Face`` objects (not cut from hosts). Default is True.
146
+ includeDoors : bool, optional
147
+ If True, include **room** doors as separate ``Face`` objects (not cut from hosts). Default is True.
148
+ includeOrphanedRooms : bool, optional
149
+ If True, include the topology of the room when a room fails to close as a ``Cell``. This may be a ``Shell`` or a ``Cluster``. Default is True.
150
+ includeOrphanedFaces : bool, optional
151
+ If True, include planar faces listed at the HBJSON root (e.g., "faces"). Default is True.
152
+ includeOrphanedShades : bool, optional
153
+ If True, include shades listed at the HBJSON root (e.g., "orphaned_shades"). Default is True.
154
+ includeOrphanedApertures : bool, optional
155
+ If True, include apertures listed at the HBJSON root (e.g., "orphaned_apertures"). Default is True.
156
+ includeOrphanedDoors : bool, optional
157
+ If True, include doors listed at the HBJSON root (e.g., "orphaned_doors"). Default is True.
158
+ tolerance : float , optional
159
+ The desired tolerance. Default is 0.0001.
160
+ silent : bool , optional
161
+ If set to True, error and warning messages are suppressed. Default is False.
162
+
163
+ Returns
164
+ -------
165
+ dict
166
+ The created cluster of vertices, edges, faces, and cells.
167
+ - 'rooms': list of Cells (one per successfully closed room)
168
+ - 'faces': list of Faces (all faces that make up the rooms)
169
+ - 'shades': list of Faces (all shade faces)
170
+ - 'apertures': list of Faces (all apertures, never cut from hosts)
171
+ - 'doors': list of Faces (all doors, never cut from hosts)
172
+ - 'orphanedRooms': list of Topologies (context/top-level topologies (e.g. Shells or Clustser) that failed to form a Cell)
173
+ - 'orphanedFaces': list of Faces (context/top-level faces + host faces of rooms that failed to form a Cell)
174
+ - 'orphanedShades': list of Faces (context/top-level shade faces that failed to have a parent cell)
175
+ - 'orphanedApertures': list of Faces (apertures that failed to have a parent face)
176
+ - 'orphanedDoors': list of Faces (doors that failed to have a parent face)
177
+ - 'properties': hierarchical dict copied verbatim from HBJSON['properties']
178
+ """
179
+
180
+ from topologicpy.Vertex import Vertex
181
+ from topologicpy.Edge import Edge
182
+ from topologicpy.Wire import Wire
183
+ from topologicpy.Face import Face
184
+ from topologicpy.Shell import Shell
185
+ from topologicpy.Cell import Cell
186
+ from topologicpy.Cluster import Cluster
187
+ from topologicpy.Topology import Topology
188
+ from topologicpy.Dictionary import Dictionary
189
+ from topologicpy.Helper import Helper
190
+ from typing import Any, Dict, List, Optional, Tuple
191
+
192
+ if not isinstance(dictionary, dict):
193
+ if not silent:
194
+ print("Honeybee.ByHBJSONDictionary - Error: The input dictionary parameter is not a valid python dictionary. Returning None.")
195
+ return None
196
+
197
+ # ---------------------- helpers ----------------------
198
+ def _close(points: List[List[float]]) -> List[List[float]]:
199
+ if not points:
200
+ return points
201
+ p0, pN = points[0], points[-1]
202
+ if (abs(p0[0]-pN[0]) > tolerance or
203
+ abs(p0[1]-pN[1]) > tolerance or
204
+ abs(p0[2]-pN[2]) > tolerance):
205
+ return points + [p0]
206
+ return points
207
+
208
+ def _V(p: List[float]) -> Vertex:
209
+ return Vertex.ByCoordinates(float(p[0]), float(p[1]), float(p[2]))
210
+
211
+ # Tolerance-filtered wire (your spec)
212
+ def _wire(points: List[List[float]], tolerance: float = 1e-6) -> Wire:
213
+ pts = _close(points)
214
+ verts = [_V(x) for x in pts]
215
+ edges = [
216
+ Edge.ByVertices(verts[i], verts[i+1], tolerance=tolerance, silent=True)
217
+ for i in range(len(verts)-1)
218
+ if Vertex.Distance(verts[i], verts[i+1]) > tolerance
219
+ ]
220
+ w = None
221
+ try:
222
+ w = Wire.ByEdges(edges, tolerance=tolerance, silent=True)
223
+ except:
224
+ w = Topology.SelfMerge(Cluster.ByTopologies(edges), tolerance=tolerance)
225
+ if w == None:
226
+ if not silent:
227
+ print("Honeybee.ByHBSJONDictionary - Error: Could not build wire. Returning None.")
228
+ return w
229
+
230
+ def _face_from_boundary(boundary: List[List[float]]) -> Face:
231
+ w = _wire(boundary, tolerance)
232
+ if w:
233
+ f = Face.ByWire(w, tolerance=tolerance, silent=True)
234
+ if not f:
235
+ if not silent:
236
+ print("Honeybee.ByHBSJONDictionary - Error: Could not build face. Returning the wire")
237
+ return w
238
+ return f
239
+ if not silent:
240
+ print("Honeybee.ByHBSJONDictionary - Error: Could not build face. Returning None")
241
+ return None
242
+
243
+ def _attach_all(top: Topology, full_py_dict: Dict[str, Any]) -> Topology:
244
+ # Attach the entire available dict (no filtering)
245
+ try:
246
+ keys = list(full_py_dict.keys())
247
+ values = [full_py_dict[k] for k in keys]
248
+ d = Dictionary.ByKeysValues(keys, values)
249
+ return Topology.SetDictionary(top, d)
250
+ except Exception:
251
+ return top # be robust to non-serializable values
252
+
253
+ def _build_host_face_cut_holes(fobj: Dict[str, Any]) -> Optional[Face]:
254
+ """
255
+ Build host face from outer boundary and cut ONLY explicit 'holes' (NOT apertures/doors).
256
+ Attach the full fobj dict.
257
+ """
258
+ geom = fobj.get("geometry") or {}
259
+ boundary = geom.get("boundary") or fobj.get("boundary")
260
+ holes = geom.get("holes") or fobj.get("holes") or []
261
+
262
+ if not boundary or len(boundary) < 3:
263
+ return None
264
+
265
+ hosts = _face_from_boundary(boundary)
266
+ if Topology.IsInstance(hosts, "face") or Topology.IsInstance(hosts, "wire"):
267
+ hosts = [hosts]
268
+
269
+ for host in hosts:
270
+ # Subtract explicit hole loops (if any)
271
+ hole_faces: List[Face] = []
272
+ for h in holes:
273
+ if h and len(h) >= 3:
274
+ hole_faces.append(_face_from_boundary(h))
275
+
276
+ if hole_faces:
277
+ hole_cluster = Cluster.ByTopologies(hole_faces)
278
+ try:
279
+ host = Topology.Difference(host, hole_cluster)
280
+ except Exception as e:
281
+ if not silent:
282
+ name = fobj.get("identifier") or fobj.get("name") or "unnamed"
283
+ print(f"HBJSON Import: Hole cutting failed on face '{name}'. Keeping uncut. Error: {e}")
284
+ _attach_all(host, fobj)
285
+ return hosts
286
+
287
+ def _aperture_faces_from(fobj: Dict[str, Any], kind: str) -> List[Face]:
288
+ """
289
+ Build separate faces for apertures/doors on a host (DO NOT cut from host).
290
+ 'kind' ∈ {'apertures','doors'}. Attach full dict for each.
291
+ """
292
+ out: List[Face] = []
293
+ ap_list = fobj.get(kind) or []
294
+ for ap in ap_list:
295
+ g = ap.get("geometry") or {}
296
+ boundary = g.get("boundary") or ap.get("boundary")
297
+ if not boundary or len(boundary) < 3:
298
+ continue
299
+ f = _face_from_boundary(boundary)
300
+ out.append(_attach_all(f, ap))
301
+ return out
302
+
303
+ def _orphaned_aperture_faces(ap_list: List[Dict[str, Any]]) -> List[Face]:
304
+ out: List[Face] = []
305
+ for ap in ap_list or []:
306
+ g = ap.get("geometry") or {}
307
+ boundary = g.get("boundary") or ap.get("boundary")
308
+ if not boundary or len(boundary) < 3:
309
+ continue
310
+ f = _face_from_boundary(boundary)
311
+ out.append(_attach_all(f, ap))
312
+ return out
313
+
314
+ def _room_to_cell_and_apertures(room: Dict[str, Any]) -> Tuple[Optional[Cell], List[Face], List[Face]]:
315
+ """
316
+ Build host faces (cut 'holes' only) and aperture faces for a room.
317
+ Return (cell_or_none, host_faces, aperture_faces).
318
+ """
319
+ hb_faces = room.get("faces") or room.get("Faces") or []
320
+ rm_faces: List[Face] = []
321
+ sh_faces = room.get("shades") or room.get("Shades") or []
322
+ ap_faces: List[Face] = []
323
+ dr_faces: List[Face] = []
324
+
325
+
326
+ for fobj in hb_faces:
327
+ hosts = _build_host_face_cut_holes(fobj)
328
+ if hosts:
329
+ rm_faces.extend(hosts)
330
+ ap_faces.extend(_aperture_faces_from(fobj, "apertures"))
331
+ dr_faces.extend(_aperture_faces_from(fobj, "doors"))
332
+
333
+ # Room Shades
334
+ for sh in sh_faces:
335
+ shades = _build_host_face_cut_holes(sh)
336
+ if shades:
337
+ sh_faces.extend(shades)
338
+ # Try to make a Cell. If it fails, we DO NOT return a Shell/Cluster in rooms;
339
+ # instead we will salvage host faces into the 'faces' bucket.
340
+ if rm_faces:
341
+ selectors = []
342
+ for rm_face in rm_faces:
343
+ s = Topology.InternalVertex(rm_face)
344
+ face_d = Topology.Dictionary(rm_face)
345
+ s = Topology.SetDictionary(s, face_d)
346
+ selectors.append(s)
347
+ cell = Cell.ByFaces(Helper.Flatten(rm_faces), tolerance=0.001, silent=True)
348
+ if Topology.IsInstance(cell, "cell"):
349
+ cell = _attach_all(cell, room) # attach full room dict
350
+ else:
351
+ cell = Shell.ByFaces(Helper.Flatten(rm_faces), tolerance=0.001, silent=True)
352
+ if not cell:
353
+ cell = Cluster.ByTopologies(Helper.Flatten(rm_faces), silent=True)
354
+ if Topology.IsInstance(cell, "topology"):
355
+ cell = _attach_all(cell, room) # attach full room dict
356
+
357
+ if Topology.IsInstance(cell, "Topology"):
358
+ cell = Topology.TransferDictionariesBySelectors(cell, selectors,tranFaces=True, numWorkers=1)
359
+ return cell, rm_faces, sh_faces, ap_faces, dr_faces
360
+ # No host faces -> no cell
361
+ return None, [], sh_faces, ap_faces, dr_faces
362
+
363
+ rooms: List[Cell] = []
364
+ faces: List[Face] = []
365
+ shades: List[Face] = []
366
+ apertures: List[Face] = []
367
+ doors: List[Face] = []
368
+ orphaned_rooms: List[Cell] = []
369
+ orphaned_faces: List[Face] = []
370
+ orphaned_shades: List[Face] = []
371
+ orphaned_apertures: List[Face] = []
372
+ orphaned_doors: List[Face] = []
373
+
374
+ # Rooms → Cells (when possible) + collect apertures. If a Cell cannot be made,
375
+ # the room goes to the orphaned_rooms list.
376
+ for room in (dictionary.get("rooms") or dictionary.get("Rooms") or []):
377
+ cell, host_faces, sh_faces, ap_faces, dr_faces = _room_to_cell_and_apertures(room)
378
+
379
+ if includeRooms and Topology.IsInstance(cell, "cell"):
380
+ rooms.append(cell)
381
+ elif includeOrphanedRooms and Topology.IsInstance(cell, "topology"):
382
+ orphaned_rooms.append(cell)
383
+ if cell:
384
+ if includeFaces and host_faces:
385
+ faces.extend(host_faces)
386
+ if includeShades and sh_faces:
387
+ shades.extend(sh_faces)
388
+ if includeApertures and ap_faces:
389
+ apertures.extend(ap_faces)
390
+ if includeDoors and dr_faces:
391
+ doors.extend(dr_faces)
392
+
393
+ # Explicit orphaned faces → 'orphaned_faces'
394
+ if includeOrphanedFaces:
395
+ explicit_orphaned_faces = dictionary.get("orphaned_faces") or dictionary.get("OrphanedFaces") or []
396
+ for f in explicit_orphaned_faces:
397
+ hf = _build_host_face_cut_holes(f)
398
+ if hf:
399
+ orphaned_faces.extend(hf)
400
+ # Some files also place planar surfaces at top-level 'faces'
401
+ for fobj in (dictionary.get("faces") or dictionary.get("Faces") or []):
402
+ hf = _build_host_face_cut_holes(fobj)
403
+ if hf:
404
+ orphaned_faces.extend(hf)
405
+
406
+ # Explicit orphaned shades (and/or context shades)
407
+ if includeOrphanedShades:
408
+ explicit_orphaned_shades = dictionary.get("orphaned_shades") or dictionary.get("OrphanedShades") or []
409
+ for s in explicit_orphaned_shades:
410
+ hf = _build_host_face_cut_holes(s)
411
+ if hf:
412
+ orphaned_shades.extend(hf)
413
+
414
+ ctx = dictionary.get("context_geometry") or dictionary.get("Context") or {}
415
+ shade_list = []
416
+ if isinstance(ctx, dict):
417
+ shade_list = ctx.get("shades") or ctx.get("Shades") or []
418
+ elif isinstance(ctx, list):
419
+ shade_list = ctx
420
+ for s in shade_list:
421
+ hf = _build_host_face_cut_holes(s)
422
+ if hf:
423
+ orphaned_shades.extend(hf)
424
+ # Some files might also place planar shade surfaces at top-level 'shades'
425
+ for fobj in (dictionary.get("shades") or dictionary.get("Shades") or []):
426
+ hf = _build_host_face_cut_holes(fobj)
427
+ if hf:
428
+ orphaned_shades.extend(hf)
429
+
430
+ # Explicit orphaned apertures → 'orphaned_apertures'
431
+ if includeOrphanedApertures:
432
+ orphaned_ap_list = dictionary.get("orphaned_apertures") or dictionary.get("OrphanedApertures") or []
433
+ if orphaned_ap_list:
434
+ orphaned_apertures.extend(_orphaned_aperture_faces(orphaned_ap_list))
435
+
436
+ # Explicit orphaned doors → 'orphaned_doors'
437
+ if includeOrphanedDoors:
438
+ orphaned_dr_list = dictionary.get("orphaned_doors") or dictionary.get("OrphanedDoors") or []
439
+ if orphaned_dr_list:
440
+ orphaned_doors.extend(_orphaned_aperture_faces(orphaned_dr_list)) #You can use the same function as apertures.
441
+
442
+ # Properties → hierarchical dict verbatim
443
+ props_root = dictionary.get("properties") or dictionary.get("Properties") or {}
444
+ properties = {
445
+ "radiance": props_root.get("radiance") or props_root.get("Radiance") or {},
446
+ "energy": props_root.get("energy") or props_root.get("Energy") or {},
447
+ }
448
+
449
+ return {
450
+ "rooms": rooms,
451
+ "faces": faces,
452
+ "shades": shades,
453
+ "apertures": apertures,
454
+ "doors": doors,
455
+ "orphanedRooms": orphaned_rooms,
456
+ "orphanedFaces": orphaned_faces,
457
+ "orphanedShades": orphaned_shades,
458
+ "orphanedApertures": orphaned_apertures,
459
+ "orphanedDoors": orphaned_doors,
460
+ "properties": properties
461
+ }
462
+
463
+ @staticmethod
464
+ def ByHBJSONPath(
465
+ path: str,
466
+ includeRooms: bool = True,
467
+ includeFaces: bool = True,
468
+ includeShades: bool = True,
469
+ includeApertures: bool = True,
470
+ includeDoors: bool = True,
471
+ includeOrphanedRooms: bool = True,
472
+ includeOrphanedFaces: bool = True,
473
+ includeOrphanedShades: bool = True,
474
+ includeOrphanedApertures: bool = True,
475
+ includeOrphanedDoors: bool = True,
476
+ tolerance: float = 0.0001,
477
+ silent: bool = False):
478
+ """
479
+ Import an HBJSON model from a file path and return a python dictionary. See: https://github.com/ladybug-tools/honeybee-schema/wiki/1.1-Model-Schema
480
+
481
+ Parameters
482
+ ----------
483
+ dictionary : dict
484
+ The HBJSON model as a Python dictionary (e.g., loaded via ``json.load``).
485
+ includeRooms : bool, optional
486
+ If True, parse rooms and attempt to create one ``Cell`` per room. Default is True.
487
+ includeFaces : bool, optional
488
+ If True, include top-level planar faces found outside rooms (e.g., at root "faces"). Default is True.
489
+ includeShades : bool, optional
490
+ If True, include context/standalone shades (e.g., ``context_geometry.shades``). Default is True.
491
+ includeApertures : bool, optional
492
+ If True, include **room** apertures (e.g., windows) as separate ``Face`` objects (not cut from hosts). Default is True.
493
+ includeDoors : bool, optional
494
+ If True, include **room** doors as separate ``Face`` objects (not cut from hosts). Default is True.
495
+ includeOrphanedRooms : bool, optional
496
+ If True, include the topology of the room when a room fails to close as a ``Cell``. This may be a ``Shell`` or a ``Cluster``. Default is True.
497
+ includeOrphanedFaces : bool, optional
498
+ If True, include planar faces listed at the HBJSON root (e.g., "faces"). Default is True.
499
+ includeOrphanedShades : bool, optional
500
+ If True, include shades listed at the HBJSON root (e.g., "orphaned_shades"). Default is True.
501
+ includeOrphanedApertures : bool, optional
502
+ If True, include apertures listed at the HBJSON root (e.g., "orphaned_apertures"). Default is True.
503
+ includeOrphanedDoors : bool, optional
504
+ If True, include doors listed at the HBJSON root (e.g., "orphaned_doors"). Default is True.
505
+ tolerance : float , optional
506
+ The desired tolerance. Default is 0.0001.
507
+ silent : bool , optional
508
+ If set to True, error and warning messages are suppressed. Default is False.
509
+
510
+ Returns
511
+ -------
512
+ dict
513
+ The created cluster of vertices, edges, faces, and cells.
514
+ - 'rooms': list of Cells (one per successfully closed room)
515
+ - 'faces': list of Faces (all faces that make up the rooms)
516
+ - 'shades': list of Faces (all shade faces)
517
+ - 'apertures': list of Faces (all apertures, never cut from hosts)
518
+ - 'doors': list of Faces (all doors, never cut from hosts)
519
+ - 'orphanedRooms': list of Topologies (context/top-level topologies (e.g. Shells or Clustser) that failed to form a Cell)
520
+ - 'orphanedFaces': list of Faces (context/top-level faces + host faces of rooms that failed to form a Cell)
521
+ - 'orphanedShades': list of Faces (context/top-level shade faces that failed to have a parent cell)
522
+ - 'orphanedApertures': list of Faces (apertures that failed to have a parent face)
523
+ - 'orphanedDoors': list of Faces (doors that failed to have a parent face)
524
+ - 'properties': hierarchical dict copied verbatim from HBJSON['properties']
525
+ """
526
+
527
+ import json
528
+ if not path:
529
+ if not silent:
530
+ print("Honeybee.ByHBJSONPath - Error: the input path parameter is not a valid path. Returning None.")
531
+ return None
532
+ with open(path) as file:
533
+ try:
534
+ hbjson_dict = json.load(file)
535
+ except:
536
+ if not silent:
537
+ print("Honeybee.ByHBJSONPath - Error: Could not open the HBJSON file. Returning None.")
538
+ return None
539
+ return Honeybee.ByHBJSONDictionary(hbjson_dict,
540
+ includeRooms = includeRooms,
541
+ includeFaces = includeFaces,
542
+ includeShades = includeShades,
543
+ includeApertures = includeApertures,
544
+ includeDoors = includeDoors,
545
+ includeOrphanedRooms = includeOrphanedRooms,
546
+ includeOrphanedFaces = includeOrphanedFaces,
547
+ includeOrphanedShades = includeOrphanedShades,
548
+ includeOrphanedApertures = includeOrphanedApertures,
549
+ includeOrphanedDoors = includeOrphanedDoors,
550
+ tolerance = tolerance,
551
+ silent = silent)
552
+
553
+ @staticmethod
554
+ def ByHBJSONString(
555
+ string,
556
+ includeRooms: bool = True,
557
+ includeFaces: bool = True,
558
+ includeShades: bool = True,
559
+ includeApertures: bool = True,
560
+ includeDoors: bool = True,
561
+ includeOrphanedRooms: bool = True,
562
+ includeOrphanedFaces: bool = True,
563
+ includeOrphanedShades: bool = True,
564
+ includeOrphanedApertures: bool = True,
565
+ includeOrphanedDoors: bool = True,
566
+ tolerance: float = 0.0001,
567
+ silent: bool = False):
568
+ """
569
+ Import an HBJSON model from a file path and return a python dictionary. See: https://github.com/ladybug-tools/honeybee-schema/wiki/1.1-Model-Schema
570
+
571
+ Parameters
572
+ ----------
573
+ string : str
574
+ The HBJSON model as a string.
575
+ includeRooms : bool, optional
576
+ If True, parse rooms and attempt to create one ``Cell`` per room. Default is True.
577
+ includeFaces : bool, optional
578
+ If True, include top-level planar faces found outside rooms (e.g., at root "faces"). Default is True.
579
+ includeShades : bool, optional
580
+ If True, include context/standalone shades (e.g., ``context_geometry.shades``). Default is True.
581
+ includeApertures : bool, optional
582
+ If True, include **room** apertures (e.g., windows) as separate ``Face`` objects (not cut from hosts). Default is True.
583
+ includeDoors : bool, optional
584
+ If True, include **room** doors as separate ``Face`` objects (not cut from hosts). Default is True.
585
+ includeOrphanedRooms : bool, optional
586
+ If True, include the topology of the room when a room fails to close as a ``Cell``. This may be a ``Shell`` or a ``Cluster``. Default is True.
587
+ includeOrphanedFaces : bool, optional
588
+ If True, include planar faces listed at the HBJSON root (e.g., "faces"). Default is True.
589
+ includeOrphanedShades : bool, optional
590
+ If True, include shades listed at the HBJSON root (e.g., "orphaned_shades"). Default is True.
591
+ includeOrphanedApertures : bool, optional
592
+ If True, include apertures listed at the HBJSON root (e.g., "orphaned_apertures"). Default is True.
593
+ includeOrphanedDoors : bool, optional
594
+ If True, include doors listed at the HBJSON root (e.g., "orphaned_doors"). Default is True.
595
+ tolerance : float , optional
596
+ The desired tolerance. Default is 0.0001.
597
+ silent : bool , optional
598
+ If set to True, error and warning messages are suppressed. Default is False.
599
+
600
+ Returns
601
+ -------
602
+ dict
603
+ The created cluster of vertices, edges, faces, and cells.
604
+ - 'rooms': list of Cells (one per successfully closed room)
605
+ - 'faces': list of Faces (all faces that make up the rooms)
606
+ - 'shades': list of Faces (all shade faces)
607
+ - 'apertures': list of Faces (all apertures, never cut from hosts)
608
+ - 'doors': list of Faces (all doors, never cut from hosts)
609
+ - 'orphanedRooms': list of Topologies (context/top-level topologies (e.g. Shells or Clustser) that failed to form a Cell)
610
+ - 'orphanedFaces': list of Faces (context/top-level faces + host faces of rooms that failed to form a Cell)
611
+ - 'orphanedShades': list of Faces (context/top-level shade faces that failed to have a parent cell)
612
+ - 'orphanedApertures': list of Faces (apertures that failed to have a parent face)
613
+ - 'orphanedDoors': list of Faces (doors that failed to have a parent face)
614
+ - 'properties': hierarchical dict copied verbatim from HBJSON['properties']
615
+ """
616
+
617
+ if not isinstance(string, str):
618
+ if not silent:
619
+ print("Honeybee.ByHBJSONString - Error: The input string parameter is not a valid string. Returning None.")
620
+ return None
621
+ hbjson_dict = json.loads(string)
622
+ if not isinstance(hbjson_dict, dict):
623
+ if not silent:
624
+ print("Honeybee.ByHBJSONString - Error: Could not convert the input string into a valid HBJSON dictionary. Returning None.")
625
+ return None
626
+ return Honeybee.ByHBJSONDictionary(hbjson_dict,
627
+ includeRooms = includeRooms,
628
+ includeFaces = includeFaces,
629
+ includeShades = includeShades,
630
+ includeApertures = includeApertures,
631
+ includeDoors = includeDoors,
632
+ includeOrphanedRooms = includeOrphanedRooms,
633
+ includeOrphanedFaces = includeOrphanedFaces,
634
+ includeOrphanedShades = includeOrphanedShades,
635
+ includeOrphanedApertures = includeOrphanedApertures,
636
+ includeOrphanedDoors = includeOrphanedDoors,
637
+ tolerance = tolerance,
638
+ silent = silent)
639
+
115
640
  @staticmethod
116
641
  def ConstructionSetByIdentifier(id):
117
642
  """
@@ -175,7 +700,7 @@ class Honeybee:
175
700
  path = path+".hbjson"
176
701
 
177
702
  if not overwrite and exists(path):
178
- print("DGL.ExportToHBJSON - Error: a file already exists at the specified path and overwrite is set to False. Returning None.")
703
+ print("Honeybee.ExportToHBJSON - Error: a file already exists at the specified path and overwrite is set to False. Returning None.")
179
704
  return None
180
705
  f = None
181
706
  try:
@@ -184,7 +709,7 @@ class Honeybee:
184
709
  else:
185
710
  f = open(path, "x") # Try to create a new File
186
711
  except:
187
- print("DGL.ExportToHBJSON - Error: Could not create a new file at the following location: "+path+". Returning None.")
712
+ print("Honeybee.ExportToHBJSON - Error: Could not create a new file at the following location: "+path+". Returning None.")
188
713
  return None
189
714
  if (f):
190
715
  json.dump(model.to_dict(), f, indent=4)
topologicpy/Plotly.py CHANGED
@@ -1447,8 +1447,10 @@ class Plotly:
1447
1447
  ----------
1448
1448
  matrix : list or numpy.array
1449
1449
  The matrix to display.
1450
- categories : list
1451
- The list of categories to use on the X and Y axes.
1450
+ xCategories : list
1451
+ The list of categories to use on the X axis.
1452
+ yCategories : list
1453
+ The list of categories to use on the Y axis.
1452
1454
  minValue : float , optional
1453
1455
  The desired minimum value to use for the color scale. If set to None, the minmum value found in the input matrix will be used. Default is None.
1454
1456
  maxValue : float , optional
topologicpy/Topology.py CHANGED
@@ -8699,6 +8699,23 @@ class Topology():
8699
8699
  if showFigure:
8700
8700
  Plotly.Show(figure=figure, renderer=renderer, camera=camera, center=center, up=up, projection=projection)
8701
8701
  return None
8702
+ if "ortho" in projection.lower():
8703
+ camera_settings = dict(eye=dict(x=camera[0], y=camera[1], z=camera[2]),
8704
+ center=dict(x=center[0], y=center[1], z=center[2]),
8705
+ up=dict(x=up[0], y=up[1], z=up[2]),
8706
+ projection=dict(type="orthographic"))
8707
+ else:
8708
+ camera_settings = dict(eye=dict(x=camera[0], y=camera[1], z=camera[2]),
8709
+ center=dict(x=center[0], y=center[1], z=center[2]),
8710
+ up=dict(x=up[0], y=up[1], z=up[2]),
8711
+ projection=dict(type="perspective"))
8712
+
8713
+ figure.update_layout(
8714
+ scene_camera = camera_settings,
8715
+ scene=dict(aspectmode="data"),
8716
+ autosize=True,
8717
+ margin=dict(l=40, r=40, t=40, b=40)
8718
+ )
8702
8719
  return figure
8703
8720
 
8704
8721
  @staticmethod
topologicpy/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = '0.8.52'
1
+ __version__ = '0.8.55'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: topologicpy
3
- Version: 0.8.52
3
+ Version: 0.8.55
4
4
  Summary: An AI-Powered Spatial Modelling and Analysis Software Library for Architecture, Engineering, and Construction.
5
5
  Author-email: Wassim Jabi <wassim.jabi@gmail.com>
6
6
  License: AGPL v3 License
@@ -2,7 +2,7 @@ topologicpy/ANN.py,sha256=gpflv4lFypOW789vO7mSkMLaMF_ZftVOCqCvtGr6-JA,47873
2
2
  topologicpy/Aperture.py,sha256=wNn5miB_IrGCBYuQ18HXQYRva20dUC3id4AJCulL7to,2723
3
3
  topologicpy/BVH.py,sha256=JA4bb-9hgMfVZ_syzmSmTL3ueCq-0vMUGMPZxNcawAY,13023
4
4
  topologicpy/CSG.py,sha256=09la1-xzS9vr-WnV7tpJ0I-mkZ-XY0MRSd5iB50Nfgw,15556
5
- topologicpy/Cell.py,sha256=-QoKvAsLjKp4xXO1bXCIpKdKNrPF7o232QTj60WCADE,177462
5
+ topologicpy/Cell.py,sha256=ndrfovjwEEQTJ0B5DuthuCBDdVg9-1i_MX8Dtj-wD4E,177330
6
6
  topologicpy/CellComplex.py,sha256=Kbz63rGeE08bJfMXFvB-AptoKHiaCK5OtiV1wz8Y-Fk,68081
7
7
  topologicpy/Cluster.py,sha256=G49AuhJHQ1s819cB5MtVdmAGgkag19IC3dRP1ub1Wh4,58608
8
8
  topologicpy/Color.py,sha256=hzSmgBWhiuYc55RSipkQNIgGtgyhC5BqY8AakNYEK-U,24486
@@ -10,29 +10,29 @@ topologicpy/Context.py,sha256=G3CwMvN8Jw2rnQRwB-n4MaQq_wLS0vPimbXKwsdMJ80,3055
10
10
  topologicpy/DGL.py,sha256=O7r22ss0tgak4kWaACkyExhR_rZ46O_bqIBpxAcwJkw,138918
11
11
  topologicpy/Dictionary.py,sha256=Z4YQ88tONWd-0X0dENQ8IZqIOa9mbBqhJkTBsHmft2g,44619
12
12
  topologicpy/Edge.py,sha256=DifItuyabFDUFC7CVMlt2DeMFMNaGOqCg43iU9CPP0A,74029
13
- topologicpy/EnergyModel.py,sha256=IiBJNx7F9-8TPMaQn1iQON1ZtTv2nT5kbZHxM_gBCTQ,53773
13
+ topologicpy/EnergyModel.py,sha256=hB1aiJe45gdDMFm1AhkBr-1djjtXSzn24iRpQMk43-4,57749
14
14
  topologicpy/Face.py,sha256=aX9EcR3JGbLITElhd25J0Z8m9U8KkmbYivGg3oZN-Uw,202296
15
- topologicpy/Graph.py,sha256=o6aK18tCTkfzsNFOTAU-wIPG2p3g_vpm6n7YHc44bYU,691239
15
+ topologicpy/Graph.py,sha256=Oa0oOrPoOSUGL5fvJYHBH_r6kRZ944wk-P828GyAjk4,705757
16
16
  topologicpy/Grid.py,sha256=3OsBMyHh4w8gpFOTMKHMNTpo62V0CwRNu5cwm87yDUA,18421
17
- topologicpy/Helper.py,sha256=aGmndgmEztjVNU-wW9OoHDel7wzapprM0TjA7f2AoS8,31188
18
- topologicpy/Honeybee.py,sha256=C7Am0kCK3a5rt7Jpu2EIgqeR114ZJWtsx4_DBcr5hQA,21716
17
+ topologicpy/Helper.py,sha256=Nr6pyzl0sZm4Cu11wOqoYKu6yYal5N6A9jErXnaZBJc,31765
18
+ topologicpy/Honeybee.py,sha256=DzaG9wpkJdcDWcjOGXhuN5X0gCqypmZGBa1y5E2MkjU,48964
19
19
  topologicpy/Matrix.py,sha256=bOofT34G3YHu9aMIWx60YHAJga4R0GbDjsZBUD4Hu_k,22706
20
20
  topologicpy/Neo4j.py,sha256=J8jU_mr5-mWC0Lg_D2dMjMlx1rY_eh8ks_aubUuTdWw,22319
21
- topologicpy/Plotly.py,sha256=RXGeEBwVs8unJaT9vv_FBmAFfKd-1Z5x3V8oeKE43Bs,122924
21
+ topologicpy/Plotly.py,sha256=kF7JwBMWJQAuGezaJYI6Cq7ErNwEtcKzaExOfdGPIMc,123003
22
22
  topologicpy/Polyskel.py,sha256=oVfM4lqSMPTjnkHfsRU9VI8Blt6Vf0LVPkD9ebz7Wmw,27082
23
23
  topologicpy/PyG.py,sha256=wOsoBFxMgwZYWjj86OMkz_PJuQ02locV_djhSDD6dVc,109644
24
24
  topologicpy/ShapeGrammar.py,sha256=KYsKDLXWdflAcYMAIz84AUF-GMkbTmaBDd2-ovbilqU,23336
25
25
  topologicpy/Shell.py,sha256=ioO4raCJfXtYldQg-adpcLVeJPEA6od6cAA5ro7t6r4,96792
26
26
  topologicpy/Speckle.py,sha256=-eiTqJugd7pHiHpD3pDUcDO6CGhVyPV14HFRzaqEoaw,18187
27
27
  topologicpy/Sun.py,sha256=8S6dhCKfOhUGVny-jEk87Q08anLYMB1JEBKRGCklvbQ,36670
28
- topologicpy/Topology.py,sha256=Oz6I0jerzgXJaOEAS-WZVko5wj4_PYq67UH707jpwE0,471685
28
+ topologicpy/Topology.py,sha256=R5Ac3V_ADDRDZjhpcNvhM3AvDOLU6ORvB3yILyEkxnI,472559
29
29
  topologicpy/Vector.py,sha256=pEC8YY3TeHGfGdeNgvdHjgMDwxGabp5aWjwYC1HSvMk,42236
30
30
  topologicpy/Vertex.py,sha256=0f6HouARKaCuxhdxsUEYi8T9giJycnWhQ8Cn70YILBA,84885
31
31
  topologicpy/Wire.py,sha256=gjgQUGHdBdXUIijgZc_VIW0E39w-smaVhhdl0jF63fQ,230466
32
32
  topologicpy/__init__.py,sha256=RMftibjgAnHB1vdL-muo71RwMS4972JCxHuRHOlU428,928
33
- topologicpy/version.py,sha256=KGLw5DoKAzuYOCwcR3luB2Dw4oq0tDBaQ67alLPVna8,23
34
- topologicpy-0.8.52.dist-info/licenses/LICENSE,sha256=FK0vJ73LuE8PYJAn7LutsReWR47-Ooovw2dnRe5yV6Q,681
35
- topologicpy-0.8.52.dist-info/METADATA,sha256=haOTIpPxEwBfVeLm4SOIow_qanvOQ-4bzWRFNCGnWPg,10535
36
- topologicpy-0.8.52.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
37
- topologicpy-0.8.52.dist-info/top_level.txt,sha256=J30bDzW92Ob7hw3zA8V34Jlp-vvsfIkGzkr8sqvb4Uw,12
38
- topologicpy-0.8.52.dist-info/RECORD,,
33
+ topologicpy/version.py,sha256=EKCMooHLmkTpTBR1XTlkHxj-YkXZosf7ysuCeCcjiR8,23
34
+ topologicpy-0.8.55.dist-info/licenses/LICENSE,sha256=FK0vJ73LuE8PYJAn7LutsReWR47-Ooovw2dnRe5yV6Q,681
35
+ topologicpy-0.8.55.dist-info/METADATA,sha256=BIWmEWd275UZuMnl423fCxI3Bv6-vZpot8niPNApQAE,10535
36
+ topologicpy-0.8.55.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
37
+ topologicpy-0.8.55.dist-info/top_level.txt,sha256=J30bDzW92Ob7hw3zA8V34Jlp-vvsfIkGzkr8sqvb4Uw,12
38
+ topologicpy-0.8.55.dist-info/RECORD,,