topologicpy 0.8.90__py3-none-any.whl → 0.8.92__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 +1 -1
- topologicpy/Graph.py +41 -68
- topologicpy/Topology.py +7 -7
- topologicpy/Wire.py +652 -64
- topologicpy/version.py +1 -1
- {topologicpy-0.8.90.dist-info → topologicpy-0.8.92.dist-info}/METADATA +1 -1
- {topologicpy-0.8.90.dist-info → topologicpy-0.8.92.dist-info}/RECORD +10 -10
- {topologicpy-0.8.90.dist-info → topologicpy-0.8.92.dist-info}/WHEEL +0 -0
- {topologicpy-0.8.90.dist-info → topologicpy-0.8.92.dist-info}/licenses/LICENSE +0 -0
- {topologicpy-0.8.90.dist-info → topologicpy-0.8.92.dist-info}/top_level.txt +0 -0
topologicpy/Cell.py
CHANGED
|
@@ -1753,7 +1753,7 @@ class Cell():
|
|
|
1753
1753
|
if placement.lower() == "bottom":
|
|
1754
1754
|
egg = Topology.Translate(egg, 0, 0, height/2)
|
|
1755
1755
|
elif placement.lower() == "lowerleft":
|
|
1756
|
-
bb =
|
|
1756
|
+
bb = Topology.BoundingBox(egg)
|
|
1757
1757
|
d = Topology.Dictionary(bb)
|
|
1758
1758
|
width = Dictionary.ValueAtKey(d, 'width')
|
|
1759
1759
|
length = Dictionary.ValueAtKey(d, 'length')
|
topologicpy/Graph.py
CHANGED
|
@@ -22,6 +22,7 @@ import warnings
|
|
|
22
22
|
|
|
23
23
|
from collections import namedtuple
|
|
24
24
|
from multiprocessing import Process, Queue
|
|
25
|
+
from typing import Any
|
|
25
26
|
|
|
26
27
|
try:
|
|
27
28
|
import numpy as np
|
|
@@ -15540,7 +15541,16 @@ class Graph:
|
|
|
15540
15541
|
return graph
|
|
15541
15542
|
|
|
15542
15543
|
@staticmethod
|
|
15543
|
-
def ShortestPath(graph,
|
|
15544
|
+
def ShortestPath(graph,
|
|
15545
|
+
vertexA,
|
|
15546
|
+
vertexB,
|
|
15547
|
+
vertexKey: str = "",
|
|
15548
|
+
edgeKey: str = "Length",
|
|
15549
|
+
transferDictionaries: bool = False,
|
|
15550
|
+
straighten: bool = False,
|
|
15551
|
+
face: Any = None,
|
|
15552
|
+
tolerance: float = 0.0001,
|
|
15553
|
+
silent: bool = False):
|
|
15544
15554
|
"""
|
|
15545
15555
|
Returns the shortest path that connects the input vertices. The shortest path will take into consideration both the vertexKey and the edgeKey if both are specified and will minimize the total "cost" of the path. Otherwise, it will take into consideration only whatever key is specified.
|
|
15546
15556
|
|
|
@@ -15556,8 +15566,19 @@ class Graph:
|
|
|
15556
15566
|
The vertex key to minimise. If set the vertices dictionaries will be searched for this key and the associated value will be used to compute the shortest path that minimized the total value. The value must be numeric. Default is None.
|
|
15557
15567
|
edgeKey : string , optional
|
|
15558
15568
|
The edge key to minimise. If set the edges dictionaries will be searched for this key and the associated value will be used to compute the shortest path that minimized the total value. The value of the key must be numeric. If set to "length" (case insensitive), the shortest path by length is computed. Default is "length".
|
|
15569
|
+
transferDictionaries : bool , optional
|
|
15570
|
+
If set to True, the dictionaries from the graph vertices will be transferred to the vertices of the shortest path. Otherwise, they won't. Default is False.
|
|
15571
|
+
Note: Edge dictionaries are not transferred (In straightened paths, the path edges are no longer the same as
|
|
15572
|
+
the original graph edges. Thus, you must implement your own logic to transfer edge dictionaries if needed).
|
|
15573
|
+
straighten : bool , optional
|
|
15574
|
+
If set to True, the path will be straightened as much as possible while remaining inside the specified face.
|
|
15575
|
+
Thus, the face input must be a valid topologic Face that is planar and residing on the XY plane. Default is False.
|
|
15576
|
+
face : topologic_core.Face , optional
|
|
15577
|
+
The face on which the path resides. This is used for straightening the path. Default is None.
|
|
15559
15578
|
tolerance : float , optional
|
|
15560
15579
|
The desired tolerance. Default is 0.0001.
|
|
15580
|
+
silent : bool , optional
|
|
15581
|
+
If set to True, error and warning messages are suppressed. Default is False.
|
|
15561
15582
|
|
|
15562
15583
|
Returns
|
|
15563
15584
|
-------
|
|
@@ -15570,14 +15591,22 @@ class Graph:
|
|
|
15570
15591
|
from topologicpy.Topology import Topology
|
|
15571
15592
|
|
|
15572
15593
|
if not Topology.IsInstance(graph, "Graph"):
|
|
15573
|
-
|
|
15594
|
+
if not silent:
|
|
15595
|
+
print("Graph.ShortestPath - Error: The input graph is not a valid graph. Returning None.")
|
|
15574
15596
|
return None
|
|
15575
15597
|
if not Topology.IsInstance(vertexA, "Vertex"):
|
|
15576
|
-
|
|
15598
|
+
if not silent:
|
|
15599
|
+
print("Graph.ShortestPath - Error: The input vertexA is not a valid vertex. Returning None.")
|
|
15577
15600
|
return None
|
|
15578
15601
|
if not Topology.IsInstance(vertexB, "Vertex"):
|
|
15579
|
-
|
|
15602
|
+
if not silent:
|
|
15603
|
+
print("Graph.ShortestPath - Error: The input vertexB is not a valid vertex. Returning None.")
|
|
15580
15604
|
return None
|
|
15605
|
+
if straighten == True:
|
|
15606
|
+
if not Topology.IsInstance(face, "face"):
|
|
15607
|
+
if not silent:
|
|
15608
|
+
print("Graph.ShortestPath - Error: Straighten is set to True, but the face parameter is not a valid toopologic face. Returning None.")
|
|
15609
|
+
return None
|
|
15581
15610
|
if edgeKey:
|
|
15582
15611
|
if edgeKey.lower() == "length":
|
|
15583
15612
|
edgeKey = "Length"
|
|
@@ -15593,74 +15622,18 @@ class Graph:
|
|
|
15593
15622
|
if Topology.IsInstance(shortest_path, "Wire"):
|
|
15594
15623
|
shortest_path = Wire.Reverse(shortest_path)
|
|
15595
15624
|
shortest_path = Wire.OrientEdges(shortest_path, Wire.StartVertex(shortest_path), tolerance=tolerance)
|
|
15625
|
+
if Topology.IsInstance(shortest_path, "wire"):
|
|
15626
|
+
if straighten == True and Topology.IsInstance(face, "face"):
|
|
15627
|
+
shortest_path = Wire.StraightenInFace(shortest_path, face)
|
|
15628
|
+
if transferDictionaries == True:
|
|
15629
|
+
path_verts = Topology.Vertices(shortest_path)
|
|
15630
|
+
for p_v in path_verts:
|
|
15631
|
+
g_v = Graph.NearestVertex(graph, p_v)
|
|
15632
|
+
p_v = Topology.SetDictionary(p_v, Topology.Dictionary(g_v))
|
|
15596
15633
|
return shortest_path
|
|
15597
15634
|
except:
|
|
15598
15635
|
return None
|
|
15599
15636
|
|
|
15600
|
-
@staticmethod
|
|
15601
|
-
def shortestPathInFace(graph, face, vertexA, vertexB, mode: int = 0, meshSize: float = None, tolerance: float = 0.0001, silent: bool = False):
|
|
15602
|
-
"""
|
|
15603
|
-
Returns the shortest path that connects the input vertices.
|
|
15604
|
-
|
|
15605
|
-
Parameters
|
|
15606
|
-
----------
|
|
15607
|
-
face : topologic_core.Face
|
|
15608
|
-
The input face. This is assumed to be planar and resting on the XY plane (z = 0)
|
|
15609
|
-
vertexA : topologic_core.Vertex
|
|
15610
|
-
The first input vertex.
|
|
15611
|
-
vertexB : topologic_core.Vertex
|
|
15612
|
-
The second input vertex.
|
|
15613
|
-
mode : int , optional
|
|
15614
|
-
The desired mode of meshing algorithm. Several options are available:
|
|
15615
|
-
0: Classic
|
|
15616
|
-
1: MeshAdapt
|
|
15617
|
-
3: Initial Mesh Only
|
|
15618
|
-
5: Delaunay
|
|
15619
|
-
6: Frontal-Delaunay
|
|
15620
|
-
7: BAMG
|
|
15621
|
-
8: Fontal-Delaunay for Quads
|
|
15622
|
-
9: Packing of Parallelograms
|
|
15623
|
-
All options other than 0 (Classic) use the gmsh library. See https://gmsh.info/doc/texinfo/gmsh.html#Mesh-options
|
|
15624
|
-
WARNING: The options that use gmsh can be very time consuming and can create very heavy geometry.
|
|
15625
|
-
meshSize : float , optional
|
|
15626
|
-
The desired size of the mesh when using the "mesh" option. If set to None, it will be
|
|
15627
|
-
calculated automatically and set to 10% of the overall size of the face.
|
|
15628
|
-
tolerance : float , optional
|
|
15629
|
-
The desired tolerance. Default is 0.0001.
|
|
15630
|
-
silent : bool , optional
|
|
15631
|
-
If set to True, error and warning messages are suppressed. Default is False.
|
|
15632
|
-
|
|
15633
|
-
Returns
|
|
15634
|
-
-------
|
|
15635
|
-
topologic_core.Wire
|
|
15636
|
-
The shortest path between the input vertices.
|
|
15637
|
-
|
|
15638
|
-
"""
|
|
15639
|
-
from topologicpy.Wire import Wire
|
|
15640
|
-
from topologicpy.Topology import Topology
|
|
15641
|
-
|
|
15642
|
-
if not Topology.IsInstance(face, "face"):
|
|
15643
|
-
if not silent:
|
|
15644
|
-
print("Face.ShortestPath - Error: The input face parameter is not a topologic face. Returning None.")
|
|
15645
|
-
return None
|
|
15646
|
-
if not Topology.IsInstance(vertexA, "vertex"):
|
|
15647
|
-
if not silent:
|
|
15648
|
-
print("Face.ShortestPath - Error: The input vertexA parameter is not a topologic vertex. Returning None.")
|
|
15649
|
-
return None
|
|
15650
|
-
if not Topology.IsInstance(vertexB, "vertex"):
|
|
15651
|
-
if not silent:
|
|
15652
|
-
print("Face.ShortestPath - Error: The input vertexB parameter is not a topologic vertex. Returning None.")
|
|
15653
|
-
return None
|
|
15654
|
-
|
|
15655
|
-
sp = Graph.ShortestPath(graph, vertexA, vertexB)
|
|
15656
|
-
if sp == None:
|
|
15657
|
-
if not silent:
|
|
15658
|
-
print("Face.ShortestPath - Error: Could not find the shortest path. Returning None.")
|
|
15659
|
-
return None
|
|
15660
|
-
|
|
15661
|
-
new_path = Wire.StraightenInFace(sp, face, tolerance = tolerance)
|
|
15662
|
-
return new_path
|
|
15663
|
-
|
|
15664
15637
|
@staticmethod
|
|
15665
15638
|
def ShortestPaths(graph, vertexA, vertexB, vertexKey="", edgeKey="length", timeLimit=10,
|
|
15666
15639
|
pathLimit=10, tolerance=0.0001):
|
topologicpy/Topology.py
CHANGED
|
@@ -11935,13 +11935,13 @@ class Topology():
|
|
|
11935
11935
|
final_str = verts_str+edges_str+dict_str
|
|
11936
11936
|
uuid_str = uuid.uuid5(namespace_uuid, final_str)
|
|
11937
11937
|
else:
|
|
11938
|
-
cellComplexes = Topology.CellComplexes(topology)
|
|
11939
|
-
cells = Topology.Cells(topology)
|
|
11940
|
-
shells = Topology.Shells(topology)
|
|
11941
|
-
faces = Topology.Faces(topology)
|
|
11942
|
-
wires = Topology.Wires(topology)
|
|
11943
|
-
edges = Topology.Edges(topology)
|
|
11944
|
-
vertices = Topology.Vertices(topology)
|
|
11938
|
+
cellComplexes = Topology.CellComplexes(topology, silent=True)
|
|
11939
|
+
cells = Topology.Cells(topology, silent=True)
|
|
11940
|
+
shells = Topology.Shells(topology, silent=True)
|
|
11941
|
+
faces = Topology.Faces(topology, silent=True)
|
|
11942
|
+
wires = Topology.Wires(topology, silent=True)
|
|
11943
|
+
edges = Topology.Edges(topology, silent=True)
|
|
11944
|
+
vertices = Topology.Vertices(topology, silent=True)
|
|
11945
11945
|
apertures = Topology.Apertures(topology, subTopologyType="all")
|
|
11946
11946
|
subTopologies = cellComplexes+cells+shells+faces+wires+edges+vertices+apertures
|
|
11947
11947
|
dictionaries = [Dictionary.PythonDictionary(Topology.Dictionary(topology))]
|
topologicpy/Wire.py
CHANGED
|
@@ -946,6 +946,168 @@ class Wire():
|
|
|
946
946
|
vertices = Topology.Vertices(cluster)
|
|
947
947
|
return Wire.ByVertices(vertices, close=close, tolerance=tolerance, silent=silent)
|
|
948
948
|
|
|
949
|
+
|
|
950
|
+
@staticmethod
|
|
951
|
+
def Cage(origin=None,
|
|
952
|
+
width: float = 1.0, length: float = 1.0, height: float = 1.0,
|
|
953
|
+
uSides: int = 2, vSides: int = 2, wSides: int = 2,
|
|
954
|
+
direction: list = [0, 0, 1], placement: str = "center",
|
|
955
|
+
mantissa: int = 6, tolerance: float = 0.0001):
|
|
956
|
+
"""
|
|
957
|
+
Creates a prismatic 3D cage as a Wire, with edges only on the outer
|
|
958
|
+
surfaces of the volume (no interior lines).
|
|
959
|
+
|
|
960
|
+
Parameters
|
|
961
|
+
----------
|
|
962
|
+
origin : topologic_core.Vertex , optional
|
|
963
|
+
The placement origin of the cage:
|
|
964
|
+
- If placement == "center": the geometric center of the cage
|
|
965
|
+
is placed at this origin.
|
|
966
|
+
- If placement == "corner": the minimum corner of the cage
|
|
967
|
+
is placed at this origin.
|
|
968
|
+
If None, the cage is created around (0, 0, 0) accordingly.
|
|
969
|
+
width : float , optional
|
|
970
|
+
The size of the cage in the local X direction. Default is 1.0.
|
|
971
|
+
length : float , optional
|
|
972
|
+
The size of the cage in the local Y direction. Default is 1.0.
|
|
973
|
+
height : float , optional
|
|
974
|
+
The size of the cage in the local Z direction. Default is 1.0.
|
|
975
|
+
uSides : int , optional
|
|
976
|
+
The number of subdivisions in the local X direction. Must be >= 1.
|
|
977
|
+
Default is 2.
|
|
978
|
+
vSides : int , optional
|
|
979
|
+
The number of subdivisions in the local Y direction. Must be >= 1.
|
|
980
|
+
Default is 2.
|
|
981
|
+
wSides : int , optional
|
|
982
|
+
The number of subdivisions in the local Z direction. Must be >= 1.
|
|
983
|
+
Default is 2.
|
|
984
|
+
direction : list , optional
|
|
985
|
+
The vector representing the up direction of the lattice. Default is [0, 0, 1].
|
|
986
|
+
placement : str , optional
|
|
987
|
+
The description of the placement of the origin of the lattice. This can be "bottom", "center", or "lowerleft". It is case insensitive. Default is "center".
|
|
988
|
+
mantissa : int , optional
|
|
989
|
+
The number of decimal places to round the result to. Default is 6.
|
|
990
|
+
tolerance : float , optional
|
|
991
|
+
The desired tolerance. Default is 0.0001.
|
|
992
|
+
|
|
993
|
+
Returns
|
|
994
|
+
-------
|
|
995
|
+
topologic_core.Wire or None
|
|
996
|
+
The resulting cage Wire, or None if inputs are invalid.
|
|
997
|
+
"""
|
|
998
|
+
from topologicpy.Vertex import Vertex
|
|
999
|
+
from topologicpy.Edge import Edge
|
|
1000
|
+
from topologicpy.Wire import Wire
|
|
1001
|
+
from topologicpy.Topology import Topology
|
|
1002
|
+
from topologicpy.Vector import Vector
|
|
1003
|
+
import math
|
|
1004
|
+
|
|
1005
|
+
# -------------------------
|
|
1006
|
+
# Validation
|
|
1007
|
+
# -------------------------
|
|
1008
|
+
if uSides < 1 or vSides < 1 or wSides < 1:
|
|
1009
|
+
print("Wire.Cage - Error: uSides, vSides, and wSides must be >= 1. Returning None.")
|
|
1010
|
+
return None
|
|
1011
|
+
|
|
1012
|
+
if origin is None:
|
|
1013
|
+
origin = Vertex.ByCoordinates(0, 0, 0)
|
|
1014
|
+
|
|
1015
|
+
# Local origin at (0,0,0) for construction and rotation
|
|
1016
|
+
local_origin = Vertex.ByCoordinates(0, 0, 0)
|
|
1017
|
+
|
|
1018
|
+
# -------------------------
|
|
1019
|
+
# Local Placement Offsets
|
|
1020
|
+
# -------------------------
|
|
1021
|
+
# We construct the cage in a local coordinate system.
|
|
1022
|
+
if str(placement).lower() == "center":
|
|
1023
|
+
ox = -width * 0.5
|
|
1024
|
+
oy = -length * 0.5
|
|
1025
|
+
oz = -height * 0.5
|
|
1026
|
+
elif str(placement).lower() == "bottom":
|
|
1027
|
+
ox = -width * 0.5
|
|
1028
|
+
oy = -length * 0.5
|
|
1029
|
+
oz = 0
|
|
1030
|
+
else: # "lowerleft"
|
|
1031
|
+
ox = 0.0
|
|
1032
|
+
oy = 0.0
|
|
1033
|
+
oz = 0.0
|
|
1034
|
+
|
|
1035
|
+
# -------------------------
|
|
1036
|
+
# Step Sizes
|
|
1037
|
+
# -------------------------
|
|
1038
|
+
du = width / uSides
|
|
1039
|
+
dv = length / vSides
|
|
1040
|
+
dw = height / wSides
|
|
1041
|
+
|
|
1042
|
+
# -------------------------
|
|
1043
|
+
# Grid Coordinates (local)
|
|
1044
|
+
# -------------------------
|
|
1045
|
+
xs = [round(ox + i * du, mantissa) for i in range(uSides + 1)]
|
|
1046
|
+
ys = [round(oy + j * dv, mantissa) for j in range(vSides + 1)]
|
|
1047
|
+
zs = [round(oz + k * dw, mantissa) for k in range(wSides + 1)]
|
|
1048
|
+
|
|
1049
|
+
edges = []
|
|
1050
|
+
|
|
1051
|
+
# ------------------------------------------------------------------
|
|
1052
|
+
# X-direction edges on boundary surfaces (y,z)
|
|
1053
|
+
# Edge from (x_min, y_j, z_k) to (x_max, y_j, z_k)
|
|
1054
|
+
# Only if j is boundary OR k is boundary → lies on outer surface.
|
|
1055
|
+
# ------------------------------------------------------------------
|
|
1056
|
+
for j in range(vSides + 1):
|
|
1057
|
+
for k in range(wSides + 1):
|
|
1058
|
+
if j in (0, vSides) or k in (0, wSides):
|
|
1059
|
+
y = ys[j]
|
|
1060
|
+
z = zs[k]
|
|
1061
|
+
v0 = Vertex.ByCoordinates(xs[0], y, z)
|
|
1062
|
+
v1 = Vertex.ByCoordinates(xs[-1], y, z)
|
|
1063
|
+
edges.append(Edge.ByVertices(v0, v1))
|
|
1064
|
+
|
|
1065
|
+
# ------------------------------------------------------------------
|
|
1066
|
+
# Y-direction edges on boundary surfaces (x,z)
|
|
1067
|
+
# Edge from (x_i, y_min, z_k) to (x_i, y_max, z_k)
|
|
1068
|
+
# Only if i is boundary OR k is boundary.
|
|
1069
|
+
# ------------------------------------------------------------------
|
|
1070
|
+
for i in range(uSides + 1):
|
|
1071
|
+
for k in range(wSides + 1):
|
|
1072
|
+
if i in (0, uSides) or k in (0, wSides):
|
|
1073
|
+
x = xs[i]
|
|
1074
|
+
z = zs[k]
|
|
1075
|
+
v0 = Vertex.ByCoordinates(x, ys[0], z)
|
|
1076
|
+
v1 = Vertex.ByCoordinates(x, ys[-1], z)
|
|
1077
|
+
edges.append(Edge.ByVertices(v0, v1))
|
|
1078
|
+
|
|
1079
|
+
# ------------------------------------------------------------------
|
|
1080
|
+
# Z-direction edges on boundary surfaces (x,y)
|
|
1081
|
+
# Edge from (x_i, y_j, z_min) to (x_i, y_j, z_max)
|
|
1082
|
+
# Only if i is boundary OR j is boundary.
|
|
1083
|
+
# ------------------------------------------------------------------
|
|
1084
|
+
for i in range(uSides + 1):
|
|
1085
|
+
for j in range(vSides + 1):
|
|
1086
|
+
if i in (0, uSides) or j in (0, vSides):
|
|
1087
|
+
x = xs[i]
|
|
1088
|
+
y = ys[j]
|
|
1089
|
+
v0 = Vertex.ByCoordinates(x, y, zs[0])
|
|
1090
|
+
v1 = Vertex.ByCoordinates(x, y, zs[-1])
|
|
1091
|
+
edges.append(Edge.ByVertices(v0, v1))
|
|
1092
|
+
|
|
1093
|
+
# -------------------------
|
|
1094
|
+
# Build Wire in Local Space
|
|
1095
|
+
# -------------------------
|
|
1096
|
+
if not edges:
|
|
1097
|
+
print("Wire.Cage - Warning: No edges created. Returning None.")
|
|
1098
|
+
return None
|
|
1099
|
+
|
|
1100
|
+
cage = Wire.ByEdges(edges)
|
|
1101
|
+
|
|
1102
|
+
# -------------------------
|
|
1103
|
+
# Orient and Place
|
|
1104
|
+
# -------------------------
|
|
1105
|
+
cage = Topology.Orient(cage, origin=Vertex.Origin(), dirA=[0, 0, 1], dirB=direction)
|
|
1106
|
+
cage = Topology.Place(cage, originA=Vertex.Origin(), originB=origin)
|
|
1107
|
+
|
|
1108
|
+
return cage
|
|
1109
|
+
|
|
1110
|
+
|
|
949
1111
|
@staticmethod
|
|
950
1112
|
def Circle(origin= None, radius: float = 0.5, sides: int = 16, fromAngle: float = 0.0, toAngle: float = 360.0, close: bool = True, direction: list = [0, 0, 1], placement: str = "center", tolerance: float = 0.0001, silent: bool = False):
|
|
951
1113
|
"""
|
|
@@ -3199,6 +3361,124 @@ class Wire():
|
|
|
3199
3361
|
i_shape = Topology.Orient(i_shape, origin=origin, dirA=[0, 0, 1], dirB=direction)
|
|
3200
3362
|
return i_shape
|
|
3201
3363
|
|
|
3364
|
+
|
|
3365
|
+
|
|
3366
|
+
@staticmethod
|
|
3367
|
+
def Lattice(origin=None,
|
|
3368
|
+
width: float = 1.0, length: float = 1.0, height: float = 1.0,
|
|
3369
|
+
uSides: int = 2, vSides: int = 2, wSides: int = 2,
|
|
3370
|
+
direction: list = [0, 0, 1], placement: str = "center",
|
|
3371
|
+
mantissa: int = 6, tolerance: float = 0.0001):
|
|
3372
|
+
"""
|
|
3373
|
+
Creates a prismatic 3D lattice as a Wire.
|
|
3374
|
+
|
|
3375
|
+
Parameters
|
|
3376
|
+
----------
|
|
3377
|
+
origin : topologic_core.Vertex , optional
|
|
3378
|
+
Placement origin.
|
|
3379
|
+
width, length, height : float
|
|
3380
|
+
Lattice extents.
|
|
3381
|
+
uSides, vSides, wSides : int
|
|
3382
|
+
Divisions along X, Y, Z.
|
|
3383
|
+
direction : list , optional
|
|
3384
|
+
The vector representing the up direction of the lattice. Default is [0, 0, 1].
|
|
3385
|
+
placement : str , optional
|
|
3386
|
+
The description of the placement of the origin of the lattice. This can be "bottom", "center", or "lowerleft". It is case insensitive. Default is "center".
|
|
3387
|
+
mantissa : int , optional
|
|
3388
|
+
The number of decimal places to round the result to. Default is 6.
|
|
3389
|
+
tolerance : float , optional
|
|
3390
|
+
The desired tolerance. Default is 0.0001.
|
|
3391
|
+
|
|
3392
|
+
Returns
|
|
3393
|
+
-------
|
|
3394
|
+
topologic_core.Wire
|
|
3395
|
+
"""
|
|
3396
|
+
|
|
3397
|
+
from topologicpy.Vertex import Vertex
|
|
3398
|
+
from topologicpy.Edge import Edge
|
|
3399
|
+
from topologicpy.Wire import Wire
|
|
3400
|
+
from topologicpy.Topology import Topology
|
|
3401
|
+
from topologicpy.Vector import Vector
|
|
3402
|
+
import math
|
|
3403
|
+
|
|
3404
|
+
# -------------------------
|
|
3405
|
+
# Validation
|
|
3406
|
+
# -------------------------
|
|
3407
|
+
if uSides < 1 or vSides < 1 or wSides < 1:
|
|
3408
|
+
return None
|
|
3409
|
+
|
|
3410
|
+
if origin is None:
|
|
3411
|
+
origin = Vertex.ByCoordinates(0, 0, 0)
|
|
3412
|
+
|
|
3413
|
+
# -------------------------
|
|
3414
|
+
# Placement Offsets
|
|
3415
|
+
# -------------------------
|
|
3416
|
+
if placement.lower() == "center":
|
|
3417
|
+
ox = -width * 0.5
|
|
3418
|
+
oy = -length * 0.5
|
|
3419
|
+
oz = -height * 0.5
|
|
3420
|
+
elif placement.lower() == "bottom":
|
|
3421
|
+
ox = -width * 0.5
|
|
3422
|
+
oy = -length * 0.5
|
|
3423
|
+
oz = 0
|
|
3424
|
+
else:
|
|
3425
|
+
ox = oy = oz = 0.0
|
|
3426
|
+
|
|
3427
|
+
# -------------------------
|
|
3428
|
+
# Step Sizes
|
|
3429
|
+
# -------------------------
|
|
3430
|
+
du = width / uSides
|
|
3431
|
+
dv = length / vSides
|
|
3432
|
+
dw = height / wSides
|
|
3433
|
+
|
|
3434
|
+
# -------------------------
|
|
3435
|
+
# Precompute Grid Coordinates
|
|
3436
|
+
# -------------------------
|
|
3437
|
+
xs = [round(ox + i * du, mantissa) for i in range(uSides + 1)]
|
|
3438
|
+
ys = [round(oy + j * dv, mantissa) for j in range(vSides + 1)]
|
|
3439
|
+
zs = [round(oz + k * dw, mantissa) for k in range(wSides + 1)]
|
|
3440
|
+
|
|
3441
|
+
edges = []
|
|
3442
|
+
|
|
3443
|
+
# -------------------------
|
|
3444
|
+
# X-Direction Lines
|
|
3445
|
+
# -------------------------
|
|
3446
|
+
for y in ys:
|
|
3447
|
+
for z in zs:
|
|
3448
|
+
v0 = Vertex.ByCoordinates(xs[0], y, z)
|
|
3449
|
+
v1 = Vertex.ByCoordinates(xs[-1], y, z)
|
|
3450
|
+
edges.append(Edge.ByVertices(v0, v1))
|
|
3451
|
+
|
|
3452
|
+
# -------------------------
|
|
3453
|
+
# Y-Direction Lines
|
|
3454
|
+
# -------------------------
|
|
3455
|
+
for x in xs:
|
|
3456
|
+
for z in zs:
|
|
3457
|
+
v0 = Vertex.ByCoordinates(x, ys[0], z)
|
|
3458
|
+
v1 = Vertex.ByCoordinates(x, ys[-1], z)
|
|
3459
|
+
edges.append(Edge.ByVertices(v0, v1))
|
|
3460
|
+
|
|
3461
|
+
# -------------------------
|
|
3462
|
+
# Z-Direction Lines
|
|
3463
|
+
# -------------------------
|
|
3464
|
+
for x in xs:
|
|
3465
|
+
for y in ys:
|
|
3466
|
+
v0 = Vertex.ByCoordinates(x, y, zs[0])
|
|
3467
|
+
v1 = Vertex.ByCoordinates(x, y, zs[-1])
|
|
3468
|
+
edges.append(Edge.ByVertices(v0, v1))
|
|
3469
|
+
|
|
3470
|
+
# -------------------------
|
|
3471
|
+
# Build Wire
|
|
3472
|
+
# -------------------------
|
|
3473
|
+
lattice = Wire.ByEdges(edges)
|
|
3474
|
+
|
|
3475
|
+
# -------------------------
|
|
3476
|
+
# Orient and Place
|
|
3477
|
+
# -------------------------
|
|
3478
|
+
lattice = Topology.Orient(lattice, origin=Vertex.Origin(), dirA=[0, 0, 1], dirB=direction)
|
|
3479
|
+
lattice = Topology.Place(lattice, originA=Vertex.Origin(), originB=origin)
|
|
3480
|
+
return lattice
|
|
3481
|
+
|
|
3202
3482
|
@staticmethod
|
|
3203
3483
|
def Length(wire, mantissa: int = 6) -> float:
|
|
3204
3484
|
"""
|
|
@@ -4964,34 +5244,32 @@ class Wire():
|
|
|
4964
5244
|
return sv
|
|
4965
5245
|
|
|
4966
5246
|
@staticmethod
|
|
4967
|
-
def
|
|
5247
|
+
def Straighten(wire, host, obstacles: list = None, portals: list = None,
|
|
5248
|
+
tolerance: float = 0.0001, silent: bool = False):
|
|
4968
5249
|
"""
|
|
4969
5250
|
Returns a new Wire obtained by recursively replacing segments of the
|
|
4970
|
-
input wire with the longest possible straight edge that
|
|
4971
|
-
embedded in the given
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
v_i and v_j satisfies:
|
|
4976
|
-
|
|
4977
|
-
Topology.Difference(edge, face) == None
|
|
4978
|
-
|
|
4979
|
-
i.e. the edge lies completely within (or on the boundary of) the face.
|
|
4980
|
-
All edges of the original wire between vertex indices i and j are then
|
|
4981
|
-
replaced by this straight edge, and the process is repeated recursively
|
|
4982
|
-
from index j.
|
|
5251
|
+
input wire with the longest possible straight edge that:
|
|
5252
|
+
1. Is fully embedded in the given host.
|
|
5253
|
+
2. Avoids intersection with an optional list of obstacle topologies.
|
|
5254
|
+
3. Continues to pass through (intersects) an optional list of portal
|
|
5255
|
+
topologies that the original input wire intersects.
|
|
4983
5256
|
|
|
4984
5257
|
Parameters
|
|
4985
5258
|
----------
|
|
4986
5259
|
wire : topologic_core.Wire
|
|
4987
5260
|
The input path wire whose vertices define the route to be
|
|
4988
5261
|
straightened.
|
|
4989
|
-
|
|
4990
|
-
The
|
|
5262
|
+
host : topologic_core.Topology
|
|
5263
|
+
The host within which the straightened edges must lie.
|
|
5264
|
+
obstacles : list, optional
|
|
5265
|
+
The list of topologies with which the straightened edges must not intersect.
|
|
5266
|
+
portals : list, optional
|
|
5267
|
+
The list of topologies with which the straightened edges must intersect.
|
|
5268
|
+
Portals with which the original wire does NOT intersect are ignored.
|
|
4991
5269
|
tolerance : float , optional
|
|
4992
|
-
|
|
4993
|
-
|
|
4994
|
-
Default is
|
|
5270
|
+
The desired tolerance. Default is 0.0001.
|
|
5271
|
+
silent : bool , optional
|
|
5272
|
+
If set to True, error and warning messages are suppressed. Default is False.
|
|
4995
5273
|
|
|
4996
5274
|
Returns
|
|
4997
5275
|
-------
|
|
@@ -5001,8 +5279,45 @@ class Wire():
|
|
|
5001
5279
|
from topologicpy.Vertex import Vertex
|
|
5002
5280
|
from topologicpy.Edge import Edge
|
|
5003
5281
|
from topologicpy.Wire import Wire
|
|
5004
|
-
from topologicpy.
|
|
5282
|
+
from topologicpy.Cluster import Cluster
|
|
5005
5283
|
from topologicpy.Topology import Topology
|
|
5284
|
+
|
|
5285
|
+
# Defensive defaults
|
|
5286
|
+
if obstacles is None:
|
|
5287
|
+
obstacles = []
|
|
5288
|
+
if portals is None:
|
|
5289
|
+
portals = []
|
|
5290
|
+
|
|
5291
|
+
# ----------------------------------------------------------------------
|
|
5292
|
+
# Basic validation
|
|
5293
|
+
# ----------------------------------------------------------------------
|
|
5294
|
+
if not Topology.IsInstance(wire, "Wire"):
|
|
5295
|
+
if not silent:
|
|
5296
|
+
print("Wire.Straighten - Error: The input wire parameter is not a valid Wire. Returning None.")
|
|
5297
|
+
return None
|
|
5298
|
+
|
|
5299
|
+
if not Topology.IsInstance(host, "Topology"):
|
|
5300
|
+
if not silent:
|
|
5301
|
+
print("Wire.Straighten - Error: The input host parameter is not a valid Topology. Returning None.")
|
|
5302
|
+
return None
|
|
5303
|
+
|
|
5304
|
+
if not isinstance(portals, list):
|
|
5305
|
+
if not silent:
|
|
5306
|
+
print("Wire.Straighten - Error: The input portals parameter is not a valid list. Returning None.")
|
|
5307
|
+
return None
|
|
5308
|
+
|
|
5309
|
+
if not isinstance(obstacles, list):
|
|
5310
|
+
if not silent:
|
|
5311
|
+
print("Wire.Straighten - Error: The input obstacles parameter is not a valid list. Returning None.")
|
|
5312
|
+
return None
|
|
5313
|
+
|
|
5314
|
+
# Filter valid obstacles and portals
|
|
5315
|
+
obstacle_list = [o for o in obstacles if Topology.IsInstance(o, "Topology")]
|
|
5316
|
+
portal_list = [p for p in portals if Topology.IsInstance(p, "Topology")]
|
|
5317
|
+
|
|
5318
|
+
# Make a cluster of the obstacles (if any)
|
|
5319
|
+
ob_cluster = Cluster.ByTopologies(obstacle_list) if obstacle_list else None
|
|
5320
|
+
|
|
5006
5321
|
# Get ordered vertices of the wire
|
|
5007
5322
|
vertices = Topology.Vertices(wire)
|
|
5008
5323
|
n = len(vertices)
|
|
@@ -5011,75 +5326,249 @@ class Wire():
|
|
|
5011
5326
|
# Nothing to straighten
|
|
5012
5327
|
return wire
|
|
5013
5328
|
|
|
5014
|
-
|
|
5329
|
+
# ----------------------------------------------------------------------
|
|
5330
|
+
# Helper: check if a straight edge between two vertices is valid
|
|
5331
|
+
# ----------------------------------------------------------------------
|
|
5332
|
+
def _edge_is_valid(v_start, v_end):
|
|
5015
5333
|
"""
|
|
5016
5334
|
Returns True if the straight edge between v_start and v_end is
|
|
5017
|
-
fully embedded in the
|
|
5018
|
-
returns None.
|
|
5335
|
+
fully embedded in the host and does not intersect the obstacles.
|
|
5019
5336
|
"""
|
|
5020
|
-
|
|
5337
|
+
# Avoid constructing degenerate edges
|
|
5338
|
+
if Topology.IsSame(v_start, v_end):
|
|
5021
5339
|
return True
|
|
5022
|
-
edge = Edge.ByStartVertexEndVertex(v_start, v_end)
|
|
5023
|
-
diff = Topology.Difference(edge, face)
|
|
5024
|
-
return diff is None
|
|
5025
5340
|
|
|
5026
|
-
|
|
5027
|
-
""
|
|
5028
|
-
|
|
5029
|
-
|
|
5030
|
-
|
|
5341
|
+
edge = Edge.ByStartVertexEndVertex(v_start, v_end, tolerance=tolerance)
|
|
5342
|
+
if not Topology.IsInstance(edge, "Edge"):
|
|
5343
|
+
return False
|
|
5344
|
+
|
|
5345
|
+
diff = Topology.Difference(edge, host)
|
|
5346
|
+
if diff is not None:
|
|
5347
|
+
# Part of the edge lies outside the host
|
|
5348
|
+
return False
|
|
5349
|
+
|
|
5350
|
+
if ob_cluster is not None:
|
|
5351
|
+
inter = Topology.Intersect(edge, ob_cluster)
|
|
5352
|
+
if inter is not None:
|
|
5353
|
+
# Edge hits an obstacle
|
|
5354
|
+
return False
|
|
5031
5355
|
|
|
5032
|
-
|
|
5033
|
-
|
|
5356
|
+
return True
|
|
5357
|
+
|
|
5358
|
+
# ----------------------------------------------------------------------
|
|
5359
|
+
# Helper: for a fixed start index, find the furthest valid vertex index
|
|
5360
|
+
# ----------------------------------------------------------------------
|
|
5361
|
+
def _find_longest_valid_index(start_idx, local_vertices):
|
|
5362
|
+
"""
|
|
5363
|
+
Given a list of vertices local_vertices (a sub-path),
|
|
5364
|
+
for a fixed start_idx, search for the largest index j >= start_idx+1
|
|
5365
|
+
such that the direct edge (local_vertices[start_idx], local_vertices[j])
|
|
5366
|
+
is valid.
|
|
5034
5367
|
"""
|
|
5035
|
-
|
|
5368
|
+
m = len(local_vertices)
|
|
5369
|
+
v_start = local_vertices[start_idx]
|
|
5036
5370
|
best_j = None
|
|
5037
5371
|
|
|
5038
|
-
for j in range(start_idx + 1,
|
|
5039
|
-
v_end =
|
|
5040
|
-
if
|
|
5372
|
+
for j in range(start_idx + 1, m):
|
|
5373
|
+
v_end = local_vertices[j]
|
|
5374
|
+
if _edge_is_valid(v_start, v_end):
|
|
5041
5375
|
best_j = j
|
|
5042
|
-
# Do NOT break on failure: a further vertex might still
|
|
5043
|
-
# be reachable by a straight edge that stays in the face.
|
|
5376
|
+
# Do NOT break on failure: a further vertex might still be valid.
|
|
5044
5377
|
|
|
5045
5378
|
if best_j is None:
|
|
5046
5379
|
# Fallback: use the immediate next vertex to avoid stalling
|
|
5047
|
-
best_j = min(start_idx + 1,
|
|
5380
|
+
best_j = min(start_idx + 1, m - 1)
|
|
5048
5381
|
|
|
5049
5382
|
return best_j
|
|
5050
5383
|
|
|
5051
|
-
|
|
5384
|
+
# ----------------------------------------------------------------------
|
|
5385
|
+
# Helper: straighten a list of vertices (single segment, no portals)
|
|
5386
|
+
# ----------------------------------------------------------------------
|
|
5387
|
+
def _straighten_vertices(local_vertices):
|
|
5052
5388
|
"""
|
|
5053
|
-
|
|
5389
|
+
Straightens a simple path defined by local_vertices (no portal constraints).
|
|
5390
|
+
Returns a new list of vertices.
|
|
5391
|
+
"""
|
|
5392
|
+
m = len(local_vertices)
|
|
5393
|
+
if m <= 2:
|
|
5394
|
+
return local_vertices[:]
|
|
5395
|
+
|
|
5396
|
+
out_vertices = []
|
|
5397
|
+
idx = 0
|
|
5398
|
+
while idx < m - 1:
|
|
5399
|
+
out_vertices.append(local_vertices[idx])
|
|
5400
|
+
idx = _find_longest_valid_index(idx, local_vertices)
|
|
5401
|
+
|
|
5402
|
+
# Ensure the last vertex is present
|
|
5403
|
+
if not Topology.IsSame(out_vertices[-1], local_vertices[-1]):
|
|
5404
|
+
out_vertices.append(local_vertices[-1])
|
|
5405
|
+
|
|
5406
|
+
return out_vertices
|
|
5407
|
+
|
|
5408
|
+
# ----------------------------------------------------------------------
|
|
5409
|
+
# Portal support
|
|
5410
|
+
# ----------------------------------------------------------------------
|
|
5411
|
+
def _portal_cuts():
|
|
5412
|
+
"""
|
|
5413
|
+
Returns a sorted list of (u, v_on_wire) where:
|
|
5414
|
+
- u is the parameter along the wire in [0, 1]
|
|
5415
|
+
- v_on_wire is a vertex on the wire at the same location
|
|
5054
5416
|
|
|
5055
|
-
|
|
5056
|
-
decides how far it can jump from start_idx with a single straight
|
|
5057
|
-
edge inside the face, then recurses from that new index.
|
|
5417
|
+
Only portals that actually intersect the original wire are considered.
|
|
5058
5418
|
"""
|
|
5059
|
-
|
|
5060
|
-
if start_idx == n - 1:
|
|
5061
|
-
out_vertices.append(vertices[start_idx])
|
|
5062
|
-
return
|
|
5419
|
+
cuts = []
|
|
5063
5420
|
|
|
5064
|
-
|
|
5065
|
-
|
|
5421
|
+
if not portal_list:
|
|
5422
|
+
return cuts
|
|
5066
5423
|
|
|
5067
|
-
|
|
5068
|
-
|
|
5424
|
+
for portal in portal_list:
|
|
5425
|
+
inter = Topology.Intersect(wire, portal)
|
|
5426
|
+
if not Topology.IsInstance(inter, "Topology"):
|
|
5427
|
+
# This portal does not intersect the wire, ignore it
|
|
5428
|
+
continue
|
|
5069
5429
|
|
|
5070
|
-
|
|
5071
|
-
|
|
5430
|
+
centroid = Topology.Centroid(inter)
|
|
5431
|
+
if not Topology.IsInstance(centroid, "Vertex"):
|
|
5432
|
+
continue
|
|
5072
5433
|
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
|
|
5434
|
+
# First try parameter at centroid directly
|
|
5435
|
+
u_target = Wire.ParameterAtVertex(wire, centroid, silent=True)
|
|
5436
|
+
|
|
5437
|
+
if u_target is not None:
|
|
5438
|
+
v_on_wire = centroid
|
|
5439
|
+
else:
|
|
5440
|
+
# Fall back to the closest point on the wire
|
|
5441
|
+
shortest_edge = Topology.ShortestEdge(centroid, wire, silent=True)
|
|
5442
|
+
if not Topology.IsInstance(shortest_edge, "Edge"):
|
|
5443
|
+
# Can't locate a good cut point for this portal
|
|
5444
|
+
continue
|
|
5445
|
+
v_on_wire = Edge.EndVertex(shortest_edge)
|
|
5446
|
+
if not Topology.IsInstance(v_on_wire, "Vertex"):
|
|
5447
|
+
continue
|
|
5448
|
+
u_target = Wire.ParameterAtVertex(wire, v_on_wire, silent=True)
|
|
5449
|
+
|
|
5450
|
+
# If still None, skip this portal
|
|
5451
|
+
if u_target is None:
|
|
5452
|
+
continue
|
|
5453
|
+
|
|
5454
|
+
# Keep u in [0,1], ignoring exact endpoints
|
|
5455
|
+
if 0.0 < u_target < 1.0:
|
|
5456
|
+
cuts.append((u_target, v_on_wire))
|
|
5457
|
+
|
|
5458
|
+
# Sort by parameter, ensure uniqueness
|
|
5459
|
+
cuts = sorted(cuts, key=lambda x: x[0])
|
|
5460
|
+
unique_cuts = []
|
|
5461
|
+
last_u = None
|
|
5462
|
+
for u, v in cuts:
|
|
5463
|
+
if last_u is None or abs(u - last_u) > tolerance:
|
|
5464
|
+
unique_cuts.append((u, v))
|
|
5465
|
+
last_u = u
|
|
5466
|
+
|
|
5467
|
+
return unique_cuts
|
|
5468
|
+
|
|
5469
|
+
def _subdivide_by_portals():
|
|
5470
|
+
"""
|
|
5471
|
+
Splits the original wire into sub-wires between portal cuts.
|
|
5472
|
+
Each sub-wire is a wire segment between:
|
|
5473
|
+
start -> first portal,
|
|
5474
|
+
portal i -> portal i+1,
|
|
5475
|
+
last portal -> end.
|
|
5476
|
+
"""
|
|
5477
|
+
cuts = _portal_cuts()
|
|
5478
|
+
if not cuts:
|
|
5479
|
+
return [wire] # No usable portal intersections
|
|
5480
|
+
|
|
5481
|
+
# Extract only parameters, append 0.0 and 1.0 for full coverage
|
|
5482
|
+
params = [u for (u, _) in cuts]
|
|
5483
|
+
params = [0.0] + params + [1.0]
|
|
5076
5484
|
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
new_vertices.append(vertices[-1])
|
|
5485
|
+
verts_orig = Topology.Vertices(wire)
|
|
5486
|
+
sub_wires = []
|
|
5080
5487
|
|
|
5081
|
-
|
|
5082
|
-
|
|
5488
|
+
for a, b in zip(params[:-1], params[1:]):
|
|
5489
|
+
if b - a <= tolerance:
|
|
5490
|
+
continue # Degenerate segment
|
|
5491
|
+
|
|
5492
|
+
# Build the vertex list for this segment
|
|
5493
|
+
seg_vertices = []
|
|
5494
|
+
|
|
5495
|
+
# Start vertex at parameter a
|
|
5496
|
+
v_a = Wire.VertexByParameter(wire, a)
|
|
5497
|
+
if Topology.IsInstance(v_a, "Vertex"):
|
|
5498
|
+
seg_vertices.append(v_a)
|
|
5499
|
+
|
|
5500
|
+
# Intermediate original vertices whose parameter lies between a and b
|
|
5501
|
+
for v in verts_orig:
|
|
5502
|
+
u = Wire.ParameterAtVertex(wire, v, silent=True)
|
|
5503
|
+
if u is None:
|
|
5504
|
+
continue
|
|
5505
|
+
if a < u < b:
|
|
5506
|
+
seg_vertices.append(v)
|
|
5507
|
+
|
|
5508
|
+
# End vertex at parameter b
|
|
5509
|
+
v_b = Wire.VertexByParameter(wire, b)
|
|
5510
|
+
if Topology.IsInstance(v_b, "Vertex"):
|
|
5511
|
+
seg_vertices.append(v_b)
|
|
5512
|
+
|
|
5513
|
+
# Make sure we have at least two vertices
|
|
5514
|
+
if len(seg_vertices) >= 2:
|
|
5515
|
+
sub_wires.append(Wire.ByVertices(seg_vertices, close=False, silent=True))
|
|
5516
|
+
|
|
5517
|
+
if not sub_wires:
|
|
5518
|
+
# Fallback: return the original wire if subdivision failed
|
|
5519
|
+
return [wire]
|
|
5520
|
+
|
|
5521
|
+
return sub_wires
|
|
5522
|
+
|
|
5523
|
+
# ----------------------------------------------------------------------
|
|
5524
|
+
# Main logic
|
|
5525
|
+
# ----------------------------------------------------------------------
|
|
5526
|
+
# If there are portals, divide the wire into segments between portals,
|
|
5527
|
+
# then straighten each segment independently and reassemble.
|
|
5528
|
+
if portal_list:
|
|
5529
|
+
result_vertices = []
|
|
5530
|
+
sub_wires = _subdivide_by_portals()
|
|
5531
|
+
|
|
5532
|
+
for i, sub_wire in enumerate(sub_wires):
|
|
5533
|
+
if not Topology.IsInstance(sub_wire, "Wire"):
|
|
5534
|
+
continue
|
|
5535
|
+
|
|
5536
|
+
sub_verts = Topology.Vertices(sub_wire)
|
|
5537
|
+
if len(sub_verts) <= 2:
|
|
5538
|
+
straight_verts = sub_verts
|
|
5539
|
+
else:
|
|
5540
|
+
# IMPORTANT: recursive call with portals=[]
|
|
5541
|
+
straight_wire = Wire.Straighten(sub_wire, host=host,
|
|
5542
|
+
obstacles=obstacles,
|
|
5543
|
+
portals=[],
|
|
5544
|
+
tolerance=tolerance,
|
|
5545
|
+
silent=silent)
|
|
5546
|
+
if not Topology.IsInstance(straight_wire, "Wire"):
|
|
5547
|
+
straight_verts = sub_verts
|
|
5548
|
+
else:
|
|
5549
|
+
straight_verts = Topology.Vertices(straight_wire)
|
|
5550
|
+
|
|
5551
|
+
if not straight_verts:
|
|
5552
|
+
continue
|
|
5553
|
+
|
|
5554
|
+
# Avoid duplicate vertices between consecutive segments
|
|
5555
|
+
if not result_vertices:
|
|
5556
|
+
result_vertices.extend(straight_verts)
|
|
5557
|
+
else:
|
|
5558
|
+
result_vertices.extend(straight_verts[1:])
|
|
5559
|
+
|
|
5560
|
+
if len(result_vertices) < 2:
|
|
5561
|
+
# Fallback
|
|
5562
|
+
return wire
|
|
5563
|
+
|
|
5564
|
+
return Wire.ByVertices(result_vertices, close=False, silent=True)
|
|
5565
|
+
|
|
5566
|
+
# No portals: simple global straightening
|
|
5567
|
+
new_vertices = _straighten_vertices(vertices)
|
|
5568
|
+
if len(new_vertices) < 2:
|
|
5569
|
+
return wire
|
|
5570
|
+
|
|
5571
|
+
return Wire.ByVertices(new_vertices, close=False, silent=True)
|
|
5083
5572
|
|
|
5084
5573
|
@staticmethod
|
|
5085
5574
|
def Trapezoid(origin= None, widthA: float = 1.0, widthB: float = 0.75, offsetA: float = 0.0, offsetB: float = 0.0, length: float = 1.0, direction: list = [0, 0, 1], placement: str = "center", tolerance: float = 0.0001):
|
|
@@ -5440,6 +5929,105 @@ class Wire():
|
|
|
5440
5929
|
|
|
5441
5930
|
return Wire.VertexByParameter(wire, u=compute_u(u))
|
|
5442
5931
|
|
|
5932
|
+
|
|
5933
|
+
|
|
5934
|
+
@staticmethod
|
|
5935
|
+
def ParameterAtVertex(wire, vertex, mantissa : int = 6, tolerance: float = 0.0001, silent: bool = False):
|
|
5936
|
+
"""
|
|
5937
|
+
Returns the u-parameter of a vertex located on a manifold wire.
|
|
5938
|
+
u ranges from 0.0 (start) to 1.0 (end).
|
|
5939
|
+
|
|
5940
|
+
Parameters
|
|
5941
|
+
----------
|
|
5942
|
+
wire : topologic_core.Wire
|
|
5943
|
+
The input wire.
|
|
5944
|
+
vertex : topologic_core.Vertex
|
|
5945
|
+
A vertex that lies somewhere on the wire.
|
|
5946
|
+
mantissa : int , optional
|
|
5947
|
+
The number of decimal places to round the result to. Default is 6.
|
|
5948
|
+
tolerance : float, optional
|
|
5949
|
+
Distance tolerance for matching the vertex to an edge. Default is 0.0001.
|
|
5950
|
+
silent : bool , optional
|
|
5951
|
+
If set to True, error and warning messages are suppressed. Default is False.
|
|
5952
|
+
|
|
5953
|
+
Returns
|
|
5954
|
+
-------
|
|
5955
|
+
float or None
|
|
5956
|
+
The global u-parameter ∈ [0, 1] of the vertex, or None on error.
|
|
5957
|
+
"""
|
|
5958
|
+
from topologicpy.Topology import Topology
|
|
5959
|
+
from topologicpy.Vertex import Vertex
|
|
5960
|
+
from topologicpy.Edge import Edge
|
|
5961
|
+
from topologicpy.Wire import Wire
|
|
5962
|
+
|
|
5963
|
+
# --- Input validation ----------------------------------------------------
|
|
5964
|
+
if not Topology.IsInstance(wire, "Wire"):
|
|
5965
|
+
if not silent:
|
|
5966
|
+
print("Wire.ParameterAtVertex - Error: Input wire is not a valid wire. Returning None.")
|
|
5967
|
+
return None
|
|
5968
|
+
|
|
5969
|
+
if not Topology.IsInstance(vertex, "Vertex"):
|
|
5970
|
+
if not silent:
|
|
5971
|
+
print("Wire.ParameterAtVertex - Error: Input vertex is not a valid wertex. Returning None.")
|
|
5972
|
+
return None
|
|
5973
|
+
|
|
5974
|
+
if not Wire.IsManifold(wire):
|
|
5975
|
+
if not silent:
|
|
5976
|
+
print("Wire.ParameterAtVertex - Error: Input wire is non-manifold. Returning None.")
|
|
5977
|
+
return None
|
|
5978
|
+
|
|
5979
|
+
# --- Prepare wire edges ---------------------------------------------------
|
|
5980
|
+
edges = Wire.Edges(wire)
|
|
5981
|
+
if not edges:
|
|
5982
|
+
if not silent:
|
|
5983
|
+
print("Wire.ParameterAtVertex - Error: Wire has no edges. Returning None.")
|
|
5984
|
+
return None
|
|
5985
|
+
|
|
5986
|
+
edge_lengths = [Edge.Length(e) for e in edges]
|
|
5987
|
+
total_length = sum(edge_lengths)
|
|
5988
|
+
if total_length == 0:
|
|
5989
|
+
if not silent:
|
|
5990
|
+
print("Wire.ParameterAtVertex - Error: Wire has zero length. Returning None.")
|
|
5991
|
+
return None
|
|
5992
|
+
|
|
5993
|
+
# --- Special cases: endpoint vertices ------------------------------------
|
|
5994
|
+
if Vertex.Distance(vertex, Wire.StartVertex(wire)) <= tolerance:
|
|
5995
|
+
return 0.0
|
|
5996
|
+
if Vertex.Distance(vertex, Wire.EndVertex(wire)) <= tolerance:
|
|
5997
|
+
return 1.0
|
|
5998
|
+
|
|
5999
|
+
# --- Locate the edge containing the vertex -------------------------------
|
|
6000
|
+
accumulated = 0.0
|
|
6001
|
+
|
|
6002
|
+
for edge, e_length in zip(edges, edge_lengths):
|
|
6003
|
+
|
|
6004
|
+
# Check if vertex lies on this edge
|
|
6005
|
+
d = Vertex.Distance(vertex, edge)
|
|
6006
|
+
if d <= tolerance:
|
|
6007
|
+
|
|
6008
|
+
# Compute local parameter on this edge
|
|
6009
|
+
sv = Edge.StartVertex(edge)
|
|
6010
|
+
ev = Edge.EndVertex(edge)
|
|
6011
|
+
|
|
6012
|
+
# Local distances
|
|
6013
|
+
dist_sv = Vertex.Distance(sv, vertex)
|
|
6014
|
+
dist_ev = Vertex.Distance(ev, vertex)
|
|
6015
|
+
|
|
6016
|
+
if dist_sv + dist_ev == 0:
|
|
6017
|
+
local_u = 0.0
|
|
6018
|
+
else:
|
|
6019
|
+
local_u = dist_sv / (dist_sv + dist_ev)
|
|
6020
|
+
|
|
6021
|
+
# Global parameter u
|
|
6022
|
+
global_u = (accumulated + local_u * e_length) / total_length
|
|
6023
|
+
return round(global_u, mantissa)
|
|
6024
|
+
|
|
6025
|
+
accumulated += e_length
|
|
6026
|
+
|
|
6027
|
+
if not silent:
|
|
6028
|
+
print("Wire.ParameterAtVertex - Error: Vertex does not appear to lie on the wire. Returning None.")
|
|
6029
|
+
return None
|
|
6030
|
+
|
|
5443
6031
|
@staticmethod
|
|
5444
6032
|
def VertexByParameter(wire, u: float = 0):
|
|
5445
6033
|
"""
|
topologicpy/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = '0.8.
|
|
1
|
+
__version__ = '0.8.92'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: topologicpy
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.92
|
|
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=ts0Ru24ILjjfHa54SYNhMc8Jkyxwej1DV0Jv7P_6BoU,22513
|
|
4
4
|
topologicpy/CSG.py,sha256=09la1-xzS9vr-WnV7tpJ0I-mkZ-XY0MRSd5iB50Nfgw,15556
|
|
5
|
-
topologicpy/Cell.py,sha256=
|
|
5
|
+
topologicpy/Cell.py,sha256=8GGeRJDoWV2qr__x41axOTIqtkep3U3VXRzM3qsQpPA,198783
|
|
6
6
|
topologicpy/CellComplex.py,sha256=B8bAW6M5fClfXb9nSLDhrgtNRlU888Z4EcUzBZtBqss,68558
|
|
7
7
|
topologicpy/Cluster.py,sha256=Vi5qn9dc9FRdZk1X5KrrU5YS8r4YDDA19C_nKo1IfA0,63725
|
|
8
8
|
topologicpy/Color.py,sha256=hzSmgBWhiuYc55RSipkQNIgGtgyhC5BqY8AakNYEK-U,24486
|
|
@@ -12,7 +12,7 @@ topologicpy/Dictionary.py,sha256=goODXIM6AoC5Qn_d8LGc5pRoxZKgIWbkn3IOEbsQ4c4,449
|
|
|
12
12
|
topologicpy/Edge.py,sha256=aiRd1xZgG2GGYHxva0bM-kDy3AVmwGA_S2pMur8EeMg,74911
|
|
13
13
|
topologicpy/EnergyModel.py,sha256=MEai1GF1hINeH5bhclJj_lpMU3asFTvW2RlPm40GNj4,57794
|
|
14
14
|
topologicpy/Face.py,sha256=qAl36LcwiyRclMM2pI9NyWHzmgNlaykXiJx1wu10RmA,201317
|
|
15
|
-
topologicpy/Graph.py,sha256=
|
|
15
|
+
topologicpy/Graph.py,sha256=Tokxg55wHmx5hd9eai7EMRUdptADLt7HmJNrhq00UZk,810723
|
|
16
16
|
topologicpy/Grid.py,sha256=3OsBMyHh4w8gpFOTMKHMNTpo62V0CwRNu5cwm87yDUA,18421
|
|
17
17
|
topologicpy/Helper.py,sha256=NsmMlbbKFPRX6jfoko-ZQVQ7MBsfVp9FD0ZvC2U7q-8,32002
|
|
18
18
|
topologicpy/Honeybee.py,sha256=dBk01jIvxjQMGHqSarM1Cukv16ot4Op7Dwlitn2OMoc,48990
|
|
@@ -26,14 +26,14 @@ topologicpy/ShapeGrammar.py,sha256=q_BvMKOBDW3GVSRjPLIGAZkHW2egw3mTOPzIyEpYOLg,2
|
|
|
26
26
|
topologicpy/Shell.py,sha256=2EPzDT_t0IAjBRYPDuKNAz_Ax_HaEkvNpXBxDkPdcTg,101084
|
|
27
27
|
topologicpy/Speckle.py,sha256=-eiTqJugd7pHiHpD3pDUcDO6CGhVyPV14HFRzaqEoaw,18187
|
|
28
28
|
topologicpy/Sun.py,sha256=ezisiHfc2nd7A_8w0Ykq2VgbS0A9WNSg-tBwvfTQAVM,36735
|
|
29
|
-
topologicpy/Topology.py,sha256=
|
|
29
|
+
topologicpy/Topology.py,sha256=Q2uivQaKrvfYX1e1sRZkNi1HXWrrVDqDKRhF9esUnog,548668
|
|
30
30
|
topologicpy/Vector.py,sha256=pEC8YY3TeHGfGdeNgvdHjgMDwxGabp5aWjwYC1HSvMk,42236
|
|
31
31
|
topologicpy/Vertex.py,sha256=26TrlX9OCZUN-lMlZG3g4RHTWBqw69NW4AOEgRz_YMo,91269
|
|
32
|
-
topologicpy/Wire.py,sha256=
|
|
32
|
+
topologicpy/Wire.py,sha256=OzMTI5vxJ8XJPgwDAB31666Dz2Sp7H_U9oCnm81gPQA,272312
|
|
33
33
|
topologicpy/__init__.py,sha256=RMftibjgAnHB1vdL-muo71RwMS4972JCxHuRHOlU428,928
|
|
34
|
-
topologicpy/version.py,sha256=
|
|
35
|
-
topologicpy-0.8.
|
|
36
|
-
topologicpy-0.8.
|
|
37
|
-
topologicpy-0.8.
|
|
38
|
-
topologicpy-0.8.
|
|
39
|
-
topologicpy-0.8.
|
|
34
|
+
topologicpy/version.py,sha256=S19MYIjxGesLc99Q4qttRtyqcN1JTaTrknAmmrZH4S8,23
|
|
35
|
+
topologicpy-0.8.92.dist-info/licenses/LICENSE,sha256=FK0vJ73LuE8PYJAn7LutsReWR47-Ooovw2dnRe5yV6Q,681
|
|
36
|
+
topologicpy-0.8.92.dist-info/METADATA,sha256=AUltiuak0trtBqGFWTznW6Xlc_irgAW7Y-9QUsGRXX8,10535
|
|
37
|
+
topologicpy-0.8.92.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
38
|
+
topologicpy-0.8.92.dist-info/top_level.txt,sha256=J30bDzW92Ob7hw3zA8V34Jlp-vvsfIkGzkr8sqvb4Uw,12
|
|
39
|
+
topologicpy-0.8.92.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|