topologicpy 0.7.71__py3-none-any.whl → 0.7.73__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/BVH.py CHANGED
@@ -41,6 +41,8 @@ class BVH:
41
41
 
42
42
  def intersects(self, other):
43
43
  # Check if this AABB intersects with another AABB
44
+ if other == None:
45
+ return False
44
46
  return np.all(self.min_point <= other.max_point) and np.all(self.max_point >= other.min_point)
45
47
 
46
48
  def contains(self, point):
@@ -200,13 +202,13 @@ class BVH:
200
202
  cluster = Cluster.ByTopologies(vertices)
201
203
  bb = Topology.BoundingBox(cluster)
202
204
  d = Topology.Dictionary(bb)
203
- min_x = Dictionary.ValueAtKey(d, "minx")
204
- min_y = Dictionary.ValueAtKey(d, "miny")
205
- min_z = Dictionary.ValueAtKey(d, "minz")
206
- max_x = Dictionary.ValueAtKey(d, "maxx")
207
- max_y = Dictionary.ValueAtKey(d, "maxy")
208
- max_z = Dictionary.ValueAtKey(d, "maxz")
209
- query_aabb = BVH.AABB(min_point=(min_x, min_y, min_z), max_point=(max_x,max_y,max_z))
205
+ x_min = Dictionary.ValueAtKey(d, "xmin")
206
+ y_min = Dictionary.ValueAtKey(d, "ymin")
207
+ z_min = Dictionary.ValueAtKey(d, "zmin")
208
+ x_max = Dictionary.ValueAtKey(d, "zmax")
209
+ y_max = Dictionary.ValueAtKey(d, "ymax")
210
+ z_max = Dictionary.ValueAtKey(d, "zmax")
211
+ query_aabb = BVH.AABB(min_point=(x_min, y_min, z_min), max_point=(x_max, y_max, z_max))
210
212
  return query_aabb
211
213
 
212
214
  def Clashes(bvh, query):
@@ -103,6 +103,8 @@ class CellComplex():
103
103
  If set to True, any dictionaries in the cells are transferred to the CellComplex. Otherwise, they are not. The default is False.
104
104
  tolerance : float , optional
105
105
  The desired tolerance. The default is 0.0001.
106
+ silent : bool , optional
107
+ If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
106
108
 
107
109
  Returns
108
110
  -------
@@ -192,7 +194,7 @@ class CellComplex():
192
194
  return CellComplex.ByCells(cells, tolerance)
193
195
 
194
196
  @staticmethod
