Module Face

Expand source code
import topologicpy
import topologic
from topologicpy.Vector import Vector
from topologicpy.Wire import Wire
import math

class Face(topologic.Face):
    @staticmethod
    def AddInternalBoundaries(face: topologic.Face, wires: list) -> topologic.Face:
        """
        Adds internal boundaries (closed wires) to the input face. Internal boundaries are considered holes in the input face.

        Parameters
        ----------
        face : topologic.Face
            The input face.
        wires : list
            The input list of internal boundaries (closed wires).

        Returns
        -------
        topologic.Face
            The created face with internal boundaries added to it.

        """
        if not face:
            return None
        if not isinstance(face, topologic.Face):
            return None
        if not wires:
            return face
        if not isinstance(wires, list):
            return face
        wireList = [w for w in wires if isinstance(w, topologic.Wire)]
        if len(wireList) < 1:
            return face
        faceeb = face.ExternalBoundary()
        faceibList = []
        _ = face.InternalBoundaries(faceibList)
        for wire in wires:
            faceibList.append(wire)
        return topologic.Face.ByExternalInternalBoundaries(faceeb, faceibList)

    @staticmethod
    def AddInternalBoundariesCluster(face: topologic.Face, cluster: topologic.Cluster) -> topologic.Face:
        """
        Adds the input cluster of internal boundaries (closed wires) to the input face. Internal boundaries are considered holes in the input face.

        Parameters
        ----------
        face : topologic.Face
            The input face.
        cluster : topologic.Cluster
            The input cluster of internal boundaries (topologic wires).

        Returns
        -------
        topologic.Face
            The created face with internal boundaries added to it.

        """
        if not face:
            return None
        if not isinstance(face, topologic.Face):
            return None
        if not cluster:
            return face
        if not isinstance(cluster, topologic.Cluster):
            return face
        wires = []
        _ = cluster.Wires(None, wires)
        return Face.AddInternalBoundaries(face, wires)
    
    @staticmethod
    def Angle(faceA: topologic.Face, faceB: topologic.Face, mantissa: int = 4) -> float:
        """
        Returns the angle in degrees between the two input faces.

        Parameters
        ----------
        faceA : topologic.Face
            The first input face.
        faceB : topologic.Face
            The second input face.
        mantissa : int , optional
            The desired length of the mantissa. The default is 4.

        Returns
        -------
        float
            The angle in degrees between the two input faces.

        """
        from topologicpy.Vector import Vector
        if not faceA or not isinstance(faceA, topologic.Face):
            return None
        if not faceB or not isinstance(faceB, topologic.Face):
            return None
        dirA = Face.NormalAtParameters(faceA, 0.5, 0.5, "xyz", 3)
        dirB = Face.NormalAtParameters(faceB, 0.5, 0.5, "xyz", 3)
        return round((Vector.Angle(dirA, dirB)), mantissa)

    @staticmethod
    def Area(face: topologic.Face, mantissa: int = 4) -> float:
        """
        Returns the area of the input face.

        Parameters
        ----------
        face : topologic.Face
            The input face.
        mantissa : int , optional
            The desired length of the mantissa. The default is 4.

        Returns
        -------
        float
            The area of the input face.

        """
        if not isinstance(face, topologic.Face):
            return None
        area = None
        try:
            area = round(topologic.FaceUtility.Area(face), mantissa)
        except:
            area = None
        return area

    @staticmethod
    def BoundingRectangle(topology: topologic.Topology, optimize: int = 0) -> topologic.Face:
        """
        Returns a face representing a bounding rectangle of the input topology. The returned face contains a dictionary with key "zrot" that represents rotations around the Z axis. If applied the resulting face will become axis-aligned.

        Parameters
        ----------
        topology : topologic.Topology
            The input topology.
        optimize : int , optional
            If set to an integer from 1 (low optimization) to 10 (high optimization), the method will attempt to optimize the bounding rectangle so that it reduces its surface area. The default is 0 which will result in an axis-aligned bounding rectangle. The default is 0.
        
        Returns
        -------
        topologic.Face
            The bounding rectangle of the input topology.

        """
        from topologicpy.Wire import Wire
        from topologicpy.Face import Face
        from topologicpy.Cluster import Cluster
        from topologicpy.Topology import Topology
        from topologicpy.Dictionary import Dictionary
        def bb(topology):
            vertices = []
            _ = topology.Vertices(None, vertices)
            x = []
            y = []
            for aVertex in vertices:
                x.append(aVertex.X())
                y.append(aVertex.Y())
            minX = min(x)
            minY = min(y)
            maxX = max(x)
            maxY = max(y)
            return [minX, minY, maxX, maxY]

        if not isinstance(topology, topologic.Topology):
            return None
        vertices = Topology.SubTopologies(topology, subTopologyType="vertex")
        topology = Cluster.ByTopologies(vertices)
        boundingBox = bb(topology)
        minX = boundingBox[0]
        minY = boundingBox[1]
        maxX = boundingBox[2]
        maxY = boundingBox[3]
        w = abs(maxX - minX)
        l = abs(maxY - minY)
        best_area = l*w
        orig_area = best_area
        best_z = 0
        best_bb = boundingBox
        origin = Topology.Centroid(topology)
        optimize = min(max(optimize, 0), 10)
        if optimize > 0:
            factor = (round(((11 - optimize)/30 + 0.57), 2))
            flag = False
            for n in range(10,0,-1):
                if flag:
                    break
                za = n
                zb = 90+n
                zc = n
                for z in range(za,zb,zc):
                    if flag:
                        break
                    t = Topology.Rotate(topology, origin=origin, x=0,y=0,z=1, degree=z)
                    minX, minY, maxX, maxY = bb(t)
                    w = abs(maxX - minX)
                    l = abs(maxY - minY)
                    area = l*w
                    if area < orig_area*factor:
                        best_area = area
                        best_z = z
                        best_bb = [minX, minY, maxX, maxY]
                        flag = True
                        break
                    if area < best_area:
                        best_area = area
                        best_z = z
                        best_bb = [minX, minY, maxX, maxY]
                        
        else:
            best_bb = boundingBox

        minX, minY, maxX, maxY = best_bb
        vb1 = topologic.Vertex.ByCoordinates(minX, minY, 0)
        vb2 = topologic.Vertex.ByCoordinates(maxX, minY, 0)
        vb3 = topologic.Vertex.ByCoordinates(maxX, maxY, 0)
        vb4 = topologic.Vertex.ByCoordinates(minX, maxY, 0)

        baseWire = Wire.ByVertices([vb1, vb2, vb3, vb4], close=True)
        baseFace = Face.ByWire(baseWire)
        baseFace = Topology.Rotate(baseFace, origin=origin, x=0,y=0,z=1, degree=-best_z)
        dictionary = Dictionary.ByKeysValues(["zrot"], [best_z])
        baseFace = Topology.SetDictionary(baseFace, dictionary)
        return baseFace
    
    @staticmethod
    def ByEdges(edges: list) -> topologic.Face:
        """
        Creates a face from the input list of edges.

        Parameters
        ----------
        edges : list
            The input list of edges.

        Returns
        -------
        face : topologic.Face
            The created face.

        """
        from topologicpy.Wire import Wire
        wire = Wire.ByEdges(edges)
        if not wire:
            return None
        if not isinstance(wire, topologic.Wire):
            return None
        return Face.ByWire(wire)

    @staticmethod
    def ByEdgesCluster(cluster: topologic.Cluster) -> topologic.Face:
        """
        Creates a face from the input cluster of edges.

        Parameters
        ----------
        cluster : topologic.Cluster
            The input cluster of edges.

        Returns
        -------
        face : topologic.Face
            The created face.

        """
        from topologicpy.Cluster import Cluster
        if not isinstance(cluster, topologic.Cluster):
            return None
        edges = Cluster.Edges(cluster)
        return Face.ByEdges(edges)

    @staticmethod
    def ByOffset(face: topologic.Face, offset: float = 1.0, miter: bool = False, miterThreshold: float = None, offsetKey: str = None, miterThresholdKey: str = None, step: bool = True) -> topologic.Face:
        """
        Creates an offset wire from the input wire.

        Parameters
        ----------
        wire : topologic.Wire
            The input wire.
        offset : float , optional
            The desired offset distance. The default is 1.0.
        miter : bool , optional
            if set to True, the corners will be mitered. The default is False.
        miterThreshold : float , optional
            The distance beyond which a miter should be added. The default is None which means the miter threshold is set to the offset distance multiplied by the square root of 2.
        offsetKey : str , optional
            If specified, the dictionary of the edges will be queried for this key to sepcify the desired offset. The default is None.
        miterThresholdKey : str , optional
            If specified, the dictionary of the vertices will be queried for this key to sepcify the desired miter threshold distance. The default is None.
        step : bool , optional
            If set to True, The transition between collinear edges with different offsets will be a step. Otherwise, it will be a continous edge. The default is True.

        Returns
        -------
        topologic.Wire
            The created wire.

        """
        from topologicpy.Wire import Wire

        eb = Face.Wire(face)
        internal_boundaries = Face.InternalBoundaries(face)
        offset_external_boundary = Wire.ByOffset(wire=eb, offset=offset, miter=miter, miterThreshold=miterThreshold, offsetKey=offsetKey, miterThresholdKey=miterThresholdKey, step=step)
        offset_internal_boundaries = []
        for internal_boundary in internal_boundaries:
            offset_internal_boundaries.append(Wire.ByOffset(wire=internal_boundary, offset=offset, miter=miter, miterThreshold=miterThreshold, offsetKey=offsetKey, miterThresholdKey=miterThresholdKey, step=step))
        return Face.ByWires(offset_external_boundary, offset_internal_boundaries)
    
    @staticmethod
    def ByShell(shell: topologic.Shell, angTolerance: float = 0.1)-> topologic.Face:
        """
        Creates a face by merging the faces of the input shell.

        Parameters
        ----------
        shell : topologic.Shell
            The input shell.
        angTolerance : float , optional
            The desired angular tolerance. The default is 0.1.

        Returns
        -------
        topologic.Face
            The created face.

        """
        from topologicpy.Vertex import Vertex
        from topologicpy.Wire import Wire
        from topologicpy.Shell import Shell
        from topologicpy.Topology import Topology
        
        def planarizeList(wireList):
            returnList = []
            for aWire in wireList:
                returnList.append(Wire.Planarize(aWire))
            return returnList
        
        ext_boundary = Shell.ExternalBoundary(shell)
        ext_boundary = Topology.RemoveCollinearEdges(ext_boundary, angTolerance)
        if not Topology.IsPlanar(ext_boundary):
            ext_boundary = Wire.Planarize(ext_boundary)

        if isinstance(ext_boundary, topologic.Wire):
            try:
                return topologic.Face.ByExternalBoundary(Topology.RemoveCollinearEdges(ext_boundary, angTolerance))
            except:
                try:
                    w = Wire.Planarize(ext_boundary)
                    f = Face.ByWire(w)
                    return f
                except:
                    print("FaceByPlanarShell - Error: The input Wire is not planar and could not be fixed. Returning None.")
                    return None
        elif isinstance(ext_boundary, topologic.Cluster):
            wires = []
            _ = ext_boundary.Wires(None, wires)
            faces = []
            areas = []
            for aWire in wires:
                try:
                    aFace = topologic.Face.ByExternalBoundary(Topology.RemoveCollinearEdges(aWire, angTolerance))
                except:
                    aFace = topologic.Face.ByExternalBoundary(Wire.Planarize(Topology.RemoveCollinearEdges(aWire, angTolerance)))
                anArea = Face.Area(aFace)
                faces.append(aFace)
                areas.append(anArea)
            max_index = areas.index(max(areas))
            ext_boundary = faces[max_index]
            int_boundaries = list(set(faces) - set([ext_boundary]))
            int_wires = []
            for int_boundary in int_boundaries:
                temp_wires = []
                _ = int_boundary.Wires(None, temp_wires)
                int_wires.append(Topology.RemoveCollinearEdges(temp_wires[0], angTolerance))
            temp_wires = []
            _ = ext_boundary.Wires(None, temp_wires)
            ext_wire = Topology.RemoveCollinearEdges(temp_wires[0], angTolerance)
            try:
                return topologic.Face.ByExternalInternalBoundaries(ext_wire, int_wires)
            except:
                return topologic.Face.ByExternalInternalBoundaries(Wire.Planarize(ext_wire), planarizeList(int_wires))
        else:
            return None
    
    @staticmethod
    def ByVertices(vertices: list) -> topologic.Face:
        
        """
        Creates a face from the input list of vertices.

        Parameters
        ----------
        vertices : list
            The input list of vertices.

        Returns
        -------
        topologic.Face
            The created face.

        """
        from topologicpy.Topology import Topology
        from topologicpy.Wire import Wire

        if not isinstance(vertices, list):
            return None
        vertexList = [x for x in vertices if isinstance(x, topologic.Vertex)]
        if len(vertexList) < 3:
            return None
        
        w = Wire.ByVertices(vertexList)
        f = Face.ByExternalBoundary(w)
        return f

    @staticmethod
    def ByVerticesCluster(cluster: topologic.Cluster) -> topologic.Face:
        """
        Creates a face from the input cluster of vertices.

        Parameters
        ----------
        cluster : topologic.Cluster
            The input cluster of vertices.

        Returns
        -------
        topologic.Face
            The crearted face.

        """
        from topologicpy.Cluster import Cluster
        if not isinstance(cluster, topologic.Cluster):
            return None
        vertices = Cluster.Vertices(cluster)
        return Face.ByVertices(vertices)

    @staticmethod
    def ByWire(wire: topologic.Wire) -> topologic.Face:
        """
        Creates a face from the input closed wire.

        Parameters
        ----------
        wire : topologic.Wire
            The input wire.

        Returns
        -------
        topologic.Face or list
            The created face. If the wire is non-planar, the method will attempt to triangulate the wire and return a list of faces.

        """
        from topologicpy.Vertex import Vertex
        from topologicpy.Wire import Wire
        from topologicpy.Shell import Shell
        from topologicpy.Cluster import Cluster
        from topologicpy.Topology import Topology
        from topologicpy.Dictionary import Dictionary
        import random

        def triangulateWire(wire):
            wire = Topology.RemoveCollinearEdges(wire)
            vertices = Wire.Vertices(wire)
            shell = Shell.Delaunay(vertices)
            if isinstance(shell, topologic.Shell):
                return Shell.Faces(shell)
            else:
                return []
        if not isinstance(wire, topologic.Wire):
            return None
        if not Wire.IsClosed(wire):
            return None
        
        edges = Wire.Edges(wire)
        wire = Topology.SelfMerge(Cluster.ByTopologies(edges))
        vertices = Wire.Vertices(wire)
        try:
            fList = topologic.Face.ByExternalBoundary(wire)
        except:
            if len(vertices) > 3:
                fList = triangulateWire(wire)
            else:
                fList = []
        
        if not isinstance(fList, list):
            fList = [fList]

        returnList = []
        for f in fList:
            if Face.Area(f) < 0:
                wire = Face.ExternalBoundary(f)
                wire = Wire.Invert(wire)
                try:
                    f = topologic.Face.ByExternalBoundary(wire)
                    returnList.append(f)
                except:
                    pass
            else:
                returnList.append(f)
        if len(returnList) == 0:
            return None
        elif len(returnList) == 1:
            return returnList[0]
        else:
            return returnList

    @staticmethod
    def ByWires(externalBoundary: topologic.Wire, internalBoundaries: list = []) -> topologic.Face:
        """
        Creates a face from the input external boundary (closed wire) and the input list of internal boundaries (closed wires).

        Parameters
        ----------
        externalBoundary : topologic.Wire
            The input external boundary.
        internalBoundaries : list , optional
            The input list of internal boundaries (closed wires). The default is an empty list.

        Returns
        -------
        topologic.Face
            The created face.

        """
        if not isinstance(externalBoundary, topologic.Wire):
            return None
        if not Wire.IsClosed(externalBoundary):
            return None
        ibList = [x for x in internalBoundaries if isinstance(x, topologic.Wire) and Wire.IsClosed(x)]
        return topologic.Face.ByExternalInternalBoundaries(externalBoundary, ibList)

    @staticmethod
    def ByWiresCluster(externalBoundary: topologic.Wire, internalBoundariesCluster: topologic.Cluster = None) -> topologic.Face:
        """
        Creates a face from the input external boundary (closed wire) and the input cluster of internal boundaries (closed wires).

        Parameters
        ----------
        externalBoundary : topologic.Wire
            The input external boundary (closed wire).
        internalBoundariesCluster : topologic.Cluster
            The input cluster of internal boundaries (closed wires). The default is None.

        Returns
        -------
        topologic.Face
            The created face.

        """
        from topologicpy.Wire import Wire
        from topologicpy.Cluster import Cluster
        if not isinstance(externalBoundary, topologic.Wire):
            return None
        if not Wire.IsClosed(externalBoundary):
            return None
        if not internalBoundariesCluster:
            internalBoundaries = []
        elif not isinstance(internalBoundariesCluster, topologic.Cluster):
            return None
        else:
            internalBoundaries = Cluster.Wires(internalBoundariesCluster)
        return Face.ByWires(externalBoundary, internalBoundaries)
    
    @staticmethod
    def NorthArrow(origin: topologic.Vertex = None, radius: float = 0.5, sides: int = 16, direction: list = [0,0,1], northAngle: float = 0.0,
                   placement: str = "center", tolerance: float = 0.0001) -> topologic.Face:
        """
        Creates a north arrow.

        Parameters
        ----------
        origin : topologic.Vertex, optional
            The location of the origin of the circle. The default is None which results in the circle being placed at (0,0,0).
        radius : float , optional
            The radius of the circle. The default is 1.
        sides : int , optional
            The number of sides of the circle. The default is 16.
        direction : list , optional
            The vector representing the up direction of the circle. The default is [0,0,1].
        northAngle : float , optional
            The angular offset in degrees from the positive Y axis direction. The angle is measured in a counter-clockwise fashion where 0 is positive Y, 90 is negative X, 180 is negative Y, and 270 is positive X.
        placement : str , optional
            The description of the placement of the origin of the circle. This can be "center", "lowerleft", "upperleft", "lowerright", or "upperright". It is case insensitive. The default is "center".
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        topologic.Face
            The created circle.

        """
        from topologicpy.Topology import Topology
        from topologicpy.Vertex import Vertex
        if not origin:
            origin = Vertex.Origin()
        
        c = Face.Circle(origin=origin, radius=radius, sides=sides, direction=[0,0,1], placement="center", tolerance=tolerance)
        r = Face.Rectangle(origin=origin, width=radius*0.01,length=radius*1.2, placement="lowerleft")
        r = Topology.Translate(r, -0.005*radius,0,0)
        arrow = Topology.Difference(c, r)
        arrow = Topology.Rotate(arrow, Vertex.Origin(), 0,0,1,northAngle)
        if placement.lower() == "lowerleft":
            arrow = Topology.Translate(arrow, radius, radius, 0)
        elif placement.lower() == "upperleft":
            arrow = Topology.Translate(arrow, radius, -radius, 0)
        elif placement.lower() == "lowerright":
            arrow = Topology.Translate(arrow, -radius, radius, 0)
        elif placement.lower() == "upperright":
            arrow = Topology.Translate(arrow, -radius, -radius, 0)
        x1 = origin.X()
        y1 = origin.Y()
        z1 = origin.Z()
        x2 = origin.X() + direction[0]
        y2 = origin.Y() + direction[1]
        z2 = origin.Z() + direction[2]
        dx = x2 - x1
        dy = y2 - y1
        dz = z2 - z1    
        dist = math.sqrt(dx**2 + dy**2 + dz**2)
        phi = math.degrees(math.atan2(dy, dx)) # Rotation around Y-Axis
        if dist < 0.0001:
            theta = 0
        else:
            theta = math.degrees(math.acos(dz/dist)) # Rotation around Z-Axis
        arrow = Topology.Rotate(arrow, origin, 0, 1, 0, theta)
        arrow = Topology.Rotate(arrow, origin, 0, 0, 1, phi)
        return arrow

    @staticmethod
    def Circle(origin: topologic.Vertex = None, radius: float = 0.5, sides: int = 16, fromAngle: float = 0.0, toAngle: float = 360.0, direction: list = [0,0,1],
                   placement: str = "center", tolerance: float = 0.0001) -> topologic.Face:
        """
        Creates a circle.

        Parameters
        ----------
        origin : topologic.Vertex, optional
            The location of the origin of the circle. The default is None which results in the circle being placed at (0,0,0).
        radius : float , optional
            The radius of the circle. The default is 1.
        sides : int , optional
            The number of sides of the circle. The default is 16.
        fromAngle : float , optional
            The angle in degrees from which to start creating the arc of the circle. The default is 0.
        toAngle : float , optional
            The angle in degrees at which to end creating the arc of the circle. The default is 360.
        direction : list , optional
            The vector representing the up direction of the circle. The default is [0,0,1].
        placement : str , optional
            The description of the placement of the origin of the circle. This can be "center", "lowerleft", "upperleft", "lowerright", or "upperright". It is case insensitive. The default is "center".
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        topologic.Face
            The created circle.

        """
        from topologicpy.Wire import Wire
        wire = Wire.Circle(origin=origin, radius=radius, sides=sides, fromAngle=fromAngle, toAngle=toAngle, close=True, direction=direction, placement=placement, tolerance=tolerance)
        if not isinstance(wire, topologic.Wire):
            return None
        return Face.ByWire(wire)

    @staticmethod
    def Compactness(face: topologic.Face, mantissa: int = 4) -> float:
        """
        Returns the compactness measure of the input face. See https://en.wikipedia.org/wiki/Compactness_measure_of_a_shape

        Parameters
        ----------
        face : topologic.Face
            The input face.
        mantissa : int , optional
            The desired length of the mantissa. The default is 4.

        Returns
        -------
        float
            The compactness measure of the input face.

        """
        exb = face.ExternalBoundary()
        edges = []
        _ = exb.Edges(None, edges)
        perimeter = 0.0
        for anEdge in edges:
            perimeter = perimeter + abs(topologic.EdgeUtility.Length(anEdge))
        area = abs(Face.Area(face))
        compactness  = 0
        #From https://en.wikipedia.org/wiki/Compactness_measure_of_a_shape

        if area <= 0:
            return None
        if perimeter <= 0:
            return None
        compactness = (math.pi*(2*math.sqrt(area/math.pi)))/perimeter
        return round(compactness, mantissa)

    @staticmethod
    def CompassAngle(face: topologic.Face, north: list = None, mantissa: int = 4) -> float:
        """
        Returns the horizontal compass angle in degrees between the normal vector of the input face and the input vector. The angle is measured in counter-clockwise fashion. Only the first two elements of the vectors are considered.

        Parameters
        ----------
        face : topologic.Face
            The input face.
        north : list , optional
            The second vector representing the north direction. The default is the positive YAxis ([0,1,0]).
        mantissa : int, optional
            The length of the desired mantissa. The default is 4.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        float
            The horizontal compass angle in degrees between the direction of the face and the second input vector.

        """
        from topologicpy.Vector import Vector
        if not isinstance(face, topologic.Face):
            return None
        if not north:
            north = Vector.North()
        dirA = Face.NormalAtParameters(face,mantissa=mantissa)
        return Vector.CompassAngle(vectorA=dirA, vectorB=north, mantissa=mantissa)

    @staticmethod
    def Edges(face: topologic.Face) -> list:
        """
        Returns the edges of the input face.

        Parameters
        ----------
        face : topologic.Face
            The input face.

        Returns
        -------
        list
            The list of edges.

        """
        if not isinstance(face, topologic.Face):
            return None
        edges = []
        _ = face.Edges(None, edges)
        return edges

    @staticmethod
    def Einstein(origin: topologic.Vertex = None, radius: float = 0.5, direction: list = [0,0,1], placement: str = "center") -> topologic.Face:
        """
        Creates an aperiodic monotile, also called an 'einstein' tile (meaning one tile in German, not the name of the famous physist). See https://arxiv.org/abs/2303.10798

        Parameters
        ----------
        origin : topologic.Vertex , optional
            The location of the origin of the tile. The default is None which results in the tiles first vertex being placed at (0,0,0).
        radius : float , optional
            The radius of the hexagon determining the size of the tile. The default is 0.5.
        direction : list , optional
            The vector representing the up direction of the ellipse. The default is [0,0,1].
        placement : str , optional
            The description of the placement of the origin of the hexagon determining the location of the tile. This can be "center", or "lowerleft". It is case insensitive. The default is "center".
        
        """
        from topologicpy.Wire import Wire
        wire = Wire.Einstein(origin=origin, radius=radius, direction=direction, placement=placement)
        if not isinstance(wire, topologic.Wire):
            return None
        return Face.ByWire(wire)
    
    @staticmethod
    def ExternalBoundary(face: topologic.Face) -> topologic.Wire:
        """
        Returns the external boundary (closed wire) of the input face.

        Parameters
        ----------
        face : topologic.Face
            The input face.

        Returns
        -------
        topologic.Wire
            The external boundary of the input face.

        """
        return face.ExternalBoundary()
    
    @staticmethod
    def FacingToward(face: topologic.Face, direction: list = [0,0,-1], asVertex: bool = False, tolerance: float = 0.0001) -> bool:
        """
        Returns True if the input face is facing toward the input direction.

        Parameters
        ----------
        face : topologic.Face
            The input face.
        direction : list , optional
            The input direction. The default is [0,0,-1].
        asVertex : bool , optional
            If set to True, the direction is treated as an actual vertex in 3D space. The default is False.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        bool
            True if the face is facing toward the direction. False otherwise.

        """
        faceNormal = topologic.FaceUtility.NormalAtParameters(face,0.5, 0.5)
        faceCenter = topologic.FaceUtility.VertexAtParameters(face,0.5,0.5)
        cList = [faceCenter.X(), faceCenter.Y(), faceCenter.Z()]
        try:
            vList = [direction.X(), direction.Y(), direction.Z()]
        except:
            try:
                vList = [direction[0], direction[1], direction[2]]
            except:
                raise Exception("Face.FacingToward - Error: Could not get the vector from the input direction")
        if asVertex:
            dV = [vList[0]-cList[0], vList[1]-cList[1], vList[2]-cList[2]]
        else:
            dV = vList
        uV = Vector.Normalize(dV)
        dot = sum([i*j for (i, j) in zip(uV, faceNormal)])
        if dot < tolerance:
            return False
        return True
    
    @staticmethod
    def Flatten(face: topologic.Face, originA: topologic.Vertex = None, originB: topologic.Vertex = None, direction: list = None) -> topologic.Face:
        """
        Flattens the input face such that its center of mass is located at the origin and its normal is pointed in the positive Z axis.

        Parameters
        ----------
        face : topologic.Face
            The input face.
        originA : topologic.Vertex , optional
            The old location to use as the origin of the movement. If set to None, the center of mass of the input topology is used. The default is None.
        originB : topologic.Vertex , optional
            The new location at which to place the topology. If set to None, the world origin (0,0,0) is used. The default is None.
        direction : list , optional
            The direction, expressed as a list of [X,Y,Z] that signifies the direction of the face. If set to None, the normal at *u* 0.5 and *v* 0.5 is considered the direction of the face. The deafult is None.

        Returns
        -------
        topologic.Face
            The flattened face.

        """

        from topologicpy.Vertex import Vertex

        def leftMost(vertices, tolerance = 0.0001):
            xCoords = []
            for v in vertices:
                xCoords.append(Vertex.Coordinates(vertices[0])[0])
            minX = min(xCoords)
            lmVertices = []
            for v in vertices:
                if abs(Vertex.Coordinates(vertices[0])[0] - minX) <= tolerance:
                    lmVertices.append(v)
            return lmVertices
        
        def bottomMost(vertices, tolerance = 0.0001):
            yCoords = []
            for v in vertices:
                yCoords.append(Vertex.Coordinates(vertices[0])[1])
            minY = min(yCoords)
            bmVertices = []
            for v in vertices:
                if abs(Vertex.Coordinates(vertices[0])[1] - minY) <= tolerance:
                    bmVertices.append(v)
            return bmVertices

        def vIndex(v, vList, tolerance):
            for i in range(len(vList)):
                if Vertex.Distance(v, vList[i]) < tolerance:
                    return i+1
            return None
        
        #  rotate cycle path such that it begins with the smallest node
        def rotate_to_smallest(path):
            n = path.index(min(path))
            return path[n:]+path[:n]
        
        #  rotate vertices list so that it begins with the input vertex
        def rotate_vertices(vertices, vertex):
            n = vertices.index(vertex)
            return vertices[n:]+vertices[:n]
        
        from topologicpy.Vertex import Vertex
        from topologicpy.Topology import Topology
        from topologicpy.Dictionary import Dictionary
        if not isinstance(face, topologic.Face):
            return None
        if not isinstance(originA, topologic.Vertex):
            originA = Topology.CenterOfMass(face)
        if not isinstance(originB, topologic.Vertex):
            originB = Vertex.ByCoordinates(0,0,0)
        cm = originA
        world_origin = originB
        if not direction or len(direction) < 3:
            direction = Face.NormalAtParameters(face, 0.5, 0.5)
        x1 = Vertex.X(cm)
        y1 = Vertex.Y(cm)
        z1 = Vertex.Z(cm)
        x2 = Vertex.X(cm) + direction[0]
        y2 = Vertex.Y(cm) + direction[1]
        z2 = Vertex.Z(cm) + direction[2]
        dx = x2 - x1
        dy = y2 - y1
        dz = z2 - z1    
        dist = math.sqrt(dx**2 + dy**2 + dz**2)
        phi = math.degrees(math.atan2(dy, dx)) # Rotation around Y-Axis
        if dist < 0.0001:
            theta = 0
        else:
            theta = math.degrees(math.acos(dz/dist)) # Rotation around Z-Axis
        flatFace = Topology.Translate(face, -cm.X(), -cm.Y(), -cm.Z())
        flatFace = Topology.Rotate(flatFace, world_origin, 0, 0, 1, -phi)
        flatFace = Topology.Rotate(flatFace, world_origin, 0, 1, 0, -theta)
        # Ensure flatness. Force Z to be zero
        flatExternalBoundary = Face.ExternalBoundary(flatFace)
        flatFaceVertices = Topology.SubTopologies(flatExternalBoundary, subTopologyType="vertex")
    
        tempVertices = []
        for ffv in flatFaceVertices:
            tempVertices.append(Vertex.ByCoordinates(ffv.X(), ffv.Y(), 0))
        
        temp_v = bottomMost(leftMost(tempVertices))[0]
        tempVertices = rotate_vertices(tempVertices, temp_v)
        flatExternalBoundary = Wire.ByVertices(tempVertices)

        internalBoundaries = Face.InternalBoundaries(flatFace)
        flatInternalBoundaries = []
        for internalBoundary in internalBoundaries:
            ibVertices = Wire.Vertices(internalBoundary)
            tempVertices = []
            for ibVertex in ibVertices:
                tempVertices.append(Vertex.ByCoordinates(ibVertex.X(), ibVertex.Y(), 0))
            temp_v = bottomMost(leftMost(tempVertices))[0]
            tempVertices = rotate_vertices(tempVertices, temp_v)
            flatInternalBoundaries.append(Wire.ByVertices(tempVertices))
        flatFace = Face.ByWires(flatExternalBoundary, flatInternalBoundaries)
        dictionary = Dictionary.ByKeysValues(["xTran", "yTran", "zTran", "phi", "theta"], [cm.X(), cm.Y(), cm.Z(), phi, theta])
        flatFace = Topology.SetDictionary(flatFace, dictionary)
        return flatFace

    @staticmethod
    def Harmonize(face: topologic.Face) -> topologic.Face:
        """
        Returns a harmonized version of the input face such that the *u* and *v* origins are always in the upperleft corner.

        Parameters
        ----------
        face : topologic.Face
            The input face.

        Returns
        -------
        topologic.Face
            The harmonized face.

        """
        from topologicpy.Vertex import Vertex
        from topologicpy.Wire import Wire
        from topologicpy.Topology import Topology
        from topologicpy.Dictionary import Dictionary

        if not isinstance(face, topologic.Face):
            return None
        flatFace = Face.Flatten(face)
        world_origin = Vertex.ByCoordinates(0,0,0)
        # Retrieve the needed transformations
        dictionary = Topology.Dictionary(flatFace)
        xTran = Dictionary.ValueAtKey(dictionary,"xTran")
        yTran = Dictionary.ValueAtKey(dictionary,"yTran")
        zTran = Dictionary.ValueAtKey(dictionary,"zTran")
        phi = Dictionary.ValueAtKey(dictionary,"phi")
        theta = Dictionary.ValueAtKey(dictionary,"theta")
        vertices = Wire.Vertices(Face.ExternalBoundary(flatFace))
        harmonizedEB = Wire.ByVertices(vertices)
        internalBoundaries = Face.InternalBoundaries(flatFace)
        harmonizedIB = []
        for ib in internalBoundaries:
            ibVertices = Wire.Vertices(ib)
            harmonizedIB.append(Wire.ByVertices(ibVertices))
        harmonizedFace = Face.ByWires(harmonizedEB, harmonizedIB)
        harmonizedFace = Topology.Rotate(harmonizedFace, origin=world_origin, x=0, y=1, z=0, degree=theta)
        harmonizedFace = Topology.Rotate(harmonizedFace, origin=world_origin, x=0, y=0, z=1, degree=phi)
        harmonizedFace = Topology.Translate(harmonizedFace, xTran, yTran, zTran)
        return harmonizedFace

    @staticmethod
    def InternalBoundaries(face: topologic.Face) -> list:
        """
        Returns the internal boundaries (closed wires) of the input face.

        Parameters
        ----------
        face : topologic.Face
            The input face.

        Returns
        -------
        list
            The list of internal boundaries (closed wires).

        """
        if not isinstance(face, topologic.Face):
            return None
        wires = []
        _ = face.InternalBoundaries(wires)
        return list(wires)

    @staticmethod
    def InternalVertex(face: topologic.Face, tolerance: float = 0.0001) -> topologic.Vertex:
        """
        Creates a vertex guaranteed to be inside the input face.

        Parameters
        ----------
        face : topologic.Face
            The input face.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        topologic.Vertex
            The created vertex.

        """
        from topologicpy.Topology import Topology
        if not isinstance(face, topologic.Face):
            return None
        v = Topology.Centroid(face)
        if Face.IsInside(face, v):
            return v
        l1 = [0.1,0.9]
        l2 = [0.3,0.7]
        l3 = [0.2,0.4,0.6,0.8]
        l_all = [l1,l2,l3]
        for l in l_all:
            for uv in l:
                t = max(min(uv,0.9),0.1)
                v = Face.VertexByParameters(face, t, t)
                if Face.IsInside(face, v):
                    return v
        v = topologic.FaceUtility.InternalVertex(face, tolerance)
        return v

    @staticmethod
    def Invert(face: topologic.Face) -> topologic.Face:
        """
        Creates a face that is an inverse (mirror) of the input face.

        Parameters
        ----------
        face : topologic.Face
            The input face.

        Returns
        -------
        topologic.Face
            The inverted face.

        """
        from topologicpy.Wire import Wire

        if not isinstance(face, topologic.Face):
            return None
        eb = Face.ExternalBoundary(face)
        vertices = Wire.Vertices(eb)
        vertices.reverse()
        inverted_wire = Wire.ByVertices(vertices)
        internal_boundaries = Face.InternalBoundaries(face)
        if not internal_boundaries:
            inverted_face = Face.ByWire(inverted_wire)
        else:
            inverted_face = Face.ByWires(inverted_wire, internal_boundaries)
        return inverted_face

    @staticmethod
    def IsCoplanar(faceA: topologic.Face, faceB: topologic.Face, tolerance: float = 0.0001) -> bool:
        """
        Returns True if the two input faces are coplanar. Returns False otherwise.

        Parameters
        ----------
        faceA : topologic.Face
            The first input face.
        faceB : topologic.Face
            The second input face
        tolerance : float , optional
            The desired tolerance. The deafault is 0.0001.

        Raises
        ------
        Exception
            Raises an exception if the angle between the two input faces cannot be determined.

        Returns
        -------
        bool
            True if the two input faces are coplanar. False otherwise.

        """
        if not isinstance(faceA, topologic.Face) or not isinstance(faceB, topologic.Face):
            return None
        dirA = Face.NormalAtParameters(faceA, 0.5, 0.5, "xyz", 3)
        dirB = Face.NormalAtParameters(faceB, 0.5, 0.5, "xyz", 3)
        return Vector.IsCollinear(dirA, dirB, tolerance)
    
    @staticmethod
    def IsInside(face: topologic.Face, vertex: topologic.Vertex, tolerance: float = 0.0001) -> bool:
        """
        Returns True if the input vertex is inside the input face. Returns False otherwise.

        Parameters
        ----------
        face : topologic.Face
            The input face.
        vertex : topologic.Vertex
            The input vertex.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        bool
            True if the input vertex is inside the input face. False otherwise.

        """
        from topologicpy.Vertex import Vertex
        from topologicpy.Cell import Cell
        from topologicpy.Cluster import Cluster
        from topologicpy.Topology import Topology
        from topologicpy.Dictionary import Dictionary
        
        if not isinstance(face, topologic.Face):
            return None
        if not isinstance(vertex, topologic.Vertex):
            return None
        # Test the distance first
        if Vertex.PerpendicularDistance(vertex, face) > tolerance:
            return False
        
        return topologic.FaceUtility.IsInside(face, vertex, tolerance)

    @staticmethod
    def MedialAxis(face: topologic.Face, resolution: int = 0, externalVertices: bool = False, internalVertices: bool = False, toLeavesOnly: bool = False, angTolerance: float = 0.1, tolerance: float = 0.0001) -> topologic.Wire:
        """
        Returns a wire representing an approximation of the medial axis of the input topology. See https://en.wikipedia.org/wiki/Medial_axis.

        Parameters
        ----------
        face : topologic.Face
            The input face.
        resolution : int , optional
            The desired resolution of the solution (range is 0: standard resolution to 10: high resolution). This determines the density of the sampling along each edge. The default is 0.
        externalVertices : bool , optional
            If set to True, the external vertices of the face will be connected to the nearest vertex on the medial axis. The default is False.
        internalVertices : bool , optional
            If set to True, the internal vertices of the face will be connected to the nearest vertex on the medial axis. The default is False.
        toLeavesOnly : bool , optional
            If set to True, the vertices of the face will be connected to the nearest vertex on the medial axis only if this vertex is a leaf (end point). Otherwise, it will connect to any nearest vertex. The default is False.
        angTolerance : float , optional
            The desired angular tolerance in degrees for removing collinear edges. The default is 0.1.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.
        
        Returns
        -------
        topologic.Wire
            The medial axis of the input face.

        """
        from topologicpy.Vertex import Vertex
        from topologicpy.Edge import Edge
        from topologicpy.Wire import Wire
        from topologicpy.Shell import Shell
        from topologicpy.Cluster import Cluster
        from topologicpy.Topology import Topology
        from topologicpy.Dictionary import Dictionary

        def touchesEdge(vertex,edges, tolerance=0.0001):
            if not isinstance(vertex, topologic.Vertex):
                return False
            for edge in edges:
                u = Edge.ParameterAtVertex(edge, vertex, mantissa=4)
                if not u:
                    continue
                if 0<u<1:
                    return True
            return False

        # Flatten the input face
        flatFace = Face.Flatten(face)
        # Retrieve the needed transformations
        dictionary = Topology.Dictionary(flatFace)
        xTran = Dictionary.ValueAtKey(dictionary,"xTran")
        yTran = Dictionary.ValueAtKey(dictionary,"yTran")
        zTran = Dictionary.ValueAtKey(dictionary,"zTran")
        phi = Dictionary.ValueAtKey(dictionary,"phi")
        theta = Dictionary.ValueAtKey(dictionary,"theta")

        # Create a Vertex at the world's origin (0,0,0)
        world_origin = Vertex.ByCoordinates(0,0,0)

        faceVertices = Face.Vertices(flatFace)
        faceEdges = Face.Edges(flatFace)
        vertices = []
        resolution = 10 - resolution
        resolution = min(max(resolution, 1), 10)
        for e in faceEdges:
            for n in range(resolution, 100, resolution):
                vertices.append(Edge.VertexByParameter(e,n*0.01))
        
        voronoi = Shell.Voronoi(vertices=vertices, face=flatFace)
        voronoiEdges = Shell.Edges(voronoi)

        medialAxisEdges = []
        for e in voronoiEdges:
            sv = Edge.StartVertex(e)
            ev = Edge.EndVertex(e)
            svTouchesEdge = touchesEdge(sv, faceEdges, tolerance=tolerance)
            evTouchesEdge = touchesEdge(ev, faceEdges, tolerance=tolerance)
            #connectsToCorners = (Vertex.Index(sv, faceVertices) != None) or (Vertex.Index(ev, faceVertices) != None)
            #if Face.IsInside(flatFace, sv, tolerance=tolerance) and Face.IsInside(flatFace, ev, tolerance=tolerance):
            if not svTouchesEdge and not evTouchesEdge:
                medialAxisEdges.append(e)

        extBoundary = Face.ExternalBoundary(flatFace)
        extVertices = Wire.Vertices(extBoundary)

        intBoundaries = Face.InternalBoundaries(flatFace)
        intVertices = []
        for ib in intBoundaries:
            intVertices = intVertices+Wire.Vertices(ib)
        
        theVertices = []
        if internalVertices:
            theVertices = theVertices+intVertices
        if externalVertices:
            theVertices = theVertices+extVertices

        tempWire = Topology.SelfMerge(Cluster.ByTopologies(medialAxisEdges))
        if isinstance(tempWire, topologic.Wire) and angTolerance > 0:
            tempWire = Topology.RemoveCollinearEdges(tempWire, angTolerance=angTolerance)
        medialAxisEdges = Wire.Edges(tempWire)
        for v in theVertices:
            nv = Vertex.NearestVertex(v, tempWire, useKDTree=False)

            if isinstance(nv, topologic.Vertex):
                if toLeavesOnly:
                    adjVertices = Topology.AdjacentTopologies(nv, tempWire)
                    if len(adjVertices) < 2:
                        medialAxisEdges.append(Edge.ByVertices([nv, v]))
                else:
                    medialAxisEdges.append(Edge.ByVertices([nv, v]))
        medialAxis = Topology.SelfMerge(Cluster.ByTopologies(medialAxisEdges))
        if isinstance(medialAxis, topologic.Wire) and angTolerance > 0:
            medialAxis = Topology.RemoveCollinearEdges(medialAxis, angTolerance=angTolerance)
        medialAxis = Topology.Rotate(medialAxis, origin=world_origin, x=0, y=1, z=0, degree=theta)
        medialAxis = Topology.Rotate(medialAxis, origin=world_origin, x=0, y=0, z=1, degree=phi)
        medialAxis = Topology.Translate(medialAxis, xTran, yTran, zTran)
        return medialAxis

    @staticmethod
    def Normal(face: topologic.Face, outputType: str = "xyz", mantissa: int = 4) -> list:
        """
        Returns the normal vector to the input face. A normal vector of a face is a vector perpendicular to it.

        Parameters
        ----------
        face : topologic.Face
            The input face.
        outputType : string , optional
            The string defining the desired output. This can be any subset or permutation of "xyz". It is case insensitive. The default is "xyz".
        mantissa : int , optional
            The desired length of the mantissa. The default is 4.

        Returns
        -------
        list
            The normal vector to the input face. This is computed at the approximate center of the face.

        """
        return Face.NormalAtParameters(face, u=0.5, v=0.5, outputType=outputType, mantissa=mantissa)

    @staticmethod
    def NormalAtParameters(face: topologic.Face, u: float = 0.5, v: float = 0.5, outputType: str = "xyz", mantissa: int = 4) -> list:
        """
        Returns the normal vector to the input face. A normal vector of a face is a vector perpendicular to it.

        Parameters
        ----------
        face : topologic.Face
            The input face.
        u : float , optional
            The *u* parameter at which to compute the normal to the input face. The default is 0.5.
        v : float , optional
            The *v* parameter at which to compute the normal to the input face. The default is 0.5.
        outputType : string , optional
            The string defining the desired output. This can be any subset or permutation of "xyz". It is case insensitive. The default is "xyz".
        mantissa : int , optional
            The desired length of the mantissa. The default is 4.

        Returns
        -------
        list
            The normal vector to the input face.

        """
        returnResult = []
        try:
            coords = topologic.FaceUtility.NormalAtParameters(face, u, v)
            x = round(coords[0], mantissa)
            y = round(coords[1], mantissa)
            z = round(coords[2], mantissa)
            outputType = list(outputType.lower())
            for axis in outputType:
                if axis == "x":
                    returnResult.append(x)
                elif axis == "y":
                    returnResult.append(y)
                elif axis == "z":
                    returnResult.append(z)
        except:
            returnResult = None
        return returnResult
    
    @staticmethod
    def NormalEdge(face: topologic.Face, length: float = 1.0) -> topologic.Edge:
        """
        Returns the normal vector to the input face as an edge with the desired input length. A normal vector of a face is a vector perpendicular to it.

        Parameters
        ----------
        face : topologic.Face
            The input face.
        length : float , optional
            The desired length of the normal edge. The default is 1.

        Returns
        -------
        topologic.Edge
            The created normal edge to the input face. This is computed at the approximate center of the face.

        """
        return Face.NormalEdgeAtParameters(face, u=0.5, v=0.5, length=length)

    @staticmethod
    def NormalEdgeAtParameters(face: topologic.Face, u: float = 0.5, v: float = 0.5, length: float = 1.0) -> topologic.Edge:
        """
        Returns the normal vector to the input face as an edge with the desired input length. A normal vector of a face is a vector perpendicular to it.

        Parameters
        ----------
        face : topologic.Face
            The input face.
        u : float , optional
            The *u* parameter at which to compute the normal to the input face. The default is 0.5.
        v : float , optional
            The *v* parameter at which to compute the normal to the input face. The default is 0.5.
        length : float , optional
            The desired length of the normal edge. The default is 1.

        Returns
        -------
        topologic.Edge
            The created normal edge to the input face. This is computed at the approximate center of the face.

        """
        from topologicpy.Edge import Edge
        from topologicpy.Topology import Topology
        if not isinstance(face, topologic.Face):
            return None
        sv = Face.VertexByParameters(face=face, u=u, v=v)
        vec = Face.NormalAtParameters(face, u=u, v=v)
        ev = Topology.TranslateByDirectionDistance(sv, vec, length)
        return Edge.ByVertices([sv, ev])

    @staticmethod
    def PlaneEquation(face: topologic.Face, mantissa: int = 4) -> dict:
        """
        Returns the a, b, c, d coefficients of the plane equation of the input face. The input face is assumed to be planar.
        
        Parameters
        ----------
        face : topologic.Face
            The input face.
        
        Returns
        -------
        dict
            The dictionary containing the coefficients of the plane equation. The keys of the dictionary are: ["a", "b", "c", "d"].

        """
        from topologicpy.Topology import Topology
        from topologicpy.Vertex import Vertex
        import random
        import time

        if not isinstance(face, topologic.Face):
            print("Face.PlaneEquation - Error: The input face is not a valid topologic face. Returning None.")
            return None
        all_vertices = Topology.Vertices(face)
        if len(all_vertices) < 3:
            print("Face.PlaneEquation - Error: The input face has less than 3 vertices. Returning None.")
            return None
        vertices = random.sample(all_vertices, 3)
        start = time.time()
        end = time.time()
        duration = (end - start)
        while Vertex.AreCollinear(vertices) and duration < 10:
            vertices = random.sample(all_vertices, 3)
            end = time.time()
            duration = (end - start)
        if Vertex.AreCollinear(vertices):
            print("Face.PlaneEquation - Error: Could not sample 3 non-collinear vertices. Returning None.")
            return None
        v1, v2, v3 = vertices
        x1, y1, z1 = Vertex.Coordinates(v1)
        x2, y2, z2 = Vertex.Coordinates(v2)
        x3, y3, z3 = Vertex.Coordinates(v3)
        vector1 = [x2 - x1, y2 - y1, z2 - z1]
        vector2 = [x3 - x1, y3 - y1, z3 - z1]

        cross_product = [vector1[1] * vector2[2] - vector1[2] * vector2[1], -1 * (vector1[0] * vector2[2] - vector1[2] * vector2[0]), vector1[0] * vector2[1] - vector1[1] * vector2[0]]

        a = round(cross_product[0], mantissa)
        b = round(cross_product[1], mantissa)
        c = round(cross_product[2], mantissa)
        d = round(- (cross_product[0] * x1 + cross_product[1] * y1 + cross_product[2] * z1), mantissa)
        return {"a": a, "b": b, "c": c, "d": d}
    
    @staticmethod
    def Planarize(face: topologic.Face, origin: topologic.Vertex = None, direction: list = None) -> topologic.Face:
        """
        Planarizes the input face such that its center of mass is located at the input origin and its normal is pointed in the input direction.

        Parameters
        ----------
        face : topologic.Face
            The input face.
        origin : topologic.Vertex , optional
            The old location to use as the origin of the movement. If set to None, the center of mass of the input face is used. The default is None.
        direction : list , optional
            The direction, expressed as a list of [X,Y,Z] that signifies the direction of the face. If set to None, the normal at *u* 0.5 and *v* 0.5 is considered the direction of the face. The deafult is None.

        Returns
        -------
        topologic.Face
            The planarized face.

        """

        from topologicpy.Vertex import Vertex
        from topologicpy.Wire import Wire
        from topologicpy.Topology import Topology
        from topologicpy.Dictionary import Dictionary

        if not isinstance(face, topologic.Face):
            return None
        if not isinstance(origin, topologic.Vertex):
            origin = Topology.CenterOfMass(face)
        if not isinstance(direction, list):
            direction = Face.NormalAtParameters(face, 0.5, 0.5)
        flatFace = Face.Flatten(face, oldLocation=origin, direction=direction)

        world_origin = Vertex.ByCoordinates(0,0,0)
        # Retrieve the needed transformations
        dictionary = Topology.Dictionary(flatFace)
        xTran = Dictionary.ValueAtKey(dictionary,"xTran")
        yTran = Dictionary.ValueAtKey(dictionary,"yTran")
        zTran = Dictionary.ValueAtKey(dictionary,"zTran")
        phi = Dictionary.ValueAtKey(dictionary,"phi")
        theta = Dictionary.ValueAtKey(dictionary,"theta")

        planarizedFace = Topology.Rotate(flatFace, origin=world_origin, x=0, y=1, z=0, degree=theta)
        planarizedFace = Topology.Rotate(planarizedFace, origin=world_origin, x=0, y=0, z=1, degree=phi)
        planarizedFace = Topology.Translate(planarizedFace, xTran, yTran, zTran)
        return planarizedFace

    @staticmethod
    def Project(faceA: topologic.Face, faceB: topologic.Face, direction : list = None, mantissa: int = 4) -> topologic.Face:
        """
        Creates a projection of the first input face unto the second input face.

        Parameters
        ----------
        faceA : topologic.Face
            The face to be projected.
        faceB : topologic.Face
            The face unto which the first input face will be projected.
        direction : list, optional
            The vector direction of the projection. If None, the reverse vector of the receiving face normal will be used. The default is None.
        mantissa : int , optional
            The desired length of the mantissa. The default is 4.

        Returns
        -------
        topologic.Face
            The projected Face.

        """

        from topologicpy.Wire import Wire

        if not faceA:
            return None
        if not isinstance(faceA, topologic.Face):
            return None
        if not faceB:
            return None
        if not isinstance(faceB, topologic.Face):
            return None

        eb = faceA.ExternalBoundary()
        ib_list = []
        _ = faceA.InternalBoundaries(ib_list)
        p_eb = Wire.Project(wire=eb, face = faceB, direction=direction, mantissa=mantissa)
        p_ib_list = []
        for ib in ib_list:
            temp_ib = Wire.Project(wire=ib, face = faceB, direction=direction, mantissa=mantissa)
            if temp_ib:
                p_ib_list.append(temp_ib)
        return Face.ByWires(p_eb, p_ib_list)

    @staticmethod
    def Rectangle(origin: topologic.Vertex = None, width: float = 1.0, length: float = 1.0, direction: list = [0,0,1], placement: str = "center", tolerance: float = 0.0001) -> topologic.Face:
        """
        Creates a rectangle.

        Parameters
        ----------
        origin : topologic.Vertex, optional
            The location of the origin of the rectangle. The default is None which results in the rectangle being placed at (0,0,0).
        width : float , optional
            The width of the rectangle. The default is 1.0.
        length : float , optional
            The length of the rectangle. The default is 1.0.
        direction : list , optional
            The vector representing the up direction of the rectangle. The default is [0,0,1].
        placement : str , optional
            The description of the placement of the origin of the rectangle. This can be "center", "lowerleft", "upperleft", "lowerright", "upperright". It is case insensitive. The default is "center".
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        topologic.Face
            The created face.

        """
        from topologicpy.Wire import Wire
        wire = Wire.Rectangle(origin=origin, width=width, length=length, direction=direction, placement=placement, tolerance=tolerance)
        if not isinstance(wire, topologic.Wire):
            return None
        return Face.ByWire(wire)
    
    @staticmethod
    def Skeleton(face, tolerance=0.001):
        """
            Creates a straight skeleton. This method is contributed by 高熙鹏 xipeng gao <gaoxipeng1998@gmail.com>
            This algorithm depends on the polyskel code which is included in the library. Polyskel code is found at: https://github.com/Botffy/polyskel


        Parameters
        ----------
        face : topologic.Face
            The input face.
    
        tolerance : float , optional
            The desired tolerance. The default is 0.001. (This is set to a larger number as it was found to work better)

        Returns
        -------
        topologic.Wire
            The created straight skeleton.

        """
        from topologicpy.Wire import Wire
        if not isinstance(face, topologic.Face):
            print("Face.Skeleton - Error: The input face is not a valid topologic face. Retruning None.")
            return None
        return Wire.Roof(face, degree=0, tolerance=tolerance)
    
    @staticmethod
    def Square(origin: topologic.Vertex = None, size: float = 1.0, direction: list = [0,0,1], placement: str = "center", tolerance: float = 0.0001) -> topologic.Face:
        """
        Creates a square.

        Parameters
        ----------
        origin : topologic.Vertex , optional
            The location of the origin of the square. The default is None which results in the square being placed at (0,0,0).
        size : float , optional
            The size of the square. The default is 1.0.
        direction : list , optional
            The vector representing the up direction of the square. The default is [0,0,1].
        placement : str , optional
            The description of the placement of the origin of the square. This can be "center", "lowerleft", "upperleft", "lowerright", or "upperright". It is case insensitive. The default is "center".
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        topologic.Face
            The created square.

        """
        return Face.Rectangle(origin = origin, width = size, length = size, direction = direction, placement = placement, tolerance = tolerance)
    
    @staticmethod
    def Star(origin: topologic.Vertex = None, radiusA: float = 1.0, radiusB: float = 0.4, rays: int = 5, direction: list = [0,0,1], placement: str = "center", tolerance: float = 0.0001) -> topologic.Face:
        """
        Creates a star.

        Parameters
        ----------
        origin : topologic.Vertex, optional
            The location of the origin of the star. The default is None which results in the star being placed at (0,0,0).
        radiusA : float , optional
            The outer radius of the star. The default is 1.0.
        radiusB : float , optional
            The outer radius of the star. The default is 0.4.
        rays : int , optional
            The number of star rays. The default is 5.
        direction : list , optional
            The vector representing the up direction of the star. The default is [0,0,1].
        placement : str , optional
            The description of the placement of the origin of the star. This can be "center", "lowerleft", "upperleft", "lowerright", or "upperright". It is case insensitive. The default is "center".
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        topologic.Face
            The created face.

        """
        from topologicpy.Wire import Wire
        wire = Wire.Star(origin=origin, radiusA=radiusA, radiusB=radiusB, rays=rays, direction=direction, placement=placement, tolerance=tolerance)
        if not isinstance(wire, topologic.Wire):
            return None
        return Face.ByWire(wire)

    @staticmethod
    def Trapezoid(origin: topologic.Vertex = None, widthA: float = 1.0, widthB: float = 0.75, offsetA: float = 0.0, offsetB: float = 0.0, length: float = 1.0, direction: list = [0,0,1], placement: str = "center", tolerance: float = 0.0001) -> topologic.Face:
        """
        Creates a trapezoid.

        Parameters
        ----------
        origin : topologic.Vertex, optional
            The location of the origin of the trapezoid. The default is None which results in the trapezoid being placed at (0,0,0).
        widthA : float , optional
            The width of the bottom edge of the trapezoid. The default is 1.0.
        widthB : float , optional
            The width of the top edge of the trapezoid. The default is 0.75.
        offsetA : float , optional
            The offset of the bottom edge of the trapezoid. The default is 0.0.
        offsetB : float , optional
            The offset of the top edge of the trapezoid. The default is 0.0.
        length : float , optional
            The length of the trapezoid. The default is 1.0.
        direction : list , optional
            The vector representing the up direction of the trapezoid. The default is [0,0,1].
        placement : str , optional
            The description of the placement of the origin of the trapezoid. This can be "center", or "lowerleft". It is case insensitive. The default is "center".
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        topologic.Face
            The created trapezoid.

        """
        from topologicpy.Wire import Wire
        wire = Wire.Trapezoid(origin=origin, widthA=widthA, widthB=widthB, offsetA=offsetA, offsetB=offsetB, length=length, direction=direction, placement=placement, tolerance=tolerance)
        if not isinstance(wire, topologic.Wire):
            return None
        return Face.ByWire(wire)

    @staticmethod
    def Triangulate(face:topologic.Face) -> list:
        """
        Triangulates the input face and returns a list of faces.

        Parameters
        ----------
        face : topologic.Face
            The input face.

        Returns
        -------
        list
            The list of triangles of the input face.

        """
        from topologicpy.Vertex import Vertex
        from topologicpy.Wire import Wire
        from topologicpy.Topology import Topology
        from topologicpy.Dictionary import Dictionary

        if not isinstance(face, topologic.Face):
            return None
        flatFace = Face.Flatten(face)
        world_origin = Vertex.ByCoordinates(0,0,0)
        # Retrieve the needed transformations
        dictionary = Topology.Dictionary(flatFace)
        xTran = Dictionary.ValueAtKey(dictionary,"xTran")
        yTran = Dictionary.ValueAtKey(dictionary,"yTran")
        zTran = Dictionary.ValueAtKey(dictionary,"zTran")
        phi = Dictionary.ValueAtKey(dictionary,"phi")
        theta = Dictionary.ValueAtKey(dictionary,"theta")
    
        faceTriangles = []
        for i in range(0,5,1):
            try:
                _ = topologic.FaceUtility.Triangulate(flatFace, float(i)*0.1, faceTriangles)
                break
            except:
                continue
        if len(faceTriangles) < 1:
            return [face]
        finalFaces = []
        for f in faceTriangles:
            f = Topology.Rotate(f, origin=world_origin, x=0, y=1, z=0, degree=theta)
            f = Topology.Rotate(f, origin=world_origin, x=0, y=0, z=1, degree=phi)
            f = Topology.Translate(f, xTran, yTran, zTran)
            if Face.Angle(face, f) > 90:
                wire = Face.ExternalBoundary(f)
                wire = Wire.Invert(wire)
                f = topologic.Face.ByExternalBoundary(wire)
                finalFaces.append(f)
            else:
                finalFaces.append(f)
        return finalFaces

    @staticmethod
    def TrimByWire(face: topologic.Face, wire: topologic.Wire, reverse: bool = False) -> topologic.Face:
        """
        Trims the input face by the input wire.

        Parameters
        ----------
        face : topologic.Face
            The input face.
        wire : topologic.Wire
            The input wire.
        reverse : bool , optional
            If set to True, the effect of the trim will be reversed. The default is False.

        Returns
        -------
        topologic.Face
            The resulting trimmed face.

        """
        if not isinstance(face, topologic.Face):
            return None
        if not isinstance(wire, topologic.Wire):
            return face
        trimmed_face = topologic.FaceUtility.TrimByWire(face, wire, False)
        if reverse:
            trimmed_face = face.Difference(trimmed_face)
        return trimmed_face
    
    @staticmethod
    def UnFlatten(face: topologic.Face, dictionary: topologic.Dictionary):
        from topologicpy.Vertex import Vertex
        from topologicpy.Topology import Topology
        from topologicpy.Dictionary import Dictionary
        theta = Dictionary.ValueAtKey(dictionary, "theta")
        phi = Dictionary.ValueAtKey(dictionary, "phi")
        xTran = Dictionary.ValueAtKey(dictionary, "xTran")
        yTran = Dictionary.ValueAtKey(dictionary, "yTran")
        zTran = Dictionary.ValueAtKey(dictionary, "zTran")
        newFace = Topology.Rotate(face, origin=Vertex.Origin(), x=0, y=1, z=0, degree=theta)
        newFace = Topology.Rotate(newFace, origin=Vertex.Origin(), x=0, y=0, z=1, degree=phi)
        newFace = Topology.Translate(newFace, xTran, yTran, zTran)
        return newFace
    
    @staticmethod
    def VertexByParameters(face: topologic.Face, u: float = 0.5, v: float = 0.5) -> topologic.Vertex:
        """
        Creates a vertex at the *u* and *v* parameters of the input face.

        Parameters
        ----------
        face : topologic.Face
            The input face.
        u : float , optional
            The *u* parameter of the input face. The default is 0.5.
        v : float , optional
            The *v* parameter of the input face. The default is 0.5.

        Returns
        -------
        vertex : topologic vertex
            The created vertex.

        """
        if not isinstance(face, topologic.Face):
            return None
        return topologic.FaceUtility.VertexAtParameters(face, u, v)
    
    @staticmethod
    def VertexParameters(face: topologic.Face, vertex: topologic.Vertex, outputType: str = "uv", mantissa: int = 4) -> list:
        """
        Returns the *u* and *v* parameters of the input face at the location of the input vertex.

        Parameters
        ----------
        face : topologic.Face
            The input face.
        vertex : topologic.Vertex
            The input vertex.
        outputType : string , optional
            The string defining the desired output. This can be any subset or permutation of "uv". It is case insensitive. The default is "uv".
        mantissa : int , optional
            The desired length of the mantissa. The default is 4.

        Returns
        -------
        list
            The list of *u* and/or *v* as specified by the outputType input.

        """
        if not isinstance(face, topologic.Face):
            return None
        if not isinstance(vertex, topologic.Vertex):
            return None
        params = topologic.FaceUtility.ParametersAtVertex(face, vertex)
        u = round(params[0], mantissa)
        v = round(params[1], mantissa)
        outputType = list(outputType.lower())
        returnResult = []
        for param in outputType:
            if param == "u":
                returnResult.append(u)
            elif param == "v":
                returnResult.append(v)
        return returnResult

    @staticmethod
    def Vertices(face: topologic.Face) -> list:
        """
        Returns the vertices of the input face.

        Parameters
        ----------
        face : topologic.Face
            The input face.

        Returns
        -------
        list
            The list of vertices.

        """
        if not isinstance(face, topologic.Face):
            return None
        vertices = []
        _ = face.Vertices(None, vertices)
        return vertices
    
    @staticmethod
    def Wire(face: topologic.Face) -> topologic.Wire:
        """
        Returns the external boundary (closed wire) of the input face.

        Parameters
        ----------
        face : topologic.Face
            The input face.

        Returns
        -------
        topologic.Wire
            The external boundary of the input face.

        """
        return face.ExternalBoundary()
    
    @staticmethod
    def Wires(face: topologic.Face) -> list:
        """
        Returns the wires of the input face.

        Parameters
        ----------
        face : topologic.Face
            The input face.

        Returns
        -------
        list
            The list of wires.

        """
        if not isinstance(face, topologic.Face):
            return None
        wires = []
        _ = face.Wires(None, wires)
        return wires

