topologicpy 0.7.15__py3-none-any.whl → 0.7.18__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/Face.py CHANGED
@@ -131,8 +131,8 @@ class Face():
131
131
  if not Topology.IsInstance(faceB, "Face"):
132
132
  print("Face.Angle - Warning: The input faceB parameter is not a valid topologic face. Returning None.")
133
133
  return None
134
- dirA = Face.NormalAtParameters(faceA, 0.5, 0.5, "xyz", 3)
135
- dirB = Face.NormalAtParameters(faceB, 0.5, 0.5, "xyz", 3)
134
+ dirA = Face.Normal(faceA, outputType="xyz", mantissa=3)
135
+ dirB = Face.Normal(faceB, outputType="xyz", mantissa=3)
136
136
  return round((Vector.Angle(dirA, dirB)), mantissa)
137
137
 
138
138
  @staticmethod
@@ -258,11 +258,9 @@ class Face():
258
258
  return Face.ByEdges(edges, tolerance=tolerance)
259
259
 
260
260
  @staticmethod
261
- def ByOffset(face, offset: float = 1.0, miter: bool = False,
262
- miterThreshold: float = None, offsetKey: str = None,
263
- miterThresholdKey: str = None, step: bool = True, tolerance: float = 0.0001):
261
+ def ByOffset(face, offset: float = 1.0, tolerance: float = 0.0001):
264
262
  """
265
- Creates an offset face from the input face.
263
+ Creates an offset face from the input face. A positive offset value results in a smaller face to the inside of the input face.
266
264
 
267
265
  Parameters
268
266
  ----------
@@ -270,16 +268,6 @@ class Face():
270
268
  The input face.
271
269
  offset : float , optional
272
270
  The desired offset distance. The default is 1.0.
273
- miter : bool , optional
274
- if set to True, the corners will be mitered. The default is False.
275
- miterThreshold : float , optional
276
- The distance beyond which a miter should be added. The default is None which means the miter threshold is set to the offset distance multiplied by the square root of 2.
277
- offsetKey : str , optional
278
- If specified, the dictionary of the edges will be queried for this key to sepcify the desired offset. The default is None.
279
- miterThresholdKey : str , optional
280
- If specified, the dictionary of the vertices will be queried for this key to sepcify the desired miter threshold distance. The default is None.
281
- step : bool , optional
282
- If set to True, The transition between collinear edges with different offsets will be a step. Otherwise, it will be a continous edge. The default is True.
283
271
  tolerance : float , optional
284
272
  The desired tolerance. The default is 0.0001.
285
273
 
@@ -293,18 +281,18 @@ class Face():
293
281
  from topologicpy.Topology import Topology
294
282
 
295
283
  if not Topology.IsInstance(face, "Face"):
296
- print("Face.ByOffset - Warning: The input face parameter is not a valid toplogic face. Returning None.")
284
+ print("Face.ByOffset - Warning: The input face parameter is not a valid face. Returning None.")
297
285
  return None
298
286
  eb = Face.Wire(face)
299
287
  internal_boundaries = Face.InternalBoundaries(face)
300
- offset_external_boundary = Wire.ByOffset(wire=eb, offset=offset, miter=miter, miterThreshold=miterThreshold, offsetKey=offsetKey, miterThresholdKey=miterThresholdKey, step=step)
288
+ offset_external_boundary = Wire.ByOffset(wire=eb, offset=offset, bisectors=False, tolerance=tolerance)
301
289
  offset_internal_boundaries = []
302
290
  for internal_boundary in internal_boundaries:
303
- offset_internal_boundaries.append(Wire.ByOffset(wire=internal_boundary, offset=offset, miter=miter, miterThreshold=miterThreshold, offsetKey=offsetKey, miterThresholdKey=miterThresholdKey, step=step))
291
+ offset_internal_boundaries.append(Wire.ByOffset(wire=internal_boundary, offset=offset, bisectors=False, tolerance=tolerance))
304
292
  return Face.ByWires(offset_external_boundary, offset_internal_boundaries, tolerance=tolerance)
305
293
 
306
294
  @staticmethod
307
- def ByShell(shell, origin= None, angTolerance: float = 0.1, tolerance: float = 0.0001):
295
+ def ByShell(shell, origin= None, angTolerance: float = 0.1, tolerance: float = 0.0001, silent=False):
308
296
  """