195
- def ByFaces(faces: list, tolerance: float = 0.0001):
197
+ def ByFaces(faces: list, tolerance: float = 0.0001, silent: bool = False):
196
198
  """
197
199
  Creates a cellcomplex by merging the input faces.
198
200
 
@@ -202,6 +204,8 @@ class CellComplex():
202
204
  The input faces.
203
205
  tolerance : float , optional
204
206
  The desired tolerance. The default is 0.0001.
207
+ silent : bool , optional
208
+ If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
205
209
 
206
210
  Returns
207
211
  -------
@@ -213,38 +217,45 @@ class CellComplex():
213
217
  from topologicpy.Topology import Topology
214
218
 
215
219
  if not isinstance(faces, list):
216
- print("CellComplex.ByFaces - Error: The input faces parameter is not a valid list. Returning None.")
220
+ if not silent:
221
+ print("CellComplex.ByFaces - Error: The input faces parameter is not a valid list. Returning None.")
217
222
  return None
218
223
  faces = [x for x in faces if Topology.IsInstance(x, "Face")]
219
224
  if len(faces) < 1:
220
- print("CellComplex.ByFaces - Error: The input faces parameter does not contain any valid faces. Returning None.")
225
+ if not silent:
226
+ print("CellComplex.ByFaces - Error: The input faces parameter does not contain any valid faces. Returning None.")
221
227
  return None
222
228
  try:
223
229
  cellComplex = topologic.CellComplex.ByFaces(faces, tolerance, False) # Hook to Core
224
230
  except:
225
231
  cellComplex = None
226
232
  if not cellComplex:
227
- print("CellComplex.ByFaces - Warning: The default method failed. Attempting a workaround.")
233
+ if not silent:
234
+ print("CellComplex.ByFaces - Warning: The default method failed. Attempting a workaround.")
228
235
  cellComplex = faces[0]
229
236
  for i in range(1,len(faces)):
230
237
  newCellComplex = None
231
238
  try:
232
239
  newCellComplex = cellComplex.Merge(faces[i], False, tolerance)
233
240
  except:
234
- print("CellComplex.ByFaces - Warning: Failed to merge face #"+str(i)+". Skipping.")
241
+ if not silent:
242
+ print("CellComplex.ByFaces - Warning: Failed to merge face #"+str(i)+". Skipping.")
235
243
  if newCellComplex:
236
244
  cellComplex = newCellComplex
237
245
  if not Topology.Type(cellComplex) == Topology.TypeID("CellComplex"):
238
- print("CellComplex.ByFaces - Warning: The input faces do not form a cellcomplex")
246
+ if not silent:
247
+ print("CellComplex.ByFaces - Warning: The input faces do not form a cellcomplex")
239
248
  if Topology.Type(cellComplex) == Topology.TypeID("Cluster"):
240
249
  returnCellComplexes = Cluster.CellComplexes(cellComplex)
241
250
  if len(returnCellComplexes) > 0:
242
251
  return returnCellComplexes[0]
243
252
  else:
244
- print("CellComplex.ByFaces - Error: Could not create a cellcomplex. Returning None.")
253
+ if not silent:
254
+ print("CellComplex.ByFaces - Error: Could not create a cellcomplex. Returning None.")
245
255
  return None
246
256
  else:
247
- print("CellComplex.ByFaces - Error: Could not create a cellcomplex. Returning None.")
257
+ if not silent:
258
+ print("CellComplex.ByFaces - Error: Could not create a cellcomplex. Returning None.")
248
259
  return None
249
260
  else:
250
261
  return cellComplex
@@ -892,33 +903,33 @@ class CellComplex():
892
903
  x.append(Vertex.X(aVertex, mantissa=mantissa))
893
904
  y.append(Vertex.Y(aVertex, mantissa=mantissa))
894
905
  z.append(Vertex.Z(aVertex, mantissa=mantissa))
895
- minX = min(x)
896
- minY = min(y)
897
- minZ = min(z)
906
+ x_min = min(x)
907
+ y_min = min(y)
908
+ z_min = min(z)
898
909
  maxX = max(x)
899
910
  maxY = max(y)
900
911
  maxZ = max(z)
901
- return [minX, minY, minZ, maxX, maxY, maxZ]
912
+ return [x_min, y_min, z_min, maxX, maxY, maxZ]
902
913
 
903
914
  def slice(topology, uSides, vSides, wSides):
904
- minX, minY, minZ, maxX, maxY, maxZ = bb(topology)
905
- centroid = Vertex.ByCoordinates(minX+(maxX-minX)*0.5, minY+(maxY-minY)*0.5, minZ+(maxZ-minZ)*0.5)
906
- wOrigin = Vertex.ByCoordinates(Vertex.X(centroid, mantissa=mantissa), Vertex.Y(centroid, mantissa=mantissa), minZ)
907
- wFace = Face.Rectangle(origin=wOrigin, width=(maxX-minX)*1.1, length=(maxY-minY)*1.1)
915
+ x_min, y_min, z_min, maxX, maxY, maxZ = bb(topology)
916
+ centroid = Vertex.ByCoordinates(x_min+(maxX-x_min)*0.5, y_min+(maxY-y_min)*0.5, z_min+(maxZ-z_min)*0.5)
917
+ wOrigin = Vertex.ByCoordinates(Vertex.X(centroid, mantissa=mantissa), Vertex.Y(centroid, mantissa=mantissa), z_min)
918
+ wFace = Face.Rectangle(origin=wOrigin, width=(maxX-x_min)*1.1, length=(maxY-y_min)*1.1)
908
919
  wFaces = []
909
- wOffset = (maxZ-minZ)/wSides
920
+ wOffset = (maxZ-z_min)/wSides
910
921
  for i in range(wSides-1):
911
922
  wFaces.append(Topology.Translate(wFace, 0,0,wOffset*(i+1)))
912
- uOrigin = Vertex.ByCoordinates(minX, Vertex.Y(centroid, mantissa=mantissa), Vertex.Z(centroid, mantissa=mantissa))
913
- uFace = Face.Rectangle(origin=uOrigin, width=(maxZ-minZ)*1.1, length=(maxY-minY)*1.1, direction=[1,0,0])
923
+ uOrigin = Vertex.ByCoordinates(x_min, Vertex.Y(centroid, mantissa=mantissa), Vertex.Z(centroid, mantissa=mantissa))
924
+ uFace = Face.Rectangle(origin=uOrigin, width=(maxZ-z_min)*1.1, length=(maxY-y_min)*1.1, direction=[1,0,0])
914
925
  uFaces = []
915
- uOffset = (maxX-minX)/uSides
926
+ uOffset = (maxX-x_min)/uSides
916
927
  for i in range(uSides-1):
917
928
  uFaces.append(Topology.Translate(uFace, uOffset*(i+1),0,0))
918
- vOrigin = Vertex.ByCoordinates(Vertex.X(centroid, mantissa=mantissa), minY, Vertex.Z(centroid, mantissa=mantissa))
919
- vFace = Face.Rectangle(origin=vOrigin, width=(maxX-minX)*1.1, length=(maxZ-minZ)*1.1, direction=[0,1,0])
929
+ vOrigin = Vertex.ByCoordinates(Vertex.X(centroid, mantissa=mantissa), y_min, Vertex.Z(centroid, mantissa=mantissa))
930
+ vFace = Face.Rectangle(origin=vOrigin, width=(maxX-x_min)*1.1, length=(maxZ-z_min)*1.1, direction=[0,1,0])
920
931
  vFaces = []
921
- vOffset = (maxY-minY)/vSides
932
+ vOffset = (maxY-y_min)/vSides
922
933
  for i in range(vSides-1):
923
934
  vFaces.append(Topology.Translate(vFace, 0,vOffset*(i+1),0))
924
935
  all_faces = uFaces+vFaces+wFaces
@@ -995,6 +1006,64 @@ class CellComplex():
995
1006
  shells = Topology.Shells(cellComplex)
996
1007
  return shells
997
1008
 
1009
+ @staticmethod
1010
+ def Torus(origin= None, majorRadius: float = 0.5, minorRadius: float = 0.125, uSides: int = 16, vSides: int = 8, direction: list = [0, 0, 1], placement: str = "center", tolerance: float = 0.0001):
1011
+ """
1012
+ Creates a torus.
1013
+
1014
+ Parameters
1015
+ ----------
1016
+ origin : topologic_core.Vertex , optional
1017
+ The origin location of the torus. The default is None which results in the torus being placed at (0, 0, 0).
1018
+ majorRadius : float , optional
1019
+ The major radius of the torus. The default is 0.5.
1020
+ minorRadius : float , optional
1021
+ The minor radius of the torus. The default is 0.1.
1022
+ uSides : int , optional
1023
+ The number of sides along the longitude of the torus. The default is 16.
1024
+ vSides : int , optional
1025
+ The number of sides along the latitude of the torus. The default is 8.
1026
+ direction : list , optional
1027
+ The vector representing the up direction of the torus. The default is [0, 0, 1].
1028
+ placement : str , optional
1029
+ The description of the placement of the origin of the torus. This can be "bottom", "center", or "lowerleft". It is case insensitive. The default is "center".
1030
+ tolerance : float , optional
1031
+ The desired tolerance. The default is 0.0001.
1032
+
1033
+ Returns
1034
+ -------
1035
+ topologic_core.Cell
1036
+ The created torus.
1037
+
1038
+ """
1039
+
1040
+ from topologicpy.Vertex import Vertex
1041
+ from topologicpy.Wire import Wire
1042
+ from topologicpy.Face import Face
1043
+ from topologicpy.Cell import Cell
1044
+ from topologicpy.Topology import Topology
1045
+
1046
+ if not Topology.IsInstance(origin, "Vertex"):
1047
+ origin = Vertex.ByCoordinates(0, 0, 0)
1048
+ if not Topology.IsInstance(origin, "Vertex"):
1049
+ print("Cell.Torus - Error: The input origin parameter is not a valid topologic vertex. Returning None.")
1050
+ return None
1051
+ c = Wire.Circle(origin=Vertex.Origin(), radius=minorRadius, sides=vSides, fromAngle=0, toAngle=360, close=False, direction=[0, 1, 0], placement="center")
1052
+ c = Face.ByWire(c)
1053
+ c = Topology.Translate(c, abs(majorRadius-minorRadius), 0, 0)
1054
+ torus = Topology.Spin(c, origin=Vertex.Origin(), triangulate=False, direction=[0, 0, 1], angle=360, sides=uSides, tolerance=tolerance)
1055
+ if Topology.Type(torus) == Topology.TypeID("Shell"):
1056
+ faces = Topology.Faces(torus)
1057
+ torus = CellComplex.ByFaces(faces)
1058
+ if placement.lower() == "bottom":
1059
+ torus = Topology.Translate(torus, 0, 0, minorRadius)
1060
+ elif placement.lower() == "lowerleft":
1061
+ torus = Topology.Translate(torus, majorRadius, majorRadius, minorRadius)
1062
+
1063
+ torus = Topology.Orient(torus, origin=Vertex.Origin(), dirA=[0, 0, 1], dirB=direction)
1064
+ torus = Topology.Place(torus, originA=Vertex.Origin(), originB=origin)
1065
+ return torus
1066
+
998
1067
  @staticmethod
999
1068
  def Vertices(cellComplex) -> list:
1000
1069
  """