Classes

class Face (...)

init(self: topologic.Face, rkOcctFace: TopoDS_Face, rkGuid: str = '') -> None

Expand source code
class Face(topologic.Face):
    @staticmethod
    def AddInternalBoundaries(face: topologic.Face, wires: list) -> topologic.Face:
        """
        Adds internal boundaries (closed wires) to the input face. Internal boundaries are considered holes in the input face.

        Parameters
        ----------
        face : topologic.Face
            The input face.
        wires : list
            The input list of internal boundaries (closed wires).

        Returns
        -------
        topologic.Face
            The created face with internal boundaries added to it.

        """
        if not face:
            return None
        if not isinstance(face, topologic.Face):
            return None
        if not wires:
            return face
        if not isinstance(wires, list):
            return face
        wireList = [w for w in wires if isinstance(w, topologic.Wire)]
        if len(wireList) < 1:
            return face
        faceeb = face.ExternalBoundary()
        faceibList = []
        _ = face.InternalBoundaries(faceibList)
        for wire in wires:
            faceibList.append(wire)
        return topologic.Face.ByExternalInternalBoundaries(faceeb, faceibList)

    @staticmethod
    def AddInternalBoundariesCluster(face: topologic.Face, cluster: topologic.Cluster) -> topologic.Face:
        """
        Adds the input cluster of internal boundaries (closed wires) to the input face. Internal boundaries are considered holes in the input face.

        Parameters
        ----------
        face : topologic.Face
            The input face.
        cluster : topologic.Cluster
            The input cluster of internal boundaries (topologic wires).

        Returns
        -------
        topologic.Face
            The created face with internal boundaries added to it.

        """
        if not face:
            return None
        if not isinstance(face, topologic.Face):
            return None
        if not cluster:
            return face
        if not isinstance(cluster, topologic.Cluster):
            return face
        wires = []
        _ = cluster.Wires(None, wires)
        return Face.AddInternalBoundaries(face, wires)
    
    @staticmethod
    def Angle(faceA: topologic.Face, faceB: topologic.Face, mantissa: int = 4) -> float:
        """
        Returns the angle in degrees between the two input faces.

        Parameters
        ----------
        faceA : topologic.Face
            The first input face.
        faceB : topologic.Face
            The second input face.
        mantissa : int , optional
            The desired length of the mantissa. The default is 4.

        Returns
        -------
        float
            The angle in degrees between the two input faces.

        """
        from topologicpy.Vector import Vector
        if not faceA or not isinstance(faceA, topologic.Face):
            return None
        if not faceB or not isinstance(faceB, topologic.Face):
            return None
        dirA = Face.NormalAtParameters(faceA, 0.5, 0.5, "xyz", 3)
        dirB = Face.NormalAtParameters(faceB, 0.5, 0.5, "xyz", 3)
        return round((Vector.Angle(dirA, dirB)), mantissa)

    @staticmethod
    def Area(face: topologic.Face, mantissa: int = 4) -> float:
        """
        Returns the area of the input face.

        Parameters
        ----------
        face : topologic.Face
            The input face.
        mantissa : int , optional
            The desired length of the mantissa. The default is 4.

        Returns
        -------
        float
            The area of the input face.

        """
        if not isinstance(face, topologic.Face):
            return None
        area = None
        try:
            area = round(topologic.FaceUtility.Area(face), mantissa)
        except:
            area = None
        return area

    @staticmethod
    def BoundingRectangle(topology: topologic.Topology, optimize: int = 0) -> topologic.Face:
        """
        Returns a face representing a bounding rectangle of the input topology. The returned face contains a dictionary with key "zrot" that represents rotations around the Z axis. If applied the resulting face will become axis-aligned.

        Parameters
        ----------
        topology : topologic.Topology
            The input topology.
        optimize : int , optional
            If set to an integer from 1 (low optimization) to 10 (high optimization), the method will attempt to optimize the bounding rectangle so that it reduces its surface area. The default is 0 which will result in an axis-aligned bounding rectangle. The default is 0.
        
        Returns
        -------
        topologic.Face
            The bounding rectangle of the input topology.

        """
        from topologicpy.Wire import Wire
        from topologicpy.Face import Face
        from topologicpy.Cluster import Cluster
        from topologicpy.Topology import Topology
        from topologicpy.Dictionary import Dictionary
        def bb(topology):
            vertices = []
            _ = topology.Vertices(None, vertices)
            x = []
            y = []
            for aVertex in vertices:
                x.append(aVertex.X())
                y.append(aVertex.Y())
            minX = min(x)
            minY = min(y)
            maxX = max(x)
            maxY = max(y)
            return [minX, minY, maxX, maxY]

        if not isinstance(topology, topologic.Topology):
            return None
        vertices = Topology.SubTopologies(topology, subTopologyType="vertex")
        topology = Cluster.ByTopologies(vertices)
        boundingBox = bb(topology)
        minX = boundingBox[0]
        minY = boundingBox[1]
        maxX = boundingBox[2]
        maxY = boundingBox[3]
        w = abs(maxX - minX)
        l = abs(maxY - minY)
        best_area = l*w
        orig_area = best_area
        best_z = 0
        best_bb = boundingBox
        origin = Topology.Centroid(topology)
        optimize = min(max(optimize, 0), 10)
        if optimize > 0:
            factor = (round(((11 - optimize)/30 + 0.57), 2))
            flag = False
            for n in range(10,0,-1):
                if flag:
                    break
                za = n
                zb = 90+n
                zc = n
                for z in range(za,zb,zc):
                    if flag:
                        break
                    t = Topology.Rotate(topology, origin=origin, x=0,y=0,z=1, degree=z)
                    minX, minY, maxX, maxY = bb(t)
                    w = abs(maxX - minX)
                    l = abs(maxY - minY)
                    area = l*w
                    if area < orig_area*factor:
                        best_area = area
                        best_z = z
                        best_bb = [minX, minY, maxX, maxY]
                        flag = True
                        break
                    if area < best_area:
                        best_area = area
                        best_z = z
                        best_bb = [minX, minY, maxX, maxY]
                        
        else:
            best_bb = boundingBox

        minX, minY, maxX, maxY = best_bb
        vb1 = topologic.Vertex.ByCoordinates(minX, minY, 0)
        vb2 = topologic.Vertex.ByCoordinates(maxX, minY, 0)
        vb3 = topologic.Vertex.ByCoordinates(maxX, maxY, 0)
        vb4 = topologic.Vertex.ByCoordinates(minX, maxY, 0)

        baseWire = Wire.ByVertices([vb1, vb2, vb3, vb4], close=True)
        baseFace = Face.ByWire(baseWire)
        baseFace = Topology.Rotate(baseFace, origin=origin, x=0,y=0,z=1, degree=-best_z)
        dictionary = Dictionary.ByKeysValues(["zrot"], [best_z])
        baseFace = Topology.SetDictionary(baseFace, dictionary)
        return baseFace
    
    @staticmethod
    def ByEdges(edges: list) -> topologic.Face:
        """
        Creates a face from the input list of edges.

        Parameters
        ----------
        edges : list
            The input list of edges.

        Returns
        -------
        face : topologic.Face
            The created face.

        """
        from topologicpy.Wire import Wire
        wire = Wire.ByEdges(edges)
        if not wire:
            return None
        if not isinstance(wire, topologic.Wire):
            return None
        return Face.ByWire(wire)

    @staticmethod
    def ByEdgesCluster(cluster: topologic.Cluster) -> topologic.Face:
        """
        Creates a face from the input cluster of edges.

        Parameters
        ----------
        cluster : topologic.Cluster
            The input cluster of edges.

        Returns
        -------
        face : topologic.Face
            The created face.

        """
        from topologicpy.Cluster import Cluster
        if not isinstance(cluster, topologic.Cluster):
            return None
        edges = Cluster.Edges(cluster)
        return Face.ByEdges(edges)

    @staticmethod
    def ByOffset(face: topologic.Face, offset: float = 1.0, miter: bool = False, miterThreshold: float = None, offsetKey: str = None, miterThresholdKey: str = None, step: bool = True) -> topologic.Face:
        """
        Creates an offset wire from the input wire.

        Parameters
        ----------
        wire : topologic.Wire
            The input wire.
        offset : float , optional
            The desired offset distance. The default is 1.0.
        miter : bool , optional
            if set to True, the corners will be mitered. The default is False.
        miterThreshold : float , optional
            The distance beyond which a miter should be added. The default is None which means the miter threshold is set to the offset distance multiplied by the square root of 2.
        offsetKey : str , optional
            If specified, the dictionary of the edges will be queried for this key to sepcify the desired offset. The default is None.
        miterThresholdKey : str , optional
            If specified, the dictionary of the vertices will be queried for this key to sepcify the desired miter threshold distance. The default is None.
        step : bool , optional
            If set to True, The transition between collinear edges with different offsets will be a step. Otherwise, it will be a continous edge. The default is True.

        Returns
        -------
        topologic.Wire
            The created wire.

        """
        from topologicpy.Wire import Wire

        eb = Face.Wire(face)
        internal_boundaries = Face.InternalBoundaries(face)
        offset_external_boundary = Wire.ByOffset(wire=eb, offset=offset, miter=miter, miterThreshold=miterThreshold, offsetKey=offsetKey, miterThresholdKey=miterThresholdKey, step=step)
        offset_internal_boundaries = []
        for internal_boundary in internal_boundaries:
            offset_internal_boundaries.append(Wire.ByOffset(wire=internal_boundary, offset=offset, miter=miter, miterThreshold=miterThreshold, offsetKey=offsetKey, miterThresholdKey=miterThresholdKey, step=step))
        return Face.ByWires(offset_external_boundary, offset_internal_boundaries)
    
    @staticmethod
    def ByShell(shell: topologic.Shell, angTolerance: float = 0.1)-> topologic.Face:
        """
        Creates a face by merging the faces of the input shell.

        Parameters
        ----------
        shell : topologic.Shell
            The input shell.
        angTolerance : float , optional
            The desired angular tolerance. The default is 0.1.

        Returns
        -------
        topologic.Face
            The created face.

        """
        from topologicpy.Vertex import Vertex
        from topologicpy.Wire import Wire
        from topologicpy.Shell import Shell
        from topologicpy.Topology import Topology
        
        def planarizeList(wireList):
            returnList = []
            for aWire in wireList:
                returnList.append(Wire.Planarize(aWire))
            return returnList
        
        ext_boundary = Shell.ExternalBoundary(shell)
        ext_boundary = Topology.RemoveCollinearEdges(ext_boundary, angTolerance)
        if not Topology.IsPlanar(ext_boundary):
            ext_boundary = Wire.Planarize(ext_boundary)

        if isinstance(ext_boundary, topologic.Wire):
            try:
                return topologic.Face.ByExternalBoundary(Topology.RemoveCollinearEdges(ext_boundary, angTolerance))
            except:
                try:
                    w = Wire.Planarize(ext_boundary)
                    f = Face.ByWire(w)
                    return f
                except:
                    print("FaceByPlanarShell - Error: The input Wire is not planar and could not be fixed. Returning None.")
                    return None
        elif isinstance(ext_boundary, topologic.Cluster):
            wires = []
            _ = ext_boundary.Wires(None, wires)
            faces = []
            areas = []
            for aWire in wires:
                try:
                    aFace = topologic.Face.ByExternalBoundary(Topology.RemoveCollinearEdges(aWire, angTolerance))
                except:
                    aFace = topologic.Face.ByExternalBoundary(Wire.Planarize(Topology.RemoveCollinearEdges(aWire, angTolerance)))
                anArea = Face.Area(aFace)
                faces.append(aFace)
                areas.append(anArea)
            max_index = areas.index(max(areas))
            ext_boundary = faces[max_index]
            int_boundaries = list(set(faces) - set([ext_boundary]))
            int_wires = []
            for int_boundary in int_boundaries:
                temp_wires = []
                _ = int_boundary.Wires(None, temp_wires)
                int_wires.append(Topology.RemoveCollinearEdges(temp_wires[0], angTolerance))
            temp_wires = []
            _ = ext_boundary.Wires(None, temp_wires)
            ext_wire = Topology.RemoveCollinearEdges(temp_wires[0], angTolerance)
            try:
                return topologic.Face.ByExternalInternalBoundaries(ext_wire, int_wires)
            except:
                return topologic.Face.ByExternalInternalBoundaries(Wire.Planarize(ext_wire), planarizeList(int_wires))
        else:
            return None
    
    @staticmethod
    def ByVertices(vertices: list) -> topologic.Face:
        
        """
        Creates a face from the input list of vertices.

        Parameters
        ----------
        vertices : list
            The input list of vertices.

        Returns
        -------
        topologic.Face
            The created face.

        """
        from topologicpy.Topology import Topology
        from topologicpy.Wire import Wire

        if not isinstance(vertices, list):
            return None
        vertexList = [x for x in vertices if isinstance(x, topologic.Vertex)]
        if len(vertexList) < 3:
            return None
        
        w = Wire.ByVertices(vertexList)
        f = Face.ByExternalBoundary(w)
        return f

    @staticmethod
    def ByVerticesCluster(cluster: topologic.Cluster) -> topologic.Face:
        """
        Creates a face from the input cluster of vertices.

        Parameters
        ----------
        cluster : topologic.Cluster
            The input cluster of vertices.

        Returns
        -------
        topologic.Face
            The crearted face.

        """
        from topologicpy.Cluster import Cluster
        if not isinstance(cluster, topologic.Cluster):
            return None
        vertices = Cluster.Vertices(cluster)
        return Face.ByVertices(vertices)

    @staticmethod
    def ByWire(wire: topologic.Wire) -> topologic.Face:
        """
        Creates a face from the input closed wire.

        Parameters
        ----------
        wire : topologic.Wire
            The input wire.

        Returns
        -------
        topologic.Face or list
            The created face. If the wire is non-planar, the method will attempt to triangulate the wire and return a list of faces.

        """
        from topologicpy.Vertex import Vertex
        from topologicpy.Wire import Wire
        from topologicpy.Shell import Shell
        from topologicpy.Cluster import Cluster
        from topologicpy.Topology import Topology
        from topologicpy.Dictionary import Dictionary
        import random

        def triangulateWire(wire):
            wire = Topology.RemoveCollinearEdges(wire)
            vertices = Wire.Vertices(wire)
            shell = Shell.Delaunay(vertices)
            if isinstance(shell, topologic.Shell):
                return Shell.Faces(shell)
            else:
                return []
        if not isinstance(wire, topologic.Wire):
            return None
        if not Wire.IsClosed(wire):
            return None
        
        edges = Wire.Edges(wire)
        wire = Topology.SelfMerge(Cluster.ByTopologies(edges))
        vertices = Wire.Vertices(wire)
        try:
            fList = topologic.Face.ByExternalBoundary(wire)
        except:
            if len(vertices) > 3:
                fList = triangulateWire(wire)
            else:
                fList = []
        
        if not isinstance(fList, list):
            fList = [fList]

        returnList = []
        for f in fList:
            if Face.Area(f) < 0:
                wire = Face.ExternalBoundary(f)
                wire = Wire.Invert(wire)
                try:
                    f = topologic.Face.ByExternalBoundary(wire)
                    returnList.append(f)
                except:
                    pass
            else:
                returnList.append(f)
        if len(returnList) == 0:
            return None
        elif len(returnList) == 1:
            return returnList[0]
        else:
            return returnList

    @staticmethod
    def ByWires(externalBoundary: topologic.Wire, internalBoundaries: list = []) -> topologic.Face:
        """
        Creates a face from the input external boundary (closed wire) and the input list of internal boundaries (closed wires).

        Parameters
        ----------
        externalBoundary : topologic.Wire
            The input external boundary.
        internalBoundaries : list , optional
            The input list of internal boundaries (closed wires). The default is an empty list.

        Returns
        -------
        topologic.Face
            The created face.

        """
        if not isinstance(externalBoundary, topologic.Wire):
            return None
        if not Wire.IsClosed(externalBoundary):
            return None
        ibList = [x for x in internalBoundaries if isinstance(x, topologic.Wire) and Wire.IsClosed(x)]
        return topologic.Face.ByExternalInternalBoundaries(externalBoundary, ibList)

    @staticmethod
    def ByWiresCluster(externalBoundary: topologic.Wire, internalBoundariesCluster: topologic.Cluster = None) -> topologic.Face:
        """
        Creates a face from the input external boundary (closed wire) and the input cluster of internal boundaries (closed wires).

        Parameters
        ----------
        externalBoundary : topologic.Wire
            The input external boundary (closed wire).
        internalBoundariesCluster : topologic.Cluster
            The input cluster of internal boundaries (closed wires). The default is None.

        Returns
        -------
        topologic.Face
            The created face.

        """
        from topologicpy.Wire import Wire
        from topologicpy.Cluster import Cluster
        if not isinstance(externalBoundary, topologic.Wire):
            return None
        if not Wire.IsClosed(externalBoundary):
            return None
        if not internalBoundariesCluster:
            internalBoundaries = []
        elif not isinstance(internalBoundariesCluster, topologic.Cluster):
            return None
        else:
            internalBoundaries = Cluster.Wires(internalBoundariesCluster)
        return Face.ByWires(externalBoundary, internalBoundaries)
    
    @staticmethod
    def NorthArrow(origin: topologic.Vertex = None, radius: float = 0.5, sides: int = 16, direction: list = [0,0,1], northAngle: float = 0.0,
                   placement: str = "center", tolerance: float = 0.0001) -> topologic.Face:
        """
        Creates a north arrow.

        Parameters
        ----------
        origin : topologic.Vertex, optional
            The location of the origin of the circle. The default is None which results in the circle being placed at (0,0,0).
        radius : float , optional
            The radius of the circle. The default is 1.
        sides : int , optional
            The number of sides of the circle. The default is 16.
        direction : list , optional
            The vector representing the up direction of the circle. The default is [0,0,1].
        northAngle : float , optional
            The angular offset in degrees from the positive Y axis direction. The angle is measured in a counter-clockwise fashion where 0 is positive Y, 90 is negative X, 180 is negative Y, and 270 is positive X.
        placement : str , optional
            The description of the placement of the origin of the circle. This can be "center", "lowerleft", "upperleft", "lowerright", or "upperright". It is case insensitive. The default is "center".
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        topologic.Face
            The created circle.

        """
        from topologicpy.Topology import Topology
        from topologicpy.Vertex import Vertex
        if not origin:
            origin = Vertex.Origin()
        
        c = Face.Circle(origin=origin, radius=radius, sides=sides, direction=[0,0,1], placement="center", tolerance=tolerance)
        r = Face.Rectangle(origin=origin, width=radius*0.01,length=radius*1.2, placement="lowerleft")
        r = Topology.Translate(r, -0.005*radius,0,0)
        arrow = Topology.Difference(c, r)
        arrow = Topology.Rotate(arrow, Vertex.Origin(), 0,0,1,northAngle)
        if placement.lower() == "lowerleft":
            arrow = Topology.Translate(arrow, radius, radius, 0)
        elif placement.lower() == "upperleft":
            arrow = Topology.Translate(arrow, radius, -radius, 0)
        elif placement.lower() == "lowerright":
            arrow = Topology.Translate(arrow, -radius, radius, 0)
        elif placement.lower() == "upperright":
            arrow = Topology.Translate(arrow, -radius, -radius, 0)
        x1 = origin.X()
        y1 = origin.Y()
        z1 = origin.Z()
        x2 = origin.X() + direction[0]
        y2 = origin.Y() + direction[1]
        z2 = origin.Z() + direction[2]
        dx = x2 - x1
        dy = y2 - y1
        dz = z2 - z1    
        dist = math.sqrt(dx**2 + dy**2 + dz**2)
        phi = math.degrees(math.atan2(dy, dx)) # Rotation around Y-Axis
        if dist < 0.0001:
            theta = 0
        else:
            theta = math.degrees(math.acos(dz/dist)) # Rotation around Z-Axis
        arrow = Topology.Rotate(arrow, origin, 0, 1, 0, theta)
        arrow = Topology.Rotate(arrow, origin, 0, 0, 1, phi)
        return arrow

    @staticmethod
    def Circle(origin: topologic.Vertex = None, radius: float = 0.5, sides: int = 16, fromAngle: float = 0.0, toAngle: float = 360.0, direction: list = [0,0,1],
                   placement: str = "center", tolerance: float = 0.0001) -> topologic.Face:
        """
        Creates a circle.

        Parameters
        ----------
        origin : topologic.Vertex, optional
            The location of the origin of the circle. The default is None which results in the circle being placed at (0,0,0).
        radius : float , optional
            The radius of the circle. The default is 1.
        sides : int , optional
            The number of sides of the circle. The default is 16.
        fromAngle : float , optional
            The angle in degrees from which to start creating the arc of the circle. The default is 0.
        toAngle : float , optional
            The angle in degrees at which to end creating the arc of the circle. The default is 360.
        direction : list , optional
            The vector representing the up direction of the circle. The default is [0,0,1].
        placement : str , optional
            The description of the placement of the origin of the circle. This can be "center", "lowerleft", "upperleft", "lowerright", or "upperright". It is case insensitive. The default is "center".
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        topologic.Face
            The created circle.

        """
        from topologicpy.Wire import Wire
        wire = Wire.Circle(origin=origin, radius=radius, sides=sides, fromAngle=fromAngle, toAngle=toAngle, close=True, direction=direction, placement=placement, tolerance=tolerance)
        if not isinstance(wire, topologic.Wire):
            return None
        return Face.ByWire(wire)

    @staticmethod
    def Compactness(face: topologic.Face, mantissa: int = 4) -> float:
        """
        Returns the compactness measure of the input face. See https://en.wikipedia.org/wiki/Compactness_measure_of_a_shape

        Parameters
        ----------
        face : topologic.Face
            The input face.
        mantissa : int , optional
            The desired length of the mantissa. The default is 4.

        Returns
        -------
        float
            The compactness measure of the input face.

        """
        exb = face.ExternalBoundary()
        edges = []
        _ = exb.Edges(None, edges)
        perimeter = 0.0
        for anEdge in edges:
            perimeter = perimeter + abs(topologic.EdgeUtility.Length(anEdge))
        area = abs(Face.Area(face))
        compactness  = 0
        #From https://en.wikipedia.org/wiki/Compactness_measure_of_a_shape

        if area <= 0:
            return None
        if perimeter <= 0:
            return None
        compactness = (math.pi*(2*math.sqrt(area/math.pi)))/perimeter
        return round(compactness, mantissa)

    @staticmethod
    def CompassAngle(face: topologic.Face, north: list = None, mantissa: int = 4) -> float:
        """
        Returns the horizontal compass angle in degrees between the normal vector of the input face and the input vector. The angle is measured in counter-clockwise fashion. Only the first two elements of the vectors are considered.

        Parameters
        ----------
        face : topologic.Face
            The input face.
        north : list , optional
            The second vector representing the north direction. The default is the positive YAxis ([0,1,0]).
        mantissa : int, optional
            The length of the desired mantissa. The default is 4.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        float
            The horizontal compass angle in degrees between the direction of the face and the second input vector.

        """
        from topologicpy.Vector import Vector
        if not isinstance(face, topologic.Face):
            return None
        if not north:
            north = Vector.North()
        dirA = Face.NormalAtParameters(face,mantissa=mantissa)
        return Vector.CompassAngle(vectorA=dirA, vectorB=north, mantissa=mantissa)

    @staticmethod
    def Edges(face: topologic.Face) -> list:
        """
        Returns the edges of the input face.

        Parameters
        ----------
        face : topologic.Face
            The input face.

        Returns
        -------
        list
            The list of edges.

        """
        if not isinstance(face, topologic.Face):
            return None
        edges = []
        _ = face.Edges(None, edges)
        return edges

    @staticmethod
    def Einstein(origin: topologic.Vertex = None, radius: float = 0.5, direction: list = [0,0,1], placement: str = "center") -> topologic.Face:
        """
        Creates an aperiodic monotile, also called an 'einstein' tile (meaning one tile in German, not the name of the famous physist). See https://arxiv.org/abs/2303.10798

        Parameters
        ----------
        origin : topologic.Vertex , optional
            The location of the origin of the tile. The default is None which results in the tiles first vertex being placed at (0,0,0).
        radius : float , optional
            The radius of the hexagon determining the size of the tile. The default is 0.5.
        direction : list , optional
            The vector representing the up direction of the ellipse. The default is [0,0,1].
        placement : str , optional
            The description of the placement of the origin of the hexagon determining the location of the tile. This can be "center", or "lowerleft". It is case insensitive. The default is "center".
        
        """
        from topologicpy.Wire import Wire
        wire = Wire.Einstein(origin=origin, radius=radius, direction=direction, placement=placement)
        if not isinstance(wire, topologic.Wire):
            return None
        return Face.ByWire(wire)
    
    @staticmethod
    def ExternalBoundary(face: topologic.Face) -> topologic.Wire:
        """
        Returns the external boundary (closed wire) of the input face.

        Parameters
        ----------
        face : topologic.Face
            The input face.

        Returns
        -------
        topologic.Wire
            The external boundary of the input face.

        """
        return face.ExternalBoundary()
    
    @staticmethod
    def FacingToward(face: topologic.Face, direction: list = [0,0,-1], asVertex: bool = False, tolerance: float = 0.0001) -> bool:
        """
        Returns True if the input face is facing toward the input direction.

        Parameters
        ----------
        face : topologic.Face
            The input face.
        direction : list , optional
            The input direction. The default is [0,0,-1].
        asVertex : bool , optional
            If set to True, the direction is treated as an actual vertex in 3D space. The default is False.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        bool
            True if the face is facing toward the direction. False otherwise.

        """
        faceNormal = topologic.FaceUtility.NormalAtParameters(face,0.5, 0.5)
        faceCenter = topologic.FaceUtility.VertexAtParameters(face,0.5,0.5)
        cList = [faceCenter.X(), faceCenter.Y(), faceCenter.Z()]
        try:
            vList = [direction.X(), direction.Y(), direction.Z()]
        except:
            try:
                vList = [direction[0], direction[1], direction[2]]
            except:
                raise Exception("Face.FacingToward - Error: Could not get the vector from the input direction")
        if asVertex:
            dV = [vList[0]-cList[0], vList[1]-cList[1], vList[2]-cList[2]]
        else:
            dV = vList
        uV = Vector.Normalize(dV)
        dot = sum([i*j for (i, j) in zip(uV, faceNormal)])
        if dot < tolerance:
            return False
        return True
    
    @staticmethod
    def Flatten(face: topologic.Face, originA: topologic.Vertex = None, originB: topologic.Vertex = None, direction: list = None) -> topologic.Face:
        """
        Flattens the input face such that its center of mass is located at the origin and its normal is pointed in the positive Z axis.

        Parameters
        ----------
        face : topologic.Face
            The input face.
        originA : topologic.Vertex , optional
            The old location to use as the origin of the movement. If set to None, the center of mass of the input topology is used. The default is None.
        originB : topologic.Vertex , optional
            The new location at which to place the topology. If set to None, the world origin (0,0,0) is used. The default is None.
        direction : list , optional
            The direction, expressed as a list of [X,Y,Z] that signifies the direction of the face. If set to None, the normal at *u* 0.5 and *v* 0.5 is considered the direction of the face. The deafult is None.

        Returns
        -------
        topologic.Face
            The flattened face.

        """

        from topologicpy.Vertex import Vertex

        def leftMost(vertices, tolerance = 0.0001):
            xCoords = []
            for v in vertices:
                xCoords.append(Vertex.Coordinates(vertices[0])[0])
            minX = min(xCoords)
            lmVertices = []
            for v in vertices:
                if abs(Vertex.Coordinates(vertices[0])[0] - minX) <= tolerance:
                    lmVertices.append(v)
            return lmVertices
        
        def bottomMost(vertices, tolerance = 0.0001):
            yCoords = []
            for v in vertices:
                yCoords.append(Vertex.Coordinates(vertices[0])[1])
            minY = min(yCoords)
            bmVertices = []
            for v in vertices:
                if abs(Vertex.Coordinates(vertices[0])[1] - minY) <= tolerance:
                    bmVertices.append(v)
            return bmVertices

        def vIndex(v, vList, tolerance):
            for i in range(len(vList)):
                if Vertex.Distance(v, vList[i]) < tolerance:
                    return i+1
            return None
        
        #  rotate cycle path such that it begins with the smallest node
        def rotate_to_smallest(path):
            n = path.index(min(path))
            return path[n:]+path[:n]
        
        #  rotate vertices list so that it begins with the input vertex
        def rotate_vertices(vertices, vertex):
            n = vertices.index(vertex)
            return vertices[n:]+vertices[:n]
        
        from topologicpy.Vertex import Vertex
        from topologicpy.Topology import Topology
        from topologicpy.Dictionary import Dictionary
        if not isinstance(face, topologic.Face):
            return None
        if not isinstance(originA, topologic.Vertex):
            originA = Topology.CenterOfMass(face)
        if not isinstance(originB, topologic.Vertex):
            originB = Vertex.ByCoordinates(0,0,0)
        cm = originA
        world_origin = originB
        if not direction or len(direction) < 3:
            direction = Face.NormalAtParameters(face, 0.5, 0.5)
        x1 = Vertex.X(cm)
        y1 = Vertex.Y(cm)
        z1 = Vertex.Z(cm)
        x2 = Vertex.X(cm) + direction[0]
        y2 = Vertex.Y(cm) + direction[1]
        z2 = Vertex.Z(cm) + direction[2]
        dx = x2 - x1
        dy = y2 - y1
        dz = z2 - z1    
        dist = math.sqrt(dx**2 + dy**2 + dz**2)
        phi = math.degrees(math.atan2(dy, dx)) # Rotation around Y-Axis
        if dist < 0.0001:
            theta = 0
        else:
            theta = math.degrees(math.acos(dz/dist)) # Rotation around Z-Axis
        flatFace = Topology.Translate(face, -cm.X(), -cm.Y(), -cm.Z())
        flatFace = Topology.Rotate(flatFace, world_origin, 0, 0, 1, -phi)
        flatFace = Topology.Rotate(flatFace, world_origin, 0, 1, 0, -theta)
        # Ensure flatness. Force Z to be zero
        flatExternalBoundary = Face.ExternalBoundary(flatFace)
        flatFaceVertices = Topology.SubTopologies(flatExternalBoundary, subTopologyType="vertex")
    
        tempVertices = []
        for ffv in flatFaceVertices:
            tempVertices.append(Vertex.ByCoordinates(ffv.X(), ffv.Y(), 0))
        
        temp_v = bottomMost(leftMost(tempVertices))[0]
        tempVertices = rotate_vertices(tempVertices, temp_v)
        flatExternalBoundary = Wire.ByVertices(tempVertices)

        internalBoundaries = Face.InternalBoundaries(flatFace)
        flatInternalBoundaries = []
        for internalBoundary in internalBoundaries:
            ibVertices = Wire.Vertices(internalBoundary)
            tempVertices = []
            for ibVertex in ibVertices:
                tempVertices.append(Vertex.ByCoordinates(ibVertex.X(), ibVertex.Y(), 0))
            temp_v = bottomMost(leftMost(tempVertices))[0]
            tempVertices = rotate_vertices(tempVertices, temp_v)
            flatInternalBoundaries.append(Wire.ByVertices(tempVertices))
        flatFace = Face.ByWires(flatExternalBoundary, flatInternalBoundaries)
        dictionary = Dictionary.ByKeysValues(["xTran", "yTran", "zTran", "phi", "theta"], [cm.X(), cm.Y(), cm.Z(), phi, theta])
        flatFace = Topology.SetDictionary(flatFace, dictionary)
        return flatFace

    @staticmethod
    def Harmonize(face: topologic.Face) -> topologic.Face:
        """
        Returns a harmonized version of the input face such that the *u* and *v* origins are always in the upperleft corner.

        Parameters
        ----------
        face : topologic.Face
            The input face.

        Returns
        -------
        topologic.Face
            The harmonized face.

        """
        from topologicpy.Vertex import Vertex
        from topologicpy.Wire import Wire
        from topologicpy.Topology import Topology
        from topologicpy.Dictionary import Dictionary

        if not isinstance(face, topologic.Face):
            return None
        flatFace = Face.Flatten(face)
        world_origin = Vertex.ByCoordinates(0,0,0)
        # Retrieve the needed transformations
        dictionary = Topology.Dictionary(flatFace)
        xTran = Dictionary.ValueAtKey(dictionary,"xTran")
        yTran = Dictionary.ValueAtKey(dictionary,"yTran")
        zTran = Dictionary.ValueAtKey(dictionary,"zTran")
        phi = Dictionary.ValueAtKey(dictionary,"phi")
        theta = Dictionary.ValueAtKey(dictionary,"theta")
        vertices = Wire.Vertices(Face.ExternalBoundary(flatFace))
        harmonizedEB = Wire.ByVertices(vertices)
        internalBoundaries = Face.InternalBoundaries(flatFace)
        harmonizedIB = []
        for ib in internalBoundaries:
            ibVertices = Wire.Vertices(ib)
            harmonizedIB.append(Wire.ByVertices(ibVertices))
        harmonizedFace = Face.ByWires(harmonizedEB, harmonizedIB)
        harmonizedFace = Topology.Rotate(harmonizedFace, origin=world_origin, x=0, y=1, z=0, degree=theta)
        harmonizedFace = Topology.Rotate(harmonizedFace, origin=world_origin, x=0, y=0, z=1, degree=phi)
        harmonizedFace = Topology.Translate(harmonizedFace, xTran, yTran, zTran)
        return harmonizedFace

    @staticmethod
    def InternalBoundaries(face: topologic.Face) -> list:
        """
        Returns the internal boundaries (closed wires) of the input face.

        Parameters
        ----------
        face : topologic.Face
            The input face.

        Returns
        -------
        list
            The list of internal boundaries (closed wires).

        """
        if not isinstance(face, topologic.Face):
            return None
        wires = []
        _ = face.InternalBoundaries(wires)
        return list(wires)

    @staticmethod
    def InternalVertex(face: topologic.Face, tolerance: float = 0.0001) -> topologic.Vertex:
        """
        Creates a vertex guaranteed to be inside the input face.

        Parameters
        ----------
        face : topologic.Face
            The input face.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        topologic.Vertex
            The created vertex.

        """
        from topologicpy.Topology import Topology
        if not isinstance(face, topologic.Face):
            return None
        v = Topology.Centroid(face)
        if Face.IsInside(face, v):
            return v
        l1 = [0.1,0.9]
        l2 = [0.3,0.7]
        l3 = [0.2,0.4,0.6,0.8]
        l_all = [l1,l2,l3]
        for l in l_all:
            for uv in l:
                t = max(min(uv,0.9),0.1)
                v = Face.VertexByParameters(face, t, t)
                if Face.IsInside(face, v):
                    return v
        v = topologic.FaceUtility.InternalVertex(face, tolerance)
        return v

    @staticmethod
    def Invert(face: topologic.Face) -> topologic.Face:
        """
        Creates a face that is an inverse (mirror) of the input face.

        Parameters
        ----------
        face : topologic.Face
            The input face.

        Returns
        -------
        topologic.Face
            The inverted face.

        """
        from topologicpy.Wire import Wire

        if not isinstance(face, topologic.Face):
            return None
        eb = Face.ExternalBoundary(face)
        vertices = Wire.Vertices(eb)
        vertices.reverse()
        inverted_wire = Wire.ByVertices(vertices)
        internal_boundaries = Face.InternalBoundaries(face)
        if not internal_boundaries:
            inverted_face = Face.ByWire(inverted_wire)
        else:
            inverted_face = Face.ByWires(inverted_wire, internal_boundaries)
        return inverted_face

    @staticmethod
    def IsCoplanar(faceA: topologic.Face, faceB: topologic.Face, tolerance: float = 0.0001) -> bool:
        """
        Returns True if the two input faces are coplanar. Returns False otherwise.

        Parameters
        ----------
        faceA : topologic.Face
            The first input face.
        faceB : topologic.Face
            The second input face
        tolerance : float , optional
            The desired tolerance. The deafault is 0.0001.

        Raises
        ------
        Exception
            Raises an exception if the angle between the two input faces cannot be determined.

        Returns
        -------
        bool
            True if the two input faces are coplanar. False otherwise.

        """
        if not isinstance(faceA, topologic.Face) or not isinstance(faceB, topologic.Face):
            return None
        dirA = Face.NormalAtParameters(faceA, 0.5, 0.5, "xyz", 3)
        dirB = Face.NormalAtParameters(faceB, 0.5, 0.5, "xyz", 3)
        return Vector.IsCollinear(dirA, dirB, tolerance)
    
    @staticmethod
    def IsInside(face: topologic.Face, vertex: topologic.Vertex, tolerance: float = 0.0001) -> bool:
        """
        Returns True if the input vertex is inside the input face. Returns False otherwise.

        Parameters
        ----------
        face : topologic.Face
            The input face.
        vertex : topologic.Vertex
            The input vertex.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        bool
            True if the input vertex is inside the input face. False otherwise.

        """
        from topologicpy.Vertex import Vertex
        from topologicpy.Cell import Cell
        from topologicpy.Cluster import Cluster
        from topologicpy.Topology import Topology
        from topologicpy.Dictionary import Dictionary
        
        if not isinstance(face, topologic.Face):
            return None
        if not isinstance(vertex, topologic.Vertex):
            return None
        # Test the distance first
        if Vertex.PerpendicularDistance(vertex, face) > tolerance:
            return False
        
        return topologic.FaceUtility.IsInside(face, vertex, tolerance)

    @staticmethod
    def MedialAxis(face: topologic.Face, resolution: int = 0, externalVertices: bool = False, internalVertices: bool = False, toLeavesOnly: bool = False, angTolerance: float = 0.1, tolerance: float = 0.0001) -> topologic.Wire:
        """
        Returns a wire representing an approximation of the medial axis of the input topology. See https://en.wikipedia.org/wiki/Medial_axis.

        Parameters
        ----------
        face : topologic.Face
            The input face.
        resolution : int , optional
            The desired resolution of the solution (range is 0: standard resolution to 10: high resolution). This determines the density of the sampling along each edge. The default is 0.
        externalVertices : bool , optional
            If set to True, the external vertices of the face will be connected to the nearest vertex on the medial axis. The default is False.
        internalVertices : bool , optional
            If set to True, the internal vertices of the face will be connected to the nearest vertex on the medial axis. The default is False.
        toLeavesOnly : bool , optional
            If set to True, the vertices of the face will be connected to the nearest vertex on the medial axis only if this vertex is a leaf (end point). Otherwise, it will connect to any nearest vertex. The default is False.
        angTolerance : float , optional
            The desired angular tolerance in degrees for removing collinear edges. The default is 0.1.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.
        
        Returns
        -------
        topologic.Wire
            The medial axis of the input face.

        """
        from topologicpy.Vertex import Vertex
        from topologicpy.Edge import Edge
        from topologicpy.Wire import Wire
        from topologicpy.Shell import Shell
        from topologicpy.Cluster import Cluster
        from topologicpy.Topology import Topology
        from topologicpy.Dictionary import Dictionary

        def touchesEdge(vertex,edges, tolerance=0.0001):
            if not isinstance(vertex, topologic.Vertex):
                return False
            for edge in edges:
                u = Edge.ParameterAtVertex(edge, vertex, mantissa=4)
                if not u:
                    continue
                if 0<u<1:
                    return True
            return False

        # Flatten the input face
        flatFace = Face.Flatten(face)
        # Retrieve the needed transformations
        dictionary = Topology.Dictionary(flatFace)
        xTran = Dictionary.ValueAtKey(dictionary,"xTran")
        yTran = Dictionary.ValueAtKey(dictionary,"yTran")
        zTran = Dictionary.ValueAtKey(dictionary,"zTran")
        phi = Dictionary.ValueAtKey(dictionary,"phi")
        theta = Dictionary.ValueAtKey(dictionary,"theta")

        # Create a Vertex at the world's origin (0,0,0)
        world_origin = Vertex.ByCoordinates(0,0,0)

        faceVertices = Face.Vertices(flatFace)
        faceEdges = Face.Edges(flatFace)
        vertices = []
        resolution = 10 - resolution
        resolution = min(max(resolution, 1), 10)
        for e in faceEdges:
            for n in range(resolution, 100, resolution):
                vertices.append(Edge.VertexByParameter(e,n*0.01))
        
        voronoi = Shell.Voronoi(vertices=vertices, face=flatFace)
        voronoiEdges = Shell.Edges(voronoi)

        medialAxisEdges = []
        for e in voronoiEdges:
            sv = Edge.StartVertex(e)
            ev = Edge.EndVertex(e)
            svTouchesEdge = touchesEdge(sv, faceEdges, tolerance=tolerance)
            evTouchesEdge = touchesEdge(ev, faceEdges, tolerance=tolerance)
            #connectsToCorners = (Vertex.Index(sv, faceVertices) != None) or (Vertex.Index(ev, faceVertices) != None)
            #if Face.IsInside(flatFace, sv, tolerance=tolerance) and Face.IsInside(flatFace, ev, tolerance=tolerance):
            if not svTouchesEdge and not evTouchesEdge:
                medialAxisEdges.append(e)

        extBoundary = Face.ExternalBoundary(flatFace)
        extVertices = Wire.Vertices(extBoundary)

        intBoundaries = Face.InternalBoundaries(flatFace)
        intVertices = []
        for ib in intBoundaries:
            intVertices = intVertices+Wire.Vertices(ib)
        
        theVertices = []
        if internalVertices:
            theVertices = theVertices+intVertices
        if externalVertices:
            theVertices = theVertices+extVertices

        tempWire = Topology.SelfMerge(Cluster.ByTopologies(medialAxisEdges))
        if isinstance(tempWire, topologic.Wire) and angTolerance > 0:
            tempWire = Topology.RemoveCollinearEdges(tempWire, angTolerance=angTolerance)
        medialAxisEdges = Wire.Edges(tempWire)
        for v in theVertices:
            nv = Vertex.NearestVertex(v, tempWire, useKDTree=False)

            if isinstance(nv, topologic.Vertex):
                if toLeavesOnly:
                    adjVertices = Topology.AdjacentTopologies(nv, tempWire)
                    if len(adjVertices) < 2:
                        medialAxisEdges.append(Edge.ByVertices([nv, v]))
                else:
                    medialAxisEdges.append(Edge.ByVertices([nv, v]))
        medialAxis = Topology.SelfMerge(Cluster.ByTopologies(medialAxisEdges))
        if isinstance(medialAxis, topologic.Wire) and angTolerance > 0:
            medialAxis = Topology.RemoveCollinearEdges(medialAxis, angTolerance=angTolerance)
        medialAxis = Topology.Rotate(medialAxis, origin=world_origin, x=0, y=1, z=0, degree=theta)
        medialAxis = Topology.Rotate(medialAxis, origin=world_origin, x=0, y=0, z=1, degree=phi)
        medialAxis = Topology.Translate(medialAxis, xTran, yTran, zTran)
        return medialAxis

    @staticmethod
    def Normal(face: topologic.Face, outputType: str = "xyz", mantissa: int = 4) -> list:
        """
        Returns the normal vector to the input face. A normal vector of a face is a vector perpendicular to it.

        Parameters
        ----------
        face : topologic.Face
            The input face.
        outputType : string , optional
            The string defining the desired output. This can be any subset or permutation of "xyz". It is case insensitive. The default is "xyz".
        mantissa : int , optional
            The desired length of the mantissa. The default is 4.

        Returns
        -------
        list
            The normal vector to the input face. This is computed at the approximate center of the face.

        """
        return Face.NormalAtParameters(face, u=0.5, v=0.5, outputType=outputType, mantissa=mantissa)

    @staticmethod
    def NormalAtParameters(face: topologic.Face, u: float = 0.5, v: float = 0.5, outputType: str = "xyz", mantissa: int = 4) -> list:
        """
        Returns the normal vector to the input face. A normal vector of a face is a vector perpendicular to it.

        Parameters
        ----------
        face : topologic.Face
            The input face.
        u : float , optional
            The *u* parameter at which to compute the normal to the input face. The default is 0.5.
        v : float , optional
            The *v* parameter at which to compute the normal to the input face. The default is 0.5.
        outputType : string , optional
            The string defining the desired output. This can be any subset or permutation of "xyz". It is case insensitive. The default is "xyz".
        mantissa : int , optional
            The desired length of the mantissa. The default is 4.

        Returns
        -------
        list
            The normal vector to the input face.

        """
        returnResult = []
        try:
            coords = topologic.FaceUtility.NormalAtParameters(face, u, v)
            x = round(coords[0], mantissa)
            y = round(coords[1], mantissa)
            z = round(coords[2], mantissa)
            outputType = list(outputType.lower())
            for axis in outputType:
                if axis == "x":
                    returnResult.append(x)
                elif axis == "y":
                    returnResult.append(y)
                elif axis == "z":
                    returnResult.append(z)
        except:
            returnResult = None
        return returnResult
    
    @staticmethod
    def NormalEdge(face: topologic.Face, length: float = 1.0) -> topologic.Edge:
        """
        Returns the normal vector to the input face as an edge with the desired input length. A normal vector of a face is a vector perpendicular to it.

        Parameters
        ----------
        face : topologic.Face
            The input face.
        length : float , optional
            The desired length of the normal edge. The default is 1.

        Returns
        -------
        topologic.Edge
            The created normal edge to the input face. This is computed at the approximate center of the face.

        """
        return Face.NormalEdgeAtParameters(face, u=0.5, v=0.5, length=length)

    @staticmethod
    def NormalEdgeAtParameters(face: topologic.Face, u: float = 0.5, v: float = 0.5, length: float = 1.0) -> topologic.Edge:
        """
        Returns the normal vector to the input face as an edge with the desired input length. A normal vector of a face is a vector perpendicular to it.

        Parameters
        ----------
        face : topologic.Face
            The input face.
        u : float , optional
            The *u* parameter at which to compute the normal to the input face. The default is 0.5.
        v : float , optional
            The *v* parameter at which to compute the normal to the input face. The default is 0.5.
        length : float , optional
            The desired length of the normal edge. The default is 1.

        Returns
        -------
        topologic.Edge
            The created normal edge to the input face. This is computed at the approximate center of the face.

        """
        from topologicpy.Edge import Edge
        from topologicpy.Topology import Topology
        if not isinstance(face, topologic.Face):
            return None
        sv = Face.VertexByParameters(face=face, u=u, v=v)
        vec = Face.NormalAtParameters(face, u=u, v=v)
        ev = Topology.TranslateByDirectionDistance(sv, vec, length)
        return Edge.ByVertices([sv, ev])

    @staticmethod
    def PlaneEquation(face: topologic.Face, mantissa: int = 4) -> dict:
        """
        Returns the a, b, c, d coefficients of the plane equation of the input face. The input face is assumed to be planar.
        
        Parameters
        ----------
        face : topologic.Face
            The input face.
        
        Returns
        -------
        dict
            The dictionary containing the coefficients of the plane equation. The keys of the dictionary are: ["a", "b", "c", "d"].

        """
        from topologicpy.Topology import Topology
        from topologicpy.Vertex import Vertex
        import random
        import time

        if not isinstance(face, topologic.Face):
            print("Face.PlaneEquation - Error: The input face is not a valid topologic face. Returning None.")
            return None
        all_vertices = Topology.Vertices(face)
        if len(all_vertices) < 3:
            print("Face.PlaneEquation - Error: The input face has less than 3 vertices. Returning None.")
            return None
        vertices = random.sample(all_vertices, 3)
        start = time.time()
        end = time.time()
        duration = (end - start)
        while Vertex.AreCollinear(vertices) and duration < 10:
            vertices = random.sample(all_vertices, 3)
            end = time.time()
            duration = (end - start)
        if Vertex.AreCollinear(vertices):
            print("Face.PlaneEquation - Error: Could not sample 3 non-collinear vertices. Returning None.")
            return None
        v1, v2, v3 = vertices
        x1, y1, z1 = Vertex.Coordinates(v1)
        x2, y2, z2 = Vertex.Coordinates(v2)
        x3, y3, z3 = Vertex.Coordinates(v3)
        vector1 = [x2 - x1, y2 - y1, z2 - z1]
        vector2 = [x3 - x1, y3 - y1, z3 - z1]

        cross_product = [vector1[1] * vector2[2] - vector1[2] * vector2[1], -1 * (vector1[0] * vector2[2] - vector1[2] * vector2[0]), vector1[0] * vector2[1] - vector1[1] * vector2[0]]

        a = round(cross_product[0], mantissa)
        b = round(cross_product[1], mantissa)
        c = round(cross_product[2], mantissa)
        d = round(- (cross_product[0] * x1 + cross_product[1] * y1 + cross_product[2] * z1), mantissa)
        return {"a": a, "b": b, "c": c, "d": d}
    
    @staticmethod
    def Planarize(face: topologic.Face, origin: topologic.Vertex = None, direction: list = None) -> topologic.Face:
        """
        Planarizes the input face such that its center of mass is located at the input origin and its normal is pointed in the input direction.

        Parameters
        ----------
        face : topologic.Face
            The input face.
        origin : topologic.Vertex , optional
            The old location to use as the origin of the movement. If set to None, the center of mass of the input face is used. The default is None.
        direction : list , optional
            The direction, expressed as a list of [X,Y,Z] that signifies the direction of the face. If set to None, the normal at *u* 0.5 and *v* 0.5 is considered the direction of the face. The deafult is None.

        Returns
        -------
        topologic.Face
            The planarized face.

        """

        from topologicpy.Vertex import Vertex
        from topologicpy.Wire import Wire
        from topologicpy.Topology import Topology
        from topologicpy.Dictionary import Dictionary

        if not isinstance(face, topologic.Face):
            return None
        if not isinstance(origin, topologic.Vertex):
            origin = Topology.CenterOfMass(face)
        if not isinstance(direction, list):
            direction = Face.NormalAtParameters(face, 0.5, 0.5)
        flatFace = Face.Flatten(face, oldLocation=origin, direction=direction)

        world_origin = Vertex.ByCoordinates(0,0,0)
        # Retrieve the needed transformations
        dictionary = Topology.Dictionary(flatFace)
        xTran = Dictionary.ValueAtKey(dictionary,"xTran")
        yTran = Dictionary.ValueAtKey(dictionary,"yTran")
        zTran = Dictionary.ValueAtKey(dictionary,"zTran")
        phi = Dictionary.ValueAtKey(dictionary,"phi")
        theta = Dictionary.ValueAtKey(dictionary,"theta")

        planarizedFace = Topology.Rotate(flatFace, origin=world_origin, x=0, y=1, z=0, degree=theta)
        planarizedFace = Topology.Rotate(planarizedFace, origin=world_origin, x=0, y=0, z=1, degree=phi)
        planarizedFace = Topology.Translate(planarizedFace, xTran, yTran, zTran)
        return planarizedFace

    @staticmethod
    def Project(faceA: topologic.Face, faceB: topologic.Face, direction : list = None, mantissa: int = 4) -> topologic.Face:
        """
        Creates a projection of the first input face unto the second input face.

        Parameters
        ----------
        faceA : topologic.Face
            The face to be projected.
        faceB : topologic.Face
            The face unto which the first input face will be projected.
        direction : list, optional
            The vector direction of the projection. If None, the reverse vector of the receiving face normal will be used. The default is None.
        mantissa : int , optional
            The desired length of the mantissa. The default is 4.

        Returns
        -------
        topologic.Face
            The projected Face.

        """

        from topologicpy.Wire import Wire

        if not faceA:
            return None
        if not isinstance(faceA, topologic.Face):
            return None
        if not faceB:
            return None
        if not isinstance(faceB, topologic.Face):
            return None

        eb = faceA.ExternalBoundary()
        ib_list = []
        _ = faceA.InternalBoundaries(ib_list)
        p_eb = Wire.Project(wire=eb, face = faceB, direction=direction, mantissa=mantissa)
        p_ib_list = []
        for ib in ib_list:
            temp_ib = Wire.Project(wire=ib, face = faceB, direction=direction, mantissa=mantissa)
            if temp_ib:
                p_ib_list.append(temp_ib)
        return Face.ByWires(p_eb, p_ib_list)

    @staticmethod
    def Rectangle(origin: topologic.Vertex = None, width: float = 1.0, length: float = 1.0, direction: list = [0,0,1], placement: str = "center", tolerance: float = 0.0001) -> topologic.Face:
        """
        Creates a rectangle.

        Parameters
        ----------
        origin : topologic.Vertex, optional
            The location of the origin of the rectangle. The default is None which results in the rectangle being placed at (0,0,0).
        width : float , optional
            The width of the rectangle. The default is 1.0.
        length : float , optional
            The length of the rectangle. The default is 1.0.
        direction : list , optional
            The vector representing the up direction of the rectangle. The default is [0,0,1].
        placement : str , optional
            The description of the placement of the origin of the rectangle. This can be "center", "lowerleft", "upperleft", "lowerright", "upperright". It is case insensitive. The default is "center".
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        topologic.Face
            The created face.

        """
        from topologicpy.Wire import Wire
        wire = Wire.Rectangle(origin=origin, width=width, length=length, direction=direction, placement=placement, tolerance=tolerance)
        if not isinstance(wire, topologic.Wire):
            return None
        return Face.ByWire(wire)
    
    @staticmethod
    def Skeleton(face, tolerance=0.001):
        """
            Creates a straight skeleton. This method is contributed by 高熙鹏 xipeng gao <gaoxipeng1998@gmail.com>
            This algorithm depends on the polyskel code which is included in the library. Polyskel code is found at: https://github.com/Botffy/polyskel


        Parameters
        ----------
        face : topologic.Face
            The input face.
    
        tolerance : float , optional
            The desired tolerance. The default is 0.001. (This is set to a larger number as it was found to work better)

        Returns
        -------
        topologic.Wire
            The created straight skeleton.

        """
        from topologicpy.Wire import Wire
        if not isinstance(face, topologic.Face):
            print("Face.Skeleton - Error: The input face is not a valid topologic face. Retruning None.")
            return None
        return Wire.Roof(face, degree=0, tolerance=tolerance)
    
    @staticmethod
    def Square(origin: topologic.Vertex = None, size: float = 1.0, direction: list = [0,0,1], placement: str = "center", tolerance: float = 0.0001) -> topologic.Face:
        """
        Creates a square.

        Parameters
        ----------
        origin : topologic.Vertex , optional
            The location of the origin of the square. The default is None which results in the square being placed at (0,0,0).
        size : float , optional
            The size of the square. The default is 1.0.
        direction : list , optional
            The vector representing the up direction of the square. The default is [0,0,1].
        placement : str , optional
            The description of the placement of the origin of the square. This can be "center", "lowerleft", "upperleft", "lowerright", or "upperright". It is case insensitive. The default is "center".
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        topologic.Face
            The created square.

        """
        return Face.Rectangle(origin = origin, width = size, length = size, direction = direction, placement = placement, tolerance = tolerance)
    
    @staticmethod
    def Star(origin: topologic.Vertex = None, radiusA: float = 1.0, radiusB: float = 0.4, rays: int = 5, direction: list = [0,0,1], placement: str = "center", tolerance: float = 0.0001) -> topologic.Face:
        """
        Creates a star.

        Parameters
        ----------
        origin : topologic.Vertex, optional
            The location of the origin of the star. The default is None which results in the star being placed at (0,0,0).
        radiusA : float , optional
            The outer radius of the star. The default is 1.0.
        radiusB : float , optional
            The outer radius of the star. The default is 0.4.
        rays : int , optional
            The number of star rays. The default is 5.
        direction : list , optional
            The vector representing the up direction of the star. The default is [0,0,1].
        placement : str , optional
            The description of the placement of the origin of the star. This can be "center", "lowerleft", "upperleft", "lowerright", or "upperright". It is case insensitive. The default is "center".
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        topologic.Face
            The created face.

        """
        from topologicpy.Wire import Wire
        wire = Wire.Star(origin=origin, radiusA=radiusA, radiusB=radiusB, rays=rays, direction=direction, placement=placement, tolerance=tolerance)
        if not isinstance(wire, topologic.Wire):
            return None
        return Face.ByWire(wire)

    @staticmethod
    def Trapezoid(origin: topologic.Vertex = None, widthA: float = 1.0, widthB: float = 0.75, offsetA: float = 0.0, offsetB: float = 0.0, length: float = 1.0, direction: list = [0,0,1], placement: str = "center", tolerance: float = 0.0001) -> topologic.Face:
        """
        Creates a trapezoid.

        Parameters
        ----------
        origin : topologic.Vertex, optional
            The location of the origin of the trapezoid. The default is None which results in the trapezoid being placed at (0,0,0).
        widthA : float , optional
            The width of the bottom edge of the trapezoid. The default is 1.0.
        widthB : float , optional
            The width of the top edge of the trapezoid. The default is 0.75.
        offsetA : float , optional
            The offset of the bottom edge of the trapezoid. The default is 0.0.
        offsetB : float , optional
            The offset of the top edge of the trapezoid. The default is 0.0.
        length : float , optional
            The length of the trapezoid. The default is 1.0.
        direction : list , optional
            The vector representing the up direction of the trapezoid. The default is [0,0,1].
        placement : str , optional
            The description of the placement of the origin of the trapezoid. This can be "center", or "lowerleft". It is case insensitive. The default is "center".
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        topologic.Face
            The created trapezoid.

        """
        from topologicpy.Wire import Wire
        wire = Wire.Trapezoid(origin=origin, widthA=widthA, widthB=widthB, offsetA=offsetA, offsetB=offsetB, length=length, direction=direction, placement=placement, tolerance=tolerance)
        if not isinstance(wire, topologic.Wire):
            return None
        return Face.ByWire(wire)

    @staticmethod
    def Triangulate(face:topologic.Face) -> list:
        """
        Triangulates the input face and returns a list of faces.

        Parameters
        ----------
        face : topologic.Face
            The input face.

        Returns
        -------
        list
            The list of triangles of the input face.

        """
        from topologicpy.Vertex import Vertex
        from topologicpy.Wire import Wire
        from topologicpy.Topology import Topology
        from topologicpy.Dictionary import Dictionary

        if not isinstance(face, topologic.Face):
            return None
        flatFace = Face.Flatten(face)
        world_origin = Vertex.ByCoordinates(0,0,0)
        # Retrieve the needed transformations
        dictionary = Topology.Dictionary(flatFace)
        xTran = Dictionary.ValueAtKey(dictionary,"xTran")
        yTran = Dictionary.ValueAtKey(dictionary,"yTran")
        zTran = Dictionary.ValueAtKey(dictionary,"zTran")
        phi = Dictionary.ValueAtKey(dictionary,"phi")
        theta = Dictionary.ValueAtKey(dictionary,"theta")
    
        faceTriangles = []
        for i in range(0,5,1):
            try:
                _ = topologic.FaceUtility.Triangulate(flatFace, float(i)*0.1, faceTriangles)
                break
            except:
                continue
        if len(faceTriangles) < 1:
            return [face]
        finalFaces = []
        for f in faceTriangles:
            f = Topology.Rotate(f, origin=world_origin, x=0, y=1, z=0, degree=theta)
            f = Topology.Rotate(f, origin=world_origin, x=0, y=0, z=1, degree=phi)
            f = Topology.Translate(f, xTran, yTran, zTran)
            if Face.Angle(face, f) > 90:
                wire = Face.ExternalBoundary(f)
                wire = Wire.Invert(wire)
                f = topologic.Face.ByExternalBoundary(wire)
                finalFaces.append(f)
            else:
                finalFaces.append(f)
        return finalFaces

    @staticmethod
    def TrimByWire(face: topologic.Face, wire: topologic.Wire, reverse: bool = False) -> topologic.Face:
        """
        Trims the input face by the input wire.

        Parameters
        ----------
        face : topologic.Face
            The input face.
        wire : topologic.Wire
            The input wire.
        reverse : bool , optional
            If set to True, the effect of the trim will be reversed. The default is False.

        Returns
        -------
        topologic.Face
            The resulting trimmed face.

        """
        if not isinstance(face, topologic.Face):
            return None
        if not isinstance(wire, topologic.Wire):
            return face
        trimmed_face = topologic.FaceUtility.TrimByWire(face, wire, False)
        if reverse:
            trimmed_face = face.Difference(trimmed_face)
        return trimmed_face
    
    @staticmethod
    def UnFlatten(face: topologic.Face, dictionary: topologic.Dictionary):
        from topologicpy.Vertex import Vertex
        from topologicpy.Topology import Topology
        from topologicpy.Dictionary import Dictionary
        theta = Dictionary.ValueAtKey(dictionary, "theta")
        phi = Dictionary.ValueAtKey(dictionary, "phi")
        xTran = Dictionary.ValueAtKey(dictionary, "xTran")
        yTran = Dictionary.ValueAtKey(dictionary, "yTran")
        zTran = Dictionary.ValueAtKey(dictionary, "zTran")
        newFace = Topology.Rotate(face, origin=Vertex.Origin(), x=0, y=1, z=0, degree=theta)
        newFace = Topology.Rotate(newFace, origin=Vertex.Origin(), x=0, y=0, z=1, degree=phi)
        newFace = Topology.Translate(newFace, xTran, yTran, zTran)
        return newFace
    
    @staticmethod
    def VertexByParameters(face: topologic.Face, u: float = 0.5, v: float = 0.5) -> topologic.Vertex:
        """
        Creates a vertex at the *u* and *v* parameters of the input face.

        Parameters
        ----------
        face : topologic.Face
            The input face.
        u : float , optional
            The *u* parameter of the input face. The default is 0.5.
        v : float , optional
            The *v* parameter of the input face. The default is 0.5.

        Returns
        -------
        vertex : topologic vertex
            The created vertex.

        """
        if not isinstance(face, topologic.Face):
            return None
        return topologic.FaceUtility.VertexAtParameters(face, u, v)
    
    @staticmethod
    def VertexParameters(face: topologic.Face, vertex: topologic.Vertex, outputType: str = "uv", mantissa: int = 4) -> list:
        """
        Returns the *u* and *v* parameters of the input face at the location of the input vertex.

        Parameters
        ----------
        face : topologic.Face
            The input face.
        vertex : topologic.Vertex
            The input vertex.
        outputType : string , optional
            The string defining the desired output. This can be any subset or permutation of "uv". It is case insensitive. The default is "uv".
        mantissa : int , optional
            The desired length of the mantissa. The default is 4.

        Returns
        -------
        list
            The list of *u* and/or *v* as specified by the outputType input.

        """
        if not isinstance(face, topologic.Face):
            return None
        if not isinstance(vertex, topologic.Vertex):
            return None
        params = topologic.FaceUtility.ParametersAtVertex(face, vertex)
        u = round(params[0], mantissa)
        v = round(params[1], mantissa)
        outputType = list(outputType.lower())
        returnResult = []
        for param in outputType:
            if param == "u":
                returnResult.append(u)
            elif param == "v":
                returnResult.append(v)
        return returnResult

    @staticmethod
    def Vertices(face: topologic.Face) -> list:
        """
        Returns the vertices of the input face.

        Parameters
        ----------
        face : topologic.Face
            The input face.

        Returns
        -------
        list
            The list of vertices.

        """
        if not isinstance(face, topologic.Face):
            return None
        vertices = []
        _ = face.Vertices(None, vertices)
        return vertices
    
    @staticmethod
    def Wire(face: topologic.Face) -> topologic.Wire:
        """
        Returns the external boundary (closed wire) of the input face.

        Parameters
        ----------
        face : topologic.Face
            The input face.

        Returns
        -------
        topologic.Wire
            The external boundary of the input face.

        """
        return face.ExternalBoundary()
    
    @staticmethod
    def Wires(face: topologic.Face) -> list:
        """
        Returns the wires of the input face.

        Parameters
        ----------
        face : topologic.Face
            The input face.

        Returns
        -------
        list
            The list of wires.

        """
        if not isinstance(face, topologic.Face):
            return None
        wires = []
        _ = face.Wires(None, wires)
        return wires

