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]