309
297
  Creates a face by merging the faces of the input shell.
310
298
 
@@ -351,13 +339,13 @@ class Face():
351
339
  face = None
352
340
  ext_boundary = Wire.RemoveCollinearEdges(Shell.ExternalBoundary(shell))
353
341
  if Topology.IsInstance(ext_boundary, "Wire"):
354
- face = Face.ByWire(ext_boundary)
342
+ face = Face.ByWire(ext_boundary, silent=silent)
355
343
  elif Topology.IsInstance(ext_boundary, "Cluster"):
356
344
  wires = Topology.Wires(ext_boundary)
357
- faces = [Face.ByWire(w) for w in wires]
345
+ faces = [Face.ByWire(w, silent=silent) for w in wires]
358
346
  areas = [Face.Area(f) for f in faces]
359
347
  wires = Helper.Sort(wires, areas, reverseFlags=[True])
360
- face = Face.ByWires(wires[0], wires[1:])
348
+ face = Face.ByWires(wires[0], wires[1:], silent=silent)
361
349
 
362
350
  if Topology.IsInstance(face, "Face"):
363
351
  return face
@@ -420,6 +408,70 @@ class Face():
420
408
  else:
421
409
  return None
422
410
 
411
+ @staticmethod
412
+ def ByThickenedWire(wire, offsetA: float = 1.0, offsetB: float = 0.0, tolerance: float = 0.0001):
413
+ """
414
+ Creates a face by thickening the input wire. This method assumes the wire is manifold and planar.
415
+
416
+ Parameters
417
+ ----------
418
+ wire : topologic_core.Wire
419
+ The input wire to be thickened.
420
+ offsetA : float , optional
421
+ The desired offset to the exterior of the wire. The default is 1.0.
422
+ offsetB : float , optional
423
+ The desired offset to the interior of the wire. The default is 0.0.
424
+ tolerance : float , optional
425
+ The desired tolerance. The default is 0.0001.
426
+
427
+ Returns
428
+ -------
429
+ topologic_core.Cell
430
+ The created cell.
431
+
432
+ """
433
+ from topologicpy.Vertex import Vertex
434
+ from topologicpy.Edge import Edge
435
+ from topologicpy.Wire import Wire
436
+ from topologicpy.Vector import Vector
437
+ from topologicpy.Topology import Topology
438
+
439
+ if not Topology.IsInstance(wire, "Wire"):
440
+ print("Face.ByThickenedWire - Error: The input wire parameter is not a valid wire. Returning None.")
441
+ return None
442
+ if not Wire.IsManifold(wire):
443
+ print("Face.ByThickenedWire - Error: The input wire parameter is not a manifold wire. Returning None.")
444
+ return None
445
+ three_vertices = Wire.Vertices(wire)[0:3]
446
+ temp_w = Wire.ByVertices(three_vertices, close=True)
447
+ flat_face = Face.ByWire(temp_w, tolerance=tolerance)
448
+ origin = Vertex.Origin()
449
+ normal = Face.Normal(flat_face)
450
+ flat_wire = Topology.Flatten(wire, origin=origin, direction=normal)
451
+ outside_wire = Wire.ByOffset(flat_wire, offset=abs(offsetA)*-1, bisectors = False, tolerance=tolerance)
452
+ inside_wire = Wire.ByOffset(flat_wire, offset=abs(offsetB), bisectors = False, tolerance=tolerance)
453
+ inside_wire = Wire.Reverse(inside_wire)
454
+ if not Wire.IsClosed(flat_wire):
455
+ sv = Topology.Vertices(flat_wire)[0]
456
+ ev = Topology.Vertices(flat_wire)[-1]
457
+ edges = Topology.Edges(flat_wire)
458
+ first_edge = Topology.SuperTopologies(sv, flat_wire, topologyType="edge")[0]
459
+ first_normal = Edge.Normal(first_edge)
460
+ last_edge = Topology.SuperTopologies(ev, flat_wire, topologyType="edge")[0]
461
+ last_normal = Edge.Normal(last_edge)
462
+ sv1 = Topology.TranslateByDirectionDistance(sv, first_normal, abs(offsetB))
463
+ sv2 = Topology.TranslateByDirectionDistance(sv, Vector.Reverse(first_normal), abs(offsetA))
464
+ ev1 = Topology.TranslateByDirectionDistance(ev, last_normal, abs(offsetB))
465
+ ev2 = Topology.TranslateByDirectionDistance(ev, Vector.Reverse(last_normal), abs(offsetA))
466
+ out_vertices = Topology.Vertices(outside_wire)[1:-1]
467
+ in_vertices = Topology.Vertices(inside_wire)[1:-1]
468
+ vertices = [sv2] + out_vertices + [ev2,ev1] + in_vertices + [sv1]
469
+ return_face = Face.ByWire(Wire.ByVertices(vertices))
470
+ else:
471
+ return_face = Face.ByWires(outside_wire, [inside_wire])
472
+ return_face = Topology.Unflatten(return_face, origin=origin, direction=normal)
473
+ return return_face
474
+
423
475
  @staticmethod