Ancestors

  • topologic.Face
  • topologic.Topology
  • topologic.TopologicalQuery
  • pybind11_builtins.pybind11_object

Static methods

def AddInternalBoundaries(face: topologic.Face, wires: list) ‑> topologic.Face

Adds internal boundaries (closed wires) to the input face. Internal boundaries are considered holes in the input face.

Parameters

face : topologic.Face
The input face.
wires : list
The input list of internal boundaries (closed wires).

Returns

topologic.Face
The created face with internal boundaries added to it.
Expand source code
@staticmethod
def AddInternalBoundaries(face: topologic.Face, wires: list) -> topologic.Face:
    """
    Adds internal boundaries (closed wires) to the input face. Internal boundaries are considered holes in the input face.

    Parameters
    ----------
    face : topologic.Face
        The input face.
    wires : list
        The input list of internal boundaries (closed wires).

    Returns
    -------
    topologic.Face
        The created face with internal boundaries added to it.

    """
    if not face:
        return None
    if not isinstance(face, topologic.Face):
        return None
    if not wires:
        return face
    if not isinstance(wires, list):
        return face
    wireList = [w for w in wires if isinstance(w, topologic.Wire)]
    if len(wireList) < 1:
        return face
    faceeb = face.ExternalBoundary()
    faceibList = []
    _ = face.InternalBoundaries(faceibList)
    for wire in wires:
        faceibList.append(wire)
    return topologic.Face.ByExternalInternalBoundaries(faceeb, faceibList)
