Module Vector
Expand source code
# Copyright (C) 2024
# Wassim Jabi <wassim.jabi@gmail.com>
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along with
# this program. If not, see <https://www.gnu.org/licenses/>.
import math
import os
import warnings
try:
import numpy as np
import numpy.linalg as la
from numpy import pi, arctan2, rad2deg
except:
print("Vector - Installing required numpy library.")
try:
os.system("pip install numpy")
except:
os.system("pip install numpy --user")
try:
import numpy as np
import numpy.linalg as la
from numpy import pi, arctan2, rad2deg
print("Vector - numpy library installed successfully.")
except:
warnings.warn("Vector - Error: Could not import numpy.")
class Vector(list):
@staticmethod
def Angle(vectorA, vectorB, mantissa: int = 6):
"""
Returns the angle in degrees between the two input vectors
Parameters
----------
vectorA : list
The first vector.
vectorB : list
The second vector.
mantissa : int, optional
The length of the desired mantissa. The default is 6.
Returns
-------
float
The angle in degrees between the two input vectors.
"""
n_v1=la.norm(vectorA)
n_v2=la.norm(vectorB)
if n_v1 == 0 or n_v2 == 0:
# Handle the case where one or both vectors have a magnitude of zero.
return 0.0
if (abs(np.log10(n_v1/n_v2)) > 10):
vectorA = vectorA/n_v1
vectorB = vectorB/n_v2
cosang = np.dot(vectorA, vectorB)
sinang = la.norm(np.cross(vectorA, vectorB))
return round(math.degrees(np.arctan2(sinang, cosang)), mantissa)
@staticmethod
def Average(vectors: list):
"""
Returns the average vector of the input vectors.
Parameters
----------
vectors : list
The input list of vectors.
Returns
-------
list
The average vector of the input list of vectors.
"""
if not isinstance(vectors, list):
print("Vector.Average - Error: The input vectors parameter is not a valid list. Returning None.")
return None
vectors = [vec for vec in vectors if isinstance(vec, list)]
if len(vectors) < 1:
print("Vector.Average - Error: The input vectors parameter does not contain any valid vectors. Returning None.")
return None
dimensions = len(vectors[0])
num_vectors = len(vectors)
# Initialize a list to store the sum of each dimension
sum_dimensions = [0] * dimensions
# Calculate the sum of each dimension across all vectors
for vector in vectors:
for i in range(dimensions):
sum_dimensions[i] += vector[i]
# Calculate the average for each dimension
average_dimensions = [sum_dim / num_vectors for sum_dim in sum_dimensions]
return average_dimensions
@staticmethod
def AzimuthAltitude(vector, mantissa: int = 6):
"""
Returns a dictionary of azimuth and altitude angles in degrees for the input vector. North is assumed to be the positive Y axis [0, 1, 0]. Up is assumed to be the positive Z axis [0, 0, 1].
Azimuth is calculated in a counter-clockwise fashion from North where 0 is North, 90 is East, 180 is South, and 270 is West. Altitude is calculated in a counter-clockwise fashing where -90 is straight down (negative Z axis), 0 is in the XY plane, and 90 is straight up (positive Z axis).
If the altitude is -90 or 90, the azimuth is assumed to be 0.
Parameters
----------
vectorA : list
The input vector.
mantissa : int, optional
The length of the desired mantissa. The default is 6.
Returns
-------
dict
The dictionary containing the azimuth and altitude angles in degrees. The keys in the dictionary are 'azimuth' and 'altitude'.
"""
x, y, z = vector
if x == 0 and y == 0:
if z > 0:
return {"azimuth":0, "altitude":90}
elif z < 0:
return {"azimuth":0, "altitude":-90}
else:
# undefined
return None
else:
azimuth = math.degrees(math.atan2(y, x))
if azimuth > 90:
azimuth -= 360
azimuth = round(90-azimuth, mantissa)
xy_distance = math.sqrt(x**2 + y**2)
altitude = math.degrees(math.atan2(z, xy_distance))
altitude = round(altitude, mantissa)
return {"azimuth":azimuth, "altitude":altitude}
@staticmethod
def Bisect(vectorA, vectorB):
"""
Compute the bisecting vector of two input vectors.
Parameters
----------
vectorA : list
The first input vector.
vectorB : list
The second input vector.
Returns
-------
dict
The bisecting vector.
"""
import numpy as np
# Ensure vectors are numpy arrays
vector1 = np.array(vectorA)
vector2 = np.array(vectorB)
# Normalize input vectors
vector1_norm = vector1 / np.linalg.norm(vector1)
vector2_norm = vector2 / np.linalg.norm(vector2)
# Check if the angle between vectors is either 0 or 180 degrees
dot_product = np.dot(vector1_norm, vector2_norm)
if np.isclose(dot_product, 1.0) or np.isclose(dot_product, -1.0):
print("Vector.Bisect - Warning: The two vectors are collinear and thus the bisecting vector is not well-defined.")
# Angle is either 0 or 180 degrees, return any perpendicular vector
bisecting_vector = np.array([vector1[1] - vector1[2], vector1[2] - vector1[0], vector1[0] - vector1[1]])
bisecting_vector /= np.linalg.norm(bisecting_vector)
else:
# Compute bisecting vector
bisecting_vector = (vector1_norm + vector2_norm) / np.linalg.norm(vector1_norm + vector2_norm)
return list(bisecting_vector)
@staticmethod
def ByAzimuthAltitude(azimuth, altitude, north=0, reverse=False, tolerance=0.0001):
"""
Returns the vector specified by the input azimuth and altitude angles.
Parameters
----------
azimuth : float
The input azimuth angle in degrees. The angle is computed in an anti-clockwise fashion. 0 is considered North, 90 East, 180 is South, 270 is West
altitude : float
The input altitude angle in degrees from the XY plane. Positive is above the XY plane. Negative is below the XY plane
north : float , optional
The angle of the north direction in degrees measured from positive Y-axis. The angle is added in anti-clockwise fashion. 0 is considered along the positive Y-axis,
90 is along the positive X-axis, 180 is along the negative Y-axis, and 270 along the negative Y-axis.
reverse : bool , optional
If set to True the direction of the vector is computed from the end point towards the origin. Otherwise, it is computed from the origin towards the end point.
tolerance : float , optional
The desired tolerance. The default is 0.0001.
Returns
-------
list
The resulting vector.
"""
from topologicpy.Vertex import Vertex
from topologicpy.Edge import Edge
from topologicpy.Topology import Topology
e = Edge.ByVertices([Vertex.Origin(), Vertex.ByCoordinates(0, 1, 0)], tolerance=tolerance)
e = Topology.Rotate(e, origin=Vertex.Origin(), axis=[1, 0, 0], angle=altitude)
e = Topology.Rotate(e, origin=Vertex.Origin(), axis=[0, 0, 1], angle=-azimuth-north)
if reverse:
return Vector.Reverse(Edge.Direction(e))
return Edge.Direction(e)
@staticmethod
def ByCoordinates(x, y, z):
"""
Creates a vector by the specified x, y, z inputs.
Parameters
----------
x : float
The X coordinate.
y : float
The Y coordinate.
z : float
The Z coodinate.
Returns
-------
list
The created vector.
"""
return [x, y, z]
@staticmethod
def ByVertices(vertices, normalize=True):
"""
Creates a vector by the specified input list of vertices.
Parameters
----------
vertices : list
The the input list of topologic vertices. The first element in the list is considered the start vertex. The last element in the list is considered the end vertex.
normalize : bool , optional
If set to True, the resulting vector is normalized (i.e. its length is set to 1)
Returns
-------
list
The created vector.
"""
from topologicpy.Vertex import Vertex
from topologicpy.Topology import Topology
if not isinstance(vertices, list):
return None
if not isinstance(normalize, bool):
return None
vertices = [v for v in vertices if Topology.IsInstance(v, "Vertex")]
if len(vertices) < 2:
return None
v1 = vertices[0]
v2 = vertices[-1]
vector = [Vertex.X(v2)-Vertex.X(v1), Vertex.Y(v2)-Vertex.Y(v1), Vertex.Z(v2)-Vertex.Z(v1)]
if normalize:
vector = Vector.Normalize(vector)
return vector
@staticmethod
def CompassAngle(vectorA, vectorB, mantissa=6, tolerance=0.0001):
"""
Returns the horizontal compass angle in degrees between the two input vectors. The angle is measured in clockwise fashion.
0 is along the positive Y-axis, 90 is along the positive X axis.
Only the first two elements in the input vectors are considered.
Parameters
----------
vectorA : list
The first vector.
vectorB : list
The second vector.
mantissa : int, optional
The length of the desired mantissa. The default is 6.
tolerance : float , optional
The desired tolerance. The default is 0.0001.
Returns
-------
float
The horizontal compass angle in degrees between the two input vectors.
"""
if abs(vectorA[0]) < tolerance and abs(vectorA[1]) < tolerance:
return None
if abs(vectorB[0]) < tolerance and abs(vectorB[1]) < tolerance:
return None
p1 = (vectorA[0], vectorA[1])
p2 = (vectorB[0], vectorB[1])
ang1 = arctan2(*p1[::-1])
ang2 = arctan2(*p2[::-1])
return round(rad2deg((ang1 - ang2) % (2 * pi)), mantissa)
@staticmethod
def Coordinates(vector, outputType="xyz", mantissa: int = 6):
"""
Returns the coordinates of the input vector.
Parameters
----------
vector : list
The input vector.
outputType : string, optional
The desired output type. Could be any permutation or substring of "xyz" or the string "matrix". The default is "xyz". The input is case insensitive and the coordinates will be returned in the specified order.
mantissa : int , optional
The desired length of the mantissa. The default is 6.
Returns
-------
list
The coordinates of the input vertex.
"""
if not isinstance(vector, list):
return None
x = round(vector[0], mantissa)
y = round(vector[1], mantissa)
z = round(vector[2], mantissa)
matrix = [[1, 0, 0, x],
[0, 1, 0, y],
[0, 0, 1, z],
[0, 0, 0, 1]]
output = []
outputType = outputType.lower()
if outputType == "matrix":
return matrix
else:
outputType = list(outputType)
for axis in outputType:
if axis == "x":
output.append(x)
elif axis == "y":
output.append(y)
elif axis == "z":
output.append(z)
return output
@staticmethod
def Cross(vectorA, vectorB, mantissa=6, tolerance=0.0001):
"""
Returns the cross product of the two input vectors. The resulting vector is perpendicular to the plane defined by the two input vectors.
Parameters
----------
vectorA : list
The first vector.
vectorB : list
The second vector.
mantissa : int, optional
The length of the desired mantissa. The default is 6.
tolerance : float, optional
the desired tolerance. The default is 0.0001.
Returns
-------
list
The vector representing the cross product of the two input vectors.
"""
if not isinstance(vectorA, list) or not isinstance(vectorB, list):
return None
if Vector.Magnitude(vector=vectorA, mantissa=mantissa) < tolerance or Vector.Magnitude(vector=vectorB, mantissa=mantissa) < tolerance:
return None
vecA = np.array(vectorA)
vecB = np.array(vectorB)
vecC = list(np.cross(vecA, vecB))
if Vector.Magnitude(vecC) < tolerance:
return [0, 0, 0]
return [round(vecC[0], mantissa), round(vecC[1], mantissa), round(vecC[2], mantissa)]
@staticmethod
def Down():
"""
Returns the vector representing the *down* direction. In Topologic, the negative ZAxis direction is considered *down* ([0, 0, -1]).
Returns
-------
list
The vector representing the *down* direction.
"""
return [0, 0, -1]
@staticmethod
def East():
"""
Returns the vector representing the *east* direction. In Topologic, the positive XAxis direction is considered *east* ([1, 0, 0]).
Returns
-------
list
The vector representing the *east* direction.
"""
return [1, 0, 0]
@staticmethod
def IsAntiParallel(vectorA, vectorB):
"""
Returns True if the input vectors are anti-parallel. Returns False otherwise.
Parameters
----------
vectorA : list
The first input vector.
vectorB : list
The second input vector.
Returns
-------
bool
True if the input vectors are anti-parallel. False otherwise.
"""
import numpy as np
# Ensure vectors are numpy arrays
vector1 = np.array(vectorA)
vector2 = np.array(vectorB)
# Normalize input vectors
vector1_norm = vector1 / np.linalg.norm(vector1)
vector2_norm = vector2 / np.linalg.norm(vector2)
# Check if the angle between vectors is either 0 or 180 degrees
dot_product = np.dot(vector1_norm, vector2_norm)
if np.isclose(dot_product, -1.0):
return True
else:
# Compute bisecting vector
return False
@staticmethod
def IsParallel(vectorA, vectorB):
"""
Returns True if the input vectors are parallel. Returns False otherwise.
Parameters
----------
vectorA : list
The first input vector.
vectorB : list
The second input vector.
Returns
-------
bool
True if the input vectors are parallel. False otherwise.
"""
import numpy as np
# Ensure vectors are numpy arrays
vector1 = np.array(vectorA)
vector2 = np.array(vectorB)
# Normalize input vectors
vector1_norm = vector1 / np.linalg.norm(vector1)
vector2_norm = vector2 / np.linalg.norm(vector2)
# Check if the angle between vectors is either 0 or 180 degrees
dot_product = np.dot(vector1_norm, vector2_norm)
if np.isclose(dot_product, 1.0):
return True
else:
# Compute bisecting vector
return False
@staticmethod
def IsCollinear(vectorA, vectorB):
"""
Returns True if the input vectors are collinear (parallel or anti-parallel). Returns False otherwise.
Parameters
----------
vectorA : list
The first input vector.
vectorB : list
The second input vector.
Returns
-------
bool
True if the input vectors are collinear (parallel or anti-parallel). False otherwise.
"""
import numpy as np
# Ensure vectors are numpy arrays
vector1 = np.array(vectorA)
vector2 = np.array(vectorB)
# Normalize input vectors
vector1_norm = vector1 / np.linalg.norm(vector1)
vector2_norm = vector2 / np.linalg.norm(vector2)
# Check if the angle between vectors is either 0 or 180 degrees
dot_product = np.dot(vector1_norm, vector2_norm)
if np.isclose(dot_product, 1.0) or np.isclose(dot_product, -1.0):
return True
else:
return False
@staticmethod
def IsParallel(vectorA, vectorB):
"""
Returns True if the input vectors are parallel. Returns False otherwise.
Parameters
----------
vectorA : list
The first input vector.
vectorB : list
The second input vector.
Returns
-------
bool
True if the input vectors are parallel. False otherwise.
"""
import numpy as np
# Ensure vectors are numpy arrays
vector1 = np.array(vectorA)
vector2 = np.array(vectorB)
# Normalize input vectors
vector1_norm = vector1 / np.linalg.norm(vector1)
vector2_norm = vector2 / np.linalg.norm(vector2)
# Check if the angle between vectors is either 0 or 180 degrees
dot_product = np.dot(vector1_norm, vector2_norm)
if np.isclose(dot_product, 1.0):
return True
else:
# Compute bisecting vector
return False
@staticmethod
def Magnitude(vector, mantissa: int = 6):
"""
Returns the magnitude of the input vector.
Parameters
----------
vector : list
The input vector.
mantissa : int
The length of the desired mantissa. The default is 6.
Returns
-------
float
The magnitude of the input vector.
"""
return round(np.linalg.norm(np.array(vector)), mantissa)
@staticmethod
def Multiply(vector, magnitude, tolerance=0.0001):
"""
Multiplies the input vector by the input magnitude.
Parameters
----------
vector : list
The input vector.
magnitude : float
The input magnitude.
tolerance : float, optional
the desired tolerance. The default is 0.0001.
Returns
-------
list
The created vector that multiplies the input vector by the input magnitude.
"""
if abs(magnitude) < tolerance:
return [0.0] * len(vector)
scaled_vector = [component * (magnitude) for component in vector]
return scaled_vector
@staticmethod
def Normalize(vector):
"""
Returns a normalized vector of the input vector. A normalized vector has the same direction as the input vector, but its magnitude is 1.
Parameters
----------
vector : list
The input vector.
Returns
-------
list
The normalized vector.
"""
return list(vector / np.linalg.norm(vector))
@staticmethod
def North():
"""
Returns the vector representing the *north* direction. In Topologic, the positive YAxis direction is considered *north* ([0, 1, 0]).
Returns
-------
list
The vector representing the *north* direction.
"""
return [0, 1, 0]
@staticmethod
def NorthEast():
"""
Returns the vector representing the *northeast* direction. In Topologic, the positive YAxis direction is considered *north* and the positive XAxis direction is considered *east*. Therefore *northeast* is ([1, 1, 0]).
Returns
-------
list
The vector representing the *northeast* direction.
"""
return [1, 1, 0]
@staticmethod
def NorthWest():
"""
Returns the vector representing the *northwest* direction. In Topologic, the positive YAxis direction is considered *north* and the negative XAxis direction is considered *west*. Therefore *northwest* is ([-1, 1, 0]).
Returns
-------
list
The vector representing the *northwest* direction.
"""
return [-1, 1, 0]
@staticmethod
def Reverse(vector):
"""
Returns a reverse vector of the input vector. A reverse vector multiplies all components by -1.
Parameters
----------
vector : list
The input vector.
Returns
-------
list
The normalized vector.
"""
if not isinstance(vector, list):
return None
return [x*-1 for x in vector]
@staticmethod
def SetMagnitude(vector: list, magnitude: float) -> list:
"""
Sets the magnitude of the input vector to the input magnitude.
Parameters
----------
vector : list
The input vector.
magnitude : float
The desired magnitude.
Returns
-------
list
The created vector.
"""
return (Vector.Multiply(vector=Vector.Normalize(vector), magnitude=magnitude))
@staticmethod
def South():
"""
Returns the vector representing the *south* direction. In Topologic, the negative YAxis direction is considered *south* ([0, -1, 0]).
Returns
-------
list
The vector representing the *south* direction.
"""
return [0, -1, 0]
@staticmethod
def SouthEast():
"""
Returns the vector representing the *southeast* direction. In Topologic, the negative YAxis direction is considered *south* and the positive XAxis direction is considered *east*. Therefore *southeast* is ([1, -1, 0]).
Returns
-------
list
The vector representing the *southeast* direction.
"""
return [1, -1, 0]
@staticmethod
def SouthWest():
"""
Returns the vector representing the *southwest* direction. In Topologic, the negative YAxis direction is considered *south* and the negative XAxis direction is considered *west*. Therefore *southwest* is ([-1, -1, 0]).
Returns
-------
list
The vector representing the *southwest* direction.
"""
return [-1, -1, 0]
@staticmethod
def Sum(vectors: list):
"""
Returns the sum vector of the input vectors.
Parameters
----------
vectors : list
The input list of vectors.
Returns
-------
list
The sum vector of the input list of vectors.
"""
if not isinstance(vectors, list):
print("Vector.Average - Error: The input vectors parameter is not a valid list. Returning None.")
return None
vectors = [vec for vec in vectors if isinstance(vec, list)]
if len(vectors) < 1:
print("Vector.Average - Error: The input vectors parameter does not contain any valid vectors. Returning None.")
return None
dimensions = len(vectors[0])
num_vectors = len(vectors)
# Initialize a list to store the sum of each dimension
sum_dimensions = [0] * dimensions
# Calculate the sum of each dimension across all vectors
for vector in vectors:
for i in range(dimensions):
sum_dimensions[i] += vector[i]
return sum_dimensions
@staticmethod
def TransformationMatrix(vectorA, vectorB):
"""
Returns the transformation matrix needed to align vectorA with vectorB.
Parameters
----------
vectorA : list
The input vector to be transformed.
vectorB : list
The desired vector with which to align vectorA.
Returns
-------
list
Transformation matrix that follows the Blender software convention (nested list)
"""
import numpy as np
from topologicpy.Matrix import Matrix
import numpy as np
def transformation_matrix(vec1, vec2, translation_vector=None):
"""
Compute a 4x4 transformation matrix that aligns vec1 to vec2.
:param vec1: A 3D "source" vector
:param vec2: A 3D "destination" vector
:param translation_vector: Optional translation vector (default is None)
:return: The 4x4 transformation matrix
"""
vec1 = vec1 / np.linalg.norm(vec1)
vec2 = vec2 / np.linalg.norm(vec2)
dot_product = np.dot(vec1, vec2)
if np.isclose(dot_product, 1.0):
# Vectors are parallel; return the identity matrix
return np.eye(4)
elif np.isclose(dot_product, -1.0):
# Vectors are antiparallel; reflect one of the vectors about the origin
reflection_matrix = np.eye(4)
reflection_matrix[2, 2] = -1
return reflection_matrix
cross_product = np.cross(vec1, vec2)
skew_symmetric_matrix = np.array([[0, -cross_product[2], cross_product[1], 0],
[cross_product[2], 0, -cross_product[0], 0],
[-cross_product[1], cross_product[0], 0, 0],
[0, 0, 0, 1]])
rotation_matrix = np.eye(4) + skew_symmetric_matrix + \
np.dot(skew_symmetric_matrix, skew_symmetric_matrix) * \
(1 / (1 + dot_product))
if translation_vector is not None:
translation_matrix = np.eye(4)
translation_matrix[:3, 3] = translation_vector
transformation_matrix = np.dot(translation_matrix, rotation_matrix)
else:
transformation_matrix = rotation_matrix
return transformation_matrix
tran_mat = transformation_matrix(vectorA, vectorB)
return [list(tran_mat[0]), list(tran_mat[1]), list(tran_mat[2]), list(tran_mat[3])]
@staticmethod
def Up():
"""
Returns the vector representing the up direction. In Topologic, the positive ZAxis direction is considered "up" ([0, 0, 1]).
Returns
-------
list
The vector representing the "up" direction.
"""
return [0, 0, 1]
@staticmethod
def West():
"""
Returns the vector representing the *west* direction. In Topologic, the negative XAxis direction is considered *west* ([-1, 0, 0]).
Returns
-------
list
The vector representing the *west* direction.
"""
return [-1, 0, 0]
@staticmethod
def XAxis():
"""
Returns the vector representing the XAxis ([1, 0, 0])
Returns
-------
list
The vector representing the XAxis.
"""
return [1, 0, 0]
@staticmethod
def YAxis():
"""
Returns the vector representing the YAxis ([0, 1, 0])
Returns
-------
list
The vector representing the YAxis.
"""
return [0, 1, 0]
@staticmethod
def ZAxis():
"""
Returns the vector representing the ZAxis ([0, 0, 1])
Returns
-------
list
The vector representing the ZAxis.
"""
return [0, 0, 1]
Classes
class Vector (*args, **kwargs)
-
Built-in mutable sequence.
If no argument is given, the constructor creates a new empty list. The argument must be an iterable if specified.
Expand source code
class Vector(list): @staticmethod def Angle(vectorA, vectorB, mantissa: int = 6): """ Returns the angle in degrees between the two input vectors Parameters ---------- vectorA : list The first vector. vectorB : list The second vector. mantissa : int, optional The length of the desired mantissa. The default is 6. Returns ------- float The angle in degrees between the two input vectors. """ n_v1=la.norm(vectorA) n_v2=la.norm(vectorB) if n_v1 == 0 or n_v2 == 0: # Handle the case where one or both vectors have a magnitude of zero. return 0.0 if (abs(np.log10(n_v1/n_v2)) > 10): vectorA = vectorA/n_v1 vectorB = vectorB/n_v2 cosang = np.dot(vectorA, vectorB) sinang = la.norm(np.cross(vectorA, vectorB)) return round(math.degrees(np.arctan2(sinang, cosang)), mantissa) @staticmethod def Average(vectors: list): """ Returns the average vector of the input vectors. Parameters ---------- vectors : list The input list of vectors. Returns ------- list The average vector of the input list of vectors. """ if not isinstance(vectors, list): print("Vector.Average - Error: The input vectors parameter is not a valid list. Returning None.") return None vectors = [vec for vec in vectors if isinstance(vec, list)] if len(vectors) < 1: print("Vector.Average - Error: The input vectors parameter does not contain any valid vectors. Returning None.") return None dimensions = len(vectors[0]) num_vectors = len(vectors) # Initialize a list to store the sum of each dimension sum_dimensions = [0] * dimensions # Calculate the sum of each dimension across all vectors for vector in vectors: for i in range(dimensions): sum_dimensions[i] += vector[i] # Calculate the average for each dimension average_dimensions = [sum_dim / num_vectors for sum_dim in sum_dimensions] return average_dimensions @staticmethod def AzimuthAltitude(vector, mantissa: int = 6): """ Returns a dictionary of azimuth and altitude angles in degrees for the input vector. North is assumed to be the positive Y axis [0, 1, 0]. Up is assumed to be the positive Z axis [0, 0, 1]. Azimuth is calculated in a counter-clockwise fashion from North where 0 is North, 90 is East, 180 is South, and 270 is West. Altitude is calculated in a counter-clockwise fashing where -90 is straight down (negative Z axis), 0 is in the XY plane, and 90 is straight up (positive Z axis). If the altitude is -90 or 90, the azimuth is assumed to be 0. Parameters ---------- vectorA : list The input vector. mantissa : int, optional The length of the desired mantissa. The default is 6. Returns ------- dict The dictionary containing the azimuth and altitude angles in degrees. The keys in the dictionary are 'azimuth' and 'altitude'. """ x, y, z = vector if x == 0 and y == 0: if z > 0: return {"azimuth":0, "altitude":90} elif z < 0: return {"azimuth":0, "altitude":-90} else: # undefined return None else: azimuth = math.degrees(math.atan2(y, x)) if azimuth > 90: azimuth -= 360 azimuth = round(90-azimuth, mantissa) xy_distance = math.sqrt(x**2 + y**2) altitude = math.degrees(math.atan2(z, xy_distance)) altitude = round(altitude, mantissa) return {"azimuth":azimuth, "altitude":altitude} @staticmethod def Bisect(vectorA, vectorB): """ Compute the bisecting vector of two input vectors. Parameters ---------- vectorA : list The first input vector. vectorB : list The second input vector. Returns ------- dict The bisecting vector. """ import numpy as np # Ensure vectors are numpy arrays vector1 = np.array(vectorA) vector2 = np.array(vectorB) # Normalize input vectors vector1_norm = vector1 / np.linalg.norm(vector1) vector2_norm = vector2 / np.linalg.norm(vector2) # Check if the angle between vectors is either 0 or 180 degrees dot_product = np.dot(vector1_norm, vector2_norm) if np.isclose(dot_product, 1.0) or np.isclose(dot_product, -1.0): print("Vector.Bisect - Warning: The two vectors are collinear and thus the bisecting vector is not well-defined.") # Angle is either 0 or 180 degrees, return any perpendicular vector bisecting_vector = np.array([vector1[1] - vector1[2], vector1[2] - vector1[0], vector1[0] - vector1[1]]) bisecting_vector /= np.linalg.norm(bisecting_vector) else: # Compute bisecting vector bisecting_vector = (vector1_norm + vector2_norm) / np.linalg.norm(vector1_norm + vector2_norm) return list(bisecting_vector) @staticmethod def ByAzimuthAltitude(azimuth, altitude, north=0, reverse=False, tolerance=0.0001): """ Returns the vector specified by the input azimuth and altitude angles. Parameters ---------- azimuth : float The input azimuth angle in degrees. The angle is computed in an anti-clockwise fashion. 0 is considered North, 90 East, 180 is South, 270 is West altitude : float The input altitude angle in degrees from the XY plane. Positive is above the XY plane. Negative is below the XY plane north : float , optional The angle of the north direction in degrees measured from positive Y-axis. The angle is added in anti-clockwise fashion. 0 is considered along the positive Y-axis, 90 is along the positive X-axis, 180 is along the negative Y-axis, and 270 along the negative Y-axis. reverse : bool , optional If set to True the direction of the vector is computed from the end point towards the origin. Otherwise, it is computed from the origin towards the end point. tolerance : float , optional The desired tolerance. The default is 0.0001. Returns ------- list The resulting vector. """ from topologicpy.Vertex import Vertex from topologicpy.Edge import Edge from topologicpy.Topology import Topology e = Edge.ByVertices([Vertex.Origin(), Vertex.ByCoordinates(0, 1, 0)], tolerance=tolerance) e = Topology.Rotate(e, origin=Vertex.Origin(), axis=[1, 0, 0], angle=altitude) e = Topology.Rotate(e, origin=Vertex.Origin(), axis=[0, 0, 1], angle=-azimuth-north) if reverse: return Vector.Reverse(Edge.Direction(e)) return Edge.Direction(e) @staticmethod def ByCoordinates(x, y, z): """ Creates a vector by the specified x, y, z inputs. Parameters ---------- x : float The X coordinate. y : float The Y coordinate. z : float The Z coodinate. Returns ------- list The created vector. """ return [x, y, z] @staticmethod def ByVertices(vertices, normalize=True): """ Creates a vector by the specified input list of vertices. Parameters ---------- vertices : list The the input list of topologic vertices. The first element in the list is considered the start vertex. The last element in the list is considered the end vertex. normalize : bool , optional If set to True, the resulting vector is normalized (i.e. its length is set to 1) Returns ------- list The created vector. """ from topologicpy.Vertex import Vertex from topologicpy.Topology import Topology if not isinstance(vertices, list): return None if not isinstance(normalize, bool): return None vertices = [v for v in vertices if Topology.IsInstance(v, "Vertex")] if len(vertices) < 2: return None v1 = vertices[0] v2 = vertices[-1] vector = [Vertex.X(v2)-Vertex.X(v1), Vertex.Y(v2)-Vertex.Y(v1), Vertex.Z(v2)-Vertex.Z(v1)] if normalize: vector = Vector.Normalize(vector) return vector @staticmethod def CompassAngle(vectorA, vectorB, mantissa=6, tolerance=0.0001): """ Returns the horizontal compass angle in degrees between the two input vectors. The angle is measured in clockwise fashion. 0 is along the positive Y-axis, 90 is along the positive X axis. Only the first two elements in the input vectors are considered. Parameters ---------- vectorA : list The first vector. vectorB : list The second vector. mantissa : int, optional The length of the desired mantissa. The default is 6. tolerance : float , optional The desired tolerance. The default is 0.0001. Returns ------- float The horizontal compass angle in degrees between the two input vectors. """ if abs(vectorA[0]) < tolerance and abs(vectorA[1]) < tolerance: return None if abs(vectorB[0]) < tolerance and abs(vectorB[1]) < tolerance: return None p1 = (vectorA[0], vectorA[1]) p2 = (vectorB[0], vectorB[1]) ang1 = arctan2(*p1[::-1]) ang2 = arctan2(*p2[::-1]) return round(rad2deg((ang1 - ang2) % (2 * pi)), mantissa) @staticmethod def Coordinates(vector, outputType="xyz", mantissa: int = 6): """ Returns the coordinates of the input vector. Parameters ---------- vector : list The input vector. outputType : string, optional The desired output type. Could be any permutation or substring of "xyz" or the string "matrix". The default is "xyz". The input is case insensitive and the coordinates will be returned in the specified order. mantissa : int , optional The desired length of the mantissa. The default is 6. Returns ------- list The coordinates of the input vertex. """ if not isinstance(vector, list): return None x = round(vector[0], mantissa) y = round(vector[1], mantissa) z = round(vector[2], mantissa) matrix = [[1, 0, 0, x], [0, 1, 0, y], [0, 0, 1, z], [0, 0, 0, 1]] output = [] outputType = outputType.lower() if outputType == "matrix": return matrix else: outputType = list(outputType) for axis in outputType: if axis == "x": output.append(x) elif axis == "y": output.append(y) elif axis == "z": output.append(z) return output @staticmethod def Cross(vectorA, vectorB, mantissa=6, tolerance=0.0001): """ Returns the cross product of the two input vectors. The resulting vector is perpendicular to the plane defined by the two input vectors. Parameters ---------- vectorA : list The first vector. vectorB : list The second vector. mantissa : int, optional The length of the desired mantissa. The default is 6. tolerance : float, optional the desired tolerance. The default is 0.0001. Returns ------- list The vector representing the cross product of the two input vectors. """ if not isinstance(vectorA, list) or not isinstance(vectorB, list): return None if Vector.Magnitude(vector=vectorA, mantissa=mantissa) < tolerance or Vector.Magnitude(vector=vectorB, mantissa=mantissa) < tolerance: return None vecA = np.array(vectorA) vecB = np.array(vectorB) vecC = list(np.cross(vecA, vecB)) if Vector.Magnitude(vecC) < tolerance: return [0, 0, 0] return [round(vecC[0], mantissa), round(vecC[1], mantissa), round(vecC[2], mantissa)] @staticmethod def Down(): """ Returns the vector representing the *down* direction. In Topologic, the negative ZAxis direction is considered *down* ([0, 0, -1]). Returns ------- list The vector representing the *down* direction. """ return [0, 0, -1] @staticmethod def East(): """ Returns the vector representing the *east* direction. In Topologic, the positive XAxis direction is considered *east* ([1, 0, 0]). Returns ------- list The vector representing the *east* direction. """ return [1, 0, 0] @staticmethod def IsAntiParallel(vectorA, vectorB): """ Returns True if the input vectors are anti-parallel. Returns False otherwise. Parameters ---------- vectorA : list The first input vector. vectorB : list The second input vector. Returns ------- bool True if the input vectors are anti-parallel. False otherwise. """ import numpy as np # Ensure vectors are numpy arrays vector1 = np.array(vectorA) vector2 = np.array(vectorB) # Normalize input vectors vector1_norm = vector1 / np.linalg.norm(vector1) vector2_norm = vector2 / np.linalg.norm(vector2) # Check if the angle between vectors is either 0 or 180 degrees dot_product = np.dot(vector1_norm, vector2_norm) if np.isclose(dot_product, -1.0): return True else: # Compute bisecting vector return False @staticmethod def IsParallel(vectorA, vectorB): """ Returns True if the input vectors are parallel. Returns False otherwise. Parameters ---------- vectorA : list The first input vector. vectorB : list The second input vector. Returns ------- bool True if the input vectors are parallel. False otherwise. """ import numpy as np # Ensure vectors are numpy arrays vector1 = np.array(vectorA) vector2 = np.array(vectorB) # Normalize input vectors vector1_norm = vector1 / np.linalg.norm(vector1) vector2_norm = vector2 / np.linalg.norm(vector2) # Check if the angle between vectors is either 0 or 180 degrees dot_product = np.dot(vector1_norm, vector2_norm) if np.isclose(dot_product, 1.0): return True else: # Compute bisecting vector return False @staticmethod def IsCollinear(vectorA, vectorB): """ Returns True if the input vectors are collinear (parallel or anti-parallel). Returns False otherwise. Parameters ---------- vectorA : list The first input vector. vectorB : list The second input vector. Returns ------- bool True if the input vectors are collinear (parallel or anti-parallel). False otherwise. """ import numpy as np # Ensure vectors are numpy arrays vector1 = np.array(vectorA) vector2 = np.array(vectorB) # Normalize input vectors vector1_norm = vector1 / np.linalg.norm(vector1) vector2_norm = vector2 / np.linalg.norm(vector2) # Check if the angle between vectors is either 0 or 180 degrees dot_product = np.dot(vector1_norm, vector2_norm) if np.isclose(dot_product, 1.0) or np.isclose(dot_product, -1.0): return True else: return False @staticmethod def IsParallel(vectorA, vectorB): """ Returns True if the input vectors are parallel. Returns False otherwise. Parameters ---------- vectorA : list The first input vector. vectorB : list The second input vector. Returns ------- bool True if the input vectors are parallel. False otherwise. """ import numpy as np # Ensure vectors are numpy arrays vector1 = np.array(vectorA) vector2 = np.array(vectorB) # Normalize input vectors vector1_norm = vector1 / np.linalg.norm(vector1) vector2_norm = vector2 / np.linalg.norm(vector2) # Check if the angle between vectors is either 0 or 180 degrees dot_product = np.dot(vector1_norm, vector2_norm) if np.isclose(dot_product, 1.0): return True else: # Compute bisecting vector return False @staticmethod def Magnitude(vector, mantissa: int = 6): """ Returns the magnitude of the input vector. Parameters ---------- vector : list The input vector. mantissa : int The length of the desired mantissa. The default is 6. Returns ------- float The magnitude of the input vector. """ return round(np.linalg.norm(np.array(vector)), mantissa) @staticmethod def Multiply(vector, magnitude, tolerance=0.0001): """ Multiplies the input vector by the input magnitude. Parameters ---------- vector : list The input vector. magnitude : float The input magnitude. tolerance : float, optional the desired tolerance. The default is 0.0001. Returns ------- list The created vector that multiplies the input vector by the input magnitude. """ if abs(magnitude) < tolerance: return [0.0] * len(vector) scaled_vector = [component * (magnitude) for component in vector] return scaled_vector @staticmethod def Normalize(vector): """ Returns a normalized vector of the input vector. A normalized vector has the same direction as the input vector, but its magnitude is 1. Parameters ---------- vector : list The input vector. Returns ------- list The normalized vector. """ return list(vector / np.linalg.norm(vector)) @staticmethod def North(): """ Returns the vector representing the *north* direction. In Topologic, the positive YAxis direction is considered *north* ([0, 1, 0]). Returns ------- list The vector representing the *north* direction. """ return [0, 1, 0] @staticmethod def NorthEast(): """ Returns the vector representing the *northeast* direction. In Topologic, the positive YAxis direction is considered *north* and the positive XAxis direction is considered *east*. Therefore *northeast* is ([1, 1, 0]). Returns ------- list The vector representing the *northeast* direction. """ return [1, 1, 0] @staticmethod def NorthWest(): """ Returns the vector representing the *northwest* direction. In Topologic, the positive YAxis direction is considered *north* and the negative XAxis direction is considered *west*. Therefore *northwest* is ([-1, 1, 0]). Returns ------- list The vector representing the *northwest* direction. """ return [-1, 1, 0] @staticmethod def Reverse(vector): """ Returns a reverse vector of the input vector. A reverse vector multiplies all components by -1. Parameters ---------- vector : list The input vector. Returns ------- list The normalized vector. """ if not isinstance(vector, list): return None return [x*-1 for x in vector] @staticmethod def SetMagnitude(vector: list, magnitude: float) -> list: """ Sets the magnitude of the input vector to the input magnitude. Parameters ---------- vector : list The input vector. magnitude : float The desired magnitude. Returns ------- list The created vector. """ return (Vector.Multiply(vector=Vector.Normalize(vector), magnitude=magnitude)) @staticmethod def South(): """ Returns the vector representing the *south* direction. In Topologic, the negative YAxis direction is considered *south* ([0, -1, 0]). Returns ------- list The vector representing the *south* direction. """ return [0, -1, 0] @staticmethod def SouthEast(): """ Returns the vector representing the *southeast* direction. In Topologic, the negative YAxis direction is considered *south* and the positive XAxis direction is considered *east*. Therefore *southeast* is ([1, -1, 0]). Returns ------- list The vector representing the *southeast* direction. """ return [1, -1, 0] @staticmethod def SouthWest(): """ Returns the vector representing the *southwest* direction. In Topologic, the negative YAxis direction is considered *south* and the negative XAxis direction is considered *west*. Therefore *southwest* is ([-1, -1, 0]). Returns ------- list The vector representing the *southwest* direction. """ return [-1, -1, 0] @staticmethod def Sum(vectors: list): """ Returns the sum vector of the input vectors. Parameters ---------- vectors : list The input list of vectors. Returns ------- list The sum vector of the input list of vectors. """ if not isinstance(vectors, list): print("Vector.Average - Error: The input vectors parameter is not a valid list. Returning None.") return None vectors = [vec for vec in vectors if isinstance(vec, list)] if len(vectors) < 1: print("Vector.Average - Error: The input vectors parameter does not contain any valid vectors. Returning None.") return None dimensions = len(vectors[0]) num_vectors = len(vectors) # Initialize a list to store the sum of each dimension sum_dimensions = [0] * dimensions # Calculate the sum of each dimension across all vectors for vector in vectors: for i in range(dimensions): sum_dimensions[i] += vector[i] return sum_dimensions @staticmethod def TransformationMatrix(vectorA, vectorB): """ Returns the transformation matrix needed to align vectorA with vectorB. Parameters ---------- vectorA : list The input vector to be transformed. vectorB : list The desired vector with which to align vectorA. Returns ------- list Transformation matrix that follows the Blender software convention (nested list) """ import numpy as np from topologicpy.Matrix import Matrix import numpy as np def transformation_matrix(vec1, vec2, translation_vector=None): """ Compute a 4x4 transformation matrix that aligns vec1 to vec2. :param vec1: A 3D "source" vector :param vec2: A 3D "destination" vector :param translation_vector: Optional translation vector (default is None) :return: The 4x4 transformation matrix """ vec1 = vec1 / np.linalg.norm(vec1) vec2 = vec2 / np.linalg.norm(vec2) dot_product = np.dot(vec1, vec2) if np.isclose(dot_product, 1.0): # Vectors are parallel; return the identity matrix return np.eye(4) elif np.isclose(dot_product, -1.0): # Vectors are antiparallel; reflect one of the vectors about the origin reflection_matrix = np.eye(4) reflection_matrix[2, 2] = -1 return reflection_matrix cross_product = np.cross(vec1, vec2) skew_symmetric_matrix = np.array([[0, -cross_product[2], cross_product[1], 0], [cross_product[2], 0, -cross_product[0], 0], [-cross_product[1], cross_product[0], 0, 0], [0, 0, 0, 1]]) rotation_matrix = np.eye(4) + skew_symmetric_matrix + \ np.dot(skew_symmetric_matrix, skew_symmetric_matrix) * \ (1 / (1 + dot_product)) if translation_vector is not None: translation_matrix = np.eye(4) translation_matrix[:3, 3] = translation_vector transformation_matrix = np.dot(translation_matrix, rotation_matrix) else: transformation_matrix = rotation_matrix return transformation_matrix tran_mat = transformation_matrix(vectorA, vectorB) return [list(tran_mat[0]), list(tran_mat[1]), list(tran_mat[2]), list(tran_mat[3])] @staticmethod def Up(): """ Returns the vector representing the up direction. In Topologic, the positive ZAxis direction is considered "up" ([0, 0, 1]). Returns ------- list The vector representing the "up" direction. """ return [0, 0, 1] @staticmethod def West(): """ Returns the vector representing the *west* direction. In Topologic, the negative XAxis direction is considered *west* ([-1, 0, 0]). Returns ------- list The vector representing the *west* direction. """ return [-1, 0, 0] @staticmethod def XAxis(): """ Returns the vector representing the XAxis ([1, 0, 0]) Returns ------- list The vector representing the XAxis. """ return [1, 0, 0] @staticmethod def YAxis(): """ Returns the vector representing the YAxis ([0, 1, 0]) Returns ------- list The vector representing the YAxis. """ return [0, 1, 0] @staticmethod def ZAxis(): """ Returns the vector representing the ZAxis ([0, 0, 1]) Returns ------- list The vector representing the ZAxis. """ return [0, 0, 1]
Ancestors
- builtins.list
Static methods
def Angle(vectorA, vectorB, mantissa: int = 6)
-
Returns the angle in degrees between the two input vectors
Parameters
vectorA
:list
- The first vector.
vectorB
:list
- The second vector.
mantissa
:int
, optional- The length of the desired mantissa. The default is 6.
Returns
float
- The angle in degrees between the two input vectors.
Expand source code
@staticmethod def Angle(vectorA, vectorB, mantissa: int = 6): """ Returns the angle in degrees between the two input vectors Parameters ---------- vectorA : list The first vector. vectorB : list The second vector. mantissa : int, optional The length of the desired mantissa. The default is 6. Returns ------- float The angle in degrees between the two input vectors. """ n_v1=la.norm(vectorA) n_v2=la.norm(vectorB) if n_v1 == 0 or n_v2 == 0: # Handle the case where one or both vectors have a magnitude of zero. return 0.0 if (abs(np.log10(n_v1/n_v2)) > 10): vectorA = vectorA/n_v1 vectorB = vectorB/n_v2 cosang = np.dot(vectorA, vectorB) sinang = la.norm(np.cross(vectorA, vectorB)) return round(math.degrees(np.arctan2(sinang, cosang)), mantissa)
def Average(vectors: list)
-
Returns the average vector of the input vectors.
Parameters
vectors
:list
- The input list of vectors.
Returns
list
- The average vector of the input list of vectors.
Expand source code
@staticmethod def Average(vectors: list): """ Returns the average vector of the input vectors. Parameters ---------- vectors : list The input list of vectors. Returns ------- list The average vector of the input list of vectors. """ if not isinstance(vectors, list): print("Vector.Average - Error: The input vectors parameter is not a valid list. Returning None.") return None vectors = [vec for vec in vectors if isinstance(vec, list)] if len(vectors) < 1: print("Vector.Average - Error: The input vectors parameter does not contain any valid vectors. Returning None.") return None dimensions = len(vectors[0]) num_vectors = len(vectors) # Initialize a list to store the sum of each dimension sum_dimensions = [0] * dimensions # Calculate the sum of each dimension across all vectors for vector in vectors: for i in range(dimensions): sum_dimensions[i] += vector[i] # Calculate the average for each dimension average_dimensions = [sum_dim / num_vectors for sum_dim in sum_dimensions] return average_dimensions
def AzimuthAltitude(vector, mantissa: int = 6)
-
Returns a dictionary of azimuth and altitude angles in degrees for the input vector. North is assumed to be the positive Y axis [0, 1, 0]. Up is assumed to be the positive Z axis [0, 0, 1]. Azimuth is calculated in a counter-clockwise fashion from North where 0 is North, 90 is East, 180 is South, and 270 is West. Altitude is calculated in a counter-clockwise fashing where -90 is straight down (negative Z axis), 0 is in the XY plane, and 90 is straight up (positive Z axis). If the altitude is -90 or 90, the azimuth is assumed to be 0.
Parameters
vectorA
:list
- The input vector.
mantissa
:int
, optional- The length of the desired mantissa. The default is 6.
Returns
dict
- The dictionary containing the azimuth and altitude angles in degrees. The keys in the dictionary are 'azimuth' and 'altitude'.
Expand source code
@staticmethod def AzimuthAltitude(vector, mantissa: int = 6): """ Returns a dictionary of azimuth and altitude angles in degrees for the input vector. North is assumed to be the positive Y axis [0, 1, 0]. Up is assumed to be the positive Z axis [0, 0, 1]. Azimuth is calculated in a counter-clockwise fashion from North where 0 is North, 90 is East, 180 is South, and 270 is West. Altitude is calculated in a counter-clockwise fashing where -90 is straight down (negative Z axis), 0 is in the XY plane, and 90 is straight up (positive Z axis). If the altitude is -90 or 90, the azimuth is assumed to be 0. Parameters ---------- vectorA : list The input vector. mantissa : int, optional The length of the desired mantissa. The default is 6. Returns ------- dict The dictionary containing the azimuth and altitude angles in degrees. The keys in the dictionary are 'azimuth' and 'altitude'. """ x, y, z = vector if x == 0 and y == 0: if z > 0: return {"azimuth":0, "altitude":90} elif z < 0: return {"azimuth":0, "altitude":-90} else: # undefined return None else: azimuth = math.degrees(math.atan2(y, x)) if azimuth > 90: azimuth -= 360 azimuth = round(90-azimuth, mantissa) xy_distance = math.sqrt(x**2 + y**2) altitude = math.degrees(math.atan2(z, xy_distance)) altitude = round(altitude, mantissa) return {"azimuth":azimuth, "altitude":altitude}
def Bisect(vectorA, vectorB)
-
Compute the bisecting vector of two input vectors.
Parameters
vectorA
:list
- The first input vector.
vectorB
:list
- The second input vector.
Returns
dict
- The bisecting vector.
Expand source code
@staticmethod def Bisect(vectorA, vectorB): """ Compute the bisecting vector of two input vectors. Parameters ---------- vectorA : list The first input vector. vectorB : list The second input vector. Returns ------- dict The bisecting vector. """ import numpy as np # Ensure vectors are numpy arrays vector1 = np.array(vectorA) vector2 = np.array(vectorB) # Normalize input vectors vector1_norm = vector1 / np.linalg.norm(vector1) vector2_norm = vector2 / np.linalg.norm(vector2) # Check if the angle between vectors is either 0 or 180 degrees dot_product = np.dot(vector1_norm, vector2_norm) if np.isclose(dot_product, 1.0) or np.isclose(dot_product, -1.0): print("Vector.Bisect - Warning: The two vectors are collinear and thus the bisecting vector is not well-defined.") # Angle is either 0 or 180 degrees, return any perpendicular vector bisecting_vector = np.array([vector1[1] - vector1[2], vector1[2] - vector1[0], vector1[0] - vector1[1]]) bisecting_vector /= np.linalg.norm(bisecting_vector) else: # Compute bisecting vector bisecting_vector = (vector1_norm + vector2_norm) / np.linalg.norm(vector1_norm + vector2_norm) return list(bisecting_vector)
def ByAzimuthAltitude(azimuth, altitude, north=0, reverse=False, tolerance=0.0001)
-
Returns the vector specified by the input azimuth and altitude angles.
Parameters
azimuth
:float
- The input azimuth angle in degrees. The angle is computed in an anti-clockwise fashion. 0 is considered North, 90 East, 180 is South, 270 is West
altitude
:float
- The input altitude angle in degrees from the XY plane. Positive is above the XY plane. Negative is below the XY plane
north
:float
, optional- The angle of the north direction in degrees measured from positive Y-axis. The angle is added in anti-clockwise fashion. 0 is considered along the positive Y-axis, 90 is along the positive X-axis, 180 is along the negative Y-axis, and 270 along the negative Y-axis.
reverse
:bool
, optional- If set to True the direction of the vector is computed from the end point towards the origin. Otherwise, it is computed from the origin towards the end point.
tolerance
:float
, optional- The desired tolerance. The default is 0.0001.
Returns
list
- The resulting vector.
Expand source code
@staticmethod def ByAzimuthAltitude(azimuth, altitude, north=0, reverse=False, tolerance=0.0001): """ Returns the vector specified by the input azimuth and altitude angles. Parameters ---------- azimuth : float The input azimuth angle in degrees. The angle is computed in an anti-clockwise fashion. 0 is considered North, 90 East, 180 is South, 270 is West altitude : float The input altitude angle in degrees from the XY plane. Positive is above the XY plane. Negative is below the XY plane north : float , optional The angle of the north direction in degrees measured from positive Y-axis. The angle is added in anti-clockwise fashion. 0 is considered along the positive Y-axis, 90 is along the positive X-axis, 180 is along the negative Y-axis, and 270 along the negative Y-axis. reverse : bool , optional If set to True the direction of the vector is computed from the end point towards the origin. Otherwise, it is computed from the origin towards the end point. tolerance : float , optional The desired tolerance. The default is 0.0001. Returns ------- list The resulting vector. """ from topologicpy.Vertex import Vertex from topologicpy.Edge import Edge from topologicpy.Topology import Topology e = Edge.ByVertices([Vertex.Origin(), Vertex.ByCoordinates(0, 1, 0)], tolerance=tolerance) e = Topology.Rotate(e, origin=Vertex.Origin(), axis=[1, 0, 0], angle=altitude) e = Topology.Rotate(e, origin=Vertex.Origin(), axis=[0, 0, 1], angle=-azimuth-north) if reverse: return Vector.Reverse(Edge.Direction(e)) return Edge.Direction(e)
def ByCoordinates(x, y, z)
-
Creates a vector by the specified x, y, z inputs.
Parameters
x
:float
- The X coordinate.
y
:float
- The Y coordinate.
z
:float
- The Z coodinate.
Returns
list
- The created vector.
Expand source code
@staticmethod def ByCoordinates(x, y, z): """ Creates a vector by the specified x, y, z inputs. Parameters ---------- x : float The X coordinate. y : float The Y coordinate. z : float The Z coodinate. Returns ------- list The created vector. """ return [x, y, z]
def ByVertices(vertices, normalize=True)
-
Creates a vector by the specified input list of vertices.
Parameters
vertices
:list
- The the input list of topologic vertices. The first element in the list is considered the start vertex. The last element in the list is considered the end vertex.
normalize
:bool
, optional- If set to True, the resulting vector is normalized (i.e. its length is set to 1)
Returns
list
- The created vector.
Expand source code
@staticmethod def ByVertices(vertices, normalize=True): """ Creates a vector by the specified input list of vertices. Parameters ---------- vertices : list The the input list of topologic vertices. The first element in the list is considered the start vertex. The last element in the list is considered the end vertex. normalize : bool , optional If set to True, the resulting vector is normalized (i.e. its length is set to 1) Returns ------- list The created vector. """ from topologicpy.Vertex import Vertex from topologicpy.Topology import Topology if not isinstance(vertices, list): return None if not isinstance(normalize, bool): return None vertices = [v for v in vertices if Topology.IsInstance(v, "Vertex")] if len(vertices) < 2: return None v1 = vertices[0] v2 = vertices[-1] vector = [Vertex.X(v2)-Vertex.X(v1), Vertex.Y(v2)-Vertex.Y(v1), Vertex.Z(v2)-Vertex.Z(v1)] if normalize: vector = Vector.Normalize(vector) return vector
def CompassAngle(vectorA, vectorB, mantissa=6, tolerance=0.0001)
-
Returns the horizontal compass angle in degrees between the two input vectors. The angle is measured in clockwise fashion. 0 is along the positive Y-axis, 90 is along the positive X axis. Only the first two elements in the input vectors are considered.
Parameters
vectorA
:list
- The first vector.
vectorB
:list
- The second vector.
mantissa
:int
, optional- The length of the desired mantissa. The default is 6.
tolerance
:float
, optional- The desired tolerance. The default is 0.0001.
Returns
float
- The horizontal compass angle in degrees between the two input vectors.
Expand source code
@staticmethod def CompassAngle(vectorA, vectorB, mantissa=6, tolerance=0.0001): """ Returns the horizontal compass angle in degrees between the two input vectors. The angle is measured in clockwise fashion. 0 is along the positive Y-axis, 90 is along the positive X axis. Only the first two elements in the input vectors are considered. Parameters ---------- vectorA : list The first vector. vectorB : list The second vector. mantissa : int, optional The length of the desired mantissa. The default is 6. tolerance : float , optional The desired tolerance. The default is 0.0001. Returns ------- float The horizontal compass angle in degrees between the two input vectors. """ if abs(vectorA[0]) < tolerance and abs(vectorA[1]) < tolerance: return None if abs(vectorB[0]) < tolerance and abs(vectorB[1]) < tolerance: return None p1 = (vectorA[0], vectorA[1]) p2 = (vectorB[0], vectorB[1]) ang1 = arctan2(*p1[::-1]) ang2 = arctan2(*p2[::-1]) return round(rad2deg((ang1 - ang2) % (2 * pi)), mantissa)
def Coordinates(vector, outputType='xyz', mantissa: int = 6)
-
Returns the coordinates of the input vector.
Parameters
vector
:list
- The input vector.
outputType
:string
, optional- The desired output type. Could be any permutation or substring of "xyz" or the string "matrix". The default is "xyz". The input is case insensitive and the coordinates will be returned in the specified order.
mantissa
:int
, optional- The desired length of the mantissa. The default is 6.
Returns
list
- The coordinates of the input vertex.
Expand source code
@staticmethod def Coordinates(vector, outputType="xyz", mantissa: int = 6): """ Returns the coordinates of the input vector. Parameters ---------- vector : list The input vector. outputType : string, optional The desired output type. Could be any permutation or substring of "xyz" or the string "matrix". The default is "xyz". The input is case insensitive and the coordinates will be returned in the specified order. mantissa : int , optional The desired length of the mantissa. The default is 6. Returns ------- list The coordinates of the input vertex. """ if not isinstance(vector, list): return None x = round(vector[0], mantissa) y = round(vector[1], mantissa) z = round(vector[2], mantissa) matrix = [[1, 0, 0, x], [0, 1, 0, y], [0, 0, 1, z], [0, 0, 0, 1]] output = [] outputType = outputType.lower() if outputType == "matrix": return matrix else: outputType = list(outputType) for axis in outputType: if axis == "x": output.append(x) elif axis == "y": output.append(y) elif axis == "z": output.append(z) return output
def Cross(vectorA, vectorB, mantissa=6, tolerance=0.0001)
-
Returns the cross product of the two input vectors. The resulting vector is perpendicular to the plane defined by the two input vectors.
Parameters
vectorA
:list
- The first vector.
vectorB
:list
- The second vector.
mantissa
:int
, optional- The length of the desired mantissa. The default is 6.
tolerance
:float
, optional- the desired tolerance. The default is 0.0001.
Returns
list
- The vector representing the cross product of the two input vectors.
Expand source code
@staticmethod def Cross(vectorA, vectorB, mantissa=6, tolerance=0.0001): """ Returns the cross product of the two input vectors. The resulting vector is perpendicular to the plane defined by the two input vectors. Parameters ---------- vectorA : list The first vector. vectorB : list The second vector. mantissa : int, optional The length of the desired mantissa. The default is 6. tolerance : float, optional the desired tolerance. The default is 0.0001. Returns ------- list The vector representing the cross product of the two input vectors. """ if not isinstance(vectorA, list) or not isinstance(vectorB, list): return None if Vector.Magnitude(vector=vectorA, mantissa=mantissa) < tolerance or Vector.Magnitude(vector=vectorB, mantissa=mantissa) < tolerance: return None vecA = np.array(vectorA) vecB = np.array(vectorB) vecC = list(np.cross(vecA, vecB)) if Vector.Magnitude(vecC) < tolerance: return [0, 0, 0] return [round(vecC[0], mantissa), round(vecC[1], mantissa), round(vecC[2], mantissa)]
def Down()
-
Returns the vector representing the down direction. In Topologic, the negative ZAxis direction is considered down ([0, 0, -1]).
Returns
list
- The vector representing the down direction.
Expand source code
@staticmethod def Down(): """ Returns the vector representing the *down* direction. In Topologic, the negative ZAxis direction is considered *down* ([0, 0, -1]). Returns ------- list The vector representing the *down* direction. """ return [0, 0, -1]
def East()
-
Returns the vector representing the east direction. In Topologic, the positive XAxis direction is considered east ([1, 0, 0]).
Returns
list
- The vector representing the east direction.
Expand source code
@staticmethod def East(): """ Returns the vector representing the *east* direction. In Topologic, the positive XAxis direction is considered *east* ([1, 0, 0]). Returns ------- list The vector representing the *east* direction. """ return [1, 0, 0]
def IsAntiParallel(vectorA, vectorB)
-
Returns True if the input vectors are anti-parallel. Returns False otherwise.
Parameters
vectorA
:list
- The first input vector.
vectorB
:list
- The second input vector.
Returns
bool
- True if the input vectors are anti-parallel. False otherwise.
Expand source code
@staticmethod def IsAntiParallel(vectorA, vectorB): """ Returns True if the input vectors are anti-parallel. Returns False otherwise. Parameters ---------- vectorA : list The first input vector. vectorB : list The second input vector. Returns ------- bool True if the input vectors are anti-parallel. False otherwise. """ import numpy as np # Ensure vectors are numpy arrays vector1 = np.array(vectorA) vector2 = np.array(vectorB) # Normalize input vectors vector1_norm = vector1 / np.linalg.norm(vector1) vector2_norm = vector2 / np.linalg.norm(vector2) # Check if the angle between vectors is either 0 or 180 degrees dot_product = np.dot(vector1_norm, vector2_norm) if np.isclose(dot_product, -1.0): return True else: # Compute bisecting vector return False
def IsCollinear(vectorA, vectorB)
-
Returns True if the input vectors are collinear (parallel or anti-parallel). Returns False otherwise.
Parameters
vectorA
:list
- The first input vector.
vectorB
:list
- The second input vector.
Returns
bool
- True if the input vectors are collinear (parallel or anti-parallel). False otherwise.
Expand source code
@staticmethod def IsCollinear(vectorA, vectorB): """ Returns True if the input vectors are collinear (parallel or anti-parallel). Returns False otherwise. Parameters ---------- vectorA : list The first input vector. vectorB : list The second input vector. Returns ------- bool True if the input vectors are collinear (parallel or anti-parallel). False otherwise. """ import numpy as np # Ensure vectors are numpy arrays vector1 = np.array(vectorA) vector2 = np.array(vectorB) # Normalize input vectors vector1_norm = vector1 / np.linalg.norm(vector1) vector2_norm = vector2 / np.linalg.norm(vector2) # Check if the angle between vectors is either 0 or 180 degrees dot_product = np.dot(vector1_norm, vector2_norm) if np.isclose(dot_product, 1.0) or np.isclose(dot_product, -1.0): return True else: return False
def IsParallel(vectorA, vectorB)
-
Returns True if the input vectors are parallel. Returns False otherwise.
Parameters
vectorA
:list
- The first input vector.
vectorB
:list
- The second input vector.
Returns
bool
- True if the input vectors are parallel. False otherwise.
Expand source code
@staticmethod def IsParallel(vectorA, vectorB): """ Returns True if the input vectors are parallel. Returns False otherwise. Parameters ---------- vectorA : list The first input vector. vectorB : list The second input vector. Returns ------- bool True if the input vectors are parallel. False otherwise. """ import numpy as np # Ensure vectors are numpy arrays vector1 = np.array(vectorA) vector2 = np.array(vectorB) # Normalize input vectors vector1_norm = vector1 / np.linalg.norm(vector1) vector2_norm = vector2 / np.linalg.norm(vector2) # Check if the angle between vectors is either 0 or 180 degrees dot_product = np.dot(vector1_norm, vector2_norm) if np.isclose(dot_product, 1.0): return True else: # Compute bisecting vector return False
def Magnitude(vector, mantissa: int = 6)
-
Returns the magnitude of the input vector.
Parameters
vector
:list
- The input vector.
mantissa
:int
- The length of the desired mantissa. The default is 6.
Returns
float
- The magnitude of the input vector.
Expand source code
@staticmethod def Magnitude(vector, mantissa: int = 6): """ Returns the magnitude of the input vector. Parameters ---------- vector : list The input vector. mantissa : int The length of the desired mantissa. The default is 6. Returns ------- float The magnitude of the input vector. """ return round(np.linalg.norm(np.array(vector)), mantissa)
def Multiply(vector, magnitude, tolerance=0.0001)
-
Multiplies the input vector by the input magnitude.
Parameters
vector
:list
- The input vector.
magnitude
:float
- The input magnitude.
tolerance
:float
, optional- the desired tolerance. The default is 0.0001.
Returns
list
- The created vector that multiplies the input vector by the input magnitude.
Expand source code
@staticmethod def Multiply(vector, magnitude, tolerance=0.0001): """ Multiplies the input vector by the input magnitude. Parameters ---------- vector : list The input vector. magnitude : float The input magnitude. tolerance : float, optional the desired tolerance. The default is 0.0001. Returns ------- list The created vector that multiplies the input vector by the input magnitude. """ if abs(magnitude) < tolerance: return [0.0] * len(vector) scaled_vector = [component * (magnitude) for component in vector] return scaled_vector
def Normalize(vector)
-
Returns a normalized vector of the input vector. A normalized vector has the same direction as the input vector, but its magnitude is 1.
Parameters
vector
:list
- The input vector.
Returns
list
- The normalized vector.
Expand source code
@staticmethod def Normalize(vector): """ Returns a normalized vector of the input vector. A normalized vector has the same direction as the input vector, but its magnitude is 1. Parameters ---------- vector : list The input vector. Returns ------- list The normalized vector. """ return list(vector / np.linalg.norm(vector))
def North()
-
Returns the vector representing the north direction. In Topologic, the positive YAxis direction is considered north ([0, 1, 0]).
Returns
list
- The vector representing the north direction.
Expand source code
@staticmethod def North(): """ Returns the vector representing the *north* direction. In Topologic, the positive YAxis direction is considered *north* ([0, 1, 0]). Returns ------- list The vector representing the *north* direction. """ return [0, 1, 0]
def NorthEast()
-
Returns the vector representing the northeast direction. In Topologic, the positive YAxis direction is considered north and the positive XAxis direction is considered east. Therefore northeast is ([1, 1, 0]).
Returns
list
- The vector representing the northeast direction.
Expand source code
@staticmethod def NorthEast(): """ Returns the vector representing the *northeast* direction. In Topologic, the positive YAxis direction is considered *north* and the positive XAxis direction is considered *east*. Therefore *northeast* is ([1, 1, 0]). Returns ------- list The vector representing the *northeast* direction. """ return [1, 1, 0]
def NorthWest()
-
Returns the vector representing the northwest direction. In Topologic, the positive YAxis direction is considered north and the negative XAxis direction is considered west. Therefore northwest is ([-1, 1, 0]).
Returns
list
- The vector representing the northwest direction.
Expand source code
@staticmethod def NorthWest(): """ Returns the vector representing the *northwest* direction. In Topologic, the positive YAxis direction is considered *north* and the negative XAxis direction is considered *west*. Therefore *northwest* is ([-1, 1, 0]). Returns ------- list The vector representing the *northwest* direction. """ return [-1, 1, 0]
def Reverse(vector)
-
Returns a reverse vector of the input vector. A reverse vector multiplies all components by -1.
Parameters
vector
:list
- The input vector.
Returns
list
- The normalized vector.
Expand source code
@staticmethod def Reverse(vector): """ Returns a reverse vector of the input vector. A reverse vector multiplies all components by -1. Parameters ---------- vector : list The input vector. Returns ------- list The normalized vector. """ if not isinstance(vector, list): return None return [x*-1 for x in vector]
def SetMagnitude(vector: list, magnitude: float) ‑> list
-
Sets the magnitude of the input vector to the input magnitude.
Parameters
vector
:list
- The input vector.
magnitude
:float
- The desired magnitude.
Returns
list
- The created vector.
Expand source code
@staticmethod def SetMagnitude(vector: list, magnitude: float) -> list: """ Sets the magnitude of the input vector to the input magnitude. Parameters ---------- vector : list The input vector. magnitude : float The desired magnitude. Returns ------- list The created vector. """ return (Vector.Multiply(vector=Vector.Normalize(vector), magnitude=magnitude))
def South()
-
Returns the vector representing the south direction. In Topologic, the negative YAxis direction is considered south ([0, -1, 0]).
Returns
list
- The vector representing the south direction.
Expand source code
@staticmethod def South(): """ Returns the vector representing the *south* direction. In Topologic, the negative YAxis direction is considered *south* ([0, -1, 0]). Returns ------- list The vector representing the *south* direction. """ return [0, -1, 0]
def SouthEast()
-
Returns the vector representing the southeast direction. In Topologic, the negative YAxis direction is considered south and the positive XAxis direction is considered east. Therefore southeast is ([1, -1, 0]).
Returns
list
- The vector representing the southeast direction.
Expand source code
@staticmethod def SouthEast(): """ Returns the vector representing the *southeast* direction. In Topologic, the negative YAxis direction is considered *south* and the positive XAxis direction is considered *east*. Therefore *southeast* is ([1, -1, 0]). Returns ------- list The vector representing the *southeast* direction. """ return [1, -1, 0]
def SouthWest()
-
Returns the vector representing the southwest direction. In Topologic, the negative YAxis direction is considered south and the negative XAxis direction is considered west. Therefore southwest is ([-1, -1, 0]).
Returns
list
- The vector representing the southwest direction.
Expand source code
@staticmethod def SouthWest(): """ Returns the vector representing the *southwest* direction. In Topologic, the negative YAxis direction is considered *south* and the negative XAxis direction is considered *west*. Therefore *southwest* is ([-1, -1, 0]). Returns ------- list The vector representing the *southwest* direction. """ return [-1, -1, 0]
def Sum(vectors: list)
-
Returns the sum vector of the input vectors.
Parameters
vectors
:list
- The input list of vectors.
Returns
list
- The sum vector of the input list of vectors.
Expand source code
@staticmethod def Sum(vectors: list): """ Returns the sum vector of the input vectors. Parameters ---------- vectors : list The input list of vectors. Returns ------- list The sum vector of the input list of vectors. """ if not isinstance(vectors, list): print("Vector.Average - Error: The input vectors parameter is not a valid list. Returning None.") return None vectors = [vec for vec in vectors if isinstance(vec, list)] if len(vectors) < 1: print("Vector.Average - Error: The input vectors parameter does not contain any valid vectors. Returning None.") return None dimensions = len(vectors[0]) num_vectors = len(vectors) # Initialize a list to store the sum of each dimension sum_dimensions = [0] * dimensions # Calculate the sum of each dimension across all vectors for vector in vectors: for i in range(dimensions): sum_dimensions[i] += vector[i] return sum_dimensions
def TransformationMatrix(vectorA, vectorB)
-
Returns the transformation matrix needed to align vectorA with vectorB.
Parameters
vectorA
:list
- The input vector to be transformed.
vectorB
:list
- The desired vector with which to align vectorA.
Returns
list
- Transformation matrix that follows the Blender software convention (nested list)
Expand source code
@staticmethod def TransformationMatrix(vectorA, vectorB): """ Returns the transformation matrix needed to align vectorA with vectorB. Parameters ---------- vectorA : list The input vector to be transformed. vectorB : list The desired vector with which to align vectorA. Returns ------- list Transformation matrix that follows the Blender software convention (nested list) """ import numpy as np from topologicpy.Matrix import Matrix import numpy as np def transformation_matrix(vec1, vec2, translation_vector=None): """ Compute a 4x4 transformation matrix that aligns vec1 to vec2. :param vec1: A 3D "source" vector :param vec2: A 3D "destination" vector :param translation_vector: Optional translation vector (default is None) :return: The 4x4 transformation matrix """ vec1 = vec1 / np.linalg.norm(vec1) vec2 = vec2 / np.linalg.norm(vec2) dot_product = np.dot(vec1, vec2) if np.isclose(dot_product, 1.0): # Vectors are parallel; return the identity matrix return np.eye(4) elif np.isclose(dot_product, -1.0): # Vectors are antiparallel; reflect one of the vectors about the origin reflection_matrix = np.eye(4) reflection_matrix[2, 2] = -1 return reflection_matrix cross_product = np.cross(vec1, vec2) skew_symmetric_matrix = np.array([[0, -cross_product[2], cross_product[1], 0], [cross_product[2], 0, -cross_product[0], 0], [-cross_product[1], cross_product[0], 0, 0], [0, 0, 0, 1]]) rotation_matrix = np.eye(4) + skew_symmetric_matrix + \ np.dot(skew_symmetric_matrix, skew_symmetric_matrix) * \ (1 / (1 + dot_product)) if translation_vector is not None: translation_matrix = np.eye(4) translation_matrix[:3, 3] = translation_vector transformation_matrix = np.dot(translation_matrix, rotation_matrix) else: transformation_matrix = rotation_matrix return transformation_matrix tran_mat = transformation_matrix(vectorA, vectorB) return [list(tran_mat[0]), list(tran_mat[1]), list(tran_mat[2]), list(tran_mat[3])]
def Up()
-
Returns the vector representing the up direction. In Topologic, the positive ZAxis direction is considered "up" ([0, 0, 1]).
Returns
list
- The vector representing the "up" direction.
Expand source code
@staticmethod def Up(): """ Returns the vector representing the up direction. In Topologic, the positive ZAxis direction is considered "up" ([0, 0, 1]). Returns ------- list The vector representing the "up" direction. """ return [0, 0, 1]
def West()
-
Returns the vector representing the west direction. In Topologic, the negative XAxis direction is considered west ([-1, 0, 0]).
Returns
list
- The vector representing the west direction.
Expand source code
@staticmethod def West(): """ Returns the vector representing the *west* direction. In Topologic, the negative XAxis direction is considered *west* ([-1, 0, 0]). Returns ------- list The vector representing the *west* direction. """ return [-1, 0, 0]
def XAxis()
-
Returns the vector representing the XAxis ([1, 0, 0])
Returns
list
- The vector representing the XAxis.
Expand source code
@staticmethod def XAxis(): """ Returns the vector representing the XAxis ([1, 0, 0]) Returns ------- list The vector representing the XAxis. """ return [1, 0, 0]
def YAxis()
-
Returns the vector representing the YAxis ([0, 1, 0])
Returns
list
- The vector representing the YAxis.
Expand source code
@staticmethod def YAxis(): """ Returns the vector representing the YAxis ([0, 1, 0]) Returns ------- list The vector representing the YAxis. """ return [0, 1, 0]
def ZAxis()
-
Returns the vector representing the ZAxis ([0, 0, 1])
Returns
list
- The vector representing the ZAxis.
Expand source code
@staticmethod def ZAxis(): """ Returns the vector representing the ZAxis ([0, 0, 1]) Returns ------- list The vector representing the ZAxis. """ return [0, 0, 1]