424
476
  def ByVertices(vertices: list, tolerance: float = 0.0001):
425
477
 
@@ -808,7 +860,7 @@ class Face():
808
860
  return None
809
861
  if not north:
810
862
  north = Vector.North()
811
- dirA = Face.NormalAtParameters(face,mantissa=mantissa)
863
+ dirA = Face.Normal(face, outputType="xyz", mantissa=mantissa)
812
864
  return Vector.CompassAngle(vectorA=dirA, vectorB=north, mantissa=mantissa, tolerance=tolerance)
813
865
 
814
866
  @staticmethod
@@ -839,7 +891,7 @@ class Face():
839
891
  def Einstein(origin= None, radius: float = 0.5, direction: list = [0, 0, 1],
840
892
  placement: str = "center", tolerance: float = 0.0001):
841
893
  """
842
- Creates an aperiodic monotile, also called an 'einstein' tile (meaning one tile in German, not the name of the famous physist). See https://arxiv.org/abs/2303.10798
894
+ Creates an aperiodic monotile, also called an 'einstein' tile (meaning one tile in German, not the name of the famous physicist). See https://arxiv.org/abs/2303.10798
843
895
 
844
896
  Parameters
845
897
  ----------
@@ -869,6 +921,64 @@ class Face():
869
921
  return None
870
922
  return Face.ByWire(wire, tolerance=tolerance)
871
923
 
924
+ @staticmethod
925
+ def Ellipse(origin= None, inputMode: int = 1, width: float = 2.0, length: float = 1.0, focalLength: float = 0.866025, eccentricity: float = 0.866025, majorAxisLength: float = 1.0, minorAxisLength: float = 0.5, sides: float = 32, fromAngle: float = 0.0, toAngle: float = 360.0, close: bool = True, direction: list = [0, 0, 1], placement: str = "center", tolerance: float = 0.0001):
926
+ """
927
+ Creates an ellipse and returns all its geometry and parameters.
928
+
929
+ Parameters
930
+ ----------
931
+ origin : topologic_core.Vertex , optional
932
+ The location of the origin of the ellipse. The default is None which results in the ellipse being placed at (0, 0, 0).
933
+ inputMode : int , optional
934
+ The method by which the ellipse is defined. The default is 1.
935
+ Based on the inputMode value, only the following inputs will be considered. The options are:
936
+ 1. Width and Length (considered inputs: width, length)
937
+ 2. Focal Length and Eccentricity (considered inputs: focalLength, eccentricity)
938
+ 3. Focal Length and Minor Axis Length (considered inputs: focalLength, minorAxisLength)
939
+ 4. Major Axis Length and Minor Axis Length (considered input: majorAxisLength, minorAxisLength)
940
+ width : float , optional
941
+ The width of the ellipse. The default is 2.0. This is considered if the inputMode is 1.
942
+ length : float , optional
943
+ The length of the ellipse. The default is 1.0. This is considered if the inputMode is 1.
944
+ focalLength : float , optional
945
+ The focal length of the ellipse. The default is 0.866025. This is considered if the inputMode is 2 or 3.
946
+ eccentricity : float , optional
947
+ The eccentricity of the ellipse. The default is 0.866025. This is considered if the inputMode is 2.
948
+ majorAxisLength : float , optional
949
+ The length of the major axis of the ellipse. The default is 1.0. This is considered if the inputMode is 4.
950
+ minorAxisLength : float , optional
951
+ The length of the minor axis of the ellipse. The default is 0.5. This is considered if the inputMode is 3 or 4.
952
+ sides : int , optional
953
+ The number of sides of the ellipse. The default is 32.
954
+ fromAngle : float , optional
955
+ The angle in degrees from which to start creating the arc of the ellipse. The default is 0.
956
+ toAngle : float , optional
957
+ The angle in degrees at which to end creating the arc of the ellipse. The default is 360.
958
+ close : bool , optional
959
+ If set to True, arcs will be closed by connecting the last vertex to the first vertex. Otherwise, they will be left open.
960
+ direction : list , optional
961
+ The vector representing the up direction of the ellipse. The default is [0, 0, 1].
962
+ placement : str , optional
963
+ The description of the placement of the origin of the ellipse. This can be "center", or "lowerleft". It is case insensitive. The default is "center".
964
+ tolerance : float , optional
965
+ The desired tolerance. The default is 0.0001.
966
+
967
+ Returns
968
+ -------
969
+ topologic_core.Face
970
+ The created ellipse
971
+
972
+ """
973
+ from topologicpy.Wire import Wire
974
+ w = Wire.Ellipse(origin=origin, inputMode=inputMode, width=width, length=length,
975
+ focalLength=focalLength, eccentricity=eccentricity,
976
+ majorAxisLength=majorAxisLength, minorAxisLength=minorAxisLength,
977
+ sides=sides, fromAngle=fromAngle, toAngle=toAngle,
978
+ close=close, direction=direction,
979
+ placement=placement, tolerance=tolerance)
980
+ return Face.ByWire(w)
981
+
872
982
  @staticmethod
873
983
  def ExteriorAngles(face, includeInternalBoundaries=False, mantissa: int = 6) -> list:
874
984
  """