def AddInternalBoundariesCluster(face: topologic.Face, cluster: topologic.Cluster) ‑> topologic.Face

Adds the input cluster of internal boundaries (closed wires) to the input face. Internal boundaries are considered holes in the input face.

Parameters

face : topologic.Face
The input face.
cluster : topologic.Cluster
The input cluster of internal boundaries (topologic wires).

Returns

topologic.Face
The created face with internal boundaries added to it.
Expand source code
@staticmethod
def AddInternalBoundariesCluster(face: topologic.Face, cluster: topologic.Cluster) -> topologic.Face:
    """
    Adds the input cluster of internal boundaries (closed wires) to the input face. Internal boundaries are considered holes in the input face.

    Parameters
    ----------
    face : topologic.Face
        The input face.
    cluster : topologic.Cluster
        The input cluster of internal boundaries (topologic wires).

    Returns
    -------
    topologic.Face
        The created face with internal boundaries added to it.

    """
    if not face:
        return None
    if not isinstance(face, topologic.Face):
        return None
    if not cluster:
        return face
    if not isinstance(cluster, topologic.Cluster):
        return face
    wires = []
    _ = cluster.Wires(None, wires)
    return Face.AddInternalBoundaries(face, wires)
def Angle(faceA: topologic.Face, faceB: topologic.Face, mantissa: int = 4) ‑> float