topologicpy/Cluster.py CHANGED
@@ -166,7 +166,7 @@ class Cluster():
166
166
  from topologicpy.Topology import Topology
167
167
  from topologicpy.Helper import Helper
168
168
  import inspect
169
-
169
+
170
170
  if len(args) == 0:
171
171
  if not silent:
172
172
  print("Cluster.ByTopologies - Error: The input topologies parameter is an empty list. Returning None.")
topologicpy/Color.py CHANGED
@@ -18,6 +18,44 @@ import plotly.colors
18
18
  import math
19
19
 
20
20
  class Color:
21
+
22
+ @staticmethod
23
+ def AnyToHex(color):
24
+ """
25
+ Converts a color to a hexadecimal color string.
26
+
27
+ Parameters
28
+ ----------
29
+ color : list or str
30
+ The input color parameter which can be any of RGB, CMYK, CSS Named Color, or Hex
31
+
32
+ Returns
33
+ -------
34
+ str
35
+ A hexadecimal color string in the format '#RRGGBB'.
36
+ """
37
+ return_hex = None
38
+ if isinstance(color, list):
39
+ if len(color) == 4: # Probably CMYK
40
+ if all(0 <= x <= 1 for x in color[:4]):
41
+ return_hex = Color.CMYKToHex(color[:4])
42
+ elif len(color) == 3:
43
+ if all(0 <= x <= 255 for x in color[:3]):
44
+ return_hex = Color.RGBToHex(color[:3])
45
+ elif isinstance(color, str): # Probably a CSSColor
46
+ if color.lower() in [x.lower() for x in Color.CSSNamedColors()]:
47
+ rgb = Color.ByCSSNamedColor(color.lower())
48
+ return_hex = Color.RGBToHex(rgb)
49
+ else: # Probably alread a HEX or other Plotly-compatible string
50
+ return_hex = color
51
+
52
+ if not isinstance(return_hex, str):
53
+ print("Color.AnyToHex - Error: Could not recognize the input parameter. Returning None.")
54
+ return None
55
+
56
+ return return_hex.upper()
57
+
58
+
21
59
  @staticmethod
