topologicpy 0.8.10__py3-none-any.whl → 0.8.11__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 +55 -0
- topologicpy/Graph.py +213 -214
- topologicpy/Matrix.py +211 -5
- topologicpy/Plotly.py +8 -9
- topologicpy/Topology.py +341 -397
- topologicpy/Vector.py +71 -22
- topologicpy/Vertex.py +57 -0
- topologicpy/Wire.py +0 -119
- topologicpy/version.py +1 -1
- {topologicpy-0.8.10.dist-info → topologicpy-0.8.11.dist-info}/METADATA +1 -1
- {topologicpy-0.8.10.dist-info → topologicpy-0.8.11.dist-info}/RECORD +14 -14
- {topologicpy-0.8.10.dist-info → topologicpy-0.8.11.dist-info}/LICENSE +0 -0
- {topologicpy-0.8.10.dist-info → topologicpy-0.8.11.dist-info}/WHEEL +0 -0
- {topologicpy-0.8.10.dist-info → topologicpy-0.8.11.dist-info}/top_level.txt +0 -0
topologicpy/Topology.py
CHANGED
@@ -250,77 +250,6 @@ class Topology():
|
|
250
250
|
|
251
251
|
topology = Topology.AddContent(topology, apertures, subTopologyType=subTopologyType, tolerance=tolerance)
|
252
252
|
return topology
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
@staticmethod
|
257
|
-
def AddApertures_old(topology, apertures, exclusive=False, subTopologyType=None, tolerance=0.001):
|
258
|
-
"""
|
259
|
-
Adds the input list of apertures to the input topology or to its subtopologies based on the input subTopologyType.
|
260
|
-
|
261
|
-
Parameters
|
262
|
-
----------
|
263
|
-
topology : topologic_core.Topology
|
264
|
-
The input topology.
|
265
|
-
apertures : list
|
266
|
-
The input list of apertures.
|
267
|
-
exclusive : bool , optional
|
268
|
-
If set to True, one (sub)topology will accept only one aperture. Otherwise, one (sub)topology can accept multiple apertures. The default is False.
|
269
|
-
subTopologyType : string , optional
|
270
|
-
The subtopology type to which to add the apertures. This can be "cell", "face", "edge", or "vertex". It is case insensitive. If set to None, the apertures will be added to the input topology. The default is None.
|
271
|
-
tolerance : float , optional
|
272
|
-
The desired tolerance. The default is 0.001. This is larger than the usual 0.0001 as it seems to work better.
|
273
|
-
|
274
|
-
Returns
|
275
|
-
-------
|
276
|
-
topologic_core.Topology
|
277
|
-
The input topology with the apertures added to it.
|
278
|
-
|
279
|
-
"""
|
280
|
-
from topologicpy.Vertex import Vertex
|
281
|
-
from topologicpy.Cluster import Cluster
|
282
|
-
from topologicpy.Aperture import Aperture
|
283
|
-
def processApertures(subTopologies, apertures, exclusive=False, tolerance=0.001):
|
284
|
-
usedTopologies = []
|
285
|
-
for subTopology in subTopologies:
|
286
|
-
usedTopologies.append(0)
|
287
|
-
ap = 1
|
288
|
-
for aperture in apertures:
|
289
|
-
apCenter = Topology.InternalVertex(aperture, tolerance=tolerance)
|
290
|
-
for i in range(len(subTopologies)):
|
291
|
-
subTopology = subTopologies[i]
|
292
|
-
if exclusive == True and usedTopologies[i] == 1:
|
293
|
-
continue
|
294
|
-
if Vertex.Distance(apCenter, subTopology) <= tolerance:
|
295
|
-
context = topologic.Context.ByTopologyParameters(subTopology, 0.5, 0.5, 0.5)
|
296
|
-
_ = Aperture.ByTopologyContext(aperture, context)
|
297
|
-
if exclusive == True:
|
298
|
-
usedTopologies[i] = 1
|
299
|
-
ap = ap + 1
|
300
|
-
return None
|
301
|
-
|
302
|
-
if not Topology.IsInstance(topology, "Topology"):
|
303
|
-
print("Topology.AddApertures - Error: The input topology parameter is not a valid topology. Returning None.")
|
304
|
-
return None
|
305
|
-
if not apertures:
|
306
|
-
return topology
|
307
|
-
if not isinstance(apertures, list):
|
308
|
-
print("Topology.AddApertures - Error: the input apertures parameter is not a list. Returning None.")
|
309
|
-
return None
|
310
|
-
apertures = [x for x in apertures if Topology.IsInstance(x , "Topology")]
|
311
|
-
if len(apertures) < 1:
|
312
|
-
return topology
|
313
|
-
if not subTopologyType:
|
314
|
-
subTopologyType = "self"
|
315
|
-
if not subTopologyType.lower() in ["self", "cell", "face", "edge", "vertex"]:
|
316
|
-
print("Topology.AddApertures - Error: the input subtopology type parameter is not a recognized type. Returning None.")
|
317
|
-
return None
|
318
|
-
if subTopologyType.lower() == "self":
|
319
|
-
subTopologies = [topology]
|
320
|
-
else:
|
321
|
-
subTopologies = Topology.SubTopologies(topology, subTopologyType)
|
322
|
-
processApertures(subTopologies, apertures, exclusive, tolerance=tolerance)
|
323
|
-
return topology
|
324
253
|
|
325
254
|
@staticmethod
|
326
255
|
def AddContent(topology, contents, subTopologyType=None, tolerance=0.0001):
|
@@ -1486,7 +1415,6 @@ class Topology():
|
|
1486
1415
|
st = None
|
1487
1416
|
return st
|
1488
1417
|
|
1489
|
-
|
1490
1418
|
@staticmethod
|
1491
1419
|
def ByGeometry(vertices=[], edges=[], faces=[], topologyType: str = None, tolerance: float = 0.0001, silent: bool = False):
|
1492
1420
|
"""
|
@@ -1625,180 +1553,6 @@ class Topology():
|
|
1625
1553
|
else:
|
1626
1554
|
returnTopology = Cluster.ByTopologies(topVerts)
|
1627
1555
|
return returnTopology
|
1628
|
-
|
1629
|
-
|
1630
|
-
|
1631
|
-
|
1632
|
-
|
1633
|
-
|
1634
|
-
@staticmethod
|
1635
|
-
def ByGeometry_old(vertices=[], edges=[], faces=[], color=[1.0, 1.0, 1.0, 1.0], id=None, name=None, lengthUnit="METERS", outputMode="default", tolerance=0.0001):
|
1636
|
-
"""
|
1637
|
-
Create a topology by the input lists of vertices, edges, and faces.
|
1638
|
-
|
1639
|
-
Parameters
|
1640
|
-
----------
|
1641
|
-
vertices : list
|
1642
|
-
The input list of vertices in the form of [x, y, z]
|
1643
|
-
edges : list , optional
|
1644
|
-
The input list of edges in the form of [i, j] where i and j are vertex indices.
|
1645
|
-
faces : list , optional
|
1646
|
-
The input list of faces in the form of [i, j, k, l, ...] where the items in the list are vertex indices. The face is assumed to be closed to the last vertex is connected to the first vertex automatically.
|
1647
|
-
color : list , optional
|
1648
|
-
The desired color of the object in the form of [r, g, b, a] where the components are between 0 and 1 and represent red, blue, green, and alpha (transparency) respectively. The default is [1.0, 1.0, 1.0, 1.0].
|
1649
|
-
id : str , optional
|
1650
|
-
The desired ID of the object. If set to None, an automatic uuid4 will be assigned to the object. The default is None.
|
1651
|
-
name : str , optional
|
1652
|
-
The desired name of the object. If set to None, a default name "Topologic_[topology_type]" will be assigned to the object. The default is None.
|
1653
|
-
lengthUnit : str , optional
|
1654
|
-
The length unit used for the object. The default is "METERS"
|
1655
|
-
outputMode : str , optional
|
1656
|
-
The desired output mode of the object. This can be "wire", "shell", "cell", "cellcomplex", or "default". It is case insensitive. The default is "default".
|
1657
|
-
tolerance : float , optional
|
1658
|
-
The desired tolerance. The default is 0.0001.
|
1659
|
-
|
1660
|
-
Returns
|
1661
|
-
-------
|
1662
|
-
topology : topologic_core.Topology
|
1663
|
-
The created topology. The topology will have a dictionary embedded in it that records the input attributes (color, id, lengthUnit, name, type)
|
1664
|
-
|
1665
|
-
"""
|
1666
|
-
def topologyByFaces(faces, outputMode, tolerance=0.0001):
|
1667
|
-
output = None
|
1668
|
-
if len(faces) == 1:
|
1669
|
-
return faces[0]
|
1670
|
-
if outputMode.lower() == "cell":
|
1671
|
-
output = Cell.ByFaces(faces, tolerance=tolerance)
|
1672
|
-
if output:
|
1673
|
-
return output
|
1674
|
-
else:
|
1675
|
-
return None
|
1676
|
-
if outputMode.lower() == "cellcomplex":
|
1677
|
-
output = CellComplex.ByFaces(faces, tolerance=tolerance)
|
1678
|
-
if output:
|
1679
|
-
return output
|
1680
|
-
else:
|
1681
|
-
return None
|
1682
|
-
if outputMode.lower() == "shell":
|
1683
|
-
output = Shell.ByFaces(faces, tolerance=tolerance) # This can return a list
|
1684
|
-
if Topology.IsInstance(output, "Shell"):
|
1685
|
-
return output
|
1686
|
-
else:
|
1687
|
-
return None
|
1688
|
-
if outputMode.lower() == "default":
|
1689
|
-
output = Cluster.ByTopologies(faces)
|
1690
|
-
if output:
|
1691
|
-
return output
|
1692
|
-
return output
|
1693
|
-
def topologyByEdges(edges, outputMode):
|
1694
|
-
output = None
|
1695
|
-
if len(edges) == 1:
|
1696
|
-
return edges[0]
|
1697
|
-
output = Cluster.ByTopologies(edges)
|
1698
|
-
if outputMode.lower() == "wire":
|
1699
|
-
output = Topology.SelfMerge(output, tolerance=tolerance)
|
1700
|
-
if Topology.IsInstance(output, "Wire"):
|
1701
|
-
return output
|
1702
|
-
else:
|
1703
|
-
return None
|
1704
|
-
return output
|
1705
|
-
def edgesByVertices(vertices, topVerts):
|
1706
|
-
if len(vertices) < 2:
|
1707
|
-
return []
|
1708
|
-
edges = []
|
1709
|
-
for i in range(len(vertices)-1):
|
1710
|
-
v1 = vertices[i]
|
1711
|
-
v2 = vertices[i+1]
|
1712
|
-
e1 = Edge.ByVertices([topVerts[v1], topVerts[v2]], tolerance=tolerance)
|
1713
|
-
edges.append(e1)
|
1714
|
-
# connect the last vertex to the first one
|
1715
|
-
v1 = vertices[-1]
|
1716
|
-
v2 = vertices[0]
|
1717
|
-
e1 = Edge.ByVertices([topVerts[v1], topVerts[v2]], tolerance=tolerance)
|
1718
|
-
edges.append(e1)
|
1719
|
-
return edges
|
1720
|
-
from topologicpy.Vertex import Vertex
|
1721
|
-
from topologicpy.Edge import Edge
|
1722
|
-
from topologicpy.Wire import Wire
|
1723
|
-
from topologicpy.Face import Face
|
1724
|
-
from topologicpy.Shell import Shell
|
1725
|
-
from topologicpy.Cell import Cell
|
1726
|
-
from topologicpy.CellComplex import CellComplex
|
1727
|
-
from topologicpy.Cluster import Cluster
|
1728
|
-
from topologicpy.Dictionary import Dictionary
|
1729
|
-
import uuid
|
1730
|
-
returnTopology = None
|
1731
|
-
topVerts = []
|
1732
|
-
topEdges = []
|
1733
|
-
topFaces = []
|
1734
|
-
vertices = [v for v in vertices if not len(v) == 0]
|
1735
|
-
edges = [e for e in edges if not len(e) == 0]
|
1736
|
-
faces = [f for f in faces if not len(f) == 0]
|
1737
|
-
if len(vertices) > 0:
|
1738
|
-
for aVertex in vertices:
|
1739
|
-
v = Vertex.ByCoordinates(aVertex[0], aVertex[1], aVertex[2])
|
1740
|
-
topVerts.append(v)
|
1741
|
-
else:
|
1742
|
-
return None
|
1743
|
-
if (outputMode.lower == "wire") and (len(edges) > 0):
|
1744
|
-
for anEdge in edges:
|
1745
|
-
topEdge = Edge.ByVertices([topVerts[anEdge[0]], topVerts[anEdge[1]]], tolerance=tolerance)
|
1746
|
-
topEdges.append(topEdge)
|
1747
|
-
if len(topEdges) > 0:
|
1748
|
-
returnTopology = topologyByEdges(topEdges)
|
1749
|
-
elif len(faces) > 0:
|
1750
|
-
for aFace in faces:
|
1751
|
-
faceEdges = edgesByVertices(aFace, topVerts)
|
1752
|
-
if len(faceEdges) > 2:
|
1753
|
-
faceWire = Wire.ByEdges(faceEdges, tolerance=tolerance)
|
1754
|
-
try:
|
1755
|
-
topFace = Face.ByWire(faceWire, tolerance=tolerance, silent=True)
|
1756
|
-
if Topology.IsInstance(topFace, "Face"):
|
1757
|
-
topFaces.append(topFace)
|
1758
|
-
elif isinstance(topFace, list):
|
1759
|
-
topFaces += topFace
|
1760
|
-
except:
|
1761
|
-
pass
|
1762
|
-
if len(topFaces) > 0:
|
1763
|
-
returnTopology = topologyByFaces(topFaces, outputMode=outputMode, tolerance=tolerance)
|
1764
|
-
elif len(edges) > 0:
|
1765
|
-
for anEdge in edges:
|
1766
|
-
topEdge = Edge.ByVertices([topVerts[anEdge[0]], topVerts[anEdge[1]]], tolerance=tolerance)
|
1767
|
-
topEdges.append(topEdge)
|
1768
|
-
if len(topEdges) > 0:
|
1769
|
-
returnTopology = topologyByEdges(topEdges, outputMode=outputMode)
|
1770
|
-
else:
|
1771
|
-
returnTopology = Cluster.ByTopologies(topVerts)
|
1772
|
-
if returnTopology:
|
1773
|
-
keys = []
|
1774
|
-
values = []
|
1775
|
-
keys.append("TOPOLOGIC_color")
|
1776
|
-
keys.append("TOPOLOGIC_id")
|
1777
|
-
keys.append("TOPOLOGIC_name")
|
1778
|
-
keys.append("TOPOLOGIC_type")
|
1779
|
-
keys.append("TOPOLOGIC_length_unit")
|
1780
|
-
if color:
|
1781
|
-
if isinstance(color, tuple):
|
1782
|
-
color = list(color)
|
1783
|
-
elif isinstance(color, list):
|
1784
|
-
if isinstance(color[0], tuple):
|
1785
|
-
color = list(color[0])
|
1786
|
-
values.append(color)
|
1787
|
-
else:
|
1788
|
-
values.append([1.0, 1.0, 1.0, 1.0])
|
1789
|
-
if id:
|
1790
|
-
values.append(id)
|
1791
|
-
else:
|
1792
|
-
values.append(str(uuid.uuid4()))
|
1793
|
-
if name:
|
1794
|
-
values.append(name)
|
1795
|
-
else:
|
1796
|
-
values.append("Topologic_"+Topology.TypeAsString(returnTopology))
|
1797
|
-
values.append(Topology.TypeAsString(returnTopology))
|
1798
|
-
values.append(lengthUnit)
|
1799
|
-
topDict = Dictionary.ByKeysValues(keys, values)
|
1800
|
-
Topology.SetDictionary(returnTopology, topDict)
|
1801
|
-
return returnTopology
|
1802
1556
|
|
1803
1557
|
@staticmethod
|
1804
1558
|
def ByBIMPath(path, guidKey: str = "guid", colorKey: str = "color", typeKey: str = "type",
|
@@ -4168,6 +3922,7 @@ class Topology():
|
|
4168
3922
|
from topologicpy.Grid import Grid
|
4169
3923
|
from topologicpy.Matrix import Matrix
|
4170
3924
|
import numpy as np
|
3925
|
+
from itertools import permutations
|
4171
3926
|
|
4172
3927
|
def generate_floats(n):
|
4173
3928
|
if n < 2:
|
@@ -4232,16 +3987,34 @@ class Topology():
|
|
4232
3987
|
if np.dot(eigenvectors[:, i], [1, 0, 0]) < 0:
|
4233
3988
|
eigenvectors[:, i] *= -1
|
4234
3989
|
|
4235
|
-
# Step 8:
|
4236
|
-
|
4237
|
-
|
3990
|
+
# Step 8: Generate all permutations of aligning principal axes to world axes
|
3991
|
+
world_axes = np.eye(3) # X, Y, Z unit vectors
|
3992
|
+
permutations_axes = list(permutations(world_axes))
|
3993
|
+
|
3994
|
+
# Create a list to hold all canonical matrices
|
3995
|
+
canonical_matrices = []
|
3996
|
+
|
3997
|
+
for perm in permutations_axes:
|
3998
|
+
# Construct the rotation matrix for this permutation
|
3999
|
+
rotation_matrix = np.eye(4)
|
4000
|
+
rotation_matrix[:3, :3] = np.array(perm).T @ eigenvectors.T # Align points to axes for this permutation
|
4001
|
+
|
4002
|
+
# Combine transformations: scale -> translate -> rotate
|
4003
|
+
combined_matrix = Matrix.Multiply(rotation_matrix.tolist(), scaling_matrix)
|
4004
|
+
combined_matrix = Matrix.Multiply(combined_matrix, translation_matrix)
|
4005
|
+
|
4006
|
+
# Add the combined matrix to the list
|
4007
|
+
canonical_matrices.append(combined_matrix)
|
4008
|
+
# # Step 8: Create the rotation matrix
|
4009
|
+
# rotation_matrix = np.eye(4)
|
4010
|
+
# rotation_matrix[:3, :3] = eigenvectors.T # Use transpose to align points to axes
|
4238
4011
|
|
4239
|
-
# Step 9: Rotate the object to align it
|
4240
|
-
transformation_matrix = Matrix.Multiply(scaling_matrix, translation_matrix)
|
4241
|
-
transformation_matrix = Matrix.Multiply(rotation_matrix.tolist(), transformation_matrix)
|
4012
|
+
# # Step 9: Rotate the object to align it
|
4013
|
+
# transformation_matrix = Matrix.Multiply(scaling_matrix, translation_matrix)
|
4014
|
+
# transformation_matrix = Matrix.Multiply(rotation_matrix.tolist(), transformation_matrix)
|
4242
4015
|
|
4243
4016
|
# Step 10: Return the resulting matrix
|
4244
|
-
return
|
4017
|
+
return canonical_matrices
|
4245
4018
|
|
4246
4019
|
@staticmethod
|
4247
4020
|
def CenterOfMass(topology):
|
@@ -4459,127 +4232,6 @@ class Topology():
|
|
4459
4232
|
final_clusters.append(Topology.SelfMerge(Cluster.ByTopologies(cluster), tolerance=tolerance))
|
4460
4233
|
return final_clusters
|
4461
4234
|
|
4462
|
-
@staticmethod
|
4463
|
-
def ClusterFaces_orig(topology, angTolerance=0.1, tolerance=0.0001):
|
4464
|
-
"""
|
4465
|
-
Clusters the faces of the input topology by their direction.
|
4466
|
-
|
4467
|
-
Parameters
|
4468
|
-
----------
|
4469
|
-
topology : topologic_core.Topology
|
4470
|
-
The input topology.
|
4471
|
-
angTolerance : float , optional
|
4472
|
-
The desired angular tolerance. The default is 0.1.
|
4473
|
-
tolerance : float, optional
|
4474
|
-
The desired tolerance. The default is 0.0001.
|
4475
|
-
|
4476
|
-
Returns
|
4477
|
-
-------
|
4478
|
-
list
|
4479
|
-
The list of clusters of faces where faces in the same cluster have the same direction.
|
4480
|
-
|
4481
|
-
"""
|
4482
|
-
from topologicpy.Face import Face
|
4483
|
-
from topologicpy.Cluster import Cluster
|
4484
|
-
|
4485
|
-
def angle_between(v1, v2):
|
4486
|
-
u1 = v1 / norm(v1)
|
4487
|
-
u2 = v2 / norm(v2)
|
4488
|
-
y = u1 - u2
|
4489
|
-
x = u1 + u2
|
4490
|
-
if norm(x) == 0:
|
4491
|
-
return 0
|
4492
|
-
a0 = 2 * arctan(norm(y) / norm(x))
|
4493
|
-
if (not signbit(a0)) or signbit(pi - a0):
|
4494
|
-
return a0
|
4495
|
-
elif signbit(a0):
|
4496
|
-
return 0
|
4497
|
-
else:
|
4498
|
-
return pi
|
4499
|
-
|
4500
|
-
def collinear(v1, v2, tol):
|
4501
|
-
ang = angle_between(v1, v2)
|
4502
|
-
if math.isnan(ang) or math.isinf(ang):
|
4503
|
-
raise Exception("Face.IsCollinear - Error: Could not determine the angle between the input faces")
|
4504
|
-
elif abs(ang) < tol or abs(pi - ang) < tol:
|
4505
|
-
return True
|
4506
|
-
return False
|
4507
|
-
|
4508
|
-
def sumRow(matrix, i):
|
4509
|
-
return np.sum(matrix[i,:])
|
4510
|
-
|
4511
|
-
def buildSimilarityMatrix(samples, tol):
|
4512
|
-
numOfSamples = len(samples)
|
4513
|
-
matrix = np.zeros(shape=(numOfSamples, numOfSamples))
|
4514
|
-
for i in range(len(matrix)):
|
4515
|
-
for j in range(len(matrix)):
|
4516
|
-
if collinear(samples[i], samples[j], tol):
|
4517
|
-
matrix[i, j] = 1
|
4518
|
-
return matrix
|
4519
|
-
|
4520
|
-
def determineRow(matrix):
|
4521
|
-
maxNumOfOnes = -1
|
4522
|
-
row = -1
|
4523
|
-
for i in range(len(matrix)):
|
4524
|
-
if maxNumOfOnes < sumRow(matrix, i):
|
4525
|
-
maxNumOfOnes = sumRow(matrix, i)
|
4526
|
-
row = i
|
4527
|
-
return row
|
4528
|
-
|
4529
|
-
def categorizeIntoClusters(matrix):
|
4530
|
-
groups = []
|
4531
|
-
while np.sum(matrix) > 0:
|
4532
|
-
group = []
|
4533
|
-
row = determineRow(matrix)
|
4534
|
-
indexes = addIntoGroup(matrix, row)
|
4535
|
-
groups.append(indexes)
|
4536
|
-
matrix = deleteChosenRowsAndCols(matrix, indexes)
|
4537
|
-
return groups
|
4538
|
-
|
4539
|
-
def addIntoGroup(matrix, ind):
|
4540
|
-
change = True
|
4541
|
-
indexes = []
|
4542
|
-
for col in range(len(matrix)):
|
4543
|
-
if matrix[ind, col] == 1:
|
4544
|
-
indexes.append(col)
|
4545
|
-
while change == True:
|
4546
|
-
change = False
|
4547
|
-
numIndexes = len(indexes)
|
4548
|
-
for i in indexes:
|
4549
|
-
for col in range(len(matrix)):
|
4550
|
-
if matrix[i, col] == 1:
|
4551
|
-
if col not in indexes:
|
4552
|
-
indexes.append(col)
|
4553
|
-
numIndexes2 = len(indexes)
|
4554
|
-
if numIndexes != numIndexes2:
|
4555
|
-
change = True
|
4556
|
-
return indexes
|
4557
|
-
|
4558
|
-
def deleteChosenRowsAndCols(matrix, indexes):
|
4559
|
-
for i in indexes:
|
4560
|
-
matrix[i, :] = 0
|
4561
|
-
matrix[:, i] = 0
|
4562
|
-
return matrix
|
4563
|
-
if not Topology.IsInstance(topology, "Topology"):
|
4564
|
-
print("Topology.ClusterFaces - Error: the input topology parameter is not a valid topology. Returning None.")
|
4565
|
-
return None
|
4566
|
-
faces = []
|
4567
|
-
_ = topology.Faces(None, faces)
|
4568
|
-
normals = []
|
4569
|
-
for aFace in faces:
|
4570
|
-
normals.append(Face.Normal(aFace, outputType="XYZ", mantissa=3))
|
4571
|
-
# build a matrix of similarity
|
4572
|
-
mat = buildSimilarityMatrix(normals, angTolerance)
|
4573
|
-
categories = categorizeIntoClusters(mat)
|
4574
|
-
returnList = []
|
4575
|
-
for aCategory in categories:
|
4576
|
-
tempList = []
|
4577
|
-
if len(aCategory) > 0:
|
4578
|
-
for index in aCategory:
|
4579
|
-
tempList.append(faces[index])
|
4580
|
-
returnList.append(Topology.SelfMerge(Cluster.ByTopologies(tempList), tolerance=tolerance))
|
4581
|
-
return returnList
|
4582
|
-
|
4583
4235
|
@staticmethod
|
4584
4236
|
def Contents(topology):
|
4585
4237
|
"""
|
@@ -6609,7 +6261,225 @@ class Topology():
|
|
6609
6261
|
return None
|
6610
6262
|
return topologic.Topology.IsSame(topologyA, topologyB)
|
6611
6263
|
|
6612
|
-
|
6264
|
+
@staticmethod
|
6265
|
+
def IsSimilar(topologyA, topologyB, removeCoplanarFaces: bool = False, mantissa: int = 6, epsilon: float = 0.1, tolerance: float = 0.0001, silent: bool = False):
|
6266
|
+
"""
|
6267
|
+
Returns True if the input topologies are similar. False otherwise. See https://en.wikipedia.org/wiki/Similarity_(geometry).
|
6268
|
+
|
6269
|
+
Parameters
|
6270
|
+
----------
|
6271
|
+
topologyA : topologic_core.Topology
|
6272
|
+
The first input topology.
|
6273
|
+
topologyB : topologic_core.Topology
|
6274
|
+
The second input topology.
|
6275
|
+
removeCoplanarFaces : bool , optional
|
6276
|
+
If set to True, coplanar faces are removed. Otherwise they are not. The default is False.
|
6277
|
+
mantissa : int , optional
|
6278
|
+
The desired length of the mantissa. The default is 6.
|
6279
|
+
epsilon : float , optional
|
6280
|
+
The desired epsilon (another form of percentage tolerance) for finding if two objects are similar. Should be in the range 0 to 1. The default is 0.1 (10%).
|
6281
|
+
tolerance : float , optional
|
6282
|
+
The desired tolerance. The default is 0.0001.
|
6283
|
+
silent : bool , optional
|
6284
|
+
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
6285
|
+
|
6286
|
+
Returns
|
6287
|
+
-------
|
6288
|
+
[bool, list]
|
6289
|
+
True if the input topologies are similar., False otherwise and the matrix needed to tranform topologyA to match topologyB
|
6290
|
+
|
6291
|
+
"""
|
6292
|
+
from topologicpy.Vertex import Vertex
|
6293
|
+
from topologicpy.Edge import Edge
|
6294
|
+
from topologicpy.Face import Face
|
6295
|
+
from topologicpy.Cell import Cell
|
6296
|
+
from topologicpy.Matrix import Matrix
|
6297
|
+
from topologicpy.Vector import Vector
|
6298
|
+
from topologicpy.Dictionary import Dictionary
|
6299
|
+
|
6300
|
+
if not Topology.IsInstance(topologyA, "topology"):
|
6301
|
+
if not silent:
|
6302
|
+
print("Topology.IsSimilar - Error: The input topologyA parameter is not a valid topology. Returning None.")
|
6303
|
+
return None
|
6304
|
+
if not Topology.IsInstance(topologyB, "topology"):
|
6305
|
+
if not silent:
|
6306
|
+
print("Topology.IsSimilar - Error: The input topologyB parameter is not a valid topology. Returning None.")
|
6307
|
+
return None
|
6308
|
+
if removeCoplanarFaces == True:
|
6309
|
+
topologyA = Topology.RemoveCoplanarFaces(topologyA, epsilon=epsilon, tolerance=tolerance)
|
6310
|
+
topologyB = Topology.RemoveCoplanarFaces(topologyB, epsilon=epsilon, tolerance=tolerance)
|
6311
|
+
Topology.Show(topologyA, topologyB)
|
6312
|
+
len_vertices_a = len(Topology.Vertices(topologyA))
|
6313
|
+
if len_vertices_a < 1 and not Topology.IsInstance(topologyA, "vertex"):
|
6314
|
+
if not silent:
|
6315
|
+
print("Topology.IsSimilar - Error: The input topologyA parameter is not a valid topology. Returning None.")
|
6316
|
+
return None
|
6317
|
+
len_vertices_b = len(Topology.Vertices(topologyB))
|
6318
|
+
if len_vertices_b < 1 and not Topology.IsInstance(topologyB, "vertex"):
|
6319
|
+
if not silent:
|
6320
|
+
print("Topology.IsSimilar - Error: The input topologyB parameter is not a valid topology. Returning None.")
|
6321
|
+
return None
|
6322
|
+
if not isinstance(epsilon, int) and not isinstance(epsilon, float):
|
6323
|
+
if not silent:
|
6324
|
+
print("Topology.IsSimilar - Error: The input epsilon parameter is not a valid float. Returning None.")
|
6325
|
+
return None
|
6326
|
+
if not (0 <= epsilon <= 1):
|
6327
|
+
if not silent:
|
6328
|
+
print("Topology.IsSimilar - Error: The input epsilon parameter is not within a valid range. Returning None.")
|
6329
|
+
return None
|
6330
|
+
# Trivial cases - All vertices are similar to each other.
|
6331
|
+
if Topology.IsInstance(topologyA, "vertex") and Topology.IsInstance(topologyB, "vertex"):
|
6332
|
+
centroid_a = Topology.Centroid(topologyA)
|
6333
|
+
centroid_b = Topology.Centroid(topologyB)
|
6334
|
+
trans_matrix_a = Matrix.ByTranslation(-Vertex.X(centroid_a), -Vertex.Y(centroid_a), -Vertex.Z(centroid_a))
|
6335
|
+
trans_matrix_b = Matrix.ByTranslation(Vertex.X(centroid_b), Vertex.Y(centroid_b), Vertex.Z(centroid_b))
|
6336
|
+
combined_matrix = Matrix.Multiply(trans_matrix_b, trans_matrix_a)
|
6337
|
+
return True, combined_matrix
|
6338
|
+
|
6339
|
+
# EXCLUSION TESTS
|
6340
|
+
# Topology Type
|
6341
|
+
if Topology.Type(topologyA) != Topology.Type(topologyB):
|
6342
|
+
return False, None
|
6343
|
+
# Number of vertices
|
6344
|
+
max_len = max([len_vertices_a, len_vertices_b])
|
6345
|
+
if abs(len_vertices_a - len_vertices_b)/max_len > epsilon:
|
6346
|
+
if not silent:
|
6347
|
+
print("Topology.IsSimilar - Info: Failed number of vertices check. Returning False.")
|
6348
|
+
return False, None
|
6349
|
+
# Number of edges
|
6350
|
+
len_edges_a = len(Topology.Edges(topologyA))
|
6351
|
+
len_edges_b = len(Topology.Edges(topologyB))
|
6352
|
+
max_len = max([len_edges_a, len_edges_b])
|
6353
|
+
if max_len > 0: # Check only if the topologies do actually have edges
|
6354
|
+
if abs(len_edges_a - len_edges_b)/max_len > epsilon:
|
6355
|
+
if not silent:
|
6356
|
+
print("Topology.IsSimilar - Info: Failed number of edges check. Returning False.")
|
6357
|
+
return False, None
|
6358
|
+
# Number of faces
|
6359
|
+
len_faces_a = len(Topology.Faces(topologyA))
|
6360
|
+
len_faces_b = len(Topology.Faces(topologyB))
|
6361
|
+
max_len = max([len_faces_a, len_faces_b])
|
6362
|
+
if max_len > 0: # Check only if the topologies do actually have faces
|
6363
|
+
if abs(len_faces_a - len_faces_b)/max_len > epsilon:
|
6364
|
+
if not silent:
|
6365
|
+
print("Topology.IsSimilar - Info: Failed number of faces check. Returning False.")
|
6366
|
+
return False, None
|
6367
|
+
# Number of cells
|
6368
|
+
len_cells_a = len(Topology.Cells(topologyA))
|
6369
|
+
len_cells_b = len(Topology.Cells(topologyB))
|
6370
|
+
max_len = max([len_cells_a, len_cells_b])
|
6371
|
+
if max_len > 0: # Check only if the topologies do actually have cells
|
6372
|
+
if abs(len_cells_a - len_cells_b)/max_len > epsilon:
|
6373
|
+
if not silent:
|
6374
|
+
print("Topology.IsSimilar - Info: Failed number of cells check. Returning False.")
|
6375
|
+
return False, None
|
6376
|
+
if Topology.IsInstance(topologyA, "face"):
|
6377
|
+
compactness_a = Face.Compactness(topologyA, mantissa=mantissa)
|
6378
|
+
compactness_b = Face.Compactness(topologyB, mantissa=mantissa)
|
6379
|
+
max_compactness = max([compactness_a, compactness_b])
|
6380
|
+
if max_compactness > 0: # Check only if the topologies do actually have compactness
|
6381
|
+
if abs(compactness_a - compactness_b)/max_compactness >= epsilon:
|
6382
|
+
if not silent:
|
6383
|
+
print("Topology.IsSimilar - Info: Failed compactness check. Returning False.")
|
6384
|
+
return False, None
|
6385
|
+
if Topology.IsInstance(topologyA, "cell"):
|
6386
|
+
compactness_a = Cell.Compactness(topologyA, mantissa=mantissa)
|
6387
|
+
compactness_b = Cell.Compactness(topologyB, mantissa=mantissa)
|
6388
|
+
max_compactness = max([compactness_a, compactness_b])
|
6389
|
+
if max_compactness > 0: # Check only if the topologies do actually have compactness
|
6390
|
+
if abs(compactness_a - compactness_b)/max_compactness > epsilon:
|
6391
|
+
if not silent:
|
6392
|
+
print("Topology.IsSimilar - Info: Failed compactness check. Returning False.")
|
6393
|
+
return False, None
|
6394
|
+
# Done with Exclusion Tests.
|
6395
|
+
|
6396
|
+
faces_a = Topology.Faces(topologyA)
|
6397
|
+
faces_b = Topology.Faces(topologyB)
|
6398
|
+
if len(faces_a) > 0 and len(faces_b) > 0:
|
6399
|
+
largest_faces_a = Topology.LargestFaces(topologyA)
|
6400
|
+
largest_faces_b = Topology.LargestFaces(topologyB)
|
6401
|
+
|
6402
|
+
|
6403
|
+
# Process largest faces
|
6404
|
+
largest_faces_a = Topology.LargestFaces(topologyA)
|
6405
|
+
largest_faces_b = Topology.LargestFaces(topologyB)
|
6406
|
+
face_a_d = Dictionary.ByKeyValue("faceColor", "red")
|
6407
|
+
face_b_d = Dictionary.ByKeyValue("faceColor", "blue")
|
6408
|
+
for face_a in largest_faces_a:
|
6409
|
+
centroid_a = Topology.Centroid(face_a)
|
6410
|
+
normal_a = Face.Normal(face_a)
|
6411
|
+
third_vertex_a = Face.ThirdVertex(face_a) # Pick a third vertex for orientation
|
6412
|
+
orientation_a = Vector.Normalize(Vector.ByVertices(centroid_a, third_vertex_a))
|
6413
|
+
|
6414
|
+
for face_b in largest_faces_b:
|
6415
|
+
face_copy_b = Topology.SetDictionary(Topology.Copy(face_b), face_b_d)
|
6416
|
+
centroid_b = Topology.Centroid(face_b)
|
6417
|
+
normal_b = Face.Normal(face_b)
|
6418
|
+
third_vertex_b = Face.ThirdVertex(face_b)
|
6419
|
+
orientation_b = Vector.Normalize(Vector.ByVertices(centroid_b, third_vertex_b))
|
6420
|
+
|
6421
|
+
# Compute transformation matrix
|
6422
|
+
rotation_matrix = Matrix.ByVectors(normal_a, normal_b, orientation_a, orientation_b)
|
6423
|
+
scaling_factor = (Face.Area(face_b) / Face.Area(face_a)) ** (1/2)
|
6424
|
+
scaling_matrix = Matrix.ByScaling(scaling_factor, scaling_factor, scaling_factor)
|
6425
|
+
|
6426
|
+
translation_matrix = Matrix.ByTranslation(
|
6427
|
+
Vertex.X(centroid_b) - Vertex.X(centroid_a),
|
6428
|
+
Vertex.Y(centroid_b) - Vertex.Y(centroid_a),
|
6429
|
+
Vertex.Z(centroid_b) - Vertex.Z(centroid_a)
|
6430
|
+
)
|
6431
|
+
combined_matrix = Matrix.Multiply(rotation_matrix, scaling_matrix)
|
6432
|
+
combined_matrix = Matrix.Multiply(translation_matrix, combined_matrix)
|
6433
|
+
|
6434
|
+
transformed_centroid_a = Topology.Transform(centroid_a, combined_matrix)
|
6435
|
+
|
6436
|
+
# One last translation to synchronise the two centroids.
|
6437
|
+
translation_matrix = Matrix.ByTranslation(
|
6438
|
+
Vertex.X(centroid_b) - Vertex.X(transformed_centroid_a),
|
6439
|
+
Vertex.Y(centroid_b) - Vertex.Y(transformed_centroid_a),
|
6440
|
+
Vertex.Z(centroid_b) - Vertex.Z(transformed_centroid_a)
|
6441
|
+
)
|
6442
|
+
combined_matrix = Matrix.Multiply(translation_matrix, combined_matrix)
|
6443
|
+
# Apply transformation and compare
|
6444
|
+
transformedA = Topology.Transform(topologyA, combined_matrix)
|
6445
|
+
transformed_face_a = Topology.Transform(face_a, combined_matrix)
|
6446
|
+
transformed_face_a = Topology.SetDictionary(face_a, face_a_d)
|
6447
|
+
|
6448
|
+
Topology.Show(transformedA, topologyB, transformed_face_a, face_copy_b, faceColorKey="faceColor", vertexSizeKey="vertexSize")
|
6449
|
+
if Topology.IsVertexCongruent(transformedA, topologyB, mantissa=mantissa, epsilon=epsilon, tolerance=tolerance, silent=silent):
|
6450
|
+
return True, combined_matrix
|
6451
|
+
|
6452
|
+
return False, None
|
6453
|
+
# for l_f_a in largest_faces_a:
|
6454
|
+
# l_e_a = Face.NormalEdge(l_f_a)
|
6455
|
+
# centroid_a = Edge.StartVertex(l_e_a)
|
6456
|
+
# dir_a = Vector.Normalize(Edge.Direction(l_e_a))
|
6457
|
+
# trans_matrix_a = Matrix.ByTranslation(-Vertex.X(centroid_a), -Vertex.Y(centroid_a), -Vertex.Z(centroid_a))
|
6458
|
+
# sf_a = 1/Face.Area(l_f_a)
|
6459
|
+
# scaling_matrix_a = Matrix.ByScaling(sf_a, sf_a, sf_a)
|
6460
|
+
# for l_f_b in largest_faces_b:
|
6461
|
+
# l_e_b = Face.NormalEdge(l_f_b)
|
6462
|
+
# centroid_b = Edge.StartVertex(l_e_b)
|
6463
|
+
# dir_b = Vector.Normalize(Edge.Direction(l_e_b))
|
6464
|
+
# rotation_matrix = Matrix.ByVectors(dir_a, dir_b)
|
6465
|
+
# sf_b = 1/Face.Area(l_f_b)
|
6466
|
+
# scaling_matrix_b = Matrix.ByScaling(sf_b, sf_b, sf_b)
|
6467
|
+
# trans_matrix_b = Matrix.ByTranslation(-Vertex.X(centroid_b), -Vertex.Y(centroid_b), -Vertex.Z(centroid_b))
|
6468
|
+
# combined_matrix_a = Matrix.Multiply(rotation_matrix, scaling_matrix_a)
|
6469
|
+
# combined_matrix_a = Matrix.Multiply(combined_matrix_a, trans_matrix_a)
|
6470
|
+
# combined_matrix_b = Matrix.Multiply(scaling_matrix_b, trans_matrix_b)
|
6471
|
+
# top_a = Topology.Transform(topologyA, combined_matrix_a)
|
6472
|
+
# top_b = Topology.Transform(topologyB, combined_matrix_b)
|
6473
|
+
# Topology.Show(top_a, top_b)
|
6474
|
+
# if Topology.IsVertexCongruent(top_a, top_b, mantissa=mantissa, epsilon=epsilon, tolerance=tolerance, silent=silent):
|
6475
|
+
# Topology.Show(top_a, top_b)
|
6476
|
+
# final_matrix = Matrix.Multiply(Matrix.Invert(combined_matrix_b), combined_matrix_a)
|
6477
|
+
# return True, final_matrix
|
6478
|
+
|
6479
|
+
return False, None
|
6480
|
+
|
6481
|
+
@staticmethod
|
6482
|
+
def IsVertexCongruent(topologyA, topologyB, mantissa: int = 6, epsilon: float = 0.1, tolerance: float = 0.0001, silent : bool = False):
|
6613
6483
|
"""
|
6614
6484
|
Returns True if the input topologies are vertex matched (have same number of vertices and all vertices are congruent within a tolerance). Returns False otherwise.
|
6615
6485
|
|
@@ -6620,7 +6490,7 @@ class Topology():
|
|
6620
6490
|
topologyB : topologic_core.Topology
|
6621
6491
|
The second input topology.
|
6622
6492
|
mantissa : int , optional
|
6623
|
-
The desired length of the mantissa. The default is 6
|
6493
|
+
The desired length of the mantissa. The default is 6.
|
6624
6494
|
tolerance : float , optional
|
6625
6495
|
The desired tolerance. The default is 0.0001.
|
6626
6496
|
silent : bool , optional
|
@@ -6634,10 +6504,10 @@ class Topology():
|
|
6634
6504
|
"""
|
6635
6505
|
from topologicpy.Vertex import Vertex
|
6636
6506
|
|
6637
|
-
def
|
6507
|
+
def coordinates_unmatched_ratio(list1, list2, tolerance):
|
6638
6508
|
"""
|
6639
|
-
|
6640
|
-
within a specified tolerance, with each match being unique.
|
6509
|
+
Calculates the percentage of coordinates in list1 that do not have a corresponding
|
6510
|
+
coordinate in list2 within a specified tolerance, with each match being unique.
|
6641
6511
|
|
6642
6512
|
Parameters
|
6643
6513
|
----------
|
@@ -6650,45 +6520,62 @@ class Topology():
|
|
6650
6520
|
|
6651
6521
|
Returns
|
6652
6522
|
-------
|
6653
|
-
|
6654
|
-
|
6655
|
-
within the tolerance. False otherwise.
|
6523
|
+
float
|
6524
|
+
The percentage of coordinates in list1 that did not find a match in list2.
|
6656
6525
|
"""
|
6657
6526
|
def distance(coord1, coord2):
|
6658
6527
|
"""Calculate the Euclidean distance between two coordinates."""
|
6659
6528
|
return np.linalg.norm(np.array(coord1) - np.array(coord2))
|
6660
6529
|
|
6661
|
-
|
6530
|
+
unmatched_count = 0
|
6662
6531
|
unmatched = list2.copy()
|
6663
6532
|
|
6664
6533
|
for coord1 in list1:
|
6665
6534
|
match_found = False
|
6666
6535
|
for i, coord2 in enumerate(unmatched):
|
6667
6536
|
if distance(coord1, coord2) <= tolerance:
|
6668
|
-
#
|
6669
|
-
unmatched.pop(i)
|
6537
|
+
unmatched.pop(i) # Remove matched coordinate
|
6670
6538
|
match_found = True
|
6671
6539
|
break
|
6672
6540
|
if not match_found:
|
6673
|
-
|
6674
|
-
|
6541
|
+
unmatched_count += 1
|
6542
|
+
|
6543
|
+
total_coordinates = len(list1)
|
6544
|
+
unmatched_ratio = (unmatched_count / total_coordinates)
|
6545
|
+
|
6546
|
+
return unmatched_ratio
|
6675
6547
|
|
6676
6548
|
if not Topology.IsInstance(topologyA, "topology"):
|
6677
6549
|
if not silent:
|
6678
|
-
print("Topology.
|
6550
|
+
print("Topology.IsVertexCongruent - Error: The input topologyA parameter is not a valid topology. Returning None.")
|
6679
6551
|
return None
|
6680
6552
|
if not Topology.IsInstance(topologyB, "topology"):
|
6681
6553
|
if not silent:
|
6682
|
-
print("Topology.
|
6554
|
+
print("Topology.IsVertexCongruent - Error: The input topologyB parameter is not a valid topology. Returning None.")
|
6683
6555
|
return None
|
6684
6556
|
|
6685
6557
|
vertices_a = Topology.Vertices(topologyA)
|
6558
|
+
len_vertices_a = len(vertices_a)
|
6559
|
+
if len_vertices_a < 1:
|
6560
|
+
if not silent:
|
6561
|
+
print("Topology.IsVertexCongruent - Error: The input topologyA parameter is not a valid topology. Returning None.")
|
6562
|
+
return None
|
6686
6563
|
vertices_b = Topology.Vertices(topologyB)
|
6687
|
-
|
6564
|
+
len_vertices_b = len(vertices_b)
|
6565
|
+
if len_vertices_b < 1:
|
6566
|
+
if not silent:
|
6567
|
+
print("Topology.IsVertexCongruent - Error: The input topologyB parameter is not a valid topology. Returning None.")
|
6568
|
+
return None
|
6569
|
+
# Number of vertices
|
6570
|
+
max_len = max([len_vertices_a, len_vertices_b])
|
6571
|
+
if abs(len_vertices_a - len_vertices_a)/max_len >= epsilon:
|
6688
6572
|
return False
|
6689
6573
|
coords_a = [Vertex.Coordinates(v, mantissa=mantissa) for v in vertices_a]
|
6690
6574
|
coords_b = [Vertex.Coordinates(v, mantissa=mantissa) for v in vertices_b]
|
6691
|
-
|
6575
|
+
unmatched_ratio = coordinates_unmatched_ratio(coords_a, coords_b, tolerance=tolerance)
|
6576
|
+
if unmatched_ratio <= epsilon:
|
6577
|
+
return True
|
6578
|
+
return False
|
6692
6579
|
|
6693
6580
|
@staticmethod
|
6694
6581
|
def LargestFaces(topology, removeCoplanarFaces: bool = False, epsilon: float = 0.001, tolerance: float = 0.0001, silent: bool = False):
|
@@ -6779,6 +6666,7 @@ class Topology():
|
|
6779
6666
|
if removeCoplanarFaces == True:
|
6780
6667
|
simple_topology = Topology.RemoveCoplanarFaces(topology)
|
6781
6668
|
simple_topology = Topology.RemoveCollinearEdges(simple_topology)
|
6669
|
+
|
6782
6670
|
edges = Topology.Edges(simple_topology)
|
6783
6671
|
edge_lengths = [Edge.Length(edge) for edge in edges]
|
6784
6672
|
max_length = max(edge_lengths)
|
@@ -9666,7 +9554,7 @@ class Topology():
|
|
9666
9554
|
|
9667
9555
|
|
9668
9556
|
@staticmethod
|
9669
|
-
def Transform(topology, matrix):
|
9557
|
+
def Transform(topology, matrix: list, silent: bool = False):
|
9670
9558
|
"""
|
9671
9559
|
Transforms the input topology by the input 4X4 transformation matrix.
|
9672
9560
|
|
@@ -9676,6 +9564,8 @@ class Topology():
|
|
9676
9564
|
The input topology.
|
9677
9565
|
matrix : list
|
9678
9566
|
The input 4x4 transformation matrix.
|
9567
|
+
silent : bool , optional
|
9568
|
+
If set to True, no warnings or errors will be printed. The default is False.
|
9679
9569
|
|
9680
9570
|
Returns
|
9681
9571
|
-------
|
@@ -9683,6 +9573,10 @@ class Topology():
|
|
9683
9573
|
The transformed topology.
|
9684
9574
|
|
9685
9575
|
"""
|
9576
|
+
from topologicpy.Vertex import Vertex
|
9577
|
+
|
9578
|
+
import numpy as np
|
9579
|
+
|
9686
9580
|
kTranslationX = 0.0
|
9687
9581
|
kTranslationY = 0.0
|
9688
9582
|
kTranslationZ = 0.0
|
@@ -9710,7 +9604,52 @@ class Topology():
|
|
9710
9604
|
kTranslationY = matrix[1][3]
|
9711
9605
|
kTranslationZ = matrix[2][3]
|
9712
9606
|
|
9713
|
-
|
9607
|
+
if not Topology.IsInstance(topology, "topology"):
|
9608
|
+
if not silent:
|
9609
|
+
print("Topology.Trasnform - Error: The input topology parameter is not a valid topology. Returning None.")
|
9610
|
+
return None
|
9611
|
+
|
9612
|
+
try:
|
9613
|
+
return_topology = topologic.TopologyUtility.Transform(topology, kTranslationX, kTranslationY, kTranslationZ, kRotation11, kRotation12, kRotation13, kRotation21, kRotation22, kRotation23, kRotation31, kRotation32, kRotation33)
|
9614
|
+
except:
|
9615
|
+
print("topologic.TopologyUtility.Transform failed. Attempting a workaround.")
|
9616
|
+
|
9617
|
+
# Extract translation (last column of the matrix)
|
9618
|
+
translation = [m[3] for m in matrix[:3]]
|
9619
|
+
print("translation", translation)
|
9620
|
+
x_translate, y_translate, z_translate = translation
|
9621
|
+
|
9622
|
+
# Extract rotation (top-left 3x3 part of the matrix)
|
9623
|
+
rotation_matrix = [m[:3] for m in matrix[:3]]
|
9624
|
+
|
9625
|
+
# Extract scaling (diagonal of the matrix)
|
9626
|
+
scaling_factors = [matrix[m][m] for m in [0,1,2]] # scaling is stored in the diagonal of the rotation matrix
|
9627
|
+
print(scaling_factors)
|
9628
|
+
x_scale, y_scale, z_scale = scaling_factors
|
9629
|
+
|
9630
|
+
# Step 1: Apply Scaling
|
9631
|
+
# Here, origin is assumed to be (0,0,0) for simplicity
|
9632
|
+
return_topology = Topology.Scale(topology, origin=Vertex.ByCoordinates(0, 0, 0), x=x_scale, y=y_scale, z=z_scale)
|
9633
|
+
|
9634
|
+
# Step 2: Apply Rotation
|
9635
|
+
# The rotation axis and angle need to be derived from the rotation matrix
|
9636
|
+
# For simplicity, we assume the matrix represents a standard rotation (angle and axis).
|
9637
|
+
# The angle can be computed from the matrix as the arccos of the trace.
|
9638
|
+
|
9639
|
+
angle_rad = np.arccos((np.trace(rotation_matrix) - 1) / 2.0) # Using trace to compute angle
|
9640
|
+
axis = np.array([rotation_matrix[2, 1] - rotation_matrix[1, 2],
|
9641
|
+
rotation_matrix[0, 2] - rotation_matrix[2, 0],
|
9642
|
+
rotation_matrix[1, 0] - rotation_matrix[0, 1]])
|
9643
|
+
axis = axis / np.linalg.norm(axis) # Normalize the axis
|
9644
|
+
|
9645
|
+
# Convert the angle from radians to degrees
|
9646
|
+
angle_deg = np.degrees(angle_rad)
|
9647
|
+
|
9648
|
+
# Apply rotation
|
9649
|
+
return_topology = Topology.Rotate(return_topology, origin=Vertex.ByCoordinates(0, 0, 0), axis=axis, angle=angle_deg)
|
9650
|
+
|
9651
|
+
# Step 3: Apply Translation
|
9652
|
+
return_topology = Topology.Translate(return_topology, x=x_translate, y=y_translate, z=z_translate)
|
9714
9653
|
|
9715
9654
|
vertices = Topology.Vertices(topology)
|
9716
9655
|
edges = Topology.Edges(topology)
|
@@ -9747,7 +9686,7 @@ class Topology():
|
|
9747
9686
|
return return_topology
|
9748
9687
|
|
9749
9688
|
@staticmethod
|
9750
|
-
def Translate(topology, x=0, y=0, z=0):
|
9689
|
+
def Translate(topology, x=0, y=0, z=0, silent: bool = False):
|
9751
9690
|
"""
|
9752
9691
|
Translates (moves) the input topology.
|
9753
9692
|
|
@@ -9761,6 +9700,8 @@ class Topology():
|
|
9761
9700
|
The y translation value. The default is 0.
|
9762
9701
|
z : float , optional
|
9763
9702
|
The z translation value. The default is 0.
|
9703
|
+
silent : bool , optional
|
9704
|
+
If set to True, no warnings or errors will be printed. The default is False.
|
9764
9705
|
|
9765
9706
|
Returns
|
9766
9707
|
-------
|
@@ -9772,7 +9713,8 @@ class Topology():
|
|
9772
9713
|
from topologicpy.Dictionary import Dictionary
|
9773
9714
|
|
9774
9715
|
if not Topology.IsInstance(topology, "Topology"):
|
9775
|
-
|
9716
|
+
if not silent:
|
9717
|
+
print("Topology.Translate - Error: The input topology parameter is not a valid topology. Returning None.")
|
9776
9718
|
return None
|
9777
9719
|
|
9778
9720
|
if Topology.IsInstance(topology, "vertex"):
|
@@ -9791,7 +9733,9 @@ class Topology():
|
|
9791
9733
|
try:
|
9792
9734
|
return_topology = topologic.TopologyUtility.Translate(topology, x, y, z)
|
9793
9735
|
except:
|
9794
|
-
|
9736
|
+
if not silent:
|
9737
|
+
print("Topology.Translate - Error: The operation failed. Returning the original input.")
|
9738
|
+
return topology
|
9795
9739
|
|
9796
9740
|
r_vertices = Topology.Vertices(return_topology)
|
9797
9741
|
r_edges = Topology.Edges(return_topology)
|
@@ -9965,7 +9909,7 @@ class Topology():
|
|
9965
9909
|
return topology.Type()
|
9966
9910
|
|
9967
9911
|
@staticmethod
|
9968
|
-
def TypeAsString(topology, silent=False):
|
9912
|
+
def TypeAsString(topology, silent: bool = False):
|
9969
9913
|
"""
|
9970
9914
|
Returns the type of the input topology as a string.
|
9971
9915
|
|