Returns the angle in degrees between the two input faces.

Parameters

faceA : topologic.Face
The first input face.
faceB : topologic.Face
The second input face.
mantissa : int , optional
The desired length of the mantissa. The default is 4.

Returns

float
The angle in degrees between the two input faces.
Expand source code
@staticmethod
def Angle(faceA: topologic.Face, faceB: topologic.Face, mantissa: int = 4) -> float:
    """
    Returns the angle in degrees between the two input faces.

    Parameters
    ----------
    faceA : topologic.Face
        The first input face.
    faceB : topologic.Face
        The second input face.
    mantissa : int , optional
        The desired length of the mantissa. The default is 4.

    Returns
    -------
    float
        The angle in degrees between the two input faces.

    """
    from topologicpy.Vector import Vector
    if not faceA or not isinstance(faceA, topologic.Face):
        return None
    if not faceB or not isinstance(faceB, topologic.Face):
        return None
    dirA = Face.NormalAtParameters(faceA, 0.5, 0.5, "xyz", 3)
    dirB = Face.NormalAtParameters(faceB, 0.5, 0.5, "xyz", 3)
    return round((Vector.Angle(dirA, dirB)), mantissa)
def Area(face: topologic.Face, mantissa: int = 4) ‑> float

Returns the area of the input face.

Parameters

face : topologic.Face
The input face.
mantissa : int , optional
The desired length of the mantissa. The default is 4.

Returns

float
The area of the input face.
Expand source code
@staticmethod
def Area(face: topologic.Face, mantissa: int = 4) -> float:
    """
    Returns the area of the input face.

    Parameters
    ----------
    face : topologic.Face
        The input face.
    mantissa : int , optional
        The desired length of the mantissa. The default is 4.

    Returns
    -------
    float
        The area of the input face.

    """
    if not isinstance(face, topologic.Face):
        return None
    area = None
    try:
        area = round(topologic.FaceUtility.Area(face), mantissa)
    except:
        area = None
    return area
def BoundingRectangle(topology: topologic.Topology, optimize: int = 0) ‑> topologic.Face

Returns a face representing a bounding rectangle of the input topology. The returned face contains a dictionary with key "zrot" that represents rotations around the Z axis. If applied the resulting face will become axis-aligned.

Parameters

topology : topologic.Topology
The input topology.
optimize : int , optional
If set to an integer from 1 (low optimization) to 10 (high optimization), the method will attempt to optimize the bounding rectangle so that it reduces its surface area. The default is 0 which will result in an axis-aligned bounding rectangle. The default is 0.

Returns

topologic.Face
The bounding rectangle of the input topology.
Expand source code
@staticmethod
def BoundingRectangle(topology: topologic.Topology, optimize: int = 0) -> topologic.Face:
    """
    Returns a face representing a bounding rectangle of the input topology. The returned face contains a dictionary with key "zrot" that represents rotations around the Z axis. If applied the resulting face will become axis-aligned.

    Parameters
    ----------
    topology : topologic.Topology
        The input topology.
    optimize : int , optional
        If set to an integer from 1 (low optimization) to 10 (high optimization), the method will attempt to optimize the bounding rectangle so that it reduces its surface area. The default is 0 which will result in an axis-aligned bounding rectangle. The default is 0.
    
    Returns
    -------
    topologic.Face
        The bounding rectangle of the input topology.

    """
    from topologicpy.Wire import Wire
    from topologicpy.Face import Face
    from topologicpy.Cluster import Cluster
    from topologicpy.Topology import Topology
    from topologicpy.Dictionary import Dictionary
    def bb(topology):
        vertices = []
        _ = topology.Vertices(None, vertices)
        x = []
        y = []
        for aVertex in vertices:
            x.append(aVertex.X())
            y.append(aVertex.Y())
        minX = min(x)
        minY = min(y)
        maxX = max(x)
        maxY = max(y)
        return [minX, minY, maxX, maxY]

    if not isinstance(topology, topologic.Topology):
        return None
    vertices = Topology.SubTopologies(topology, subTopologyType="vertex")
    topology = Cluster.ByTopologies(vertices)
    boundingBox = bb(topology)
    minX = boundingBox[0]
    minY = boundingBox[1]
    maxX = boundingBox[2]
    maxY = boundingBox[3]
    w = abs(maxX - minX)
    l = abs(maxY - minY)
    best_area = l*w
    orig_area = best_area
    best_z = 0
    best_bb = boundingBox
    origin = Topology.Centroid(topology)
    optimize = min(max(optimize, 0), 10)
    if optimize > 0:
        factor = (round(((11 - optimize)/30 + 0.57), 2))
        flag = False
        for n in range(10,0,-1):
            if flag:
                break
            za = n
            zb = 90+n
            zc = n
            for z in range(za,zb,zc):
                if flag:
                    break
                t = Topology.Rotate(topology, origin=origin, x=0,y=0,z=1, degree=z)
                minX, minY, maxX, maxY = bb(t)
                w = abs(maxX - minX)
                l = abs(maxY - minY)
                area = l*w
                if area < orig_area*factor:
                    best_area = area
                    best_z = z
                    best_bb = [minX, minY, maxX, maxY]
                    flag = True
                    break
                if area < best_area:
                    best_area = area
                    best_z = z
                    best_bb = [minX, minY, maxX, maxY]
                    
    else:
        best_bb = boundingBox

    minX, minY, maxX, maxY = best_bb
    vb1 = topologic.Vertex.ByCoordinates(minX, minY, 0)
    vb2 = topologic.Vertex.ByCoordinates(maxX, minY, 0)
    vb3 = topologic.Vertex.ByCoordinates(maxX, maxY, 0)
    vb4 = topologic.Vertex.ByCoordinates(minX, maxY, 0)

    baseWire = Wire.ByVertices([vb1, vb2, vb3, vb4], close=True)
    baseFace = Face.ByWire(baseWire)
    baseFace = Topology.Rotate(baseFace, origin=origin, x=0,y=0,z=1, degree=-best_z)
    dictionary = Dictionary.ByKeysValues(["zrot"], [best_z])
    baseFace = Topology.SetDictionary(baseFace, dictionary)
    return baseFace