22
60
  def ByCSSNamedColor(color, alpha: float = None):
23
61
  """
@@ -262,6 +300,34 @@ class Color:
262
300
  rgbList.append(alpha)
263
301
  return rgbList
264
302
 
303
+ @staticmethod
304
+ def CMYKToHex(cmyk):
305
+ """
306
+ Convert a CMYK color (list of 4 values) to its hexadecimal representation.
307
+
308
+ Parameters
309
+ ----------
310
+ color : list
311
+ cmyk (list or tuple): CMYK color values as [C, M, Y, K], each in the range 0 to 1.
312
+
313
+ Returns
314
+ -------
315
+ str: The hexadecimal color string for Plotly (e.g., '#FFFFFF').
316
+ """
317
+ c, m, y, k = cmyk
318
+
319
+ # Convert CMYK to RGB
320
+ r = 255 * (1 - c) * (1 - k)
321
+ g = 255 * (1 - m) * (1 - k)
322
+ b = 255 * (1 - y) * (1 - k)
323
+
324
+ # Clamp RGB values to 0-255 range and convert to integers
325
+ r, g, b = int(round(r)), int(round(g)), int(round(b))
326
+
327
+ # Convert RGB to hex format
328
+ hex_color = "#{:02x}{:02x}{:02x}".format(r, g, b)
329
+ return hex_color
330
+
265
331
  @staticmethod
266
332
  def CSSNamedColor(color):
267
333
  """