@@ -933,12 +1043,9 @@ class Face():
933
1043
 
934
1044
  eb = face.ExternalBoundary()
935
1045
  f_dir = Face.Normal(face)
936
- faceVertices = Topology.Vertices(eb)
937
- temp_face = Face.ByWire(eb)
938
- temp_dir = Face.Normal(temp_face)
939
- if Vector.IsAntiParallel(f_dir, temp_dir):
940
- faceVertices.reverse()
941
- eb = Wire.ByVertices(faceVertices)
1046
+ w_dir = Wire.Normal(eb)
1047
+ if Vector.IsAntiParallel(f_dir, w_dir):
1048
+ eb = Wire.Reverse(eb)
942
1049
  return eb
943
1050
 
944
1051
  @staticmethod
@@ -1256,10 +1363,10 @@ class Face():
1256
1363
  return None
1257
1364
 
1258
1365
  if not Topology.IsInstance(faceA, "Face"):
1259
- print("Face.IsInide - Error: The input faceA parameter is not a valid topologic face. Returning None.")
1366
+ print("Face.IsInside - Error: The input faceA parameter is not a valid topologic face. Returning None.")
1260
1367
  return None
1261
1368
  if not Topology.IsInstance(faceB, "Face"):
1262
- print("Face.IsInide - Error: The input faceB parameter is not a valid topologic face. Returning None.")
1369
+ print("Face.IsInside - Error: The input faceB parameter is not a valid topologic face. Returning None.")
1263
1370
  return None
1264
1371
 
1265
1372
  def normalize_plane_coefficients(plane):
@@ -1540,29 +1647,7 @@ class Face():
1540
1647
  return medialAxis
1541
1648
 
1542
1649
  @staticmethod