def ByEdges(edges: list) ‑> topologic.Face

Creates a face from the input list of edges.

Parameters

edges : list
The input list of edges.

Returns

face : topologic.Face
The created face.
Expand source code
@staticmethod
def ByEdges(edges: list) -> topologic.Face:
    """
    Creates a face from the input list of edges.

    Parameters
    ----------
    edges : list
        The input list of edges.

    Returns
    -------
    face : topologic.Face
        The created face.

    """
    from topologicpy.Wire import Wire
    wire = Wire.ByEdges(edges)
    if not wire:
        return None
    if not isinstance(wire, topologic.Wire):
        return None
    return Face.ByWire(wire)
def ByEdgesCluster(cluster: topologic.Cluster) ‑> topologic.Face

Creates a face from the input cluster of edges.

Parameters

cluster : topologic.Cluster
The input cluster of edges.

Returns

face : topologic.Face
The created face.
Expand source code
@staticmethod
def ByEdgesCluster(cluster: topologic.Cluster) -> topologic.Face:
    """
    Creates a face from the input cluster of edges.

    Parameters
    ----------
    cluster : topologic.Cluster
        The input cluster of edges.

    Returns
    -------
    face : topologic.Face
        The created face.

    """
    from topologicpy.Cluster import Cluster
    if not isinstance(cluster, topologic.Cluster):
        return None
    edges = Cluster.Edges(cluster)
    return Face.ByEdges(edges)
def ByOffset(face: topologic.Face, offset: float = 1.0, miter: bool = False, miterThreshold: float = None, offsetKey: str = None, miterThresholdKey: str = None, step: bool = True) ‑> topologic.Face

Creates an offset wire from the input wire.

Parameters

wire : topologic.Wire
The input wire.
offset : float , optional
The desired offset distance. The default is 1.0.
miter : bool , optional
if set to True, the corners will be mitered. The default is False.
miterThreshold : float , optional
The distance beyond which a miter should be added. The default is None which means the miter threshold is set to the offset distance multiplied by the square root of 2.
offsetKey : str , optional
If specified, the dictionary of the edges will be queried for this key to sepcify the desired offset. The default is None.
miterThresholdKey : str , optional
If specified, the dictionary of the vertices will be queried for this key to sepcify the desired miter threshold distance. The default is None.
step : bool , optional
If set to True, The transition between collinear edges with different offsets will be a step. Otherwise, it will be a continous edge. The default is True.

Returns

topologic.Wire
The created wire.
Expand source code
@staticmethod
def ByOffset(face: topologic.Face, offset: float = 1.0, miter: bool = False, miterThreshold: float = None, offsetKey: str = None, miterThresholdKey: str = None, step: bool = True) -> topologic.Face:
    """
    Creates an offset wire from the input wire.

    Parameters
    ----------
    wire : topologic.Wire
        The input wire.
    offset : float , optional
        The desired offset distance. The default is 1.0.
    miter : bool , optional
        if set to True, the corners will be mitered. The default is False.
    miterThreshold : float , optional
        The distance beyond which a miter should be added. The default is None which means the miter threshold is set to the offset distance multiplied by the square root of 2.
    offsetKey : str , optional
        If specified, the dictionary of the edges will be queried for this key to sepcify the desired offset. The default is None.
    miterThresholdKey : str , optional
        If specified, the dictionary of the vertices will be queried for this key to sepcify the desired miter threshold distance. The default is None.
    step : bool , optional
        If set to True, The transition between collinear edges with different offsets will be a step. Otherwise, it will be a continous edge. The default is True.

    Returns
    -------
    topologic.Wire
        The created wire.

    """
    from topologicpy.Wire import Wire

    eb = Face.Wire(face)
    internal_boundaries = Face.InternalBoundaries(face)
    offset_external_boundary = Wire.ByOffset(wire=eb, offset=offset, miter=miter, miterThreshold=miterThreshold, offsetKey=offsetKey, miterThresholdKey=miterThresholdKey, step=step)
    offset_internal_boundaries = []
    for internal_boundary in internal_boundaries:
        offset_internal_boundaries.append(Wire.ByOffset(wire=internal_boundary, offset=offset, miter=miter, miterThreshold=miterThreshold, offsetKey=offsetKey, miterThresholdKey=miterThresholdKey, step=step))
    return Face.ByWires(offset_external_boundary, offset_internal_boundaries)
def ByShell(shell: topologic.Shell, angTolerance: float = 0.1) ‑> topologic.Face

Creates a face by merging the faces of the input shell.

Parameters

shell : topologic.Shell
The input shell.
angTolerance : float , optional
The desired angular tolerance. The default is 0.1.

Returns

topologic.Face
The created face.
Expand source code
@staticmethod
def ByShell(shell: topologic.Shell, angTolerance: float = 0.1)-> topologic.Face:
    """
    Creates a face by merging the faces of the input shell.

    Parameters
    ----------
    shell : topologic.Shell
        The input shell.
    angTolerance : float , optional
        The desired angular tolerance. The default is 0.1.

    Returns
    -------
    topologic.Face
        The created face.

    """
    from topologicpy.Vertex import Vertex
    from topologicpy.Wire import Wire
    from topologicpy.Shell import Shell
    from topologicpy.Topology import Topology
    
    def planarizeList(wireList):
        returnList = []
        for aWire in wireList:
            returnList.append(Wire.Planarize(aWire))
        return returnList
    
    ext_boundary = Shell.ExternalBoundary(shell)
    ext_boundary = Topology.RemoveCollinearEdges(ext_boundary, angTolerance)
    if not Topology.IsPlanar(ext_boundary):
        ext_boundary = Wire.Planarize(ext_boundary)

    if isinstance(ext_boundary, topologic.Wire):
        try:
            return topologic.Face.ByExternalBoundary(Topology.RemoveCollinearEdges(ext_boundary, angTolerance))
        except:
            try:
                w = Wire.Planarize(ext_boundary)
                f = Face.ByWire(w)
                return f
            except:
                print("FaceByPlanarShell - Error: The input Wire is not planar and could not be fixed. Returning None.")
                return None
    elif isinstance(ext_boundary, topologic.Cluster):
        wires = []
        _ = ext_boundary.Wires(None, wires)
        faces = []
        areas = []
        for aWire in wires:
            try:
                aFace = topologic.Face.ByExternalBoundary(Topology.RemoveCollinearEdges(aWire, angTolerance))
            except:
                aFace = topologic.Face.ByExternalBoundary(Wire.Planarize(Topology.RemoveCollinearEdges(aWire, angTolerance)))
            anArea = Face.Area(aFace)
            faces.append(aFace)
            areas.append(anArea)
        max_index = areas.index(max(areas))
        ext_boundary = faces[max_index]
        int_boundaries = list(set(faces) - set([ext_boundary]))
        int_wires = []
        for int_boundary in int_boundaries:
            temp_wires = []
            _ = int_boundary.Wires(None, temp_wires)
            int_wires.append(Topology.RemoveCollinearEdges(temp_wires[0], angTolerance))
        temp_wires = []
        _ = ext_boundary.Wires(None, temp_wires)
        ext_wire = Topology.RemoveCollinearEdges(temp_wires[0], angTolerance)
        try:
            return topologic.Face.ByExternalInternalBoundaries(ext_wire, int_wires)
        except:
            return topologic.Face.ByExternalInternalBoundaries(Wire.Planarize(ext_wire), planarizeList(int_wires))
    else:
        return None
def ByVertices(vertices: list) ‑> topologic.Face

Creates a face from the input list of vertices.

Parameters

vertices : list
The input list of vertices.

Returns

topologic.Face
The created face.
Expand source code
@staticmethod
def ByVertices(vertices: list) -> topologic.Face:
    
    """
    Creates a face from the input list of vertices.

    Parameters
    ----------
    vertices : list
        The input list of vertices.

    Returns
    -------
    topologic.Face
        The created face.

    """
    from topologicpy.Topology import Topology
    from topologicpy.Wire import Wire

    if not isinstance(vertices, list):
        return None
    vertexList = [x for x in vertices if isinstance(x, topologic.Vertex)]
    if len(vertexList) < 3:
        return None
    
    w = Wire.ByVertices(vertexList)
    f = Face.ByExternalBoundary(w)
    return f
def ByVerticesCluster(cluster: topologic.Cluster) ‑> topologic.Face

Creates a face from the input cluster of vertices.

Parameters

cluster : topologic.Cluster
The input cluster of vertices.

Returns

topologic.Face
The crearted face.
Expand source code
@staticmethod
def ByVerticesCluster(cluster: topologic.Cluster) -> topologic.Face:
    """
    Creates a face from the input cluster of vertices.

    Parameters
    ----------
    cluster : topologic.Cluster
        The input cluster of vertices.

    Returns
    -------
    topologic.Face
        The crearted face.

    """
    from topologicpy.Cluster import Cluster
    if not isinstance(cluster, topologic.Cluster):
        return None
    vertices = Cluster.Vertices(cluster)
    return Face.ByVertices(vertices)
def ByWire(wire: topologic.Wire) ‑> topologic.Face

Creates a face from the input closed wire.

Parameters

wire : topologic.Wire
The input wire.

Returns

topologic.Face or list
The created face. If the wire is non-planar, the method will attempt to triangulate the wire and return a list of faces.
Expand source code
@staticmethod
def ByWire(wire: topologic.Wire) -> topologic.Face:
    """
    Creates a face from the input closed wire.

    Parameters
    ----------
    wire : topologic.Wire
        The input wire.

    Returns
    -------
    topologic.Face or list
        The created face. If the wire is non-planar, the method will attempt to triangulate the wire and return a list of faces.

    """
    from topologicpy.Vertex import Vertex
    from topologicpy.Wire import Wire
    from topologicpy.Shell import Shell
    from topologicpy.Cluster import Cluster
    from topologicpy.Topology import Topology
    from topologicpy.Dictionary import Dictionary
    import random

    def triangulateWire(wire):
        wire = Topology.RemoveCollinearEdges(wire)
        vertices = Wire.Vertices(wire)
        shell = Shell.Delaunay(vertices)
        if isinstance(shell, topologic.Shell):
            return Shell.Faces(shell)
        else:
            return []
    if not isinstance(wire, topologic.Wire):
        return None
    if not Wire.IsClosed(wire):
        return None
    
    edges = Wire.Edges(wire)
    wire = Topology.SelfMerge(Cluster.ByTopologies(edges))
    vertices = Wire.Vertices(wire)
    try:
        fList = topologic.Face.ByExternalBoundary(wire)
    except:
        if len(vertices) > 3:
            fList = triangulateWire(wire)
        else:
            fList = []
    
    if not isinstance(fList, list):
        fList = [fList]

    returnList = []
    for f in fList:
        if Face.Area(f) < 0:
            wire = Face.ExternalBoundary(f)
            wire = Wire.Invert(wire)
            try:
                f = topologic.Face.ByExternalBoundary(wire)
                returnList.append(f)
            except:
                pass
        else:
            returnList.append(f)
    if len(returnList) == 0:
        return None
    elif len(returnList) == 1:
        return returnList[0]
    else:
        return returnList
def ByWires(externalBoundary: topologic.Wire, internalBoundaries: list = []) ‑> topologic.Face

Creates a face from the input external boundary (closed wire) and the input list of internal boundaries (closed wires).

Parameters

externalBoundary : topologic.Wire
The input external boundary.
internalBoundaries : list , optional
The input list of internal boundaries (closed wires). The default is an empty list.

Returns

topologic.Face
The created face.
Expand source code
@staticmethod
def ByWires(externalBoundary: topologic.Wire, internalBoundaries: list = []) -> topologic.Face:
    """
    Creates a face from the input external boundary (closed wire) and the input list of internal boundaries (closed wires).

    Parameters
    ----------
    externalBoundary : topologic.Wire
        The input external boundary.
    internalBoundaries : list , optional
        The input list of internal boundaries (closed wires). The default is an empty list.

    Returns
    -------
    topologic.Face
        The created face.

    """
    if not isinstance(externalBoundary, topologic.Wire):
        return None
    if not Wire.IsClosed(externalBoundary):
        return None
    ibList = [x for x in internalBoundaries if isinstance(x, topologic.Wire) and Wire.IsClosed(x)]
    return topologic.Face.ByExternalInternalBoundaries(externalBoundary, ibList)
def ByWiresCluster(externalBoundary: topologic.Wire, internalBoundariesCluster: topologic.Cluster = None) ‑> topologic.Face

Creates a face from the input external boundary (closed wire) and the input cluster of internal boundaries (closed wires).

Parameters

externalBoundary : topologic.Wire
The input external boundary (closed wire).
internalBoundariesCluster : topologic.Cluster
The input cluster of internal boundaries (closed wires). The default is None.

Returns

topologic.Face
The created face.
Expand source code
@staticmethod
def ByWiresCluster(externalBoundary: topologic.Wire, internalBoundariesCluster: topologic.Cluster = None) -> topologic.Face:
    """
    Creates a face from the input external boundary (closed wire) and the input cluster of internal boundaries (closed wires).

    Parameters
    ----------
    externalBoundary : topologic.Wire
        The input external boundary (closed wire).
    internalBoundariesCluster : topologic.Cluster
        The input cluster of internal boundaries (closed wires). The default is None.

    Returns
    -------
    topologic.Face
        The created face.

    """
    from topologicpy.Wire import Wire
    from topologicpy.Cluster import Cluster
    if not isinstance(externalBoundary, topologic.Wire):
        return None
    if not Wire.IsClosed(externalBoundary):
        return None
    if not internalBoundariesCluster:
        internalBoundaries = []
    elif not isinstance(internalBoundariesCluster, topologic.Cluster):
        return None
    else:
        internalBoundaries = Cluster.Wires(internalBoundariesCluster)
    return Face.ByWires(externalBoundary, internalBoundaries)
def Circle(origin: topologic.Vertex = None, radius: float = 0.5, sides: int = 16, fromAngle: float = 0.0, toAngle: float = 360.0, direction: list = [0, 0, 1], placement: str = 'center', tolerance: float = 0.0001) ‑> topologic.Face

Creates a circle.

Parameters

origin : topologic.Vertex, optional
The location of the origin of the circle. The default is None which results in the circle being placed at (0,0,0).
radius : float , optional
The radius of the circle. The default is 1.
sides : int , optional
The number of sides of the circle. The default is 16.
fromAngle : float , optional
The angle in degrees from which to start creating the arc of the circle. The default is 0.
toAngle : float , optional
The angle in degrees at which to end creating the arc of the circle. The default is 360.
direction : list , optional
The vector representing the up direction of the circle. The default is [0,0,1].
placement : str , optional
The description of the placement of the origin of the circle. This can be "center", "lowerleft", "upperleft", "lowerright", or "upperright". It is case insensitive. The default is "center".
tolerance : float , optional
The desired tolerance. The default is 0.0001.

Returns

topologic.Face
The created circle.
Expand source code
@staticmethod
def Circle(origin: topologic.Vertex = None, radius: float = 0.5, sides: int = 16, fromAngle: float = 0.0, toAngle: float = 360.0, direction: list = [0,0,1],
               placement: str = "center", tolerance: float = 0.0001) -> topologic.Face:
    """
    Creates a circle.

    Parameters
    ----------
    origin : topologic.Vertex, optional
        The location of the origin of the circle. The default is None which results in the circle being placed at (0,0,0).
    radius : float , optional
        The radius of the circle. The default is 1.
    sides : int , optional
        The number of sides of the circle. The default is 16.
    fromAngle : float , optional
        The angle in degrees from which to start creating the arc of the circle. The default is 0.
    toAngle : float , optional
        The angle in degrees at which to end creating the arc of the circle. The default is 360.
    direction : list , optional
        The vector representing the up direction of the circle. The default is [0,0,1].
    placement : str , optional
        The description of the placement of the origin of the circle. This can be "center", "lowerleft", "upperleft", "lowerright", or "upperright". It is case insensitive. The default is "center".
    tolerance : float , optional
        The desired tolerance. The default is 0.0001.

    Returns
    -------
    topologic.Face
        The created circle.

    """
    from topologicpy.Wire import Wire
    wire = Wire.Circle(origin=origin, radius=radius, sides=sides, fromAngle=fromAngle, toAngle=toAngle, close=True, direction=direction, placement=placement, tolerance=tolerance)
    if not isinstance(wire, topologic.Wire):
        return None
    return Face.ByWire(wire)
def Compactness(face: topologic.Face, mantissa: int = 4) ‑> float

Returns the compactness measure of the input face. See https://en.wikipedia.org/wiki/Compactness_measure_of_a_shape

Parameters

face : topologic.Face
The input face.
mantissa : int , optional
The desired length of the mantissa. The default is 4.

Returns

float
The compactness measure of the input face.
Expand source code
@staticmethod
def Compactness(face: topologic.Face, mantissa: int = 4) -> float:
    """
    Returns the compactness measure of the input face. See https://en.wikipedia.org/wiki/Compactness_measure_of_a_shape

    Parameters
    ----------
    face : topologic.Face
        The input face.
    mantissa : int , optional
        The desired length of the mantissa. The default is 4.

    Returns
    -------
    float
        The compactness measure of the input face.

    """
    exb = face.ExternalBoundary()
    edges = []
    _ = exb.Edges(None, edges)
    perimeter = 0.0
    for anEdge in edges:
        perimeter = perimeter + abs(topologic.EdgeUtility.Length(anEdge))
    area = abs(Face.Area(face))
    compactness  = 0
    #From https://en.wikipedia.org/wiki/Compactness_measure_of_a_shape

    if area <= 0:
        return None
    if perimeter <= 0:
        return None
    compactness = (math.pi*(2*math.sqrt(area/math.pi)))/perimeter
    return round(compactness, mantissa)
def CompassAngle(face: topologic.Face, north: list = None, mantissa: int = 4) ‑> float

Returns the horizontal compass angle in degrees between the normal vector of the input face and the input vector. The angle is measured in counter-clockwise fashion. Only the first two elements of the vectors are considered.

Parameters

face : topologic.Face
The input face.
north : list , optional
The second vector representing the north direction. The default is the positive YAxis ([0,1,0]).
mantissa : int, optional
The length of the desired mantissa. The default is 4.
tolerance : float , optional
The desired tolerance. The default is 0.0001.

Returns

float
The horizontal compass angle in degrees between the direction of the face and the second input vector.
Expand source code
@staticmethod
def CompassAngle(face: topologic.Face, north: list = None, mantissa: int = 4) -> float:
    """
    Returns the horizontal compass angle in degrees between the normal vector of the input face and the input vector. The angle is measured in counter-clockwise fashion. Only the first two elements of the vectors are considered.

    Parameters
    ----------
    face : topologic.Face
        The input face.
    north : list , optional
        The second vector representing the north direction. The default is the positive YAxis ([0,1,0]).
    mantissa : int, optional
        The length of the desired mantissa. The default is 4.
    tolerance : float , optional
        The desired tolerance. The default is 0.0001.

    Returns
    -------
    float
        The horizontal compass angle in degrees between the direction of the face and the second input vector.

    """
    from topologicpy.Vector import Vector
    if not isinstance(face, topologic.Face):
        return None
    if not north:
        north = Vector.North()
    dirA = Face.NormalAtParameters(face,mantissa=mantissa)
    return Vector.CompassAngle(vectorA=dirA, vectorB=north, mantissa=mantissa)
def Edges(face: topologic.Face) ‑> list

Returns the edges of the input face.

Parameters

face : topologic.Face
The input face.

Returns

list
The list of edges.
Expand source code
@staticmethod
def Edges(face: topologic.Face) -> list:
    """
    Returns the edges of the input face.

    Parameters
    ----------
    face : topologic.Face
        The input face.

    Returns
    -------
    list
        The list of edges.

    """
    if not isinstance(face, topologic.Face):
        return None
    edges = []
    _ = face.Edges(None, edges)
    return edges
def Einstein(origin: topologic.Vertex = None, radius: float = 0.5, direction: list = [0, 0, 1], placement: str = 'center') ‑> topologic.Face

Creates an aperiodic monotile, also called an 'einstein' tile (meaning one tile in German, not the name of the famous physist). See https://arxiv.org/abs/2303.10798

Parameters

origin : topologic.Vertex , optional
The location of the origin of the tile. The default is None which results in the tiles first vertex being placed at (0,0,0).
radius : float , optional
The radius of the hexagon determining the size of the tile. The default is 0.5.
direction : list , optional
The vector representing the up direction of the ellipse. The default is [0,0,1].
placement : str , optional
The description of the placement of the origin of the hexagon determining the location of the tile. This can be "center", or "lowerleft". It is case insensitive. The default is "center".
Expand source code
@staticmethod
def Einstein(origin: topologic.Vertex = None, radius: float = 0.5, direction: list = [0,0,1], placement: str = "center") -> topologic.Face:
    """
    Creates an aperiodic monotile, also called an 'einstein' tile (meaning one tile in German, not the name of the famous physist). See https://arxiv.org/abs/2303.10798

    Parameters
    ----------
    origin : topologic.Vertex , optional
        The location of the origin of the tile. The default is None which results in the tiles first vertex being placed at (0,0,0).
    radius : float , optional
        The radius of the hexagon determining the size of the tile. The default is 0.5.
    direction : list , optional
        The vector representing the up direction of the ellipse. The default is [0,0,1].
    placement : str , optional
        The description of the placement of the origin of the hexagon determining the location of the tile. This can be "center", or "lowerleft". It is case insensitive. The default is "center".
    
    """
    from topologicpy.Wire import Wire
    wire = Wire.Einstein(origin=origin, radius=radius, direction=direction, placement=placement)
    if not isinstance(wire, topologic.Wire):
        return None
    return Face.ByWire(wire)
def ExternalBoundary(face: topologic.Face) ‑> topologic.Wire

Returns the external boundary (closed wire) of the input face.

Parameters

face : topologic.Face
The input face.

Returns

topologic.Wire
The external boundary of the input face.
Expand source code
@staticmethod
def ExternalBoundary(face: topologic.Face) -> topologic.Wire:
    """
    Returns the external boundary (closed wire) of the input face.

    Parameters
    ----------
    face : topologic.Face
        The input face.

    Returns
    -------
    topologic.Wire
        The external boundary of the input face.

    """
    return face.ExternalBoundary()
def FacingToward(face: topologic.Face, direction: list = [0, 0, -1], asVertex: bool = False, tolerance: float = 0.0001) ‑> bool

Returns True if the input face is facing toward the input direction.

Parameters

face : topologic.Face
The input face.
direction : list , optional
The input direction. The default is [0,0,-1].
asVertex : bool , optional
If set to True, the direction is treated as an actual vertex in 3D space. The default is False.
tolerance : float , optional
The desired tolerance. The default is 0.0001.

Returns

bool
True if the face is facing toward the direction. False otherwise.
Expand source code
@staticmethod
def FacingToward(face: topologic.Face, direction: list = [0,0,-1], asVertex: bool = False, tolerance: float = 0.0001) -> bool:
    """
    Returns True if the input face is facing toward the input direction.

    Parameters
    ----------
    face : topologic.Face
        The input face.
    direction : list , optional
        The input direction. The default is [0,0,-1].
    asVertex : bool , optional
        If set to True, the direction is treated as an actual vertex in 3D space. The default is False.
    tolerance : float , optional
        The desired tolerance. The default is 0.0001.

    Returns
    -------
    bool
        True if the face is facing toward the direction. False otherwise.

    """
    faceNormal = topologic.FaceUtility.NormalAtParameters(face,0.5, 0.5)
    faceCenter = topologic.FaceUtility.VertexAtParameters(face,0.5,0.5)
    cList = [faceCenter.X(), faceCenter.Y(), faceCenter.Z()]
    try:
        vList = [direction.X(), direction.Y(), direction.Z()]
    except:
        try:
            vList = [direction[0], direction[1], direction[2]]
        except:
            raise Exception("Face.FacingToward - Error: Could not get the vector from the input direction")
    if asVertex:
        dV = [vList[0]-cList[0], vList[1]-cList[1], vList[2]-cList[2]]
    else:
        dV = vList
    uV = Vector.Normalize(dV)
    dot = sum([i*j for (i, j) in zip(uV, faceNormal)])
    if dot < tolerance:
        return False
    return True
def Flatten(face: topologic.Face, originA: topologic.Vertex = None, originB: topologic.Vertex = None, direction: list = None) ‑> topologic.Face

Flattens the input face such that its center of mass is located at the origin and its normal is pointed in the positive Z axis.

Parameters

face : topologic.Face
The input face.
originA : topologic.Vertex , optional
The old location to use as the origin of the movement. If set to None, the center of mass of the input topology is used. The default is None.
originB : topologic.Vertex , optional
The new location at which to place the topology. If set to None, the world origin (0,0,0) is used. The default is None.
direction : list , optional
The direction, expressed as a list of [X,Y,Z] that signifies the direction of the face. If set to None, the normal at u 0.5 and v 0.5 is considered the direction of the face. The deafult is None.

Returns