@@ -355,7 +421,7 @@ class Color:
355
421
  "mediumorchid", "mediumpurple", "mediumseagreen", "mediumslateblue", "mediumspringgreen", "mediumturquoise",
356
422
  "mediumvioletred", "midnightblue", "mintcream", "mistyrose", "moccasin", "navajowhite", "navy", "oldlace",
357
423
  "olive", "olivedrab", "orange", "orangered", "orchid", "palegoldenrod", "palegreen", "paleturquoise",
358
- "palevioletred", "papayawhip", "peachpuff", "peru", "pink", "plum", "powderblue", "purple", "rebeccapurple",
424
+ "palevioletred", "papayawhip", "peachpuff", "peru", "pink", "plum", "powderblue", "purple",
359
425
  "red", "rosybrown", "royalblue", "saddlebrown", "salmon", "sandybrown", "seagreen", "seashell", "sienna",
360
426
  "silver", "skyblue", "slateblue", "slategray", "slategrey", "snow", "springgreen", "steelblue", "tan",
361
427
  "teal", "thistle", "tomato", "turquoise", "violet", "wheat", "white", "whitesmoke", "yellow", "yellowgreen"
topologicpy/Dictionary.py CHANGED
@@ -344,6 +344,97 @@ class Dictionary():
344
344
  values.append(pythonDictionary[key])
345
345
  return Dictionary.ByKeysValues(keys, values)
346
346
 