1543
- def Normal(face, outputType: str = "xyz", mantissa: int = 6) -> list:
1544
- """
1545
- Returns the normal vector to the input face. A normal vector of a face is a vector perpendicular to it.
1546
-
1547
- Parameters
1548
- ----------
1549
- face : topologic_core.Face
1550
- The input face.
1551
- outputType : string , optional
1552
- The string defining the desired output. This can be any subset or permutation of "xyz". It is case insensitive. The default is "xyz".
1553
- mantissa : int , optional
1554
- The desired length of the mantissa. The default is 6.
1555
-
1556
- Returns
1557
- -------
1558
- list
1559
- The normal vector to the input face. This is computed at the approximate center of the face.
1560
-
1561
- """
1562
- return Face.NormalAtParameters(face, u=0.5, v=0.5, outputType=outputType, mantissa=mantissa)
1563
-
1564
- @staticmethod
1565
- def NormalAtParameters(face, u: float = 0.5, v: float = 0.5, outputType: str = "xyz", mantissa: int = 6) -> list:
1650
+ def Normal(face, outputType="xyz", mantissa=6):
1566
1651
  """
1567
1652
  Returns the normal vector to the input face. A normal vector of a face is a vector perpendicular to it.
1568
1653
 
@@ -1570,10 +1655,6 @@ class Face():
1570
1655
  ----------
1571
1656
  face : topologic_core.Face
1572
1657
  The input face.
1573
- u : float , optional
1574
- The *u* parameter at which to compute the normal to the input face. The default is 0.5.
1575
- v : float , optional
1576
- The *v* parameter at which to compute the normal to the input face. The default is 0.5.
1577
1658
  outputType : string , optional
1578
1659
  The string defining the desired output. This can be any subset or permutation of "xyz". It is case insensitive. The default is "xyz".
1579
1660
  mantissa : int , optional
@@ -1585,23 +1666,80 @@ class Face():
1585
1666
  The normal vector to the input face.
1586
1667
 
1587
1668
  """
1588
- returnResult = []
1669
+ from topologicpy.Topology import Topology
1670
+ from topologicpy.Vertex import Vertex
1671
+ import os
1672
+ import warnings
1589
1673
  try:
1590
- coords = topologic.FaceUtility.NormalAtParameters(face, u, v) # Hook to Core
1591
- x = round(coords[0], mantissa)
1592
- y = round(coords[1], mantissa)
1593
- z = round(coords[2], mantissa)
1594
- outputType = list(outputType.lower())
1595
- for axis in outputType:
1596
- if axis == "x":
1597
- returnResult.append(x)
1598
- elif axis == "y":
1599
- returnResult.append(y)
1600
- elif axis == "z":
1601
- returnResult.append(z)
1674
+ import numpy as np
1602
1675
  except:
1603
- returnResult = None
1604
- return returnResult
1676
+ print("Face.Normal - Warning: Installing required numpy library.")
1677
+ try:
1678
+ os.system("pip install numpy")
1679
+ except:
1680
+ os.system("pip install numpy --user")
1681
+ try:
1682
+ import numpy as np
1683
+ print("Face.Normal - Warning: numpy library installed correctly.")
1684
+ except:
1685
+ warnings.warn("Face.Normal - Error: Could not import numpy. Please try to install numpy manually. Returning None.")
1686
+ return None
1687
+
1688
+ if not Topology.IsInstance(face, "Face"):
1689
+ print("Face.Normal - Error: The input face parameter is not a valid face. Returning None.")
1690
+ return None
1691
+
1692
+ vertices = Topology.Vertices(face)
1693
+ vertices = [Vertex.Coordinates(v, mantissa=mantissa) for v in vertices]
1694
+
1695
+ if len(vertices) < 3:
1696
+ print("Face.Normal - Error: At least three vertices are required to define a plane. Returning None.")
1697
+ return None
1698
+
1699
+ # Convert vertices to numpy array for easier manipulation
1700
+ vertices = np.array(vertices)
1701
+
1702
+ # Try to find two non-collinear edge vectors
1703
+ vec1 = None
1704
+ vec2 = None
1705
+ for i in range(1, len(vertices)):
1706
+ for j in range(i + 1, len(vertices)):
1707
+ temp_vec1 = vertices[i] - vertices[0]
1708
+ temp_vec2 = vertices[j] - vertices[0]
1709
+ cross_product = np.cross(temp_vec1, temp_vec2)
1710
+ if np.linalg.norm(cross_product) > 1e-6: # Check if the cross product is not near zero
1711
+ vec1 = temp_vec1
1712
+ vec2 = temp_vec2
1713
+ break
1714
+ if vec1 is not None and vec2 is not None:
1715
+ break
1716
+
1717
+ if vec1 is None or vec2 is None:
1718
+ print("Face.Normal - Error: The given vertices do not form a valid plane (all vertices might be collinear). Returning None.")
1719
+ return None
1720
+
1721
+ # Calculate the cross product of the two edge vectors
1722
+ normal = np.cross(vec1, vec2)
1723
+
1724
+ # Normalize the normal vector
1725
+ normal_length = np.linalg.norm(normal)
1726
+ if normal_length == 0:
1727
+ print("Face.Normal - Error: The given vertices do not form a valid plane (cross product resulted in a zero vector). Returning None.")
1728
+ return None
1729
+
1730
+ normal = normal / normal_length
1731
+ normal = normal.tolist()
1732
+ normal = [round(x, mantissa) for x in normal]
1733
+ return_normal = []
1734
+ outputType = list(outputType.lower())
1735
+ for axis in outputType:
1736
+ if axis == "x":
1737
+ return_normal.append(normal[0])
1738
+ elif axis == "y":
1739
+ return_normal.append(normal[1])
1740
+ elif axis == "z":
1741
+ return_normal.append(normal[2])
1742
+ return return_normal
1605
1743
 