topologic.Face
The flattened face.
Expand source code
@staticmethod
def Flatten(face: topologic.Face, originA: topologic.Vertex = None, originB: topologic.Vertex = None, direction: list = None) -> topologic.Face:
    """
    Flattens the input face such that its center of mass is located at the origin and its normal is pointed in the positive Z axis.

    Parameters
    ----------
    face : topologic.Face
        The input face.
    originA : topologic.Vertex , optional
        The old location to use as the origin of the movement. If set to None, the center of mass of the input topology is used. The default is None.
    originB : topologic.Vertex , optional
        The new location at which to place the topology. If set to None, the world origin (0,0,0) is used. The default is None.
    direction : list , optional
        The direction, expressed as a list of [X,Y,Z] that signifies the direction of the face. If set to None, the normal at *u* 0.5 and *v* 0.5 is considered the direction of the face. The deafult is None.

    Returns
    -------
    topologic.Face
        The flattened face.

    """

    from topologicpy.Vertex import Vertex

    def leftMost(vertices, tolerance = 0.0001):
        xCoords = []
        for v in vertices:
            xCoords.append(Vertex.Coordinates(vertices[0])[0])
        minX = min(xCoords)
        lmVertices = []
        for v in vertices:
            if abs(Vertex.Coordinates(vertices[0])[0] - minX) <= tolerance:
                lmVertices.append(v)
        return lmVertices
    
    def bottomMost(vertices, tolerance = 0.0001):
        yCoords = []
        for v in vertices:
            yCoords.append(Vertex.Coordinates(vertices[0])[1])
        minY = min(yCoords)
        bmVertices = []
        for v in vertices:
            if abs(Vertex.Coordinates(vertices[0])[1] - minY) <= tolerance:
                bmVertices.append(v)
        return bmVertices

    def vIndex(v, vList, tolerance):
        for i in range(len(vList)):
            if Vertex.Distance(v, vList[i]) < tolerance:
                return i+1
        return None
    
    #  rotate cycle path such that it begins with the smallest node
    def rotate_to_smallest(path):
        n = path.index(min(path))
        return path[n:]+path[:n]
    
    #  rotate vertices list so that it begins with the input vertex
    def rotate_vertices(vertices, vertex):
        n = vertices.index(vertex)
        return vertices[n:]+vertices[:n]
    
    from topologicpy.Vertex import Vertex
    from topologicpy.Topology import Topology
    from topologicpy.Dictionary import Dictionary
    if not isinstance(face, topologic.Face):
        return None
    if not isinstance(originA, topologic.Vertex):
        originA = Topology.CenterOfMass(face)
    if not isinstance(originB, topologic.Vertex):
        originB = Vertex.ByCoordinates(0,0,0)
    cm = originA
    world_origin = originB
    if not direction or len(direction) < 3:
        direction = Face.NormalAtParameters(face, 0.5, 0.5)
    x1 = Vertex.X(cm)
    y1 = Vertex.Y(cm)
    z1 = Vertex.Z(cm)
    x2 = Vertex.X(cm) + direction[0]
    y2 = Vertex.Y(cm) + direction[1]
    z2 = Vertex.Z(cm) + direction[2]
    dx = x2 - x1
    dy = y2 - y1
    dz = z2 - z1    
    dist = math.sqrt(dx**2 + dy**2 + dz**2)
    phi = math.degrees(math.atan2(dy, dx)) # Rotation around Y-Axis
    if dist < 0.0001:
        theta = 0
    else:
        theta = math.degrees(math.acos(dz/dist)) # Rotation around Z-Axis
    flatFace = Topology.Translate(face, -cm.X(), -cm.Y(), -cm.Z())
    flatFace = Topology.Rotate(flatFace, world_origin, 0, 0, 1, -phi)
    flatFace = Topology.Rotate(flatFace, world_origin, 0, 1, 0, -theta)
    # Ensure flatness. Force Z to be zero
    flatExternalBoundary = Face.ExternalBoundary(flatFace)
    flatFaceVertices = Topology.SubTopologies(flatExternalBoundary, subTopologyType="vertex")

    tempVertices = []
    for ffv in flatFaceVertices:
        tempVertices.append(Vertex.ByCoordinates(ffv.X(), ffv.Y(), 0))
    
    temp_v = bottomMost(leftMost(tempVertices))[0]
    tempVertices = rotate_vertices(tempVertices, temp_v)
    flatExternalBoundary = Wire.ByVertices(tempVertices)

    internalBoundaries = Face.InternalBoundaries(flatFace)
    flatInternalBoundaries = []
    for internalBoundary in internalBoundaries:
        ibVertices = Wire.Vertices(internalBoundary)
        tempVertices = []
        for ibVertex in ibVertices:
            tempVertices.append(Vertex.ByCoordinates(ibVertex.X(), ibVertex.Y(), 0))
        temp_v = bottomMost(leftMost(tempVertices))[0]
        tempVertices = rotate_vertices(tempVertices, temp_v)
        flatInternalBoundaries.append(Wire.ByVertices(tempVertices))
    flatFace = Face.ByWires(flatExternalBoundary, flatInternalBoundaries)
    dictionary = Dictionary.ByKeysValues(["xTran", "yTran", "zTran", "phi", "theta"], [cm.X(), cm.Y(), cm.Z(), phi, theta])
    flatFace = Topology.SetDictionary(flatFace, dictionary)
    return flatFace
def Harmonize(face: topologic.Face) ‑> topologic.Face

Returns a harmonized version of the input face such that the u and v origins are always in the upperleft corner.

Parameters

face : topologic.Face
The input face.

Returns

topologic.Face
The harmonized face.
Expand source code
@staticmethod
def Harmonize(face: topologic.Face) -> topologic.Face:
    """
    Returns a harmonized version of the input face such that the *u* and *v* origins are always in the upperleft corner.

    Parameters
    ----------
    face : topologic.Face
        The input face.

    Returns
    -------
    topologic.Face
        The harmonized face.

    """
    from topologicpy.Vertex import Vertex
    from topologicpy.Wire import Wire
    from topologicpy.Topology import Topology
    from topologicpy.Dictionary import Dictionary

    if not isinstance(face, topologic.Face):
        return None
    flatFace = Face.Flatten(face)
    world_origin = Vertex.ByCoordinates(0,0,0)
    # Retrieve the needed transformations
    dictionary = Topology.Dictionary(flatFace)
    xTran = Dictionary.ValueAtKey(dictionary,"xTran")
    yTran = Dictionary.ValueAtKey(dictionary,"yTran")
    zTran = Dictionary.ValueAtKey(dictionary,"zTran")
    phi = Dictionary.ValueAtKey(dictionary,"phi")
    theta = Dictionary.ValueAtKey(dictionary,"theta")
    vertices = Wire.Vertices(Face.ExternalBoundary(flatFace))
    harmonizedEB = Wire.ByVertices(vertices)
    internalBoundaries = Face.InternalBoundaries(flatFace)
    harmonizedIB = []
    for ib in internalBoundaries:
        ibVertices = Wire.Vertices(ib)
        harmonizedIB.append(Wire.ByVertices(ibVertices))
    harmonizedFace = Face.ByWires(harmonizedEB, harmonizedIB)
    harmonizedFace = Topology.Rotate(harmonizedFace, origin=world_origin, x=0, y=1, z=0, degree=theta)
    harmonizedFace = Topology.Rotate(harmonizedFace, origin=world_origin, x=0, y=0, z=1, degree=phi)
    harmonizedFace = Topology.Translate(harmonizedFace, xTran, yTran, zTran)
    return harmonizedFace
def InternalBoundaries(face: topologic.Face) ‑> list

Returns the internal boundaries (closed wires) of the input face.

Parameters

face : topologic.Face
The input face.

Returns

list
The list of internal boundaries (closed wires).
Expand source code
@staticmethod
def InternalBoundaries(face: topologic.Face) -> list:
    """
    Returns the internal boundaries (closed wires) of the input face.

    Parameters
    ----------
    face : topologic.Face
        The input face.

    Returns
    -------
    list
        The list of internal boundaries (closed wires).

    """
    if not isinstance(face, topologic.Face):
        return None
    wires = []
    _ = face.InternalBoundaries(wires)
    return list(wires)
def InternalVertex(face: topologic.Face, tolerance: float = 0.0001) ‑> topologic.Vertex

Creates a vertex guaranteed to be inside the input face.

Parameters

face : topologic.Face
The input face.
tolerance : float , optional
The desired tolerance. The default is 0.0001.

Returns

topologic.Vertex
The created vertex.
Expand source code
@staticmethod
def InternalVertex(face: topologic.Face, tolerance: float = 0.0001) -> topologic.Vertex:
    """
    Creates a vertex guaranteed to be inside the input face.

    Parameters
    ----------
    face : topologic.Face
        The input face.
    tolerance : float , optional
        The desired tolerance. The default is 0.0001.

    Returns
    -------
    topologic.Vertex
        The created vertex.

    """
    from topologicpy.Topology import Topology
    if not isinstance(face, topologic.Face):
        return None
    v = Topology.Centroid(face)
    if Face.IsInside(face, v):
        return v
    l1 = [0.1,0.9]
    l2 = [0.3,0.7]
    l3 = [0.2,0.4,0.6,0.8]
    l_all = [l1,l2,l3]
    for l in l_all:
        for uv in l:
            t = max(min(uv,0.9),0.1)
            v = Face.VertexByParameters(face, t, t)
            if Face.IsInside(face, v):
                return v
    v = topologic.FaceUtility.InternalVertex(face, tolerance)
    return v
def Invert(face: topologic.Face) ‑> topologic.Face

Creates a face that is an inverse (mirror) of the input face.

Parameters

face : topologic.Face
The input face.

Returns

topologic.Face
The inverted face.
Expand source code
@staticmethod
def Invert(face: topologic.Face) -> topologic.Face:
    """
    Creates a face that is an inverse (mirror) of the input face.

    Parameters
    ----------
    face : topologic.Face
        The input face.

    Returns
    -------
    topologic.Face
        The inverted face.

    """
    from topologicpy.Wire import Wire

    if not isinstance(face, topologic.Face):
        return None
    eb = Face.ExternalBoundary(face)
    vertices = Wire.Vertices(eb)
    vertices.reverse()
    inverted_wire = Wire.ByVertices(vertices)
    internal_boundaries = Face.InternalBoundaries(face)
    if not internal_boundaries:
        inverted_face = Face.ByWire(inverted_wire)
    else:
        inverted_face = Face.ByWires(inverted_wire, internal_boundaries)
    return inverted_face
def IsCoplanar(faceA: topologic.Face, faceB: topologic.Face, tolerance: float = 0.0001) ‑> bool

Returns True if the two input faces are coplanar. Returns False otherwise.

Parameters

faceA : topologic.Face
The first input face.
faceB : topologic.Face
The second input face
tolerance : float , optional
The desired tolerance. The deafault is 0.0001.

Raises

Exception
Raises an exception if the angle between the two input faces cannot be determined.

Returns

bool
True if the two input faces are coplanar. False otherwise.
Expand source code
@staticmethod
def IsCoplanar(faceA: topologic.Face, faceB: topologic.Face, tolerance: float = 0.0001) -> bool:
    """
    Returns True if the two input faces are coplanar. Returns False otherwise.

    Parameters
    ----------
    faceA : topologic.Face
        The first input face.
    faceB : topologic.Face
        The second input face
    tolerance : float , optional
        The desired tolerance. The deafault is 0.0001.

    Raises
    ------
    Exception
        Raises an exception if the angle between the two input faces cannot be determined.

    Returns
    -------
    bool
        True if the two input faces are coplanar. False otherwise.

    """
    if not isinstance(faceA, topologic.Face) or not isinstance(faceB, topologic.Face):
        return None
    dirA = Face.NormalAtParameters(faceA, 0.5, 0.5, "xyz", 3)
    dirB = Face.NormalAtParameters(faceB, 0.5, 0.5, "xyz", 3)
    return Vector.IsCollinear(dirA, dirB, tolerance)
def IsInside(face: topologic.Face, vertex: topologic.Vertex, tolerance: float = 0.0001) ‑> bool

Returns True if the input vertex is inside the input face. Returns False otherwise.

Parameters

face : topologic.Face
The input face.
vertex : topologic.Vertex
The input vertex.
tolerance : float , optional
The desired tolerance. The default is 0.0001.

Returns

bool
True if the input vertex is inside the input face. False otherwise.
Expand source code
@staticmethod
def IsInside(face: topologic.Face, vertex: topologic.Vertex, tolerance: float = 0.0001) -> bool:
    """
    Returns True if the input vertex is inside the input face. Returns False otherwise.

    Parameters
    ----------
    face : topologic.Face
        The input face.
    vertex : topologic.Vertex
        The input vertex.
    tolerance : float , optional
        The desired tolerance. The default is 0.0001.

    Returns
    -------
    bool
        True if the input vertex is inside the input face. False otherwise.

    """
    from topologicpy.Vertex import Vertex
    from topologicpy.Cell import Cell
    from topologicpy.Cluster import Cluster
    from topologicpy.Topology import Topology
    from topologicpy.Dictionary import Dictionary
    
    if not isinstance(face, topologic.Face):
        return None
    if not isinstance(vertex, topologic.Vertex):
        return None
    # Test the distance first
    if Vertex.PerpendicularDistance(vertex, face) > tolerance:
        return False
    
    return topologic.FaceUtility.IsInside(face, vertex, tolerance)
def MedialAxis(face: topologic.Face, resolution: int = 0, externalVertices: bool = False, internalVertices: bool = False, toLeavesOnly: bool = False, angTolerance: float = 0.1, tolerance: float = 0.0001) ‑> topologic.Wire

Returns a wire representing an approximation of the medial axis of the input topology. See https://en.wikipedia.org/wiki/Medial_axis.

Parameters

face : topologic.Face
The input face.
resolution : int , optional
The desired resolution of the solution (range is 0: standard resolution to 10: high resolution). This determines the density of the sampling along each edge. The default is 0.
externalVertices : bool , optional
If set to True, the external vertices of the face will be connected to the nearest vertex on the medial axis. The default is False.
internalVertices : bool , optional
If set to True, the internal vertices of the face will be connected to the nearest vertex on the medial axis. The default is False.
toLeavesOnly : bool , optional
If set to True, the vertices of the face will be connected to the nearest vertex on the medial axis only if this vertex is a leaf (end point). Otherwise, it will connect to any nearest vertex. The default is False.
angTolerance : float , optional
The desired angular tolerance in degrees for removing collinear edges. The default is 0.1.
tolerance : float , optional
The desired tolerance. The default is 0.0001.

Returns

topologic.Wire
The medial axis of the input face.
Expand source code
@staticmethod
def MedialAxis(face: topologic.Face, resolution: int = 0, externalVertices: bool = False, internalVertices: bool = False, toLeavesOnly: bool = False, angTolerance: float = 0.1, tolerance: float = 0.0001) -> topologic.Wire:
    """
    Returns a wire representing an approximation of the medial axis of the input topology. See https://en.wikipedia.org/wiki/Medial_axis.

    Parameters
    ----------
    face : topologic.Face
        The input face.
    resolution : int , optional
        The desired resolution of the solution (range is 0: standard resolution to 10: high resolution). This determines the density of the sampling along each edge. The default is 0.
    externalVertices : bool , optional
        If set to True, the external vertices of the face will be connected to the nearest vertex on the medial axis. The default is False.
    internalVertices : bool , optional
        If set to True, the internal vertices of the face will be connected to the nearest vertex on the medial axis. The default is False.
    toLeavesOnly : bool , optional
        If set to True, the vertices of the face will be connected to the nearest vertex on the medial axis only if this vertex is a leaf (end point). Otherwise, it will connect to any nearest vertex. The default is False.
    angTolerance : float , optional
        The desired angular tolerance in degrees for removing collinear edges. The default is 0.1.
    tolerance : float , optional
        The desired tolerance. The default is 0.0001.
    
    Returns
    -------
    topologic.Wire
        The medial axis of the input face.

    """
    from topologicpy.Vertex import Vertex
    from topologicpy.Edge import Edge
    from topologicpy.Wire import Wire
    from topologicpy.Shell import Shell
    from topologicpy.Cluster import Cluster
    from topologicpy.Topology import Topology
    from topologicpy.Dictionary import Dictionary

    def touchesEdge(vertex,edges, tolerance=0.0001):
        if not isinstance(vertex, topologic.Vertex):
            return False
        for edge in edges:
            u = Edge.ParameterAtVertex(edge, vertex, mantissa=4)
            if not u:
                continue
            if 0<u<1:
                return True
        return False

    # Flatten the input face
    flatFace = Face.Flatten(face)
    # Retrieve the needed transformations
    dictionary = Topology.Dictionary(flatFace)
    xTran = Dictionary.ValueAtKey(dictionary,"xTran")
    yTran = Dictionary.ValueAtKey(dictionary,"yTran")
    zTran = Dictionary.ValueAtKey(dictionary,"zTran")
    phi = Dictionary.ValueAtKey(dictionary,"phi")
    theta = Dictionary.ValueAtKey(dictionary,"theta")

    # Create a Vertex at the world's origin (0,0,0)
    world_origin = Vertex.ByCoordinates(0,0,0)

    faceVertices = Face.Vertices(flatFace)
    faceEdges = Face.Edges(flatFace)
    vertices = []
    resolution = 10 - resolution
    resolution = min(max(resolution, 1), 10)
    for e in faceEdges:
        for n in range(resolution, 100, resolution):
            vertices.append(Edge.VertexByParameter(e,n*0.01))
    
    voronoi = Shell.Voronoi(vertices=vertices, face=flatFace)
    voronoiEdges = Shell.Edges(voronoi)

    medialAxisEdges = []
    for e in voronoiEdges:
        sv = Edge.StartVertex(e)
        ev = Edge.EndVertex(e)
        svTouchesEdge = touchesEdge(sv, faceEdges, tolerance=tolerance)
        evTouchesEdge = touchesEdge(ev, faceEdges, tolerance=tolerance)
        #connectsToCorners = (Vertex.Index(sv, faceVertices) != None) or (Vertex.Index(ev, faceVertices) != None)
        #if Face.IsInside(flatFace, sv, tolerance=tolerance) and Face.IsInside(flatFace, ev, tolerance=tolerance):
        if not svTouchesEdge and not evTouchesEdge:
            medialAxisEdges.append(e)

    extBoundary = Face.ExternalBoundary(flatFace)
    extVertices = Wire.Vertices(extBoundary)

    intBoundaries = Face.InternalBoundaries(flatFace)
    intVertices = []
    for ib in intBoundaries:
        intVertices = intVertices+Wire.Vertices(ib)
    
    theVertices = []
    if internalVertices:
        theVertices = theVertices+intVertices
    if externalVertices:
        theVertices = theVertices+extVertices

    tempWire = Topology.SelfMerge(Cluster.ByTopologies(medialAxisEdges))
    if isinstance(tempWire, topologic.Wire) and angTolerance > 0:
        tempWire = Topology.RemoveCollinearEdges(tempWire, angTolerance=angTolerance)
    medialAxisEdges = Wire.Edges(tempWire)
    for v in theVertices:
        nv = Vertex.NearestVertex(v, tempWire, useKDTree=False)

        if isinstance(nv, topologic.Vertex):
            if toLeavesOnly:
                adjVertices = Topology.AdjacentTopologies(nv, tempWire)
                if len(adjVertices) < 2:
                    medialAxisEdges.append(Edge.ByVertices([nv, v]))
            else:
                medialAxisEdges.append(Edge.ByVertices([nv, v]))
    medialAxis = Topology.SelfMerge(Cluster.ByTopologies(medialAxisEdges))
    if isinstance(medialAxis, topologic.Wire) and angTolerance > 0:
        medialAxis = Topology.RemoveCollinearEdges(medialAxis, angTolerance=angTolerance)
    medialAxis = Topology.Rotate(medialAxis, origin=world_origin, x=0, y=1, z=0, degree=theta)
    medialAxis = Topology.Rotate(medialAxis, origin=world_origin, x=0, y=0, z=1, degree=phi)
    medialAxis = Topology.Translate(medialAxis, xTran, yTran, zTran)
    return medialAxis
def Normal(face: topologic.Face, outputType: str = 'xyz', mantissa: int = 4) ‑> list

Returns the normal vector to the input face. A normal vector of a face is a vector perpendicular to it.

Parameters

face : topologic.Face
The input face.
outputType : string , optional
The string defining the desired output. This can be any subset or permutation of "xyz". It is case insensitive. The default is "xyz".
mantissa : int , optional
The desired length of the mantissa. The default is 4.

Returns

list
The normal vector to the input face. This is computed at the approximate center of the face.
Expand source code
@staticmethod
def Normal(face: topologic.Face, outputType: str = "xyz", mantissa: int = 4) -> list:
    """
    Returns the normal vector to the input face. A normal vector of a face is a vector perpendicular to it.

    Parameters
    ----------
    face : topologic.Face
        The input face.
    outputType : string , optional
        The string defining the desired output. This can be any subset or permutation of "xyz". It is case insensitive. The default is "xyz".
    mantissa : int , optional
        The desired length of the mantissa. The default is 4.

    Returns
    -------
    list
        The normal vector to the input face. This is computed at the approximate center of the face.

    """
    return Face.NormalAtParameters(face, u=0.5, v=0.5, outputType=outputType, mantissa=mantissa)
def NormalAtParameters(face: topologic.Face, u: float = 0.5, v: float = 0.5, outputType: str = 'xyz', mantissa: int = 4) ‑> list

Returns the normal vector to the input face. A normal vector of a face is a vector perpendicular to it.

Parameters

face : topologic.Face
The input face.
u : float , optional
The u parameter at which to compute the normal to the input face. The default is 0.5.
v : float , optional
The v parameter at which to compute the normal to the input face. The default is 0.5.
outputType : string , optional
The string defining the desired output. This can be any subset or permutation of "xyz". It is case insensitive. The default is "xyz".
mantissa : int , optional
The desired length of the mantissa. The default is 4.

Returns

list
The normal vector to the input face.
Expand source code
@staticmethod
def NormalAtParameters(face: topologic.Face, u: float = 0.5, v: float = 0.5, outputType: str = "xyz", mantissa: int = 4) -> list:
    """
    Returns the normal vector to the input face. A normal vector of a face is a vector perpendicular to it.

    Parameters
    ----------
    face : topologic.Face
        The input face.
    u : float , optional
        The *u* parameter at which to compute the normal to the input face. The default is 0.5.
    v : float , optional
        The *v* parameter at which to compute the normal to the input face. The default is 0.5.
    outputType : string , optional
        The string defining the desired output. This can be any subset or permutation of "xyz". It is case insensitive. The default is "xyz".
    mantissa : int , optional
        The desired length of the mantissa. The default is 4.

    Returns
    -------
    list
        The normal vector to the input face.

    """
    returnResult = []
    try:
        coords = topologic.FaceUtility.NormalAtParameters(face, u, v)
        x = round(coords[0], mantissa)
        y = round(coords[1], mantissa)
        z = round(coords[2], mantissa)
        outputType = list(outputType.lower())
        for axis in outputType:
            if axis == "x":
                returnResult.append(x)
            elif axis == "y":
                returnResult.append(y)
            elif axis == "z":
                returnResult.append(z)
    except:
        returnResult = None
    return returnResult
def NormalEdge(face: topologic.Face, length: float = 1.0) ‑> topologic.Edge

Returns the normal vector to the input face as an edge with the desired input length. A normal vector of a face is a vector perpendicular to it.

Parameters

face : topologic.Face
The input face.
length : float , optional
The desired length of the normal edge. The default is 1.

Returns

topologic.Edge
The created normal edge to the input face. This is computed at the approximate center of the face.
Expand source code
@staticmethod
def NormalEdge(face: topologic.Face, length: float = 1.0) -> topologic.Edge:
    """
    Returns the normal vector to the input face as an edge with the desired input length. A normal vector of a face is a vector perpendicular to it.

    Parameters
    ----------
    face : topologic.Face
        The input face.
    length : float , optional
        The desired length of the normal edge. The default is 1.

    Returns
    -------
    topologic.Edge
        The created normal edge to the input face. This is computed at the approximate center of the face.

    """
    return Face.NormalEdgeAtParameters(face, u=0.5, v=0.5, length=length)
def NormalEdgeAtParameters(face: topologic.Face, u: float = 0.5, v: float = 0.5, length: float = 1.0) ‑> topologic.Edge

Returns the normal vector to the input face as an edge with the desired input length. A normal vector of a face is a vector perpendicular to it.

Parameters

face : topologic.Face
The input face.
u : float , optional
The u parameter at which to compute the normal to the input face. The default is 0.5.
v : float , optional
The v parameter at which to compute the normal to the input face. The default is 0.5.
length : float , optional
The desired length of the normal edge. The default is 1.

Returns

topologic.Edge
The created normal edge to the input face. This is computed at the approximate center of the face.
Expand source code
@staticmethod
def NormalEdgeAtParameters(face: topologic.Face, u: float = 0.5, v: float = 0.5, length: float = 1.0) -> topologic.Edge:
    """
    Returns the normal vector to the input face as an edge with the desired input length. A normal vector of a face is a vector perpendicular to it.

    Parameters
    ----------
    face : topologic.Face
        The input face.
    u : float , optional
        The *u* parameter at which to compute the normal to the input face. The default is 0.5.
    v : float , optional
        The *v* parameter at which to compute the normal to the input face. The default is 0.5.
    length : float , optional
        The desired length of the normal edge. The default is 1.

    Returns
    -------
    topologic.Edge
        The created normal edge to the input face. This is computed at the approximate center of the face.

    """
    from topologicpy.Edge import Edge
    from topologicpy.Topology import Topology
    if not isinstance(face, topologic.Face):
        return None
    sv = Face.VertexByParameters(face=face, u=u, v=v)
    vec = Face.NormalAtParameters(face, u=u, v=v)
    ev = Topology.TranslateByDirectionDistance(sv, vec, length)
    return Edge.ByVertices([sv, ev])
def NorthArrow(origin: topologic.Vertex = None, radius: float = 0.5, sides: int = 16, direction: list = [0, 0, 1], northAngle: float = 0.0, placement: str = 'center', tolerance: float = 0.0001) ‑> topologic.Face

Creates a north arrow.

Parameters

origin : topologic.Vertex, optional
The location of the origin of the circle. The default is None which results in the circle being placed at (0,0,0).
radius : float , optional
The radius of the circle. The default is 1.
sides : int , optional
The number of sides of the circle. The default is 16.
direction : list , optional
The vector representing the up direction of the circle. The default is [0,0,1].
northAngle : float , optional
The angular offset in degrees from the positive Y axis direction. The angle is measured in a counter-clockwise fashion where 0 is positive Y, 90 is negative X, 180 is negative Y, and 270 is positive X.
placement : str , optional
The description of the placement of the origin of the circle. This can be "center", "lowerleft", "upperleft", "lowerright", or "upperright". It is case insensitive. The default is "center".
tolerance : float , optional
The desired tolerance. The default is 0.0001.

Returns

topologic.Face
The created circle.
Expand source code
@staticmethod
def NorthArrow(origin: topologic.Vertex = None, radius: float = 0.5, sides: int = 16, direction: list = [0,0,1], northAngle: float = 0.0,
               placement: str = "center", tolerance: float = 0.0001) -> topologic.Face:
    """
    Creates a north arrow.

    Parameters
    ----------
    origin : topologic.Vertex, optional
        The location of the origin of the circle. The default is None which results in the circle being placed at (0,0,0).
    radius : float , optional
        The radius of the circle. The default is 1.
    sides : int , optional
        The number of sides of the circle. The default is 16.
    direction : list , optional
        The vector representing the up direction of the circle. The default is [0,0,1].
    northAngle : float , optional
        The angular offset in degrees from the positive Y axis direction. The angle is measured in a counter-clockwise fashion where 0 is positive Y, 90 is negative X, 180 is negative Y, and 270 is positive X.
    placement : str , optional
        The description of the placement of the origin of the circle. This can be "center", "lowerleft", "upperleft", "lowerright", or "upperright". It is case insensitive. The default is "center".
    tolerance : float , optional
        The desired tolerance. The default is 0.0001.

    Returns
    -------
    topologic.Face
        The created circle.

    """
    from topologicpy.Topology import Topology
    from topologicpy.Vertex import Vertex
    if not origin:
        origin = Vertex.Origin()
    
    c = Face.Circle(origin=origin, radius=radius, sides=sides, direction=[0,0,1], placement="center", tolerance=tolerance)
    r = Face.Rectangle(origin=origin, width=radius*0.01,length=radius*1.2, placement="lowerleft")
    r = Topology.Translate(r, -0.005*radius,0,0)
    arrow = Topology.Difference(c, r)
    arrow = Topology.Rotate(arrow, Vertex.Origin(), 0,0,1,northAngle)
    if placement.lower() == "lowerleft":
        arrow = Topology.Translate(arrow, radius, radius, 0)
    elif placement.lower() == "upperleft":
        arrow = Topology.Translate(arrow, radius, -radius, 0)
    elif placement.lower() == "lowerright":
        arrow = Topology.Translate(arrow, -radius, radius, 0)
    elif placement.lower() == "upperright":
        arrow = Topology.Translate(arrow, -radius, -radius, 0)
    x1 = origin.X()
    y1 = origin.Y()
    z1 = origin.Z()
    x2 = origin.X() + direction[0]
    y2 = origin.Y() + direction[1]
    z2 = origin.Z() + direction[2]
    dx = x2 - x1
    dy = y2 - y1
    dz = z2 - z1    
    dist = math.sqrt(dx**2 + dy**2 + dz**2)
    phi = math.degrees(math.atan2(dy, dx)) # Rotation around Y-Axis
    if dist < 0.0001:
        theta = 0
    else:
        theta = math.degrees(math.acos(dz/dist)) # Rotation around Z-Axis
    arrow = Topology.Rotate(arrow, origin, 0, 1, 0, theta)
    arrow = Topology.Rotate(arrow, origin, 0, 0, 1, phi)
    return arrow