347
+ @staticmethod
348
+ def Filter(elements, dictionaries, searchType="any", key=None, value=None):
349
+ """
350
+ Filters the input list of dictionaries based on the input parameters.
351
+
352
+ Parameters
353
+ ----------
354
+ elements : list
355
+ The input list of elements to be filtered according to the input dictionaries.
356
+ dictionaries : list
357
+ The input list of dictionaries to be filtered.
358
+ searchType : str , optional
359
+ The type of search query to conduct in the topology's dictionary. This can be one of "any", "equal to", "contains", "starts with", "ends with", "not equal to", "does not contain". The default is "any".
360
+ key : str , optional
361
+ The dictionary key to search within. The default is None which means it will filter by topology type only.
362
+ value : str , optional
363
+ The value to search for at the specified key. The default is None which means it will filter by topology type only.
364
+
365
+ Returns
366
+ -------
367
+ dict
368
+ A dictionary of filtered and other elements. The dictionary has two keys:
369
+ - "filtered" The filtered dictionaries.
370
+ - "other" The other dictionaries that did not meet the filter criteria.
371
+ - "filteredIndices" The filtered indices of dictionaries.
372
+ - "otherIndices" The other indices of dictionaries that did not meet the filter criteria.
373
+
374
+ """
375
+
376
+ from topologicpy.Topology import Topology
377
+
378
+ def listToString(item):
379
+ returnString = ""
380
+ if isinstance(item, list):
381
+ if len(item) < 2:
382
+ return str(item[0])
383
+ else:
384
+ returnString = item[0]
385
+ for i in range(1, len(item)):
386
+ returnString = returnString+str(item[i])
387
+ return returnString
388
+
389
+ filteredDictionaries = []
390
+ otherDictionaries = []
391
+ filteredElements = []
392
+ otherElements = []
393
+ filteredIndices = []
394
+ otherIndices = []
395
+ for i, aDictionary in enumerate(dictionaries):
396
+ if not Topology.IsInstance(aDictionary, "Dictionary"):
397
+ continue
398
+ if value == "" or key == "" or value == None or key == None:
399
+ filteredDictionaries.append(aDictionary)
400
+ filteredIndices.append(i)
401
+ else:
402
+ if isinstance(value, list):
403
+ value.sort()
404
+ value = str(value)
405
+ value.replace("*",".+")
406
+ value = value.lower()
407
+ v = Dictionary.ValueAtKey(aDictionary, key)
408
+ if v == None:
409
+ otherDictionaries.append(aDictionary)
410
+ otherIndices.append(i)
411
+ otherElements.append(elements[i])
412
+ else:
413
+ v = str(v).lower()
414
+ if searchType.lower() == "equal to":
415
+ searchResult = (value == v)
416
+ elif searchType.lower() == "contains":
417
+ searchResult = (value in v)
418
+ elif searchType.lower() == "starts with":
419
+ searchResult = (value == v[0: len(value)])
420
+ elif searchType.lower() == "ends with":
421
+ searchResult = (value == v[len(v)-len(value):len(v)])
422
+ elif searchType.lower() == "not equal to":
423
+ searchResult = not (value == v)
424
+ elif searchType.lower() == "does not contain":
425
+ searchResult = not (value in v)
426
+ else:
427
+ searchResult = False
428
+ if searchResult == True:
429
+ filteredDictionaries.append(aDictionary)
430
+ filteredIndices.append(i)
431
+ filteredElements.append(elements[i])
432
+ else:
433
+ otherDictionaries.append(aDictionary)
434
+ otherIndices.append(i)
435
+ otherElements.append(elements[i])
436
+ return {"filteredDictionaries": filteredDictionaries, "otherDictionaries": otherDictionaries, "filteredIndices": filteredIndices, "otherIndices": otherIndices, "filteredElements": filteredElements, "otherElements": otherElements}
437
+
347
438
  @staticmethod
348
439
  def Keys(dictionary):