1606
1744
  @staticmethod
1607
1745
  def NormalEdge(face, length: float = 1.0, tolerance: float = 0.0001, silent: bool = False):
@@ -1636,43 +1774,10 @@ class Face():
1636
1774
  return None
1637
1775
  iv = Face.InternalVertex(face)
1638
1776
  u, v = Face.VertexParameters(face, iv)
1639
- vec = Face.NormalAtParameters(face, u=u, v=v)
1777
+ vec = Face.Normal(face)
1640
1778
  ev = Topology.TranslateByDirectionDistance(iv, vec, length)
1641
1779
  return Edge.ByVertices([iv, ev], tolerance=tolerance, silent=silent)
1642
1780
 
1643
- @staticmethod
1644
- def NormalEdgeAtParameters(face, u: float = 0.5, v: float = 0.5, length: float = 1.0, tolerance: float = 0.0001):
1645
- """
1646
- Returns the normal vector to the input face as an edge with the desired input length. A normal vector of a face is a vector perpendicular to it.
1647
-
1648
- Parameters
1649
- ----------
1650
- face : topologic_core.Face
1651
- The input face.
1652
- u : float , optional
1653
- The *u* parameter at which to compute the normal to the input face. The default is 0.5.
1654
- v : float , optional
1655
- The *v* parameter at which to compute the normal to the input face. The default is 0.5.
1656
- length : float , optional
1657
- The desired length of the normal edge. The default is 1.
1658
- tolerance : float , optional
1659
- The desired tolerance. The default is 0.0001.
1660
-
1661
- Returns
1662
- -------
1663
- topologic_core.Edge
1664
- The created normal edge to the input face. This is computed at the approximate center of the face.
1665
-
1666
- """
1667
- from topologicpy.Edge import Edge
1668
- from topologicpy.Topology import Topology
1669
- if not Topology.IsInstance(face, "Face"):
1670
- return None
1671
- sv = Face.VertexByParameters(face=face, u=u, v=v)
1672
- vec = Face.NormalAtParameters(face, u=u, v=v)
1673
- ev = Topology.TranslateByDirectionDistance(sv, vec, length)
1674
- return Edge.ByVertices([sv, ev], tolerance=tolerance, silent=True)
1675
-
1676
1781
  @staticmethod
1677
1782
  def PlaneEquation(face, mantissa: int = 6) -> dict:
1678
1783
  """
@@ -1924,7 +2029,7 @@ class Face():
1924
2029
  from topologicpy.Topology import Topology
1925
2030
 
1926
2031
  if not Topology.IsInstance(face, "Face"):
1927
- print("Face.Skeleton - Error: The input face is not a valid topologic face. Retruning None.")
2032
+ print("Face.Skeleton - Error: The input face is not a valid topologic face. Returning None.")
1928
2033
  return None
1929
2034
  return Wire.Skeleton(face, tolerance=tolerance)
1930
2035