def Planarize(face: topologic.Face, origin: topologic.Vertex = None, direction: list = None) ‑> topologic.Face

Planarizes the input face such that its center of mass is located at the input origin and its normal is pointed in the input direction.

Parameters

face : topologic.Face
The input face.
origin : topologic.Vertex , optional
The old location to use as the origin of the movement. If set to None, the center of mass of the input face is used. The default is None.
direction : list , optional
The direction, expressed as a list of [X,Y,Z] that signifies the direction of the face. If set to None, the normal at u 0.5 and v 0.5 is considered the direction of the face. The deafult is None.

Returns

topologic.Face
The planarized face.
Expand source code
@staticmethod
def Planarize(face: topologic.Face, origin: topologic.Vertex = None, direction: list = None) -> topologic.Face:
    """
    Planarizes the input face such that its center of mass is located at the input origin and its normal is pointed in the input direction.

    Parameters
    ----------
    face : topologic.Face
        The input face.
    origin : topologic.Vertex , optional
        The old location to use as the origin of the movement. If set to None, the center of mass of the input face is used. The default is None.
    direction : list , optional
        The direction, expressed as a list of [X,Y,Z] that signifies the direction of the face. If set to None, the normal at *u* 0.5 and *v* 0.5 is considered the direction of the face. The deafult is None.

    Returns
    -------
    topologic.Face
        The planarized face.

    """

    from topologicpy.Vertex import Vertex
    from topologicpy.Wire import Wire
    from topologicpy.Topology import Topology
    from topologicpy.Dictionary import Dictionary

    if not isinstance(face, topologic.Face):
        return None
    if not isinstance(origin, topologic.Vertex):
        origin = Topology.CenterOfMass(face)
    if not isinstance(direction, list):
        direction = Face.NormalAtParameters(face, 0.5, 0.5)
    flatFace = Face.Flatten(face, oldLocation=origin, direction=direction)

    world_origin = Vertex.ByCoordinates(0,0,0)
    # Retrieve the needed transformations
    dictionary = Topology.Dictionary(flatFace)
    xTran = Dictionary.ValueAtKey(dictionary,"xTran")
    yTran = Dictionary.ValueAtKey(dictionary,"yTran")
    zTran = Dictionary.ValueAtKey(dictionary,"zTran")
    phi = Dictionary.ValueAtKey(dictionary,"phi")
    theta = Dictionary.ValueAtKey(dictionary,"theta")

    planarizedFace = Topology.Rotate(flatFace, origin=world_origin, x=0, y=1, z=0, degree=theta)
    planarizedFace = Topology.Rotate(planarizedFace, origin=world_origin, x=0, y=0, z=1, degree=phi)
    planarizedFace = Topology.Translate(planarizedFace, xTran, yTran, zTran)
    return planarizedFace
def PlaneEquation(face: topologic.Face, mantissa: int = 4) ‑> dict

Returns the a, b, c, d coefficients of the plane equation of the input face. The input face is assumed to be planar.

Parameters

face : topologic.Face
The input face.

Returns

dict
The dictionary containing the coefficients of the plane equation. The keys of the dictionary are: ["a", "b", "c", "d"].
Expand source code
@staticmethod
def PlaneEquation(face: topologic.Face, mantissa: int = 4) -> dict:
    """
    Returns the a, b, c, d coefficients of the plane equation of the input face. The input face is assumed to be planar.
    
    Parameters
    ----------
    face : topologic.Face
        The input face.
    
    Returns
    -------
    dict
        The dictionary containing the coefficients of the plane equation. The keys of the dictionary are: ["a", "b", "c", "d"].

    """
    from topologicpy.Topology import Topology
    from topologicpy.Vertex import Vertex
    import random
    import time

    if not isinstance(face, topologic.Face):
        print("Face.PlaneEquation - Error: The input face is not a valid topologic face. Returning None.")
        return None
    all_vertices = Topology.Vertices(face)
    if len(all_vertices) < 3:
        print("Face.PlaneEquation - Error: The input face has less than 3 vertices. Returning None.")
        return None
    vertices = random.sample(all_vertices, 3)
    start = time.time()
    end = time.time()
    duration = (end - start)
    while Vertex.AreCollinear(vertices) and duration < 10:
        vertices = random.sample(all_vertices, 3)
        end = time.time()
        duration = (end - start)
    if Vertex.AreCollinear(vertices):
        print("Face.PlaneEquation - Error: Could not sample 3 non-collinear vertices. Returning None.")
        return None
    v1, v2, v3 = vertices
    x1, y1, z1 = Vertex.Coordinates(v1)
    x2, y2, z2 = Vertex.Coordinates(v2)
    x3, y3, z3 = Vertex.Coordinates(v3)
    vector1 = [x2 - x1, y2 - y1, z2 - z1]
    vector2 = [x3 - x1, y3 - y1, z3 - z1]

    cross_product = [vector1[1] * vector2[2] - vector1[2] * vector2[1], -1 * (vector1[0] * vector2[2] - vector1[2] * vector2[0]), vector1[0] * vector2[1] - vector1[1] * vector2[0]]

    a = round(cross_product[0], mantissa)
    b = round(cross_product[1], mantissa)
    c = round(cross_product[2], mantissa)
    d = round(- (cross_product[0] * x1 + cross_product[1] * y1 + cross_product[2] * z1), mantissa)
    return {"a": a, "b": b, "c": c, "d": d}
def Project(faceA: topologic.Face, faceB: topologic.Face, direction: list = None, mantissa: int = 4) ‑> topologic.Face

Creates a projection of the first input face unto the second input face.

Parameters

faceA : topologic.Face
The face to be projected.
faceB : topologic.Face
The face unto which the first input face will be projected.
direction : list, optional
The vector direction of the projection. If None, the reverse vector of the receiving face normal will be used. The default is None.
mantissa : int , optional
The desired length of the mantissa. The default is 4.

Returns

topologic.Face
The projected Face.
Expand source code
@staticmethod
def Project(faceA: topologic.Face, faceB: topologic.Face, direction : list = None, mantissa: int = 4) -> topologic.Face:
    """
    Creates a projection of the first input face unto the second input face.

    Parameters
    ----------
    faceA : topologic.Face
        The face to be projected.
    faceB : topologic.Face
        The face unto which the first input face will be projected.
    direction : list, optional
        The vector direction of the projection. If None, the reverse vector of the receiving face normal will be used. The default is None.
    mantissa : int , optional
        The desired length of the mantissa. The default is 4.

    Returns
    -------
    topologic.Face
        The projected Face.

    """

    from topologicpy.Wire import Wire

    if not faceA:
        return None
    if not isinstance(faceA, topologic.Face):
        return None
    if not faceB:
        return None
    if not isinstance(faceB, topologic.Face):
        return None

    eb = faceA.ExternalBoundary()
    ib_list = []
    _ = faceA.InternalBoundaries(ib_list)
    p_eb = Wire.Project(wire=eb, face = faceB, direction=direction, mantissa=mantissa)
    p_ib_list = []
    for ib in ib_list:
        temp_ib = Wire.Project(wire=ib, face = faceB, direction=direction, mantissa=mantissa)
        if temp_ib:
            p_ib_list.append(temp_ib)
    return Face.ByWires(p_eb, p_ib_list)
def Rectangle(origin: topologic.Vertex = None, width: float = 1.0, length: float = 1.0, direction: list = [0, 0, 1], placement: str = 'center', tolerance: float = 0.0001) ‑> topologic.Face

Creates a rectangle.

Parameters

origin : topologic.Vertex, optional
The location of the origin of the rectangle. The default is None which results in the rectangle being placed at (0,0,0).
width : float , optional
The width of the rectangle. The default is 1.0.
length : float , optional
The length of the rectangle. The default is 1.0.
direction : list , optional
The vector representing the up direction of the rectangle. The default is [0,0,1].
placement : str , optional
The description of the placement of the origin of the rectangle. This can be "center", "lowerleft", "upperleft", "lowerright", "upperright". It is case insensitive. The default is "center".
tolerance : float , optional
The desired tolerance. The default is 0.0001.

Returns

topologic.Face
The created face.
Expand source code
@staticmethod
def Rectangle(origin: topologic.Vertex = None, width: float = 1.0, length: float = 1.0, direction: list = [0,0,1], placement: str = "center", tolerance: float = 0.0001) -> topologic.Face:
    """
    Creates a rectangle.

    Parameters
    ----------
    origin : topologic.Vertex, optional
        The location of the origin of the rectangle. The default is None which results in the rectangle being placed at (0,0,0).
    width : float , optional
        The width of the rectangle. The default is 1.0.
    length : float , optional
        The length of the rectangle. The default is 1.0.
    direction : list , optional
        The vector representing the up direction of the rectangle. The default is [0,0,1].
    placement : str , optional
        The description of the placement of the origin of the rectangle. This can be "center", "lowerleft", "upperleft", "lowerright", "upperright". It is case insensitive. The default is "center".
    tolerance : float , optional
        The desired tolerance. The default is 0.0001.

    Returns
    -------
    topologic.Face
        The created face.

    """
    from topologicpy.Wire import Wire
    wire = Wire.Rectangle(origin=origin, width=width, length=length, direction=direction, placement=placement, tolerance=tolerance)
    if not isinstance(wire, topologic.Wire):
        return None
    return Face.ByWire(wire)
def Skeleton(face, tolerance=0.001)

Creates a straight skeleton. This method is contributed by 高熙鹏 xipeng gao gaoxipeng1998@gmail.com This algorithm depends on the polyskel code which is included in the library. Polyskel code is found at: https://github.com/Botffy/polyskel

Parameters

face : topologic.Face
The input face.
tolerance : float , optional
The desired tolerance. The default is 0.001. (This is set to a larger number as it was found to work better)

Returns

topologic.Wire
The created straight skeleton.
Expand source code
@staticmethod
def Skeleton(face, tolerance=0.001):
    """
        Creates a straight skeleton. This method is contributed by 高熙鹏 xipeng gao <gaoxipeng1998@gmail.com>
        This algorithm depends on the polyskel code which is included in the library. Polyskel code is found at: https://github.com/Botffy/polyskel


    Parameters
    ----------
    face : topologic.Face
        The input face.

    tolerance : float , optional
        The desired tolerance. The default is 0.001. (This is set to a larger number as it was found to work better)

    Returns
    -------
    topologic.Wire
        The created straight skeleton.

    """
    from topologicpy.Wire import Wire
    if not isinstance(face, topologic.Face):
        print("Face.Skeleton - Error: The input face is not a valid topologic face. Retruning None.")
        return None
    return Wire.Roof(face, degree=0, tolerance=tolerance)
def Square(origin: topologic.Vertex = None, size: float = 1.0, direction: list = [0, 0, 1], placement: str = 'center', tolerance: float = 0.0001) ‑> topologic.Face

Creates a square.

Parameters

origin : topologic.Vertex , optional
The location of the origin of the square. The default is None which results in the square being placed at (0,0,0).
size : float , optional
The size of the square. The default is 1.0.
direction : list , optional
The vector representing the up direction of the square. The default is [0,0,1].
placement : str , optional
The description of the placement of the origin of the square. This can be "center", "lowerleft", "upperleft", "lowerright", or "upperright". It is case insensitive. The default is "center".
tolerance : float , optional
The desired tolerance. The default is 0.0001.

Returns

topologic.Face
The created square.
Expand source code
@staticmethod
def Square(origin: topologic.Vertex = None, size: float = 1.0, direction: list = [0,0,1], placement: str = "center", tolerance: float = 0.0001) -> topologic.Face:
    """
    Creates a square.

    Parameters
    ----------
    origin : topologic.Vertex , optional
        The location of the origin of the square. The default is None which results in the square being placed at (0,0,0).
    size : float , optional
        The size of the square. The default is 1.0.
    direction : list , optional
        The vector representing the up direction of the square. The default is [0,0,1].
    placement : str , optional
        The description of the placement of the origin of the square. This can be "center", "lowerleft", "upperleft", "lowerright", or "upperright". It is case insensitive. The default is "center".
    tolerance : float , optional
        The desired tolerance. The default is 0.0001.

    Returns
    -------
    topologic.Face
        The created square.

    """
    return Face.Rectangle(origin = origin, width = size, length = size, direction = direction, placement = placement, tolerance = tolerance)
def Star(origin: topologic.Vertex = None, radiusA: float = 1.0, radiusB: float = 0.4, rays: int = 5, direction: list = [0, 0, 1], placement: str = 'center', tolerance: float = 0.0001) ‑> topologic.Face

Creates a star.

Parameters

origin : topologic.Vertex, optional
The location of the origin of the star. The default is None which results in the star being placed at (0,0,0).
radiusA : float , optional
The outer radius of the star. The default is 1.0.
radiusB : float , optional
The outer radius of the star. The default is 0.4.
rays : int , optional
The number of star rays. The default is 5.
direction : list , optional
The vector representing the up direction of the star. The default is [0,0,1].
placement : str , optional
The description of the placement of the origin of the star. This can be "center", "lowerleft", "upperleft", "lowerright", or "upperright". It is case insensitive. The default is "center".
tolerance : float , optional
The desired tolerance. The default is 0.0001.

Returns

topologic.Face
The created face.
Expand source code
@staticmethod
def Star(origin: topologic.Vertex = None, radiusA: float = 1.0, radiusB: float = 0.4, rays: int = 5, direction: list = [0,0,1], placement: str = "center", tolerance: float = 0.0001) -> topologic.Face:
    """
    Creates a star.

    Parameters
    ----------
    origin : topologic.Vertex, optional
        The location of the origin of the star. The default is None which results in the star being placed at (0,0,0).
    radiusA : float , optional
        The outer radius of the star. The default is 1.0.
    radiusB : float , optional
        The outer radius of the star. The default is 0.4.
    rays : int , optional
        The number of star rays. The default is 5.
    direction : list , optional
        The vector representing the up direction of the star. The default is [0,0,1].
    placement : str , optional
        The description of the placement of the origin of the star. This can be "center", "lowerleft", "upperleft", "lowerright", or "upperright". It is case insensitive. The default is "center".
    tolerance : float , optional
        The desired tolerance. The default is 0.0001.

    Returns
    -------
    topologic.Face
        The created face.

    """
    from topologicpy.Wire import Wire
    wire = Wire.Star(origin=origin, radiusA=radiusA, radiusB=radiusB, rays=rays, direction=direction, placement=placement, tolerance=tolerance)
    if not isinstance(wire, topologic.Wire):
        return None
    return Face.ByWire(wire)
def Trapezoid(origin: topologic.Vertex = None, widthA: float = 1.0, widthB: float = 0.75, offsetA: float = 0.0, offsetB: float = 0.0, length: float = 1.0, direction: list = [0, 0, 1], placement: str = 'center', tolerance: float = 0.0001) ‑> topologic.Face

Creates a trapezoid.

Parameters

origin : topologic.Vertex, optional
The location of the origin of the trapezoid. The default is None which results in the trapezoid being placed at (0,0,0).
widthA : float , optional
The width of the bottom edge of the trapezoid. The default is 1.0.
widthB : float , optional
The width of the top edge of the trapezoid. The default is 0.75.
offsetA : float , optional
The offset of the bottom edge of the trapezoid. The default is 0.0.
offsetB : float , optional
The offset of the top edge of the trapezoid. The default is 0.0.
length : float , optional
The length of the trapezoid. The default is 1.0.
direction : list , optional
The vector representing the up direction of the trapezoid. The default is [0,0,1].
placement : str , optional
The description of the placement of the origin of the trapezoid. This can be "center", or "lowerleft". It is case insensitive. The default is "center".
tolerance : float , optional
The desired tolerance. The default is 0.0001.

Returns

topologic.Face
The created trapezoid.
Expand source code
@staticmethod
def Trapezoid(origin: topologic.Vertex = None, widthA: float = 1.0, widthB: float = 0.75, offsetA: float = 0.0, offsetB: float = 0.0, length: float = 1.0, direction: list = [0,0,1], placement: str = "center", tolerance: float = 0.0001) -> topologic.Face:
    """
    Creates a trapezoid.

    Parameters
    ----------
    origin : topologic.Vertex, optional
        The location of the origin of the trapezoid. The default is None which results in the trapezoid being placed at (0,0,0).
    widthA : float , optional
        The width of the bottom edge of the trapezoid. The default is 1.0.
    widthB : float , optional
        The width of the top edge of the trapezoid. The default is 0.75.
    offsetA : float , optional
        The offset of the bottom edge of the trapezoid. The default is 0.0.
    offsetB : float , optional
        The offset of the top edge of the trapezoid. The default is 0.0.
    length : float , optional
        The length of the trapezoid. The default is 1.0.
    direction : list , optional
        The vector representing the up direction of the trapezoid. The default is [0,0,1].
    placement : str , optional
        The description of the placement of the origin of the trapezoid. This can be "center", or "lowerleft". It is case insensitive. The default is "center".
    tolerance : float , optional
        The desired tolerance. The default is 0.0001.

    Returns
    -------
    topologic.Face
        The created trapezoid.

    """
    from topologicpy.Wire import Wire
    wire = Wire.Trapezoid(origin=origin, widthA=widthA, widthB=widthB, offsetA=offsetA, offsetB=offsetB, length=length, direction=direction, placement=placement, tolerance=tolerance)
    if not isinstance(wire, topologic.Wire):
        return None
    return Face.ByWire(wire)
def Triangulate(face: topologic.Face) ‑> list

Triangulates the input face and returns a list of faces.

Parameters

face : topologic.Face
The input face.

Returns

list
The list of triangles of the input face.
Expand source code
@staticmethod
def Triangulate(face:topologic.Face) -> list:
    """
    Triangulates the input face and returns a list of faces.

    Parameters
    ----------
    face : topologic.Face
        The input face.

    Returns
    -------
    list
        The list of triangles of the input face.

    """
    from topologicpy.Vertex import Vertex
    from topologicpy.Wire import Wire
    from topologicpy.Topology import Topology
    from topologicpy.Dictionary import Dictionary

    if not isinstance(face, topologic.Face):
        return None
    flatFace = Face.Flatten(face)
    world_origin = Vertex.ByCoordinates(0,0,0)
    # Retrieve the needed transformations
    dictionary = Topology.Dictionary(flatFace)
    xTran = Dictionary.ValueAtKey(dictionary,"xTran")
    yTran = Dictionary.ValueAtKey(dictionary,"yTran")
    zTran = Dictionary.ValueAtKey(dictionary,"zTran")
    phi = Dictionary.ValueAtKey(dictionary,"phi")
    theta = Dictionary.ValueAtKey(dictionary,"theta")

    faceTriangles = []
    for i in range(0,5,1):
        try:
            _ = topologic.FaceUtility.Triangulate(flatFace, float(i)*0.1, faceTriangles)
            break
        except:
            continue
    if len(faceTriangles) < 1:
        return [face]
    finalFaces = []
    for f in faceTriangles:
        f = Topology.Rotate(f, origin=world_origin, x=0, y=1, z=0, degree=theta)
        f = Topology.Rotate(f, origin=world_origin, x=0, y=0, z=1, degree=phi)
        f = Topology.Translate(f, xTran, yTran, zTran)
        if Face.Angle(face, f) > 90:
            wire = Face.ExternalBoundary(f)
            wire = Wire.Invert(wire)
            f = topologic.Face.ByExternalBoundary(wire)
            finalFaces.append(f)
        else:
            finalFaces.append(f)
    return finalFaces
def TrimByWire(face: topologic.Face, wire: topologic.Wire, reverse: bool = False) ‑> topologic.Face

Trims the input face by the input wire.

Parameters

face : topologic.Face
The input face.
wire : topologic.Wire
The input wire.
reverse : bool , optional
If set to True, the effect of the trim will be reversed. The default is False.

Returns

topologic.Face
The resulting trimmed face.
Expand source code
@staticmethod
def TrimByWire(face: topologic.Face, wire: topologic.Wire, reverse: bool = False) -> topologic.Face:
    """
    Trims the input face by the input wire.

    Parameters
    ----------
    face : topologic.Face
        The input face.
    wire : topologic.Wire
        The input wire.
    reverse : bool , optional
        If set to True, the effect of the trim will be reversed. The default is False.

    Returns
    -------
    topologic.Face
        The resulting trimmed face.

    """
    if not isinstance(face, topologic.Face):
        return None
    if not isinstance(wire, topologic.Wire):
        return face
    trimmed_face = topologic.FaceUtility.TrimByWire(face, wire, False)
    if reverse:
        trimmed_face = face.Difference(trimmed_face)
    return trimmed_face
def UnFlatten(face: topologic.Face, dictionary: topologic.Dictionary)
Expand source code
@staticmethod
def UnFlatten(face: topologic.Face, dictionary: topologic.Dictionary):
    from topologicpy.Vertex import Vertex
    from topologicpy.Topology import Topology
    from topologicpy.Dictionary import Dictionary
    theta = Dictionary.ValueAtKey(dictionary, "theta")
    phi = Dictionary.ValueAtKey(dictionary, "phi")
    xTran = Dictionary.ValueAtKey(dictionary, "xTran")
    yTran = Dictionary.ValueAtKey(dictionary, "yTran")
    zTran = Dictionary.ValueAtKey(dictionary, "zTran")
    newFace = Topology.Rotate(face, origin=Vertex.Origin(), x=0, y=1, z=0, degree=theta)
    newFace = Topology.Rotate(newFace, origin=Vertex.Origin(), x=0, y=0, z=1, degree=phi)
    newFace = Topology.Translate(newFace, xTran, yTran, zTran)
    return newFace
def VertexByParameters(face: topologic.Face, u: float = 0.5, v: float = 0.5) ‑> topologic.Vertex

Creates a vertex at the u and v parameters of the input face.

Parameters

face : topologic.Face
The input face.
u : float , optional
The u parameter of the input face. The default is 0.5.
v : float , optional
The v parameter of the input face. The default is 0.5.

Returns

vertex : topologic vertex
The created vertex.
Expand source code
@staticmethod
def VertexByParameters(face: topologic.Face, u: float = 0.5, v: float = 0.5) -> topologic.Vertex:
    """
    Creates a vertex at the *u* and *v* parameters of the input face.

    Parameters
    ----------
    face : topologic.Face
        The input face.
    u : float , optional
        The *u* parameter of the input face. The default is 0.5.
    v : float , optional
        The *v* parameter of the input face. The default is 0.5.

    Returns
    -------
    vertex : topologic vertex
        The created vertex.

    """
    if not isinstance(face, topologic.Face):
        return None
    return topologic.FaceUtility.VertexAtParameters(face, u, v)
def VertexParameters(face: topologic.Face, vertex: topologic.Vertex, outputType: str = 'uv', mantissa: int = 4) ‑> list

Returns the u and v parameters of the input face at the location of the input vertex.

Parameters

face : topologic.Face
The input face.
vertex : topologic.Vertex
The input vertex.
outputType : string , optional
The string defining the desired output. This can be any subset or permutation of "uv". It is case insensitive. The default is "uv".
mantissa : int , optional
The desired length of the mantissa. The default is 4.

Returns

list
The list of u and/or v as specified by the outputType input.
Expand source code
@staticmethod
def VertexParameters(face: topologic.Face, vertex: topologic.Vertex, outputType: str = "uv", mantissa: int = 4) -> list:
    """
    Returns the *u* and *v* parameters of the input face at the location of the input vertex.

    Parameters
    ----------
    face : topologic.Face
        The input face.
    vertex : topologic.Vertex
        The input vertex.
    outputType : string , optional
        The string defining the desired output. This can be any subset or permutation of "uv". It is case insensitive. The default is "uv".
    mantissa : int , optional
        The desired length of the mantissa. The default is 4.

    Returns
    -------
    list
        The list of *u* and/or *v* as specified by the outputType input.

    """
    if not isinstance(face, topologic.Face):
        return None
    if not isinstance(vertex, topologic.Vertex):
        return None
    params = topologic.FaceUtility.ParametersAtVertex(face, vertex)
    u = round(params[0], mantissa)
    v = round(params[1], mantissa)
    outputType = list(outputType.lower())
    returnResult = []
    for param in outputType:
        if param == "u":
            returnResult.append(u)
        elif param == "v":
            returnResult.append(v)
    return returnResult
def Vertices(face: topologic.Face) ‑> list

Returns the vertices of the input face.

Parameters

face : topologic.Face
The input face.

Returns

list
The list of vertices.
Expand source code
@staticmethod
def Vertices(face: topologic.Face) -> list:
    """
    Returns the vertices of the input face.

    Parameters
    ----------
    face : topologic.Face
        The input face.

    Returns
    -------
    list
        The list of vertices.

    """
    if not isinstance(face, topologic.Face):
        return None
    vertices = []
    _ = face.Vertices(None, vertices)
    return vertices
def Wire(face: topologic.Face) ‑> topologic.Wire

Returns the external boundary (closed wire) of the input face.

Parameters

face : topologic.Face
The input face.

Returns

topologic.Wire
The external boundary of the input face.
Expand source code
@staticmethod
def Wire(face: topologic.Face) -> topologic.Wire:
    """
    Returns the external boundary (closed wire) of the input face.

    Parameters
    ----------
    face : topologic.Face
        The input face.

    Returns
    -------
    topologic.Wire
        The external boundary of the input face.

    """
    return face.ExternalBoundary()
def Wires(face: topologic.Face) ‑> list

Returns the wires of the input face.

Parameters

face : topologic.Face
The input face.

Returns

list
The list of wires.
Expand source code
@staticmethod
def Wires(face: topologic.Face) -> list:
    """
    Returns the wires of the input face.

    Parameters
    ----------
    face : topologic.Face
        The input face.

    Returns
    -------
    list
        The list of wires.

    """
    if not isinstance(face, topologic.Face):
        return None
    wires = []
    _ = face.Wires(None, wires)
    return wires