349
440
  """
@@ -579,7 +670,7 @@ class Dictionary():
579
670
  temp_value = attr.StringValue()
580
671
  topologies = None
581
672
  try:
582
- topologies = Topology.ByJSONString(temp_value, progressBar=False)
673
+ topologies = Topology.ByJSONString(temp_value)
583
674
  except:
584
675
  topologies = None
585
676
  if isinstance(topologies, list):
@@ -654,14 +745,21 @@ class Dictionary():
654
745
  if not silent == True:
655
746
  print("Dictionary.ValueAtKey - Error: The input key parameter is not a valid str. Returning None.")
656
747
  return None
657
- if isinstance(dictionary, dict):
658
- attr = dictionary[key]
659
- elif Topology.IsInstance(dictionary, "Dictionary"):
660
- attr = dictionary.ValueAtKey(key)
661
- else:
662
- return None
663
- return_value = Dictionary._ConvertAttribute(attr)
664
- return return_value
748
+ if Topology.IsInstance(dictionary, "Dictionary"):
749
+ dic = Dictionary.PythonDictionary(dictionary)
750
+ return dic.get(key, None)
751
+ elif isinstance(dictionary, dict):
752
+ return dictionary.get(key, None)
753
+ return None
754
+
755
+ # if isinstance(dictionary, dict):
756
+ # attr = dictionary[key]
757
+ # elif Topology.IsInstance(dictionary, "Dictionary"):
758
+ # attr = dictionary.ValueAtKey(key)
759
+ # else:
760
+ # return None
761
+ # return_value = Dictionary._ConvertAttribute(attr)
762
+ # return return_value
665
763
 
666
764
  @staticmethod
667
765
  def Values(dictionary):
@@ -693,7 +791,7 @@ class Dictionary():
693
791
  for key in keys:
694
792
  try:
695
793
  if isinstance(dictionary, dict):
696
- attr = dictionary[key]
794
+ attr = dictionary.get(key, None)
697
795
  elif Topology.IsInstance(dictionary, "Dictionary"):
698
796
  attr = Dictionary.ValueAtKey(dictionary,key)
699
797
  else:
topologicpy/Edge.py CHANGED
@@ -1143,6 +1143,8 @@ class Edge():
1143
1143
  from topologicpy.Vertex import Vertex
1144
1144
  from topologicpy.Topology import Topology
1145
1145
 
1146
+
1147
+
1146
1148
  def calculate_normal(start_vertex, end_vertex):
1147
1149
  start_vertex = np.array([float(x) for x in start_vertex])
1148
1150
  end_vertex = np.array([float(x) for x in end_vertex])
@@ -1150,8 +1152,11 @@ class Edge():
1150
1152
  # Calculate the direction vector of the edge
1151
1153
  direction_vector = end_vertex - start_vertex
1152
1154
 
1153
- # Handle the horizontal edge case (no Z component)
1154
- if np.isclose(direction_vector[2], 0):
1155
+ # Check if the edge is vertical (only Z component)
1156
+ if np.isclose(direction_vector[0], 0) and np.isclose(direction_vector[1], 0):
1157
+ # Choose an arbitrary perpendicular vector in the X-Y plane, e.g., [1, 0, 0]
1158
+ normal_vector = np.array([1.0, 0.0, 0.0])
1159
+ elif np.isclose(direction_vector[2], 0):
1155
1160
  # The edge lies in the X-Y plane; compute a perpendicular in the X-Y plane
1156
1161
  normal_vector = np.array([-direction_vector[1], direction_vector[0], 0.0])
1157
1162
  else:
@@ -1162,7 +1167,7 @@ class Edge():
1162
1167
  # Check if the normal vector is effectively zero before normalization
1163
1168
  if np.isclose(norm(normal_vector), 0):
1164
1169
  return normal_vector
1165
-
1170
+
1166
1171
  # Normalize the normal vector
1167
1172
  normal_vector /= norm(normal_vector)
1168
1173
  return normal_vector
@@ -1192,7 +1197,6 @@ class Edge():
1192
1197
 
1193
1198
  # Calculate the normal line
1194
1199
  normal_line_start, normal_line_end = calculate_normal_line(start_vertex, end_vertex)
1195
-
1196
1200
  # Create the normal edge in Topologic
1197
1201
  sv = Vertex.ByCoordinates(list(normal_line_start))
1198
1202
  ev = Vertex.ByCoordinates(list(normal_line_end))
topologicpy/Face.py CHANGED
@@ -2566,7 +2566,7 @@ class Face():
2566
2566
  internal_face_number = len(face_internal_boundaries)
2567
2567
  for i in range(internal_face_number):
2568
2568
  face_internal_boundary = face_internal_boundaries[i]
2569
- internal_vertices = Wire.Vertices(face_internal_boundary)
2569
+ internal_vertices = Topology.Vertices(face_internal_boundary)
2570
2570
  internal_vertex_number = len(internal_vertices)
2571
2571
  for j in range(internal_vertex_number):
2572
2572
  gmsh.model.geo.addPoint(Vertex.X(internal_vertices[j]), Vertex.Y(internal_vertices[j], mantissa=mantissa), Vertex.Z(internal_vertices[j], mantissa=mantissa), meshSize, current_vertex_number+j+1)