Module Topology

Expand source code
# Copyright (C) 2024
# Wassim Jabi <wassim.jabi@gmail.com>
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along with
# this program. If not, see <https://www.gnu.org/licenses/>.

import topologic_core as topologic
import warnings
import uuid
import json
import os

import math
from collections import namedtuple
from multiprocessing import Process, Queue

try:
    import numpy as np
    from numpy import arctan, pi, signbit
    from numpy.linalg import norm
except:
    print("Topology - Installing required numpy library.")
    try:
        os.system("pip install numpy")
    except:
        os.system("pip install numpy --user")
    try:
        import numpy as np
        from numpy import arctan, pi, signbit
        from numpy.linalg import norm
        print("Topology - numpy library installed successfully.")
    except:
        warnings.warn("Topology - Error: Could not import numpy.")

try:
    from scipy.spatial import ConvexHull
except:
    print("Topology - Installing required scipy library.")
    try:
        os.system("pip install scipy")
    except:
        os.system("pip install scipy --user")
    try:
        from scipy.spatial import ConvexHull
        print("Topology - scipy library installed successfully.")
    except:
        warnings.warn("Topology - Error: Could not import scipy.")

try:
    from tqdm.auto import tqdm
except:
    print("Topology - Installing required tqdm library.")
    try:
        os.system("pip install tqdm")
    except:
        os.system("pip install tqdm --user")
    try:
        from tqdm.auto import tqdm
        print("Topology - tqdm library installed correctly.")
    except:
        warnings.warn("Topology - Error: Could not import tqdm.")

QueueItem = namedtuple('QueueItem', ['ID', 'sinkKeys', 'sinkValues'])
SinkItem = namedtuple('SinkItem', ['ID', 'sink_str'])

class WorkerProcessPool(object):
    """
    Create and manage a list of Worker processes. Each worker process
    transfers the dictionaries from a subset of sources to the list of sinks.
    """
    def __init__(self, num_workers, message_queue, sources, sinks, so_dicts, tolerance=0.0001):
        self.num_workers = num_workers
        self.message_queue = message_queue
        self.sources = sources
        self.sinks = sinks
        self.so_dicts = so_dicts
        self.tolerance = tolerance
        self.process_list = []

    def startProcesses(self):
        num_item_per_worker = len(self.sources) // self.num_workers
        for i in range(self.num_workers):
            if i == self.num_workers - 1:
                begin = i * num_item_per_worker
                sub_sources = self.sources[begin:]
                sub_dict = self.so_dicts[begin:]
            else:
                begin = i * num_item_per_worker
                end = begin + num_item_per_worker
                sub_sources = self.sources[begin : end]
                sub_dict = self.so_dicts[begin : end]
            wp = WorkerProcess(self.message_queue, sub_sources, self.sinks, sub_dict, self.tolerance)
            wp.start()
            self.process_list.append(wp)

    def stopProcesses(self):
        for p in self.process_list:
            p.join()
        self.process_list = []

    def join(self):
        for p in self.process_list:
            p.join()

class WorkerProcess(Process):
    """
    Transfers the dictionaries from a subset of sources to the list of sinks.
    """
    def __init__(self, message_queue, sources, sinks, so_dicts, tolerance=0.0001):
        Process.__init__(self, target=self.run)
        self.message_queue = message_queue
        self.sources = sources
        self.sinks = sinks
        self.so_dicts = so_dicts
        self.tolerance = tolerance

    def run(self):
        from topologicpy.Vertex import Vertex
        from topologicpy.Dictionary import Dictionary
        for sink_item in self.sinks:
            sink = Topology.ByBREPString(sink_item.sink_str)
            sinkKeys = []
            sinkValues = []
            iv = Topology.InternalVertex(sink, tolerance=self.tolerance)
            for j, source_str in enumerate(self.sources):
                source = Topology.ByBREPString(source_str)
                flag = False
                if Topology.IsInstance(source, "Vertex"):
                    flag = Vertex.IsInternal(source, sink, self.tolerance)
                else:
                    flag = Vertex.IsInternal(iv, source, self.tolerance)
                if flag:
                    d = Dictionary.ByPythonDictionary(self.so_dicts[j])
                    if d == None:
                        continue
                    stlKeys = d.Keys()
                    if len(stlKeys) > 0:
                        sourceKeys = d.Keys()
                        for aSourceKey in sourceKeys:
                            if aSourceKey not in sinkKeys:
                                sinkKeys.append(aSourceKey)
                                sinkValues.append("")
                        for i in range(len(sourceKeys)):
                            index = sinkKeys.index(sourceKeys[i])
                            sourceValue = Dictionary.ValueAtKey(d, sourceKeys[i])
                            if sourceValue != None:
                                if sinkValues[index] != "":
                                    if isinstance(sinkValues[index], list):
                                        sinkValues[index].append(sourceValue)
                                    else:
                                        sinkValues[index] = [sinkValues[index], sourceValue]
                                else:
                                    sinkValues[index] = sourceValue
                    break
            if len(sinkKeys) > 0 and len(sinkValues) > 0:
                self.message_queue.put(QueueItem(sink_item.ID, sinkKeys, sinkValues))

class MergingProcess(Process):
    """
    Receive message from other processes and merging the result
    """
    def __init__(self, message_queue, sources, sinks, so_dicts):
        Process.__init__(self, target=self.wait_message)
        self.message_queue = message_queue
        self.sources = sources
        self.sinks = sinks
        self.so_dicts = so_dicts
        self.sinkMap = self._init_sink_map()

    def _init_sink_map(self):
        sinkMap = {}
        for sink in self.sinks:
            sinkMap[sink.ID] = QueueItem(sink.ID, [], [])
        return sinkMap

    def wait_message(self):
        while True:
            try:
                item = self.message_queue.get()
                if item is None:
                    self.message_queue.put(self.sinkMap)
                    break
                mapItem = self.sinkMap[item.ID]
                mapItem.sinkKeys.extend(item.sinkKeys)
                mapItem.sinkValues.extend(item.sinkValues)
            except Exception as e:
                print(str(e))

class Topology():
    @staticmethod
    def AddApertures(topology, apertures, exclusive=False, subTopologyType=None, tolerance=0.001):
        """
        Adds the input list of apertures to the input topology or to its subtpologies based on the input subTopologyType.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        apertures : list
            The input list of apertures.
        exclusive : bool , optional
            If set to True, one (sub)topology will accept only one aperture. Otherwise, one (sub)topology can accept multiple apertures. The default is False.
        subTopologyType : string , optional
            The subtopology type to which to add the apertures. This can be "cell", "face", "edge", or "vertex". It is case insensitive. If set to None, the apertures will be added to the input topology. The default is None.
        tolerance : float , optional
            The desired tolerance. The default is 0.001. This is larger than the usual 0.0001 as it seems to work better.

        Returns
        -------
        topologic_core.Topology
            The input topology with the apertures added to it.

        """
        from topologicpy.Vertex import Vertex
        from topologicpy.Cluster import Cluster
        from topologicpy.Aperture import Aperture
        def processApertures(subTopologies, apertures, exclusive=False, tolerance=0.001):
            usedTopologies = []
            for subTopology in subTopologies:
                    usedTopologies.append(0)
            ap = 1
            for aperture in apertures:
                apCenter = Topology.InternalVertex(aperture, tolerance)
                for i in range(len(subTopologies)):
                    subTopology = subTopologies[i]
                    if exclusive == True and usedTopologies[i] == 1:
                        continue
                    if Vertex.Distance(apCenter, subTopology) < tolerance:
                        context = topologic.Context.ByTopologyParameters(subTopology, 0.5, 0.5, 0.5)
                        _ = Aperture.ByTopologyContext(aperture, context)
                        if exclusive == True:
                            usedTopologies[i] = 1
                ap = ap + 1
            return None

        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.AddApertures - Error: The input topology parameter is not a valid topology. Returning None.")
            return None
        if not apertures:
            return topology
        if not isinstance(apertures, list):
            print("Topology.AddApertures - Error: the input apertures parameter is not a list. Returning None.")
            return None
        apertures = [x for x in apertures if Topology.IsInstance(x , "Topology")]
        if len(apertures) < 1:
            return topology
        if not subTopologyType:
            subTopologyType = "self"
        if not subTopologyType.lower() in ["self", "cell", "face", "edge", "vertex"]:
            print("Topology.AddApertures - Error: the input subtopology type parameter is not a recognized type. Returning None.")
            return None
        if subTopologyType.lower() == "self":
            subTopologies = [topology]
        else:
            subTopologies = Topology.SubTopologies(topology, subTopologyType)
        processApertures(subTopologies, apertures, exclusive, tolerance)
        return topology
    
    @staticmethod
    def AddContent(topology, contents, subTopologyType=None, tolerance=0.0001):
        """
        Adds the input list of contents to the input topology or to its subtpologies based on the input subTopologyType.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        contents : list or topologic_core.Topology
            The input list of contents (of type topologic_core.Topology). A single topology is also accepted as input.
        subTopologyType : string , optional
            The subtopology type to which to add the contents. This can be "cellcomplex", "cell", "shell", "face", "wire", "edge", or "vertex". It is case insensitive. If set to None, the contents will be added to the input topology. The default is None.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        topologic_core.Topology
            The input topology with the contents added to it.

        """
        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.AddContent - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        if not contents:
            return topology
        if not isinstance(contents, list):
            contents = [contents]
        if not isinstance(contents, list):
            print("Topology.AddContent - Error: the input contents parameter is not a list. Returning None.")
            return None
        contents = [x for x in contents if Topology.IsInstance(x, "Topology")]
        if len(contents) < 1:
            return topology
        if not subTopologyType:
            subTopologyType = "self"
        if not subTopologyType.lower() in ["self", "cellcomplex", "cell", "shell", "face", "wire", "edge", "vertex"]:
            print("Topology.AddContent - Error: the input subtopology type parameter is not a recognized type. Returning None.")
            return None
        if subTopologyType.lower() == "self":
            t = 0
        else:
            t = Topology.TypeID(subTopologyType)
        return topology.AddContents(contents, t)
    
    @staticmethod
    def AddDictionary(topology, dictionary):
        """
        Adds the input dictionary to the input topology.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        dictionary : topologic_core.Dictionary
            The input dictionary.

        Returns
        -------
        topologic_core.Topology
            The input topology with the input dictionary added to it.

        """
        
        from topologicpy.Dictionary import Dictionary
        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.AddDictionary - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        if not Topology.IsInstance(dictionary, "Dictionary"):
            print("Topology.AddDictionary - Error: the input dictionary parameter is not a dictionary. Returning None.")
            return None
        tDict = Topology.Dictionary(topology)
        if len(tDict.Keys()) < 1:
            _ = topology.SetDictionary(dictionary)
        else:
            newDict = Dictionary.ByMergedDictionaries([tDict, dictionary])
            if newDict:
                _ = topology.SetDictionary(newDict)
        return topology
    
    @staticmethod
    def AdjacentTopologies(topology, hostTopology, topologyType=None):
        """
        Returns the topologies, as specified by the input topology type, adjacent to the input topology witin the input host topology.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        hostTopology : topologic_core.Topology
            The host topology in which to search.
        topologyType : str
            The type of topology for which to search. This can be one of "vertex", "edge", "wire", "face", "shell", "cell", "cellcomplex". It is case-insensitive. If it is set to None, the type will be set to the same type as the input topology. The default is None.

        Returns
        -------
        adjacentTopologies : list
            The list of adjacent topologies.

        """
        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.AdjacentTopologies - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        if not Topology.IsInstance(hostTopology, "Topology"):
            print("Topology.AdjacentTopologies - Error: the input hostTopology parameter is not a valid topology. Returning None.")
            return None
        if not topologyType:
            topologyType = Topology.TypeAsString(topology).lower()
        if not isinstance(topologyType, str):
            print("Topology.AdjacentTopologies - Error: the input topologyType parameter is not a string. Returning None.")
            return None
        if not topologyType.lower() in ["vertex", "edge", "wire", "face", "shell", "cell", "cellcomplex"]:
            print("Topology.AdjacentTopologies - Error: the input topologyType parameter is not a recognized type. Returning None.")
            return None
        adjacentTopologies = []
        error = False
        if Topology.IsInstance(topology, "Vertex"):
            if topologyType.lower() == "vertex":
                try:
                    _ = topology.AdjacentVertices(hostTopology, adjacentTopologies)
                except:
                    try:
                        _ = topology.Vertices(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "edge":
                try:
                    _ = topologic.VertexUtility.AdjacentEdges(topology, hostTopology, adjacentTopologies)
                except:
                    try:
                        _ = topology.Edges(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "wire":
                try:
                    _ = topologic.VertexUtility.AdjacentWires(topology, hostTopology, adjacentTopologies)
                except:
                    try:
                        _ = topology.Wires(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "face":
                try:
                    _ = topologic.VertexUtility.AdjacentFaces(topology, hostTopology, adjacentTopologies)
                except:
                    try:
                        _ = topology.Faces(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "shell":
                try:
                    _ = topologic.VertexUtility.AdjacentShells(topology, hostTopology, adjacentTopologies)
                except:
                    try:
                        _ = topology.Shells(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "cell":
                try:
                    _ = topologic.VertexUtility.AdjacentCells(topology, hostTopology, adjacentTopologies)
                except:
                    try:
                        _ = topology.Cells(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "cellcomplex":
                try:
                    _ = topologic.VertexUtility.AdjacentCellComplexes(topology, hostTopology, adjacentTopologies)
                except:
                    try:
                        _ = topology.CellComplexes(hostTopology, adjacentTopologies)
                    except:
                        error = True
        elif Topology.IsInstance(topology, "Edge"):
            if topologyType.lower() == "vertex":
                try:
                    _ = topology.Vertices(hostTopology, adjacentTopologies)
                except:
                    error = True
            elif topologyType.lower() == "edge":
                try:
                    _ = topology.AdjacentEdges(hostTopology, adjacentTopologies)
                except:
                    error = True
            elif topologyType.lower() == "wire":
                try:
                    _ = topologic.EdgeUtility.AdjacentWires(topology, adjacentTopologies)
                except:
                    try:
                        _ = topology.Wires(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "face":
                try:
                    _ = topologic.EdgeUtility.AdjacentFaces(topology, adjacentTopologies)
                except:
                    try:
                        _ = topology.Faces(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "shell":
                try:
                    _ = topologic.EdgeUtility.AdjacentShells(adjacentTopologies)
                except:
                    try:
                        _ = topology.Shells(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "cell":
                try:
                    _ = topologic.EdgeUtility.AdjacentCells(adjacentTopologies)
                except:
                    try:
                        _ = topology.Cells(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "cellcomplex":
                try:
                    _ = topologic.EdgeUtility.AdjacentCellComplexes(adjacentTopologies)
                except:
                    try:
                        _ = topology.CellComplexes(hostTopology, adjacentTopologies)
                    except:
                        error = True
        elif Topology.IsInstance(topology, "Wire"):
            if topologyType.lower() == "vertex":
                try:
                    _ = topologic.WireUtility.AdjacentVertices(topology, adjacentTopologies)
                except:
                    try:
                        _ = topology.Vertices(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "edge":
                try:
                    _ = topologic.WireUtility.AdjacentEdges(topology, adjacentTopologies)
                except:
                    try:
                        _ = topology.Edges(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "wire":
                try:
                    _ = topologic.WireUtility.AdjacentWires(topology, adjacentTopologies)
                except:
                    try:
                        _ = topology.Wires(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "face":
                try:
                    _ = topologic.WireUtility.AdjacentFaces(topology, adjacentTopologies)
                except:
                    try:
                        _ = topology.Faces(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "shell":
                try:
                    _ = topologic.WireUtility.AdjacentShells(adjacentTopologies)
                except:
                    try:
                        _ = topology.Shells(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "cell":
                try:
                    _ = topologic.WireUtility.AdjacentCells(adjacentTopologies)
                except:
                    try:
                        _ = topology.Cells(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "cellcomplex":
                try:
                    _ = topologic.WireUtility.AdjacentCellComplexes(adjacentTopologies)
                except:
                    try:
                        _ = topology.CellComplexes(hostTopology, adjacentTopologies)
                    except:
                        error = True
        elif Topology.IsInstance(topology, "Face"):
            if topologyType.lower() == "vertex":
                try:
                    _ = topologic.FaceUtility.AdjacentVertices(topology, adjacentTopologies)
                except:
                    try:
                        _ = topology.Vertices(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "edge":
                try:
                    _ = topologic.FaceUtility.AdjacentEdges(topology, adjacentTopologies)
                except:
                    try:
                        _ = topology.Edges(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "wire":
                try:
                    _ = topologic.FaceUtility.AdjacentWires(topology, adjacentTopologies)
                except:
                    try:
                        _ = topology.Wires(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "face":
                _ = topology.AdjacentFaces(hostTopology, adjacentTopologies)
            elif topologyType.lower() == "shell":
                try:
                    _ = topologic.FaceUtility.AdjacentShells(adjacentTopologies)
                except:
                    try:
                        _ = topology.Shells(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "cell":
                try:
                    _ = topologic.FaceUtility.AdjacentCells(adjacentTopologies)
                except:
                    try:
                        _ = topology.Cells(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "cellcomplex":
                try:
                    _ = topologic.FaceUtility.AdjacentCellComplexes(adjacentTopologies)
                except:
                    try:
                        _ = topology.CellComplexes(hostTopology, adjacentTopologies)
                    except:
                        error = True
        elif Topology.IsInstance(topology, "Shell"):
            if topologyType.lower() == "vertex":
                try:
                    _ = topologic.ShellUtility.AdjacentVertices(topology, adjacentTopologies)
                except:
                    try:
                        _ = topology.Vertices(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "edge":
                try:
                    _ = topologic.ShellUtility.AdjacentEdges(topology, adjacentTopologies)
                except:
                    try:
                        _ = topology.Edges(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "wire":
                try:
                    _ = topologic.ShellUtility.AdjacentWires(topology, adjacentTopologies)
                except:
                    try:
                        _ = topology.Wires(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "face":
                try:
                    _ = topologic.ShellUtility.AdjacentFaces(topology, adjacentTopologies)
                except:
                    try:
                        _ = topology.Faces(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "shell":
                try:
                    _ = topologic.ShellUtility.AdjacentShells(adjacentTopologies)
                except:
                    try:
                        _ = topology.Shells(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "cell":
                try:
                    _ = topologic.ShellUtility.AdjacentCells(adjacentTopologies)
                except:
                    try:
                        _ = topology.Cells(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "cellcomplex":
                try:
                    _ = topologic.ShellUtility.AdjacentCellComplexes(adjacentTopologies)
                except:
                    try:
                        _ = topology.CellComplexes(hostTopology, adjacentTopologies)
                    except:
                        error = True
        elif Topology.IsInstance(topology, "Cell"):
            if topologyType.lower() == "vertex":
                try:
                    _ = topologic.CellUtility.AdjacentVertices(topology, adjacentTopologies)
                except:
                    try:
                        _ = topology.Vertices(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "edge":
                try:
                    _ = topologic.CellUtility.AdjacentEdges(topology, adjacentTopologies)
                except:
                    try:
                        _ = topology.Edges(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "wire":
                try:
                    _ = topologic.CellUtility.AdjacentWires(topology, adjacentTopologies)
                except:
                    try:
                        _ = topology.Wires(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "face":
                try:
                    _ = topologic.CellUtility.AdjacentFaces(topology, adjacentTopologies)
                except:
                    try:
                        _ = topology.Faces(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "shell":
                try:
                    _ = topologic.CellUtility.AdjacentShells(adjacentTopologies)
                except:
                    try:
                        _ = topology.Shells(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "cell":
                try:
                    _ = topology.AdjacentCells(hostTopology, adjacentTopologies)
                except:
                    try:
                        _ = topology.Cells(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "cellcomplex":
                try:
                    _ = topologic.CellUtility.AdjacentCellComplexes(adjacentTopologies)
                except:
                    try:
                        _ = topology.CellComplexes(hostTopology, adjacentTopologies)
                    except:
                        error = True
        elif Topology.IsInstance(topology, "CellComplex"):
            if topologyType.lower() == "vertex":
                try:
                    _ = topology.Vertices(hostTopology, adjacentTopologies)
                except:
                    error = True
            elif topologyType.lower() == "edge":
                try:
                    _ = topology.Edges(hostTopology, adjacentTopologies)
                except:
                    error = True
            elif topologyType.lower() == "wire":
                try:
                    _ = topology.Wires(hostTopology, adjacentTopologies)
                except:
                    error = True
            elif topologyType.lower() == "face":
                try:
                    _ = topology.Faces(hostTopology, adjacentTopologies)
                except:
                    error = True
            elif topologyType.lower() == "shell":
                try:
                    _ = topology.Shells(hostTopology, adjacentTopologies)
                except:
                    error = True
            elif topologyType.lower() == "cell":
                try:
                    _ = topology.Cells(hostTopology, adjacentTopologies)
                except:
                    error = True
            elif topologyType.lower() == "cellcomplex":
                raise Exception("Topology.AdjacentTopologies - Error: Cannot search for adjacent topologies of a CellComplex")
        elif Topology.IsInstance(topology, "Cluster"):
            raise Exception("Topology.AdjacentTopologies - Error: Cannot search for adjacent topologies of a Cluster")
        if error:
            raise Exception("Topology.AdjacentTopologies - Error: Failure in search for adjacent topologies of type "+topologyType)
        return adjacentTopologies

    @staticmethod
    def Analyze(topology):
        """
        Returns an analysis string that describes the input topology.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.

        Returns
        -------
        str
            The analysis string.

        """
        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.Analyze - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        return topologic.Topology.Analyze(topology)
    
    @staticmethod
    def Apertures(topology, subTopologyType=None):
        """
        Returns the apertures of the input topology.
        
        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        subTopologyType : string , optional
            The subtopology type from which to retrieve the apertures. This can be "cell", "face", "edge", or "vertex" or "all". It is case insensitive. If set to "all", then all apertures will be returned. If set to None, the apertures will be retrieved only from the input topology. The default is None.
        
        Returns
        -------
        list
            The list of apertures belonging to the input topology.

        """
        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.Apertures - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        
        apertures = []
        subTopologies = []
        if not subTopologyType:
            _ = topology.Apertures(apertures)
        elif subTopologyType.lower() == "vertex":
            subTopologies = Topology.Vertices(topology)
        elif subTopologyType.lower() == "edge":
            subTopologies = Topology.Edges(topology)
        elif subTopologyType.lower() == "face":
            subTopologies = Topology.Faces(topology)
        elif subTopologyType.lower() == "cell":
            subTopologies = Topology.Cells(topology)
        elif subTopologyType.lower() == "all":
            _ = topology.Apertures(apertures)
            subTopologies = Topology.Vertices(topology)
            subTopologies += Topology.Edges(topology)
            subTopologies += Topology.Faces(topology)
            subTopologies += Topology.Cells(topology)
        else:
            print("Topology.Apertures - Error: the input subtopologyType parameter is not a recognized type. Returning None.")
            return None
        for subTopology in subTopologies:
            apertures += Topology.Apertures(subTopology, subTopologyType=None)
        return apertures


    @staticmethod
    def ApertureTopologies(topology, subTopologyType=None):
        """
        Returns the aperture topologies of the input topology.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        subTopologyType : string , optional
            The subtopology type from which to retrieve the apertures. This can be "cell", "face", "edge", or "vertex" or "all". It is case insensitive. If set to "all", then all apertures will be returned. If set to None, the apertures will be retrieved only from the input topology. The default is None.
       
        Returns
        -------
        list
            The list of aperture topologies found in the input topology.

        """
        from topologicpy.Aperture import Aperture
        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.ApertureTopologies - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        apertures = Topology.Apertures(topology=topology, subTopologyType=subTopologyType)
        apTopologies = []
        for aperture in apertures:
            apTopologies.append(Aperture.Topology(aperture))
        return apTopologies
    
    @staticmethod
    def Union(topologyA, topologyB, tranDict=False, tolerance=0.0001):
        """
        See Topology.Boolean()

        """
        return Topology.Boolean(topologyA, topologyB, operation="union", tranDict=tranDict, tolerance=tolerance)
    
    @staticmethod
    def Difference(topologyA, topologyB, tranDict=False, tolerance=0.0001):
        """
        See Topology.Boolean().

        """
        return Topology.Boolean(topologyA=topologyA, topologyB=topologyB, operation="difference", tranDict=tranDict, tolerance=tolerance)
    
    @staticmethod
    def Intersect(topologyA, topologyB, tranDict=False, tolerance=0.0001):
        """
        See Topology.Boolean().

        """

        if topologyA == None:
            return None
        if topologyB == None:
            return None
        
        from topologicpy.Vertex import Vertex
        # Sort the two topologies by their type from lower to higher so comparison can be eased.
        if Topology.Type(topologyB) < Topology.Type(topologyA):
            temp = topologyA
            topologyA = topologyB
            topologyB = temp
        
        if Topology.IsInstance(topologyB, "CellComplex") or Topology.IsInstance(topologyB, "Cluster"):
            merge = Topology.Merge(topologyA, topologyB)
            symdif = Topology.SymDif(topologyA, topologyB)
            return Topology.Difference(merge, symdif)
        else:
        # Vertex:
            if Topology.IsInstance(topologyA, "Vertex"):
                # Vertex:
                if Topology.IsInstance(topologyB, "Vertex"):
                    if Vertex.Distance(topologyA, topologyB) < tolerance:
                        return topologyA
                    else:
                        return None
                # Edge/Wire/Face/Shell/Cell:
                else:
                    if Vertex.IsInternal(topologyA, topologyB):
                        return topologyA
                    else:
                        return None
            else:
                return topologyA.Intersect(topologyB)
    
    @staticmethod
    def SymmetricDifference(topologyA, topologyB, tranDict=False, tolerance=0.0001):
        """
        See Topology.Boolean().

        """
        return Topology.Boolean(topologyA=topologyA, topologyB=topologyB, operation="symdif", tranDict=tranDict, tolerance=tolerance)
    
    @staticmethod
    def SymDif(topologyA, topologyB, tranDict=False, tolerance=0.0001):
        """
        See Topology.Boolean().

        """
        return Topology.Boolean(topologyA=topologyA, topologyB=topologyB, operation="symdif", tranDict=tranDict, tolerance=tolerance)
    
    @staticmethod
    def XOR(topologyA, topologyB, tranDict=False, tolerance=0.0001):
        """
        See Topology.Boolean().

        """
        return Topology.Boolean(topologyA=topologyA, topologyB=topologyB, operation="symdif", tranDict=tranDict, tolerance=tolerance)
    
    @staticmethod
    def Merge(topologyA, topologyB, tranDict=False, tolerance=0.0001):
        """
        See Topology.Boolean().

        """
        return Topology.Boolean(topologyA=topologyA, topologyB=topologyB, operation="merge", tranDict=tranDict, tolerance=tolerance)
    
    @staticmethod
    def Slice(topologyA, topologyB, tranDict=False, tolerance=0.0001):
        """
        See Topology.Boolean().

        """
        return Topology.Boolean(topologyA=topologyA, topologyB=topologyB, operation="slice", tranDict=tranDict, tolerance=tolerance)
    
    @staticmethod
    def Merge(topologyA, topologyB, tranDict=False, tolerance=0.0001):
        """
        See Topology.Boolean().

        """
        return Topology.Boolean(topologyA=topologyA, topologyB=topologyB, operation="merge", tranDict=tranDict, tolerance=tolerance)
    
    @staticmethod
    def Impose(topologyA, topologyB, tranDict=False, tolerance=0.0001):
        """
        See Topology.Boolean().

        """
        return Topology.Boolean(topologyA=topologyA, topologyB=topologyB, operation="impose", tranDict=tranDict, tolerance=tolerance)
    
    @staticmethod
    def Imprint(topologyA, topologyB, tranDict=False, tolerance=0.0001):
        """
        See Topology.Boolean().

        """
        return Topology.Boolean(topologyA=topologyA, topologyB=topologyB, operation="imprint", tranDict=tranDict, tolerance=tolerance)
    
    @staticmethod
    def Boolean(topologyA, topologyB, operation="union", tranDict=False, tolerance=0.0001):
        """
        Execute the input boolean operation type on the input operand topologies and return the result. See https://en.wikipedia.org/wiki/Boolean_operation.

        Parameters
        ----------
        topologyA : topologic_core.Topology
            The first input topology.
        topologyB : topologic_core.Topology
            The second input topology.
        operation : str , optional
            The boolean operation. This can be one of "union", "difference", "intersect", "symdif", "merge", "slice", "impose", "imprint". It is case insensitive. The default is "union".
        tranDict : bool , optional
            If set to True the dictionaries of the operands are merged and transferred to the result. The default is False.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        topologic_core.Topology
            the resultant topology.

        """
        from topologicpy.Dictionary import Dictionary
        if not Topology.IsInstance(topologyA, "Topology"):
            print("Topology.Boolean - Error: the input topologyA parameter is not a valid topology. Returning None.")
            return None
        if not Topology.IsInstance(topologyB, "Topology"):
            print("Topology.Boolean - Error: the input topologyB parameter is not a valid topology. Returning None.")
            return None
        if not isinstance(operation, str):
            print("Topology.Boolean - Error: the input operation parameter is not a valid string. Returning None.")
            return None
        if not operation.lower() in ["union", "difference", "intersect", "symdif", "merge", "slice", "impose", "imprint"]:
            print("Topology.Boolean - Error: the input operation parameter is not a recognized operation. Returning None.")
            return None
        if not isinstance(tranDict, bool):
            print("Topology.Boolean - Error: the input tranDict parameter is not a valid boolean. Returning None.")
            return None
        topologyC = None
        #topologyC = Topology.Intersect(topologyA, topologyB)
        #try:
        if operation.lower() == "union":
            topologyC = topologyA.Union(topologyB, False)
        elif operation.lower() == "difference":
            topologyC = topologyA.Difference(topologyB, False)
        elif operation.lower() == "intersect": #Intersect in Topologic (Core) is faulty. This is a workaround.
            #topologyC = topologyA.Intersect(topologyB, False)
            
            topologyC = Topology.Intersect(topologyA, topologyB)
        elif operation.lower() == "symdif":
            topologyC = topologyA.XOR(topologyB, False)
        elif operation.lower() == "merge":
            topologyC = topologyA.Merge(topologyB, False)
        elif operation.lower() == "slice":
            topologyC = topologyA.Slice(topologyB, False)
        elif operation.lower() == "impose":
            topologyC = topologyA.Impose(topologyB, False)
        elif operation.lower() == "imprint":
            topologyC = topologyA.Imprint(topologyB, False)
        else:
            print("1. Topology.Boolean - Error: the boolean operation failed. Returning None.")
            return None
        #except:
            #print("2. Topology.Boolean - Error: the boolean operation failed. Returning None.")
            #return None
        if tranDict == True:
            sourceVertices = []
            sourceEdges = []
            sourceFaces = []
            sourceCells = []
            sinkVertices = []
            sinkEdges = []
            sinkFaces = []
            sinkCells = []
            hidimA = Topology.HighestType(topologyA)
            hidimB = Topology.HighestType(topologyB)
            hidimC = Topology.HighestType(topologyC)

            if Topology.Type(topologyA) == Topology.TypeID("Vertex"):
                sourceVertices += [topologyA]
            elif hidimA >= Topology.TypeID("Vertex"):
                sourceVertices += Topology.Vertices(topologyA)
            if Topology.Type(topologyB) == Topology.TypeID("Vertex"):
                sourceVertices += [topologyB]
            elif hidimB >= Topology.TypeID("Vertex"):
                sourceVertices += Topology.Vertices(topologyB)
            if Topology.Type(topologyC) == Topology.TypeID("Vertex"):
                sinkVertices = [topologyC]
            elif hidimC >= Topology.TypeID("Vertex"):
                sinkVertices = Topology.Vertices(topologyC)
            if len(sourceVertices) > 0 and len(sinkVertices) > 0:
                _ = Topology.TransferDictionaries(sourceVertices, sinkVertices, tolerance=tolerance)

            if Topology.Type(topologyA) == Topology.TypeID("Edge"):
                sourceEdges += [topologyA]
            elif hidimA >= Topology.TypeID("Edge"):
                sourceEdges += Topology.Edges(topologyA)
            if Topology.Type(topologyB) == Topology.TypeID("Edge"):
                sourceEdges += [topologyB]
            elif hidimB >= Topology.TypeID("Edge"):
                sourceEdges += Topology.Edges(topologyB)
            if Topology.Type(topologyC) == Topology.TypeID("Edge"):
                sinkEdges = [topologyC]
            elif hidimC >= Topology.TypeID("Edge"):
                sinkEdges = Topology.Edges(topologyC)
            if len(sourceEdges) > 0 and len(sinkEdges) > 0:
                _ = Topology.TransferDictionaries(sourceEdges, sinkEdges, tolerance=tolerance)

            if Topology.Type(topologyA) == Topology.TypeID("Face"):
                sourceFaces += [topologyA]
            elif hidimA >= Topology.TypeID("Face"):
                sourceFaces += Topology.Faces(topologyA)
            if Topology.Type(topologyB) == Topology.TypeID("Face"):
                sourceFaces += [topologyB]
            elif hidimB >= Topology.TypeID("Face"):
                sourceFaces += Topology.Faces(topologyB)
            if Topology.Type(topologyC) == Topology.TypeID("Face"):
                sinkFaces += [topologyC]
            elif hidimC >= Topology.TypeID("Face"):
                sinkFaces += Topology.Faces(topologyC)
            if len(sourceFaces) > 0 and len(sinkFaces) > 0:
                _ = Topology.TransferDictionaries(sourceFaces, sinkFaces, tolerance=tolerance)

            if Topology.Type(topologyA) == Topology.TypeID("Cell"):
                sourceCells += [topologyA]
            elif hidimA >= Topology.TypeID("Cell"):
                sourceCells += Topology.Cells(topologyA)
            if Topology.Type(topologyB) == Topology.TypeID("Cell"):
                sourceCells += [topologyB]
            elif hidimB >= Topology.TypeID("Cell"):
                sourceCells += Topology.Cells(topologyB)
            if Topology.Type(topologyC) == Topology.TypeID("Cell"):
                sinkCells = [topologyC]
            elif hidimC >= Topology.TypeID("Cell"):
                sinkCells = Topology.Cells(topologyC)
            if len(sourceCells) > 0 and len(sinkCells) > 0:
                _ = Topology.TransferDictionaries(sourceCells, sinkCells, tolerance=tolerance)
        return topologyC

    
    @staticmethod
    def BoundingBox(topology, optimize=0, axes="xyz", tolerance=0.0001):
        """
        Returns a cell representing a bounding box of the input topology. The returned cell contains a dictionary with keys "xrot", "yrot", and "zrot" that represents rotations around the X, Y, and Z axes. If applied in the order of Z, Y, X, the resulting box will become axis-aligned.

        Parameters
        ----------
        topology : topologic_core.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 box so that it reduces its surface area. The default is 0 which will result in an axis-aligned bounding box. The default is 0.
        axes : str , optional
            Sets what axes are to be used for rotating the bounding box. This can be any permutation or substring of "xyz". It is not case sensitive. The default is "xyz".
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.
        
        Returns
        -------
        topologic_core.Cell or topologic_core.Face
            The bounding box of the input topology.

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

        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.BoundingBox - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        if not isinstance(axes, str):
            print("Topology.BoundingBox - Error: the input axes parameter is not a valid string. Returning None.")
            return None
        axes = axes.lower()
        x_flag = "x" in axes
        y_flag = "y" in axes
        z_flag = "z" in axes
        if not x_flag and not y_flag and not z_flag:
            print("Topology.BoundingBox - Error: the input axes parameter is not a recognized string. Returning None.")
            return None
        vertices = Topology.SubTopologies(topology, subTopologyType="vertex")
        topology = Cluster.ByTopologies(vertices)
        boundingBox = bb(topology)
        minX = boundingBox[0]
        minY = boundingBox[1]
        minZ = boundingBox[2]
        maxX = boundingBox[3]
        maxY = boundingBox[4]
        maxZ = boundingBox[5]
        w = abs(maxX - minX)
        l = abs(maxY - minY)
        h = abs(maxZ - minZ)
        best_area = 2*l*w + 2*l*h + 2*w*h
        orig_area = best_area
        best_x = 0
        best_y = 0
        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
                if x_flag:
                    xa = n
                    xb = 90+n
                    xc = n
                else:
                    xa = 0
                    xb = 1
                    xc = 1
                if y_flag:
                    ya = n
                    yb = 90+n
                    yc = n
                else:
                    ya = 0
                    yb = 1
                    yc = 1
                if z_flag:
                    za = n
                    zb = 90+n
                    zc = n
                else:
                    za = 0
                    zb = 1
                    zc = 1
                for x in range(xa,xb,xc):
                    if flag:
                        break
                    for y in range(ya,yb,yc):
                        if flag:
                            break
                        for z in range(za,zb,zc):
                            if flag:
                                break
                            t = Topology.Rotate(topology, origin=origin, axis=[0, 0, 1], angle=z)
                            t = Topology.Rotate(t, origin=origin, axis=[0, 1, 0], angle=y)
                            t = Topology.Rotate(t, origin=origin, axis=[1, 0, 0], angle=x)
                            minX, minY, minZ, maxX, maxY, maxZ = bb(t)
                            w = abs(maxX - minX)
                            l = abs(maxY - minY)
                            h = abs(maxZ - minZ)
                            area = 2*l*w + 2*l*h + 2*w*h
                            if area < orig_area*factor:
                                best_area = area
                                best_x = x
                                best_y = y
                                best_z = z
                                best_bb = [minX, minY, minZ, maxX, maxY, maxZ]
                                flag = True
                                break
                            if area < best_area:
                                best_area = area
                                best_x = x
                                best_y = y
                                best_z = z
                                best_bb = [minX, minY, minZ, maxX, maxY, maxZ]
                        
        else:
            best_bb = boundingBox

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

        baseWire = Wire.ByVertices([vb1, vb2, vb3, vb4], close=True)
        baseFace = Face.ByWire(baseWire, tolerance=tolerance)
        if abs(maxZ-minZ) < tolerance:
            box = baseFace
        else:
            box = Cell.ByThickenedFace(baseFace, planarize=False, thickness=abs(maxZ-minZ), bothSides=False)
        box = Topology.Rotate(box, origin=origin, axis=[1, 0, 0], angle=-best_x)
        box = Topology.Rotate(box, origin=origin, axis=[0, 1, 0], angle=-best_y)
        box = Topology.Rotate(box, origin=origin, axis=[0, 0, 1], angle=-best_z)
        dictionary = Dictionary.ByKeysValues(["xrot","yrot","zrot", "minx", "miny", "minz", "maxx", "maxy", "maxz", "width", "length", "height"], [best_x, best_y, best_z, minX, minY, minZ, maxX, maxY, maxZ, (maxX-minX), (maxY-minY), (maxZ-minZ)])
        box = Topology.SetDictionary(box, dictionary)
        return box

    @staticmethod
    def BREPString(topology, version=3):
        """
        Returns the BRep string of the input topology.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        version : int , optional
            The desired BRep version number. The default is 3.

        Returns
        -------
        str
            The BREP string.

        """
        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.BREPString - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        st = None
        try:
            st = topologic.Topology.String(topology, version)
        except:
            try:
                st = topologic.Topology.BREPString(topology, version)
            except:
                st = None
        return st
    
    @staticmethod
    def ByGeometry(vertices=[], edges=[], faces=[], color=[1.0, 1.0, 1.0, 1.0], id=None, name=None, lengthUnit="METERS", outputMode="default", tolerance=0.0001):
        """
        Create a topology by the input lists of vertices, edges, and faces.

        Parameters
        ----------
        vertices : list
            The input list of vertices in the form of [x, y, z]
        edges : list , optional
            The input list of edges in the form of [i, j] where i and j are vertex indices.
        faces : list , optional
            The input list of faces in the form of [i, j, k, l, ...] where the items in the list are vertex indices. The face is assumed to be closed to the last vertex is connected to the first vertex automatically.
        color : list , optional
            The desired color of the object in the form of [r, g, b, a] where the components are between 0 and 1 and represent red, blue, green, and alpha (transparency) repsectively. The default is [1.0, 1.0, 1.0, 1.0].
        id : str , optional
            The desired ID of the object. If set to None, an automatic uuid4 will be assigned to the object. The default is None.
        name : str , optional
            The desired name of the object. If set to None, a default name "Topologic_[topology_type]" will be assigned to the object. The default is None.
        lengthUnit : str , optional
            The length unit used for the object. The default is "METERS"
        outputMode : str , optional
            The desired otuput mode of the object. This can be "wire", "shell", "cell", "cellcomplex", or "default". It is case insensitive. The default is "default".
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        topology : topologic_core.Topology
            The created topology. The topology will have a dictionary embedded in it that records the input attributes (color, id, lengthUnit, name, type)

        """
        def topologyByFaces(faces, outputMode, tolerance):
            output = None
            if len(faces) == 1:
                return faces[0]
            if outputMode.lower() == "cell":
                output = Cell.ByFaces(faces, tolerance=tolerance)
                if output:
                    return output
                else:
                    return None
            if outputMode.lower() == "cellcomplex":
                output = CellComplex.ByFaces(faces, tolerance=tolerance)
                if output:
                    return output
                else:
                    return None
            if outputMode.lower() == "shell":
                output = Shell.ByFaces(faces, tolerance=tolerance) # This can return a list
                if Topology.IsInstance(output, "Shell"):
                    return output
                else:
                    return None
            if outputMode.lower() == "default":
                output = Cluster.ByTopologies(faces)
                if output:
                    return output
            return output
        def topologyByEdges(edges, outputMode):
            output = None
            if len(edges) == 1:
                return edges[0]
            output = Cluster.ByTopologies(edges)
            if outputMode.lower() == "wire":
                output = Topology.SelfMerge(output, tolerance=tolerance)
                if Topology.IsInstance(output, "Wire"):
                    return output
                else:
                    return None
            return output
        def edgesByVertices(vertices, topVerts):
            if len(vertices) < 2:
                return []
            edges = []
            for i in range(len(vertices)-1):
                v1 = vertices[i]
                v2 = vertices[i+1]
                e1 = Edge.ByVertices([topVerts[v1], topVerts[v2]], tolerance=tolerance)
                edges.append(e1)
            # connect the last vertex to the first one
            v1 = vertices[-1]
            v2 = vertices[0]
            e1 = Edge.ByVertices([topVerts[v1], topVerts[v2]], tolerance=tolerance)
            edges.append(e1)
            return edges
        from topologicpy.Vertex import Vertex
        from topologicpy.Edge import Edge
        from topologicpy.Wire import Wire
        from topologicpy.Face import Face
        from topologicpy.Shell import Shell
        from topologicpy.Cell import Cell
        from topologicpy.CellComplex import CellComplex
        from topologicpy.Cluster import Cluster
        from topologicpy.Dictionary import Dictionary
        import uuid
        returnTopology = None
        topVerts = []
        topEdges = []
        topFaces = []
        vertices = [v for v in vertices if not len(v) == 0]
        edges = [e for e in edges if not len(e) == 0]
        faces = [f for f in faces if not len(f) == 0]
        if len(vertices) > 0:
            for aVertex in vertices:
                v = Vertex.ByCoordinates(aVertex[0], aVertex[1], aVertex[2])
                topVerts.append(v)
        else:
            return None
        if (outputMode.lower == "wire") and (len(edges) > 0):
            for anEdge in edges:
                topEdge = Edge.ByVertices([topVerts[anEdge[0]], topVerts[anEdge[1]]], tolerance=tolerance)
                topEdges.append(topEdge)
            if len(topEdges) > 0:
                returnTopology = topologyByEdges(topEdges)
        elif len(faces) > 0:
            for aFace in faces:
                faceEdges = edgesByVertices(aFace, topVerts)
                if len(faceEdges) > 2:
                    faceWire = Wire.ByEdges(faceEdges, tolerance=tolerance)
                    try:
                        topFace = Face.ByWire(faceWire, tolerance=tolerance, silent=True)
                        if Topology.IsInstance(topFace, "Face"):
                            topFaces.append(topFace)
                        elif isinstance(topFace, list):
                            topFaces += topFace
                    except:
                        pass
            if len(topFaces) > 0:
                returnTopology = topologyByFaces(topFaces, outputMode=outputMode, tolerance=tolerance)
        elif len(edges) > 0:
            for anEdge in edges:
                topEdge = Edge.ByVertices([topVerts[anEdge[0]], topVerts[anEdge[1]]], tolerance=tolerance)
                topEdges.append(topEdge)
            if len(topEdges) > 0:
                returnTopology = topologyByEdges(topEdges, outputMode=outputMode)
        else:
            returnTopology = Cluster.ByTopologies(topVerts)
        if returnTopology:
            keys = []
            values = []
            keys.append("TOPOLOGIC_color")
            keys.append("TOPOLOGIC_id")
            keys.append("TOPOLOGIC_name")
            keys.append("TOPOLOGIC_type")
            keys.append("TOPOLOGIC_length_unit")
            if color:
                if isinstance(color, tuple):
                    color = list(color)
                elif isinstance(color, list):
                    if isinstance(color[0], tuple):
                        color = list(color[0])
                values.append(color)
            else:
                values.append([1.0, 1.0, 1.0, 1.0])
            if id:
                values.append(id)
            else:
                values.append(str(uuid.uuid4()))
            if name:
                values.append(name)
            else:
                values.append("Topologic_"+Topology.TypeAsString(returnTopology))
            values.append(Topology.TypeAsString(returnTopology))
            values.append(lengthUnit)
            topDict = Dictionary.ByKeysValues(keys, values)
            Topology.SetDictionary(returnTopology, topDict)
        return returnTopology

    @staticmethod
    def ByBREPFile(file):
        """
        Imports a topology from a BREP file.

        Parameters
        ----------
        file : file object
            The BREP file.

        Returns
        -------
        topologic_core.Topology
            The imported topology.

        """
        topology = None
        if not file:
            print("Topology.ByBREPFile - Error: the input file parameter is not a valid file. Returning None.")
            return None
        brep_string = file.read()
        topology = Topology.ByBREPString(brep_string)
        file.close()
        return topology
    
    @staticmethod
    def ByBREPPath(path):
        """
        Imports a topology from a BREP file path.

        Parameters
        ----------
        path : str
            The path to the BREP file.

        Returns
        -------
        topologic_core.Topology
            The imported topology.

        """
        if not path:
            print("Topology.ByBREPPath - Error: the input path parameter is not a valid path. Returning None.")
            return None
        try:
            file = open(path)
        except:
            print("Topology.ByBREPPath - Error: the BREP file is not a valid file. Returning None.")
            return None
        return Topology.ByBREPFile(file)

    @staticmethod
    def ByDXFFile(file):
        """
        Imports a list of topologies from a DXF file.
        This is an experimental method with limited capabilities.

        Parameters
        ----------
        file : a DXF file object
            The DXF file object.

        Returns
        -------
        list
            The list of imported topologies.

        """
        from topologicpy.Vertex import Vertex
        from topologicpy.Edge import Edge
        from topologicpy.Wire import Wire
        from topologicpy.Shell import Shell
        from topologicpy.Cell import Cell
        from topologicpy.CellComplex import CellComplex
        from topologicpy.Topology import Topology

        try:
            import ezdxf
        except:
            print("Topology.ByDXFFile - Information: Installing required ezdxf library.")
            try:
                os.system("pip install ezdxf")
            except:
                os.system("pip install ezdxf --user")
            try:
                import ezdxf
                print("Topology.ByDXFFile - Information: ezdxf library installed successfully.")
            except:
                warnings.warn("Topology.ByDXFFile - Error: Could not import ezdxf library. Please install it manually. Returning None.")
                return None

        if not file:
            print("Topology.ByDXFFile - Error: the input file parameter is not a valid file. Returning None.")
            return None
        
        def convert_entity(entity):
            entity_type = entity.dxftype()
            converted_entity = None

            if entity_type == 'POINT':
                x, y, z = entity.dxf.location.x, entity.dxf.location.y, entity.dxf.location.z
                converted_entity = Vertex.ByCoordinates(x, y, z)

            elif entity_type == 'LINE':
                start = Vertex.ByCoordinates(entity.dxf.start.x, entity.dxf.start.y, entity.dxf.start.z)
                end = Vertex.ByCoordinates(entity.dxf.end.x, entity.dxf.end.y, entity.dxf.end.z)
                converted_entity = Edge.ByVertices(start, end)

            elif entity_type == 'CIRCLE':
                origin = Vertex.ByCoordinates(entity.dxf.center.x, entity.dxf.center.y, entity.dxf.center.z)
                radius = entity.dxf.radius
                converted_entity = Wire.Circle(origin, radius)

            elif entity_type in ['POLYLINE', 'LWPOLYLINE']:
                vertices = [Vertex.ByCoordinates(p[0], p[1], p[2]) for p in entity.points()]
                converted_entity = Wire.ByVertices(vertices)

            elif entity_type == 'ARC':
                vertices = [Vertex.ByCoordinates(p.x, p.y, p.z) for p in entity.vertices()]
                converted_entity = Wire.ByVertices(vertices)

            elif entity_type == 'MESH':
                vertices = [list(v) for v in entity.vertices]
                faces = [list(face) for face in entity.faces]
                converted_entity = Topology.SelfMerge(Topology.ByGeometry(vertices=vertices, faces=faces))
                # Try Cell
                temp = Cell.ByFaces(Topology.Faces(converted_entity), silent=True)
                if not Topology.IsInstance(temp, "Cell"):
                    temp = CellComplex.ByFaces(Topology.Faces(converted_entity))
                    if not Topology.IsInstance(temp, "CellComplex"):
                        temp = Shell.ByFaces(Topology.Faces(converted_entity))
                        if not Topology.IsInstance(temp, "Shell"):
                            temp = converted_entity
                converted_entity = temp
            return converted_entity

        def convert_insert(entity, file):
            block_name = entity.dxf.name
            block = file.blocks.get(block_name)
            converted_entities = []

            for block_entity in block:
                converted_entity = convert_entity(block_entity)
                if converted_entity is not None:
                    converted_entities.append(converted_entity)

            x, y, z = [entity.dxf.insert.x, entity.dxf.insert.y, entity.dxf.insert.z]
            return [Topology.Translate(obj, x, y, z) for obj in converted_entities]

        def convert_dxf_to_custom_types(file):
            # Read the DXF file
            msp = file.modelspace()

            # Store the converted entities
            converted_entities = []

            # Process each entity in the model space
            for entity in msp:
                entity_type = entity.dxftype()
                if entity_type in ['TEXT', 'MTEXT']:
                    continue  # Ignore TEXT and MTEXT

                if entity_type == 'INSERT':
                    converted_entities.extend(convert_insert(entity, file))
                else:
                    converted_entity = convert_entity(entity)
                    if converted_entity is not None:
                        converted_entities.append(converted_entity)

            return converted_entities
        converted_entities = convert_dxf_to_custom_types(file)
        return converted_entities

    @staticmethod
    def ByDXFPath(path):
        """
        Imports a list of topologies from a DXF file path.
        This is an experimental method with limited capabilities.

        Parameters
        ----------
        path : str
            The path to the DXF file.

        Returns
        -------
        list
            The list of imported topologies.

        """
        try:
            import ezdxf
        except:
            print("Topology.ExportToDXF - Information: Installing required ezdxf library.")
            try:
                os.system("pip install ezdxf")
            except:
                os.system("pip install ezdxf --user")
            try:
                import ezdxf
                print("Topology.ByDXFPath - Information: ezdxf library installed successfully.")
            except:
                warnings.warn("Topology.ByDXFPath - Error: Could not import ezdxf library. Please install it manually. Returning None.")
                return None
        if not path:
            print("Topology.ByDXFPath - Error: the input path parameter is not a valid path. Returning None.")
            return None
        try:
            file = ezdxf.readfile(path)
        except:
            file = None
        if not file:
            print("Topology.ByDXFPath - Error: the input file parameter is not a valid file. Returning None.")
            return None
        return Topology.ByDXFFile(file)

    @staticmethod
    def ByIFCFile(file, transferDictionaries=False, includeTypes=[], excludeTypes=[]):
        """
        Create a topology by importing it from an IFC file.

        Parameters
        ----------
        file : file object
            The input IFC file.
        transferDictionaries : bool , optional
            If set to True, the dictionaries from the IFC file will be transfered to the topology. Otherwise, they won't. The default is False.
        includeTypes : list , optional
            The list of IFC object types to include. It is case insensitive. If set to an empty list, all types are included. The default is [].
        excludeTypes : list , optional
            The list of IFC object types to exclude. It is case insensitive. If set to an empty list, no types are excluded. The default is [].
        
        Returns
        -------
        list
            The created list of topologies.
        
        """
        import multiprocessing
        from topologicpy.Cluster import Cluster
        from topologicpy.Dictionary import Dictionary
        import uuid
        
        try:
            import ifcopenshell
            import ifcopenshell.geom
        except:
            print("Topology.ByIFCFile - Warning: Installing required ifcopenshell library.")
            try:
                os.system("pip install ifcopenshell")
            except:
                os.system("pip install ifcopenshell --user")
            try:
                import ifcopenshell
                import ifcopenshell.geom
                print("Topology.ByIFCFile - Warning: ifcopenshell library installed correctly.")
            except:
                warnings.warn("Topology.ByIFCFile - Error: Could not import ifcopenshell. Please try to install ifcopenshell manually. Returning None.")
                return None
        if not file:
            print("Topology.ByIFCFile - Error: the input file parameter is not a valid file. Returning None.")
            return None
        includeTypes = [s.lower() for s in includeTypes]
        excludeTypes = [s.lower() for s in excludeTypes]
        topologies = []
        settings = ifcopenshell.geom.settings()
        settings.set(settings.DISABLE_TRIANGULATION, True)
        settings.set(settings.USE_BREP_DATA, True)
        settings.set(settings.USE_WORLD_COORDS, True)
        settings.set(settings.SEW_SHELLS, True)
        iterator = ifcopenshell.geom.iterator(settings, file, multiprocessing.cpu_count())
        if iterator.initialize():
            while True:
                shape = iterator.get()
                is_a = shape.type.lower()
                if (is_a in includeTypes or len(includeTypes) == 0) and (not is_a in excludeTypes):
                    try:
                        brep = shape.geometry.brep_data
                        topology = Topology.SelfMerge(Topology.ByBREPString(brep))
                        if transferDictionaries:
                            keys = []
                            values = []
                            keys.append("TOPOLOGIC_color")
                            values.append([1.0, 1.0, 1.0, 1.0])
                            keys.append("TOPOLOGIC_id")
                            values.append(str(uuid.uuid4()))
                            keys.append("TOPOLOGIC_name")
                            values.append(shape.name)
                            keys.append("TOPOLOGIC_type")
                            values.append(Topology.TypeAsString(topology))
                            keys.append("IFC_id")
                            values.append(str(shape.id))
                            keys.append("IFC_guid")
                            values.append(str(shape.guid))
                            keys.append("IFC_unique_id")
                            values.append(str(shape.unique_id))
                            keys.append("IFC_name")
                            values.append(shape.name)
                            keys.append("IFC_type")
                            values.append(shape.type)
                            d = Dictionary.ByKeysValues(keys, values)
                            topology = Topology.SetDictionary(topology, d)
                        topologies.append(topology)
                    except:
                        pass
                if not iterator.next():
                    break
        return topologies

    @staticmethod
    def ByIFCPath(path, transferDictionaries=False, includeTypes=[], excludeTypes=[]):
        """
        Create a topology by importing it from an IFC file path.

        Parameters
        ----------
        path : str
            The path to the IFC file.
        transferDictionaries : bool , optional
            If set to True, the dictionaries from the IFC file will be transfered to the topology. Otherwise, they won't. The default is False.
        includeTypes : list , optional
            The list of IFC object types to include. It is case insensitive. If set to an empty list, all types are included. The default is [].
        excludeTypes : list , optional
            The list of IFC object types to exclude. It is case insensitive. If set to an empty list, no types are excluded. The default is [].
        Returns
        -------
        list
            The created list of topologies.
        
        """
        import ifcopenshell

        if not path:
            print("Topology.ByIFCPath - Error: the input path parameter is not a valid path. Returning None.")
            return None
        try:
            file = ifcopenshell.open(path)
        except:
            file = None
        if not file:
            print("Topology.ByIFCPath - Error: the input file parameter is not a valid file. Returning None.")
            return None
        return Topology.ByIFCFile(file, transferDictionaries=transferDictionaries, includeTypes=includeTypes, excludeTypes=excludeTypes)
    
    '''
    @staticmethod
    def ByImportedIPFS(hash_, url, port):
        """
        NOT DONE YET.

        Parameters
        ----------
        hash : TYPE
            DESCRIPTION.
        url : TYPE
            DESCRIPTION.
        port : TYPE
            DESCRIPTION.

        Returns
        -------
        topology : TYPE
            DESCRIPTION.

        """
        # hash, url, port = item
        url = url.replace('http://','')
        url = '/dns/'+url+'/tcp/'+port+'/https'
        client = ipfshttpclient.connect(url)
        brepString = client.cat(hash_).decode("utf-8")
        topology = Topology.ByBREPString(brepString)
        return topology
    '''
    @staticmethod
    def ByJSONFile(file, tolerance=0.0001):
        """
        Imports the topology from a JSON file.

        Parameters
        ----------
        file : file object
            The input JSON file.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        list
            The list of imported topologies.

        """
        if not file:
            print("Topology.ByJSONFile - Error: the input file parameter is not a valid file. Returning None.")
            return None
        jsonData = json.load(file)
        jsonString = json.dumps(jsonData)
        return Topology.ByJSONString(jsonString, tolerance=tolerance)
    
    @staticmethod
    def ByJSONString(string, progressBar=False, tolerance=0.0001):
        """
        Imports the topology from a JSON string.

        Parameters
        ----------
        string : str
            The input JSON string.
        progressBar : bool , optional
            If set to True a tqdm progress bar is shown. If not, it will not be shown. The default is False.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        list or topologicpy.Topology
            The list of imported topologies. If the list only contains one element, it returns that element.

        """

        from topologicpy.Dictionary import Dictionary
        from topologicpy.Context import Context
        from topologicpy.Vertex import Vertex
        from topologicpy.Edge import Edge
        from topologicpy.Wire import Wire
        from topologicpy.Face import Face
        from topologicpy.Shell import Shell
        from topologicpy.Cell import Cell
        from topologicpy.CellComplex import CellComplex
        from topologicpy.Cluster import Cluster
        from topologicpy.Aperture import Aperture
        from topologicpy.Helper import Helper
        from topologicpy.Topology import Topology
        from tqdm.auto import tqdm
        import time

        def getUUID(topology, uuidKey="uuid"):
            d = Topology.Dictionary(topology)
            if d == None:
                uuidOne = str(uuid.uuid1())
                d = Dictionary.ByKeyValue(uuidKey, uuidOne)
            elif uuidKey not in Dictionary.Keys(d):
                uuidOne = str(uuid.uuid1())
                d = Dictionary.SetValueAtKey(d, uuidKey, uuidOne)
                topology = Topology.SetDictionary(topology, d)
            else:
                uuidOne = Dictionary.ValueAtKey(d, uuidKey)
            return uuidOne
            
        def find_json_item(json_list, key, value):
            for item in json_list:
                if key in item and item[key] == value:
                    return item
            return None

        def buildAperture(j_aperture):
            j_vertices = []
            j_edges = []
            j_wires = []
            j_faces = []
            j_shells = []
            j_cells = []
            j_cellComplexes = []
            for jsonItem in j_aperture:
                topology_type = jsonItem['type']
                if topology_type.lower() == "vertex":
                    j_vertices.append(jsonItem)
                elif topology_type.lower() == "edge":
                    j_edges.append(jsonItem)
                elif topology_type.lower() == "wire":
                    j_wires.append(jsonItem)
                elif topology_type.lower() == "face":
                    j_faces.append(jsonItem)
                elif topology_type.lower() == "shell":
                    j_shells.append(jsonItem)
                elif topology_type.lower() == "cell":
                    j_cells.append(jsonItem)
                elif topology_type.lower() == "cellcomplex":
                    j_cellComplexes.append(jsonItem)

            vertices = [buildVertex(j_v) for j_v in j_vertices]
            edges = [buildEdge(j_e, j_vertices, uuidKey="uuid") for j_e in j_edges]
            wires = [buildWire(j_w, j_edges, j_vertices, uuidKey="uuid") for j_w in j_wires]
            faces = [buildFace(j_f, j_wires, j_edges, j_vertices, uuidKey="uuid") for j_f in j_faces]
            faces = Helper.Flatten(faces)
            shells = [buildShell(j_s, j_faces, j_wires, j_edges, j_vertices, uuidKey="uuid") for j_s in j_shells]
            cells = [buildCell(j_c, j_shells, j_faces, j_wires, j_edges, j_vertices, uuidKey="uuid") for j_c in j_cells]
            cellComplexes = [buildCellComplex(j_cc, j_cells, j_shells, j_faces, j_wires, j_edges, j_vertices, uuidKey="uuid") for j_cc in j_cellComplexes]
            if len(cellComplexes) > 0:
                everything = cellComplexes
            elif len(cells) > 0:
                everything = cells
            elif len(shells) > 0:
                everything = shells
            elif len(faces) > 0:
                everything = faces
            elif len(wires) > 0:
                everything = wires
            elif len(edges) > 0:
                everything = edges
            elif len(vertices) > 0:
                everything = vertices
            else:
                return None
            if len(everything) == 1:
                aperture = everything[0]
            else:
                aperture = Topology.SelfMerge(Cluster.ByTopologies(everything), tolerance=tolerance)
            return aperture

        def buildVertex(json_item):
            x, y, z = json_item['coordinates']
            d = json_item['dictionary']
            v = Vertex.ByCoordinates(x, y, z)
            if v == None:
                print("Topology.ByJSONString - Error: Could not build a vertex. Returning None.")
                return None
            v = Topology.SetDictionary(v, Dictionary.ByPythonDictionary(d))
            apertures = [buildAperture(j_ap) for j_ap in json_item['apertures']]
            context = Context.ByTopologyParameters(v, u=0.5, v=0.5, w=0.5)
            for ap in apertures:
                _ = Aperture.ByTopologyContext(ap, context)
            return v

        def buildEdge(json_item, j_vertices, uuidKey="uuid", tolerance=0.0001):
            edge_vertices = json_item['vertices']
            vertices = []
            for j_v in edge_vertices:
                vertices.append(buildVertex(find_json_item(j_vertices, uuidKey, j_v)))
            e = Edge.ByVertices(vertices, tolerance=tolerance)
            if e == None:
                print("Topology.ByJSONString - Error: Could not build an edge. Returning None.")
                return None
            d = json_item['dictionary']
            e = Topology.SetDictionary(e, Dictionary.ByPythonDictionary(d))
            apertures = [buildAperture(j_ap) for j_ap in json_item['apertures']]
            context = Context.ByTopologyParameters(e, u=0.5, v=0.5, w=0.5)
            for ap in apertures:
                _ = Aperture.ByTopologyContext(ap, context)
            return e

        def buildWire(json_item, j_edges, j_vertices, uuidKey="uuid", tolerance=0.0001):
            wire_edges = json_item['edges']
            edges = []
            for j_e in wire_edges:
                edges.append(buildEdge(find_json_item(j_edges, uuidKey, j_e), j_vertices, uuidKey=uuidKey, tolerance=tolerance))
            w = Wire.ByEdges(edges, tolerance=tolerance)
            if w == None:
                print("Topology.ByJSONString - Error: Could not build a wire. Returning None.")
                return None
            d = json_item['dictionary']
            w = Topology.SetDictionary(w, Dictionary.ByPythonDictionary(d))
            apertures = [buildAperture(j_ap) for j_ap in json_item['apertures']]
            context = Context.ByTopologyParameters(w, u=0.5, v=0.5, w=0.5)
            for ap in apertures:
                _ = Aperture.ByTopologyContext(ap, context)
            return w

        def buildFace(json_item, j_wires, j_edges, j_vertices, uuidKey="uuid", tolerance=0.0001):
            face_wires = json_item['wires']
            external_boundary = buildWire(find_json_item(j_wires, uuidKey, face_wires[0]), j_edges, j_vertices, uuidKey=uuidKey, tolerance=tolerance)
            if not Topology.IsInstance(external_boundary, "Wire"):
                print("Topology.ByJSONString - ERROR: Something went wrong with original external boundary. Returning None.")
                return None
            if not Topology.IsPlanar(external_boundary, tolerance=tolerance):
                temp_boundary = Wire.Planarize(external_boundary, tolerance=tolerance)
                if temp_boundary == None or not Topology.IsInstance(temp_boundary, "Wire"):
                    print("Topology.ByJSONString - Error: Something went wrong with external boundary. Returning None.")
                    return None
                else:
                    external_boundary = temp_boundary
            if not Wire.IsClosed(external_boundary):
                external_boundary = Wire.Close(external_boundary)
            internal_boundaries = []
            for j_w in face_wires[1:]:
                ib = buildWire(find_json_item(j_wires, uuidKey, j_w),j_edges, j_vertices, uuidKey=uuidKey)
                if not Topology.IsPlanar(external_boundary):
                    ib = Wire.Planarize(ib)
                    if not Topology.IsInstance(ib, "Wire"):
                        print("Topology.ByJSONString - ERROR: Something went wrong with original internal boundary. Returning None.")
                        return None
                if not Wire.IsClosed(ib):
                    ib = Wire.Close(ib)
                internal_boundaries.append(ib)

            f = Face.ByWires(external_boundary, internal_boundaries, tolerance=tolerance)
            if not Topology.IsInstance(f, "Face"):
                print("Topology.ByJSONString - Error: Could not build a face. Returning None.", f, "Ex Bound:", external_boundary)
                return None
            area = Face.Area(f)
            if area == None:
                print("Topology.ByJSONString - Error: Could not compute the area of the built face. Returning None.")
                return None
            if Face.Area(f) < 0:
                external_boundary = Wire.Invert(external_boundary)
            f = Face.ByWires(external_boundary, internal_boundaries, tolerance=tolerance)
            if f == None:
                print("Topology.ByJSONString - Error: Could not build a face. Returning None.")
                return None
            d = json_item['dictionary']
            f = Topology.SetDictionary(f, Dictionary.ByPythonDictionary(d))
            apertures = [buildAperture(j_ap) for j_ap in json_item['apertures']]
            if len(apertures) > 0:
                context = Context.ByTopologyParameters(f, u=0.5, v=0.5, w=0.5)
                for ap in apertures:
                    _ = Aperture.ByTopologyContext(ap, context)
            return f

        def buildShell(json_item, j_faces, j_wires, j_edges, j_vertices, uuidKey="uuid", tolerance=0.0001):
            shell_faces = json_item['faces']
            faces = []
            for j_f in shell_faces:
                faces.append(buildFace(find_json_item(j_faces, uuidKey, j_f), j_wires, j_edges, j_vertices, uuidKey=uuidKey))
            faces = Helper.Flatten(faces)
            s = Shell.ByFaces(faces, tolerance=tolerance) # This can return a list
            if not Topology.IsInstance(s, "Shell"):
                print("Topology.ByJSONString - Error: Could not build a shell. Returning None.")
                return None
            d = json_item['dictionary']
            s = Topology.SetDictionary(s, Dictionary.ByPythonDictionary(d))
            apertures = [buildAperture(j_ap) for j_ap in json_item['apertures']]
            if len(apertures) > 0:
                context = Context.ByTopologyParameters(s, u=0.5, v=0.5, w=0.5)
                for ap in apertures:
                    _ = Aperture.ByTopologyContext(ap, context)
            return s

        def buildCell(json_item, j_shells, j_faces, j_wires, j_edges, j_vertices, uuidKey="uuid", tolerance=0.0001):
            cell_shells = json_item['shells']
            shells = []
            external_boundary = buildShell(find_json_item(j_shells, uuidKey, cell_shells[0]), j_faces, j_wires, j_edges, j_vertices, uuidKey=uuidKey, tolerance=tolerance)
            internal_boundaries = []
            for j_s in cell_shells[1:]:
                internal_boundaries.append(buildShell(find_json_item(j_shells, uuidKey, j_s), j_faces, j_wires, j_edges, j_vertices, uuidKey=uuidKey, tolerance=tolerance))
            c = Cell.ByShell(external_boundary)
            if c == None:
                print("Topology.ByJSONString - Error: Could not build a cell. Returning None.")
                return None
            for ib in internal_boundaries:
                ib_c = Cell.ByShell(ib)
                c = Topology.Difference(c, ib_c, tolerance=tolerance)
            d = json_item['dictionary']
            c = Topology.SetDictionary(c, Dictionary.ByPythonDictionary(d))
            apertures = [buildAperture(j_ap) for j_ap in json_item['apertures']]
            context = Context.ByTopologyParameters(c, u=0.5, v=0.5, w=0.5)
            for ap in apertures:
                _ = Aperture.ByTopologyContext(ap, context)
            return c

        def buildCellComplex(json_item, j_cells, j_shells, j_faces, j_wires, j_edges, j_vertices, uuidKey="uuid", tolerance=0.0001):
            cc_cells = json_item['cells']
            cells = []
            for j_c in cc_cells:
                cells.append(buildCell(find_json_item(j_cells, uuidKey, j_c), j_shells, j_faces, j_wires, j_edges, j_vertices, uuidKey=uuidKey, tolerance=tolerance))
            cc = CellComplex.ByCells(cells, tolerance=tolerance)
            if cc == None:
                print("Topology.ByJSONString - Error: Could not build a cellcomplex. Returning None.")
                return None
            d = json_item['dictionary']
            cc = Topology.SetDictionary(cc, Dictionary.ByPythonDictionary(d))
            apertures = [buildAperture(j_ap) for j_ap in json_item['apertures']]
            context = Context.ByTopologyParameters(cc, u=0.5, v=0.5, w=0.5)
            for ap in apertures:
                _ = Aperture.ByTopologyContext(ap, context)
            return cc

        def addAperturesUUID(topology, uuidKey="uuid"):
            topology_apertures = [Aperture.Topology(ap) for ap in Topology.Apertures(topology)]
            apertures_uuid = []
            for top_a in topology_apertures:
                uuid = getUUID(top_a, uuidKey=uuidKey)
                apertures_uuid.append(uuid)
            d = Topology.Dictionary(topology)
            d = Dictionary.SetValueAtKey(d, 'apertures', apertures_uuid)
            topology = Topology.SetDictionary(topology, d)
            s = Topology.InternalVertex(topology, tolerance=tolerance)
            s = Topology.SetDictionary(s, d)
            return topology, s, topology_apertures

        def findAperture(uuid, apertures, uuidKey="uuid"):
            for ap in apertures:
                d = Topology.Dictionary(ap)
                ap_uuid = Dictionary.ValueAtKey(d, uuidKey)
                if uuid == ap_uuid:
                    return ap
            return None

        def setApertures(topology, allApertures, uuidKey="uuid"):
            apertures = []
            d = Topology.Dictionary(topology)
            apertures_uuid = Dictionary.ValueAtKey(d, 'apertures')
            if not isinstance(apertures_uuid, list):
                apertures_uuid = [apertures_uuid]
            for ap_uuid in apertures_uuid:
                ap = findAperture(ap_uuid, allApertures, uuidKey=uuidKey)
                if ap != None:
                    apertures.append(ap)
            context = Context.ByTopologyParameters(topology, u=0.5, v=0.5, w=0.5)
            for ap in apertures:
                _ = Aperture.ByTopologyContext(ap, context)
            return topology
                    
                
        jsondata = json.loads(string)
        if not isinstance(jsondata, list):
            jsondata = [jsondata]

        j_vertices = []
        j_edges = []
        j_wires = []
        j_faces = []
        j_shells = []
        j_cells = []
        j_cellComplexes = []

        vertices = []
        edges = []
        wires = []
        faces = []
        shells = []
        cells = []
        cellComplexes = []

        if progressBar:
            for jsonItem in tqdm(jsondata):
                try:
                    topology_type = jsonItem['type']
                    if topology_type.lower() == "vertex":
                        j_vertices.append(jsonItem)
                    elif topology_type.lower() == "edge":
                        j_edges.append(jsonItem)
                    elif topology_type.lower() == "wire":
                        j_wires.append(jsonItem)
                    elif topology_type.lower() == "face":
                        j_faces.append(jsonItem)
                    elif topology_type.lower() == "shell":
                        j_shells.append(jsonItem)
                    elif topology_type.lower() == "cell":
                        j_cells.append(jsonItem)
                    elif topology_type.lower() == "cellcomplex":
                        j_cellComplexes.append(jsonItem)
                except:
                    continue
        else:
            for jsonItem in jsondata:
                try:
                    topology_type = jsonItem['type']
                    if topology_type.lower() == "vertex":
                        j_vertices.append(jsonItem)
                    elif topology_type.lower() == "edge":
                        j_edges.append(jsonItem)
                    elif topology_type.lower() == "wire":
                        j_wires.append(jsonItem)
                    elif topology_type.lower() == "face":
                        j_faces.append(jsonItem)
                    elif topology_type.lower() == "shell":
                        j_shells.append(jsonItem)
                    elif topology_type.lower() == "cell":
                        j_cells.append(jsonItem)
                    elif topology_type.lower() == "cellcomplex":
                        j_cellComplexes.append(jsonItem)
                except:
                    continue

        vertices = [buildVertex(j_v) for j_v in j_vertices]
        vertex_selectors = []
        all_vertex_apertures = []
        for v in vertices:
            v, s, vertex_apertures = addAperturesUUID(v, uuidKey="uuid")
            all_vertex_apertures += vertex_apertures
            vertex_selectors.append(s)

        edges = [buildEdge(j_e, j_vertices, uuidKey="uuid") for j_e in j_edges]
        edge_selectors = []
        all_edge_apertures = []
        for e in edges:
            e, s, edge_apertures = addAperturesUUID(e, uuidKey="uuid")
            all_edge_apertures += edge_apertures
            edge_selectors.append(s)

        wires = [buildWire(j_w, j_edges, j_vertices, uuidKey="uuid") for j_w in j_wires]
        wire_selectors = []
        all_wire_apertures = []
        for w in wires:
            w, s, wire_apertures = addAperturesUUID(w, uuidKey="uuid")
            all_wire_apertures += wire_apertures
            wire_selectors.append(s)

        faces = []
        for j_f in j_faces:
            f = buildFace(j_f, j_wires, j_edges, j_vertices, uuidKey="uuid")
            faces.append(f)
       
        faces = Helper.Flatten(faces)
        face_selectors = []
        all_face_apertures = []
        for f in faces:
            f, s, face_apertures = addAperturesUUID(f, uuidKey="uuid")
            all_face_apertures += face_apertures
            face_selectors.append(s)

        shells = [buildShell(j_s, j_faces, j_wires, j_edges, j_vertices, uuidKey="uuid") for j_s in j_shells]
        shell_selectors = []
        all_shell_apertures = []
        for sh in shells:
            sh, s, shell_apertures = addAperturesUUID(sh, uuidKey="uuid")
            all_shell_apertures += shell_apertures
            shell_selectors.append(s)

        cells = [buildCell(j_c, j_shells, j_faces, j_wires, j_edges, j_vertices, uuidKey="uuid") for j_c in j_cells]
        cell_selectors = []
        all_cell_apertures = []
        for c in cells:
            c, s, cell_apertures = addAperturesUUID(c, uuidKey="uuid")
            all_cell_apertures += cell_apertures
            cell_selectors.append(s)

        cellComplexes = [buildCellComplex(j_cc, j_cells, j_shells, j_faces, j_wires, j_edges, j_vertices, uuidKey="uuid") for j_cc in j_cellComplexes]
        cellComplex_selectors = []
        all_cellComplex_apertures = []
        for cc in cellComplexes:
            cc, s, cellComplex_apertures = addAperturesUUID(cc, uuidKey="uuid")
            all_cellComplex_apertures += cellComplex_apertures
            cellComplex_selectors.append(s)

        everything = vertices+edges+wires+faces+shells+cells+cellComplexes
        toplevelTopologies = []
        for ev in everything:
            d = Topology.Dictionary(ev)
            if Dictionary.ValueAtKey(d,'toplevel') == True:
                toplevelTopologies.append(ev)

        for tp in toplevelTopologies:
            # This is a hack because sometimes the imported topologies get weird. I think it is an opencascade bug.
            tp = Topology.ByBREPString(Topology.BREPString(tp))
            if len(vertex_selectors) > 0:
                _ = Topology.TransferDictionariesBySelectors(tp, vertex_selectors, tranVertices=True, tolerance=tolerance)
            if len(edge_selectors) > 0:
                _ = Topology.TransferDictionariesBySelectors(tp, edge_selectors, tranEdges=True, tolerance=tolerance)
            if len(face_selectors) > 0:
                _ = Topology.TransferDictionariesBySelectors(tp, face_selectors, tranFaces=True, tolerance=tolerance)
            if len(cell_selectors) > 0:
                _ = Topology.TransferDictionariesBySelectors(tp, cell_selectors, tranCells=True, tolerance=tolerance)
            if len(all_vertex_apertures) > 0:
                tp_vertices = Topology.Vertices(tp)
                for tp_vertex in tp_vertices:
                    tp_vertex = setApertures(tp_vertex, all_vertex_apertures, uuidKey="uuid")
            if len(all_edge_apertures) > 0:
                tp_edges = Topology.Edges(tp)
                for tp_edge in tp_edges:
                    tp_edge = setApertures(tp_edge, all_edge_apertures, uuidKey="uuid")
            if len(all_face_apertures) > 0:
                tp_faces = Topology.Faces(tp)
                for tp_face in tp_faces:
                    tp_face = setApertures(tp_face, all_face_apertures, uuidKey="uuid")
            if len(all_cell_apertures) > 0:
                tp_cells = Topology.Cells(tp)
                for tp_cell in tp_cells:
                    tp_cell = setApertures(tp_cell, all_cell_apertures, uuidKey="uuid")
    
        if len(toplevelTopologies) == 1:
            return toplevelTopologies[0]
        else:
            return toplevelTopologies
    
    @staticmethod
    def ByJSONPath(path, tolerance=0.0001):
        """
        Imports the topology from a JSON file.

        Parameters
        ----------
        path : str
            The file path to the json file.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        list
            The list of imported topologies.

        """
        if not path:
            print("Topology.ByJSONPath - Error: the input path parameter is not a valid path. Returning None.")
            return None
        data = None
        with open(path) as file:
            data = Topology.ByJSONFile(file=file, tolerance=tolerance)
        return data

    @staticmethod
    def ByOBJString(string, transposeAxes = True, progressBar=False, tolerance=0.0001):
        """
        Creates a topology from the input Waverfront OBJ string. This is a very experimental method and only works with simple planar solids. Materials and Colors are ignored.

        Parameters
        ----------
        string : str
            The input OBJ string.
        transposeAxes : bool , optional
            If set to True the Z and Y coordinates are transposed so that Y points "up" 
        progressBar : bool , optional
            If set to True a tqdm progress bar is shown. If not, it will not be shown. The default is False.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        topology
            The created topology.

        """
        from topologicpy.Vertex import Vertex
        from tqdm.auto import tqdm

        def parse(lines):
            vertices = []
            faces = []
            for i in range(len(lines)):
                l = lines[i].replace(",", " ")
                s = l.split()
                if isinstance(s, list):
                    if len(s) > 3:
                        if s[0].lower() == "v":
                            vertices.append([float(s[1]), float(s[2]), float(s[3])])
                        elif s[0].lower() == "f":
                            temp_faces = []
                            for j in range(1,len(s)):
                                f = s[j].split("/")[0]
                                temp_faces.append(int(f)-1)
                            faces.append(temp_faces)
            return [vertices, faces]
        
        def parsetqdm(lines):
            vertices = []
            faces = []
            for i in tqdm(range(len(lines))):
                s = lines[i].split()
                if isinstance(s, list):
                    if len(s) > 3:
                        if s[0].lower() == "v":
                                vertices.append([float(s[1]), float(s[2]), float(s[3])])
                        elif s[0].lower() == "f":
                            temp_faces = []
                            for j in range(1,len(s)):
                                f = s[j].split("/")[0]
                                temp_faces.append(int(f)-1)
                            faces.append(temp_faces)
            return [vertices, faces]
        
        
        lines = string.split("\n")
        if lines:
            if progressBar:
                vertices, faces = parsetqdm(lines)
            else:
                vertices, faces = parse(lines)
        if vertices or faces:
            topology = Topology.ByGeometry(vertices = vertices, faces = faces, outputMode="default", tolerance=tolerance)
            if transposeAxes == True:
                topology = Topology.Rotate(topology, origin=Vertex.Origin(), axis=[1, 0, 0], angle=90)
            return Topology.SelfMerge(topology)
        print("Topology.ByOBJString - Error: Could not find vertices or faces. Returning None.")
        return None

    @staticmethod
    def ByOBJFile(file, transposeAxes=True, progressBar=False, tolerance=0.0001):
        """
        Imports the topology from a Weverfront OBJ file. This is a very experimental method and only works with simple planar solids. Materials and Colors are ignored.

        Parameters
        ----------
        file : file object
            The input OBJ file.
        transposeAxes : bool , optional
            If set to True the Z and Y coordinates are transposed so that Y points "up"
        progressBar : bool , optional
            If set to True a tqdm progress bar is shown. If not, it will not be shown. The default is False.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        topology
            The imported topology.

        """
        if not file:
            print("Topology.ByOBJFile - Error: the input file parameter is not a valid file. Returning None.")
            return None
        obj_string = file.read()
        topology = Topology.ByOBJString(obj_string, transposeAxes=transposeAxes, progressBar=progressBar, tolerance=tolerance)
        file.close()
        return topology
    
    @staticmethod
    def ByOBJPath(path, transposeAxes=True, progressBar=False, tolerance=0.0001):
        """
        Imports the topology from a Weverfront OBJ file path. This is a very experimental method and only works with simple planar solids. Materials and Colors are ignored.

        Parameters
        ----------
        path : str
            The file path to the OBJ file.
        transposeAxes : bool , optional
            If set to True the Z and Y coordinates are transposed so that Y points "up".
        progressBar : bool , optional
            If set to True a tqdm progress bar is shown. If not, it will not be shown. The default is False.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        topology
            The imported topology.

        """
        if not path:
            print("Topology.ByOBJPath - Error: the input path parameter is not a valid path. Returning None.")
            return None
        try:
            file = open(path)
        except:
            print("Topology.ByOBJPath - Error: the OBJ file is not a valid file. Returning None.")
            return None
        return Topology.ByOBJFile(file, transposeAxes=transposeAxes, progressBar=progressBar, tolerance=tolerance)

    @staticmethod
    def ByOCCTShape(occtShape):
        """
        Creates a topology from the input OCCT shape. See https://dev.opencascade.org/doc/overview/html/occt_user_guides__modeling_data.html.

        Parameters
        ----------
        occtShape : topologic_core.TopoDS_Shape
            The inoput OCCT Shape.

        Returns
        -------
        topologic_core.Topology
            The created topology.

        """
        return topologic.Topology.ByOcctShape(occtShape, "")
    
    @staticmethod
    def ByBREPString(string):
        """
        Creates a topology from the input brep string

        Parameters
        ----------
        string : str
            The input brep string.

        Returns
        -------
        topologic_core.Topology
            The created topology.

        """
        if not isinstance(string, str):
            print("Topology.ByBREPString - Error: the input string parameter is not a valid string. Returning None.")
            return None
        returnTopology = None
        try:
            returnTopology = topologic.Topology.ByString(string)
        except:
            print("Topology.ByBREPString - Error: the input string parameter is not a valid string. Returning None.")
            returnTopology = None
        return returnTopology
    
    @staticmethod
    def ByXYZFile(file, frameIdKey="id", vertexIdKey="id"):
        """
        Imports the topology from an XYZ file path. This is a very experimental method. While variants of the format exist, topologicpy reads XYZ files that conform to the following:
        An XYZ file can be made out of one or more frames. Each frame will be stored in a sepatate topologic cluster.
        First line: total number of vertices in the frame. This must be an integer. No other words or characters are allowed on this line.
        Second line: frame label. This is free text and will be stored in the dictionary of each frame (topologic_core.Cluster)
        All other lines: vertex_label, x, y, and z coordinates, separated by spaces, tabs, or commas. The vertex label must be one word with no spaces. It is stored in the dictionary of each vertex.

        Example:
        3
        Frame 1
        A 5.67 -3.45 2.61
        B 3.91 -1.91 4
        A 3.2 1.2 -12.3
        4
        Frame 2
        B 5.47 -3.45 2.61
        B 3.91 -1.93 3.1
        A 3.2 1.2 -22.4
        A 3.2 1.2 -12.3
        3
        Frame 3
        A 5.67 -3.45 2.61
        B 3.91 -1.91 4
        C 3.2 1.2 -12.3

        Parameters
        ----------
        file : file object
            The input XYZ file.
        frameIdKey : str , optional
            The desired id key to use to store the ID of each frame in its dictionary. The default is "id".
        vertexIdKey : str , optional
            The desired id key to use to store the ID of each point in its dictionary. The default is "id".

        Returns
        -------
        list
            The list of frames (topologic_core.Cluster).

        """
        from topologicpy.Vertex import Vertex
        from topologicpy.Cluster import Cluster
        from topologicpy.Dictionary import Dictionary

        def parse(lines):
            frames = []
            line_index = 0
            while line_index < len(lines):
                try:
                    n_vertices = int(lines[line_index])
                except:
                    return frames
                frame_label = lines[line_index+1][:-1]
                vertices = []
                for i in range(n_vertices):
                    one_line = lines[line_index+2+i]
                    s = one_line.split()
                    vertex_label = s[0]
                    v = Vertex.ByCoordinates(float(s[1]), float(s[2]), float(s[3]))
                    vertex_dict = Dictionary.ByKeysValues([vertexIdKey], [vertex_label])
                    v = Topology.SetDictionary(v, vertex_dict)
                    vertices.append(v)
                frame = Cluster.ByTopologies(vertices)
                frame_dict = Dictionary.ByKeysValues([frameIdKey], [frame_label])
                frame = Topology.SetDictionary(frame, frame_dict)
                frames.append(frame)
                line_index = line_index + 2 + n_vertices
            return frames
        
        if not file:
            print("Topology.ByXYZFile - Error: the input file parameter is not a valid file. Returning None.")
            return None
        lines = []
        for lineo, line in enumerate(file):
                lines.append(line)
        if len(lines) > 0:
            frames = parse(lines)
        file.close()
        return frames
    
    @staticmethod
    def ByXYZPath(path, frameIdKey="id", vertexIdKey="id"):
        """
        Imports the topology from an XYZ file path. This is a very experimental method. While variants of the format exist, topologicpy reads XYZ files that conform to the following:
        An XYZ file can be made out of one or more frames. Each frame will be stored in a sepatate topologic cluster.
        First line: total number of vertices in the frame. This must be an integer. No other words or characters are allowed on this line.
        Second line: frame label. This is free text and will be stored in the dictionary of each frame (topologic_core.Cluster)
        All other lines: vertex_label, x, y, and z coordinates, separated by spaces, tabs, or commas. The vertex label must be one word with no spaces. It is stored in the dictionary of each vertex.

        Example:
        3
        Frame 1
        A 5.67 -3.45 2.61
        B 3.91 -1.91 4
        A 3.2 1.2 -12.3
        4
        Frame 2
        B 5.47 -3.45 2.61
        B 3.91 -1.93 3.1
        A 3.2 1.2 -22.4
        A 3.2 1.2 -12.3
        3
        Frame 3
        A 5.67 -3.45 2.61
        B 3.91 -1.91 4
        C 3.2 1.2 -12.3

        Parameters
        ----------
        path : str
            The input XYZ file path.
        frameIdKey : str , optional
            The desired id key to use to store the ID of each frame in its dictionary. The default is "id".
        vertexIdKey : str , optional
            The desired id key to use to store the ID of each point in its dictionary. The default is "id".

        Returns
        -------
        list
            The list of frames (topologic_core.Cluster).

        """
        if not path:
            print("Topology.ByXYZPath - Error: the input path parameter is not a valid path. Returning None.")
            return None
        try:
            file = open(path)
        except:
            print("Topology.ByXYZPath - Error: the XYZ file is not a valid file. Returning None.")
            return None
        return Topology.ByXYZFile(file, frameIdKey=frameIdKey, vertexIdKey=frameIdKey)
    
    @staticmethod
    def CenterOfMass(topology):
        """
        Returns the center of mass of the input topology. See https://en.wikipedia.org/wiki/Center_of_mass.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.

        Returns
        -------
        topologic_core.Vertex
            The center of mass of the input topology.

        """
        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.CenterofMass - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        return topology.CenterOfMass()
    
    @staticmethod
    def Centroid(topology):
        """
        Returns the centroid of the vertices of the input topology. See https://en.wikipedia.org/wiki/Centroid.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.

        Returns
        -------
        topologic_core.Vertex
            The centroid of the input topology.

        """
        from topologicpy.Aperture import Aperture

        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.Centroid - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        if Topology.IsInstance(topology, "Aperture"):
            return Aperture.Topology(topology).Centroid()
        return topology.Centroid()
    
    @staticmethod
    def ClusterFaces(topology, angTolerance=2, tolerance=0.0001):
        """
        Clusters the faces of the input topology by their direction.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        angTolerance : float , optional
            The desired angular tolerance. The default is 0.1.
        tolerance : float, optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        list
            The list of clusters of faces where faces in the same cluster have the same direction.

        """
        from topologicpy.Vector import Vector
        from topologicpy.Face import Face
        from topologicpy.Cluster import Cluster

        faces = Topology.SubTopologies(topology, subTopologyType="face")
        face_normals = []
        for face in faces:
            face_normals.append(Face.Normal(face))
        bins = []
        for face_normal in face_normals:
            minAngle = angTolerance * 100
            for bin in bins:
                ang = Vector.Angle(bin, face_normal)
                if ang < minAngle:
                    minAngle = ang
            if minAngle > angTolerance:
                bins.append(face_normal)
        num_bins = len(bins)
        # Convert face_normals to a numpy array for efficient computation
        face_normals_array = np.array(face_normals)

        # Compute the bounds for each bin along each dimension
        bin_bounds = [np.linspace(-1, 1, num_bins + 1) for _ in range(3)]

        # Assign each face to a bin based on its normal
        bin_indices = [np.digitize(face_normal, bounds) - 1 for face_normal, bounds in zip(face_normals_array.T, bin_bounds)]
        # Combine the indices along the three dimensions to get a single bin index for each face
        cluster_labels = bin_indices[0] * (num_bins**2) + bin_indices[1] * num_bins + bin_indices[2]
        cluster_labels = list(cluster_labels)
        bins = list(set(cluster_labels))
        clusters = []
        for bin in bins:
            clusters.append([])
        for i, face in enumerate(faces):
            ind = bins.index(cluster_labels[i])
            clusters[ind].append(face)
        final_clusters = []
        for cluster in clusters:
            final_clusters.append(Topology.SelfMerge(Cluster.ByTopologies(cluster), tolerance=tolerance))
        return final_clusters
    
    @staticmethod
    def ClusterFaces_orig(topology, angTolerance=0.1, tolerance=0.0001):
        """
        Clusters the faces of the input topology by their direction.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        angTolerance : float , optional
            The desired angular tolerance. The default is 0.1.
        tolerance : float, optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        list
            The list of clusters of faces where faces in the same cluster have the same direction.

        """
        from topologicpy.Face import Face
        from topologicpy.Cluster import Cluster

        def angle_between(v1, v2):
            u1 = v1 / norm(v1)
            u2 = v2 / norm(v2)
            y = u1 - u2
            x = u1 + u2
            if norm(x) == 0:
                return 0
            a0 = 2 * arctan(norm(y) / norm(x))
            if (not signbit(a0)) or signbit(pi - a0):
                return a0
            elif signbit(a0):
                return 0
            else:
                return pi

        def collinear(v1, v2, tol):
            ang = angle_between(v1, v2)
            if math.isnan(ang) or math.isinf(ang):
                raise Exception("Face.IsCollinear - Error: Could not determine the angle between the input faces")
            elif abs(ang) < tol or abs(pi - ang) < tol:
                return True
            return False
        
        def sumRow(matrix, i):
            return np.sum(matrix[i,:])
        
        def buildSimilarityMatrix(samples, tol):
            numOfSamples = len(samples)
            matrix = np.zeros(shape=(numOfSamples, numOfSamples))
            for i in range(len(matrix)):
                for j in range(len(matrix)):
                    if collinear(samples[i], samples[j], tol):
                        matrix[i, j] = 1
            return matrix

        def determineRow(matrix):
            maxNumOfOnes = -1
            row = -1
            for i in range(len(matrix)):
                if maxNumOfOnes < sumRow(matrix, i):
                    maxNumOfOnes = sumRow(matrix, i)
                    row = i
            return row

        def categorizeIntoClusters(matrix):
            groups = []
            while np.sum(matrix) > 0:
                group = []
                row = determineRow(matrix)
                indexes = addIntoGroup(matrix, row)
                groups.append(indexes)
                matrix = deleteChosenRowsAndCols(matrix, indexes)
            return groups

        def addIntoGroup(matrix, ind):
            change = True
            indexes = []
            for col in range(len(matrix)):
                if matrix[ind, col] == 1:
                    indexes.append(col)
            while change == True:
                change = False
                numIndexes = len(indexes)
                for i in indexes:
                    for col in range(len(matrix)):
                        if matrix[i, col] == 1:
                            if col not in indexes:
                                indexes.append(col)
                numIndexes2 = len(indexes)
                if numIndexes != numIndexes2:
                    change = True
            return indexes

        def deleteChosenRowsAndCols(matrix, indexes):
            for i in indexes:
                matrix[i, :] = 0
                matrix[:, i] = 0
            return matrix
        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.ClusterFaces - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        faces = []
        _ = topology.Faces(None, faces)
        normals = []
        for aFace in faces:
            normals.append(Face.NormalAtParameters(aFace, 0.5, 0.5, "XYZ", 3))
        # build a matrix of similarity
        mat = buildSimilarityMatrix(normals, angTolerance)
        categories = categorizeIntoClusters(mat)
        returnList = []
        for aCategory in categories:
            tempList = []
            if len(aCategory) > 0:
                for index in aCategory:
                    tempList.append(faces[index])
                returnList.append(Topology.SelfMerge(Cluster.ByTopologies(tempList), tolerance=tolerance))
        return returnList

    @staticmethod
    def Contents(topology):
        """
        Returns the contents of the input topology

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.

        Returns
        -------
        list
            The list of contents of the input topology.

        """
        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.Contents - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        contents = []
        _ = topology.Contents(contents)
        return contents
    
    @staticmethod
    def Contexts(topology):
        """
        Returns the list of contexts of the input topology

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.

        Returns
        -------
        list
            The list of contexts of the input topology.

        """
        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.Contexts - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        contexts = []
        _ = topology.Contexts(contexts)
        return contexts

    @staticmethod
    def ConvexHull(topology, tolerance=0.0001):
        """
        Creates a convex hull

        Parameters
        ----------
        topology : topologic_core.Topology
            The input Topology.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        topologic_core.Topology
            The created convex hull of the input topology.

        """
        from topologicpy.Vertex import Vertex
        from topologicpy.Edge import Edge
        from topologicpy.Wire import Wire
        from topologicpy.Face import Face
        from topologicpy.Shell import Shell
        from topologicpy.Cell import Cell
        from topologicpy.Cluster import Cluster        
        
        def convexHull3D(item, tolerance, option):
            if item:
                vertices = []
                _ = item.Vertices(None, vertices)
                pointList = []
                for v in vertices:
                    pointList.append([v.X(), v.Y(), v.Z()])
                points = np.array(pointList)
                if option:
                    hull = ConvexHull(points, qhull_options=option)
                else:
                    hull = ConvexHull(points)
                faces = []
                for simplex in hull.simplices:
                    edges = []
                    for i in range(len(simplex)-1):
                        sp = hull.points[simplex[i]]
                        ep = hull.points[simplex[i+1]]
                        sv = Vertex.ByCoordinates(sp[0], sp[1], sp[2])
                        ev = Vertex.ByCoordinates(ep[0], ep[1], ep[2])
                        edges.append(Edge.ByVertices([sv, ev], tolerance=tolerance))
                    sp = hull.points[simplex[-1]]
                    ep = hull.points[simplex[0]]
                    sv = Vertex.ByCoordinates(sp[0], sp[1], sp[2])
                    ev = Vertex.ByCoordinates(ep[0], ep[1], ep[2])
                    edges.append(Edge.ByVertices([sv, ev], tolerance=tolerance))
                    faces.append(Face.ByWire(Wire.ByEdges(edges, tolerance=tolerance), tolerance=tolerance))
            try:
                c = Cell.ByFaces(faces, tolerance=tolerance)
                return c
            except:
                returnTopology = Topology.SelfMerge(Cluster.ByTopologies(faces), tolerance=tolerance)
                if Topology.Type(returnTopology) == Topology.TypeID("Shell"):
                    return Shell.ExternalBoundary(returnTopology, tolerance=tolerance)
        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.ConvexHull - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        returnObject = None
        try:
            returnObject = convexHull3D(topology, tolerance, None)
        except:
            returnObject = convexHull3D(topology, tolerance, 'QJ')
        return returnObject
    
    @staticmethod
    def Copy(topology, deep=False):
        """
        Returns a copy of the input topology

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        deep : bool , optional
            If set to True, a deep copy will be performed (this is slow). Othwerwise, it will not. The default is False

        Returns
        -------
        topologic_core.Topology
            A copy of the input topology.

        """
        from topologicpy.Dictionary import Dictionary
        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.Copy - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        if deep:
            return Topology.ByJSONString(Topology.JSONString([topology]), progressBar=False)
        d = Topology.Dictionary(topology)
        return_topology = Topology.ByBREPString(Topology.BREPString(topology))
        keys = Dictionary.Keys(d)
        if len(keys) > 0:
            return_topology = Topology.SetDictionary(return_topology, d)
        return return_topology
    
    @staticmethod
    def Dictionary(topology):
        """
        Returns the dictionary of the input topology

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.

        Returns
        -------
        topologic_core.Dictionary
            The dictionary of the input topology.

        """
        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.Dictionary - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        return topology.GetDictionary()
    
    @staticmethod
    def Dimensionality(topology):
        """
        Returns the dimensionality of the input topology

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.

        Returns
        -------
        int
            The dimensionality of the input topology.

        """
        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.Dimensionality - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        return topology.Dimensionality()
    
    @staticmethod
    def Divide(topologyA, topologyB, transferDictionary=False, addNestingDepth=False):
        """
        Divides the input topology by the input tool and places the results in the contents of the input topology.

        Parameters
        ----------
        topologyA : topologic_core.Topology
            The input topology to be divided.
        topologyB : topologic_core.Topology
            the tool used to divide the input topology.
        transferDictionary : bool , optional
            If set to True the dictionary of the input topology is transferred to the divided topologies.
        addNestingDepth : bool , optional
            If set to True the nesting depth of the division is added to the dictionaries of the divided topologies.

        Returns
        -------
        topologic_core.Topology
            The input topology with the divided topologies added to it as contents.

        """
        
        from topologicpy.Dictionary import Dictionary
        if not Topology.IsInstance(topologyA, "Topology"):
            print("Topology.Divide - Error: the input topologyA parameter is not a valid topology. Returning None.")
            return None
        if not Topology.IsInstance(topologyB, "Topology"):
            print("Topology.Divide - Error: the input topologyB parameter is not a valid topology. Returning None.")
            return None
        try:
            _ = topologyA.Divide(topologyB, False) # Don't transfer dictionaries just yet
        except:
            raise Exception("TopologyDivide - Error: Divide operation failed.")
        nestingDepth = "1"
        keys = ["nesting_depth"]
        values = [nestingDepth]

        if not addNestingDepth and not transferDictionary:
            return topologyA

        contents = []
        _ = topologyA.Contents(contents)
        for i in range(len(contents)):
            if not addNestingDepth and transferDictionary:
                parentDictionary = Topology.Dictionary(topologyA)
                if parentDictionary != None:
                    _ = contents[i].SetDictionary(parentDictionary)
            if addNestingDepth and transferDictionary:
                parentDictionary = Topology.Dictionary(topologyA)
                if parentDictionary != None:
                    keys = Dictionary.Keys(parentDictionary)
                    values = Dictionary.Values(parentDictionary)
                    if ("nesting_depth" in keys):
                        nestingDepth = parentDictionary.ValueAtKey("nesting_depth").StringValue()
                    else:
                        keys.append("nesting_depth")
                        values.append(nestingDepth)
                    parentDictionary = Dictionary.ByKeysValues(keys, values)
                else:
                    keys = ["nesting_depth"]
                    values = [nestingDepth]
                parentDictionary = Dictionary.ByKeysValues(keys, values)
                _ = Topology.SetDictionary(topologyA, parentDictionary)
                values[keys.index("nesting_depth")] = nestingDepth+"_"+str(i+1)
                d = Dictionary.ByKeysValues(keys, values)
                _ = contents[i].SetDictionary(d)
            if addNestingDepth and  not transferDictionary:
                parentDictionary = Topology.Dictionary(topologyA)
                if parentDictionary != None:
                    keys, values = Dictionary.ByKeysValues(parentDictionary)
                    if ("nesting_depth" in keys):
                        nestingDepth = parentDictionary.ValueAtKey("nesting_depth").StringValue()
                    else:
                        keys.append("nesting_depth")
                        values.append(nestingDepth)
                    parentDictionary = Dictionary.ByKeysValues(keys, values)
                else:
                    keys = ["nesting_depth"]
                    values = [nestingDepth]
                parentDictionary = Dictionary.ByKeysValues(keys, values)
                _ = Topology.SetDictionary(topologyA, parentDictionary)
                keys = ["nesting_depth"]
                v = nestingDepth+"_"+str(i+1)
                values = [v]
                d = Dictionary.ByKeysValues(keys, values)
                _ = Topology.SetDictionary(contents[i], d)
        return topologyA
    
    @staticmethod
    def Explode(topology, origin=None, scale=1.25, typeFilter=None, axes="xyz", tolerance=0.0001):
        """
        Explodes the input topology. See https://en.wikipedia.org/wiki/Exploded-view_drawing.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        origin : topologic_core.Vertex , optional
            The origin of the explosion. If set to None, the centroid of the input topology will be used. The default is None.
        scale : float , optional
            The scale factor of the explosion. The default is 1.25.
        typeFilter : str , optional
            The type of the subtopologies to explode. This can be any of "vertex", "edge", "face", or "cell". If set to None, a subtopology one level below the type of the input topology will be used. The default is None.
        axes : str , optional
            Sets what axes are to be used for exploding the topology. This can be any permutation or substring of "xyz". It is not case sensitive. The default is "xyz".
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.
        
        Returns
        -------
        topologic_core.Cluster
            The exploded topology.

        """
        from topologicpy.Vertex import Vertex
        from topologicpy.Cluster import Cluster
        from topologicpy.Graph import Graph

        def processClusterTypeFilter(cluster):
            if len(Cluster.CellComplexes(cluster)) > 0:
                return "cell"
            elif len(Cluster.Cells(cluster)) > 0:
                return "face"
            elif len(Cluster.Shells(cluster)) > 0:
                return "face"
            elif len(Cluster.Faces(cluster)) > 0:
                return "edge"
            elif len(Cluster.Wires(cluster)) > 0:
                return "edge"
            elif len(Cluster.Edges(cluster)) > 0:
                return "vertex"
            else:
                return "self"

        def getTypeFilter(topology):
            typeFilter = "self"
            if Topology.IsInstance(topology, "Vertex"):
                typeFilter = "self"
            elif Topology.IsInstance(topology, "Edge"):
                typeFilter = "vertex"
            elif Topology.IsInstance(topology, "Wire"):
                typeFilter = "edge"
            elif Topology.IsInstance(topology, "Face"):
                typeFilter = "edge"
            elif Topology.IsInstance(topology, "Shell"):
                typeFilter = "face"
            elif Topology.IsInstance(topology, "Cell"):
                typeFilter = "face"
            elif Topology.IsInstance(topology, "CellComplex"):
                typeFilter = "cell"
            elif Topology.IsInstance(topology, "Cluster"):
                typeFilter = processClusterTypeFilter(topology)
            elif Topology.IsInstance(topology, "Graph"):
                typeFilter = "edge"
            return typeFilter
        
        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.Explode - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        if not Topology.IsInstance(origin, "Vertex"):
            origin = Topology.CenterOfMass(topology)
        if not typeFilter:
            typeFilter = getTypeFilter(topology)
        if not isinstance(typeFilter, str):
            print("Topology.Explode - Error: the input typeFilter parameter is not a valid string. Returning None.")
            return None
        if not isinstance(axes, str):
            print("Topology.Explode - Error: the input axes parameter is not a valid string. Returning None.")
            return None
        if Topology.IsInstance(topology, "Topology"):
            # Hack to fix a weird bug that seems to be a problem with OCCT memory handling.
            topology = Topology.ByBREPString(Topology.BREPString(topology))
        axes = axes.lower()
        x_flag = "x" in axes
        y_flag = "y" in axes
        z_flag = "z" in axes
        if not x_flag and not y_flag and not z_flag:
            print("Topology.Explode - Error: the input axes parameter is not a valid string. Returning None.")
            return None

        topologies = []
        newTopologies = []
        if Topology.IsInstance(topology, "Graph"):
            topology = Graph.Topology(topology)

        if typeFilter.lower() == "self":
            topologies = [topology]
        else:
            topologies = Topology.SubTopologies(topology, subTopologyType=typeFilter.lower())
        for aTopology in topologies:
            c = Topology.InternalVertex(aTopology, tolerance=tolerance)
            oldX = c.X()
            oldY = c.Y()
            oldZ = c.Z()
            if x_flag:
                newX = (oldX - origin.X())*scale + origin.X()
            else:
                newX = oldX
            if y_flag:
                newY = (oldY - origin.Y())*scale + origin.Y()
            else:
                newY = oldY
            if z_flag:
                newZ = (oldZ - origin.Z())*scale + origin.Z()
            else:
                newZ = oldZ
            xT = newX - oldX
            yT = newY - oldY
            zT = newZ - oldZ
            newTopology = Topology.Translate(aTopology, xT, yT, zT)
            newTopologies.append(newTopology)
        return Cluster.ByTopologies(newTopologies)
    
    @staticmethod
    def ExportToBREP(topology, path, overwrite=False, version=3):
        """
        Exports the input topology to a BREP file. See https://dev.opencascade.org/doc/occt-6.7.0/overview/html/occt_brep_format.html.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        path : str
            The input file path.
        overwrite : bool , optional
            If set to True the ouptut file will overwrite any pre-existing file. Otherwise, it won't. The default is False.
        version : int , optional
            The desired version number for the BREP file. The default is 3.

        Returns
        -------
        bool
            True if the export operation is successful. False otherwise.

        """
        from os.path import exists
        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.ExportToBREP - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        if not isinstance(path, str):
            print("Topology.ExportToBREP - Error: the input path parameter is not a valid string. Returning None.")
            return None
        # Make sure the file extension is .brep
        ext = path[len(path)-5:len(path)]
        if ext.lower() != ".brep":
            path = path+".brep"
        if not overwrite and exists(path):
            print("Topology.ExportToBREP - Error: a file already exists at the specified path and overwrite is set to False. Returning None.")
            return None
        f = None
        try:
            if overwrite == True:
                f = open(path, "w")
            else:
                f = open(path, "x") # Try to create a new File
        except:
            raise Exception("Error: Could not create a new file at the following location: "+path)
        if (f):
            s = Topology.BREPString(topology, version)
            f.write(s)
            f.close()    
            return True
        return False


    def ExportToDXF(topologies, path,  overwrite=False):
        """
        Exports the input topology to a DXF file. See https://en.wikipedia.org/wiki/AutoCAD_DXF.
        THe DXF version is 'R2010'
        This is experimental and only geometry is exported.

        Parameters
        ----------
        topologies : list or topologic_core.Topology
            The input list of topologies. This can also be a single topologic_core.Topology.
        path : str
            The input file path.
        overwrite : bool , optional
            If set to True the ouptut file will overwrite any pre-existing file. Otherwise, it won't. The default is False.

        Returns
        -------
        bool
            True if the export operation is successful. False otherwise.

        """
        import os
        import warnings

        try:
            import ezdxf
        except:
            print("Topology.ExportToDXF - Information: Installing required ezdxf library.")
            try:
                os.system("pip install ezdxf")
            except:
                os.system("pip install ezdxf --user")
            try:
                import ezdxf
                print("Topology.ExportToDXF - Information: ezdxf library installed successfully.")
            except:
                warnings.warn("Topology.ExportToDXF - Error: Could not import ezdxf library. Please install it manually. Returning None.")
                return None
        
        from topologicpy.Vertex import Vertex
        from topologicpy.Edge import Edge
        from topologicpy.Cluster import Cluster
        from topologicpy.Topology import Topology

        from os.path import exists
        if not isinstance(topologies, list):
            topologies = [topologies]
        topologies = [topology for topology in topologies if Topology.IsInstance(topology, "Topology")]
        if len(topologies) < 1:
            print("Topology.ExportToDXF - Error: The inupt list parameter topologies does not contain any valid topologies. Returning None.")
        # Make sure the file extension is .brep
        ext = path[len(path)-4:len(path)]
        if ext.lower() != ".dxf":
            path = path+".dxf"
        if not overwrite and exists(path):
            print("Topology.ExportToDXF - Error: a file already exists at the specified path and overwrite is set to False. Returning None.")
            return None
        
        def add_vertices(vertices, msp):
            for v in vertices:
                if Topology.IsInstance(v, "Vertex"):
                    msp.add_point((Vertex.X(v), Vertex.Y(v), Vertex.Z(v)))
        def add_edges(edges, msp):
            for e in edges:
                if Topology.IsInstance(e, "Edge"):
                    sv = Edge.StartVertex(e)
                    ev = Edge.EndVertex(e)
                    start = (Vertex.X(sv), Vertex.Y(sv), Vertex.Z(sv))
                    end = (Vertex.X(ev), Vertex.Y(ev), Vertex.Z(ev))
                    msp.add_line(start, end)
        def add_wires(wires, msp):
            for i, w in enumerate(wires):
                if Topology.IsInstance(w, "Wire"):
                    block_name = "Wire_"+str(i+1).zfill(3)
                    block = doc.blocks.new(name=block_name)
                    # Add edges to the block
                    edges = Topology.Edges(w)
                    for edge in edges:
                        sv = Edge.StartVertex(edge)
                        ev = Edge.EndVertex(edge)
                        start = (Vertex.X(sv), Vertex.Y(sv), Vertex.Z(sv))
                        end = (Vertex.X(ev), Vertex.Y(ev), Vertex.Z(ev))
                        block.add_line(start, end)
                    # Insert the block into the model space
                    msp.add_blockref(block_name, insert=(0, 0, 0))
        def add_meshes(meshes, msp):
            for m in meshes:
                data = Topology.Geometry(m)
                vertices = data['vertices']
                faces = data['faces']
                mesh = msp.add_mesh()
                mesh.dxf.subdivision_levels = 0
                with mesh.edit_data() as mesh_data:
                    mesh_data.vertices = vertices
                    mesh_data.faces = faces
        # Create a new DXF document
        doc = ezdxf.new(dxfversion='R2010')
        msp = doc.modelspace()
        i = 1
        for topology in topologies:
            if Topology.IsInstance(topology, "Vertex"):
                add_vertices([topology], msp)
            elif Topology.IsInstance(topology, "Edge"):
                add_edges([topology], msp)
            elif Topology.IsInstance(topology, "Wire"):
                add_wires([topology], msp)
            elif Topology.IsInstance(topology, "Face"):
                add_meshes([topology], msp)
            elif Topology.IsInstance(topology, "Shell"):
                add_meshes([topology], msp)
            elif Topology.IsInstance(topology, "Cell"):
                add_meshes([topology], msp)
            elif Topology.IsInstance(topology, "CellComplex"):
                add_meshes([topology], msp)
            elif Topology.IsInstance(topology, "Cluster"):
                cellComplexes = Topology.CellComplexes(topology)
                add_meshes(cellComplexes, msp)
                cells = Cluster.FreeCells(topology)
                add_meshes(cells, msp)
                shells = Cluster.FreeShells(topology)
                add_meshes(shells, msp)
                faces = Cluster.FreeFaces(topology)
                add_meshes(faces, msp)
                wires = Cluster.FreeWires(topology)
                add_wires(wires, msp)
                edges = Cluster.FreeEdges(topology)
                add_edges(edges, msp)
                vertices = Cluster.FreeVertices(topology)
                add_vertices(vertices, msp)
        
        # Save the DXF document
        status = False
        try:
            doc.saveas(path)
            status = True
        except:
            status = False
        return status

    '''
    @staticmethod
    def ExportToIPFS(topology, url, port, user, password):
        """
        NOT DONE YET

        Parameters
        ----------
        topology : TYPE
            DESCRIPTION.
        url : TYPE
            DESCRIPTION.
        port : TYPE
            DESCRIPTION.
        user : TYPE
            DESCRIPTION.
        password : TYPE
            DESCRIPTION.

        Returns
        -------
        TYPE
            DESCRIPTION.

        """
        # topology, url, port, user, password = item
        
        def exportToBREP(topology, path, overwrite):
            # Make sure the file extension is .brep
            ext = path[len(path)-5:len(path)]
            if ext.lower() != ".brep":
                path = path+".brep"
            f = None
            try:
                if overwrite == True:
                    f = open(path, "w")
                else:
                    f = open(path, "x") # Try to create a new File
            except:
                raise Exception("Error: Could not create a new file at the following location: "+path)
            if (f):
                topString = topology.BREPString()
                f.write(topString)
                f.close()       
                return True
            return False
        
        path = os.path.expanduser('~')+"/tempFile.brep"
        if exportToBREP(topology, path, True):
            url = url.replace('http://','')
            url = '/dns/'+url+'/tcp/'+port+'/https'
            client = ipfshttpclient.connect(url, auth=(user, password))
            newfile = client.add(path)
            os.remove(path)
            return newfile['Hash']
        return ''
    '''
    @staticmethod
    def ExportToJSON(topologies, path, overwrite=False):
        """
        Exports the input list of topologies to a JSON file.

        Parameters
        ----------
        topologies : list
            The input list of topologies.
        path : str
            The path to the JSON file.
        overwrite : bool , optional
            If set to True the ouptut file will overwrite any pre-existing file. Otherwise, it won't. The default is False.

        Returns
        -------
        bool
            The status of exporting the JSON file. If True, the operation was successful. Otherwise, it was unsuccesful.

        """
        from os.path import exists
        # Make sure the file extension is .json
        ext = path[len(path)-5:len(path)]
        if ext.lower() != ".json":
            path = path+".json"
        if not overwrite and exists(path):
            print("Topology.ExportToJSON - Error: a file already exists at the specified path and overwrite is set to False. Returning None.")
            return None
        f = None
        try:
            if overwrite == True:
                f = open(path, "w")
            else:
                f = open(path, "x") # Try to create a new File
        except:
            raise Exception("Error: Could not create a new file at the following location: "+path)
        if (f):
            jsondata = json.loads(Topology.JSONString(topologies))
            if jsondata != None:
                json.dump(jsondata, f, indent=4, sort_keys=True)
                f.close()
                return True
            else:
                f.close()
                return False
        return False

    @staticmethod
    def Fix(topology, topologyType: str = "CellComplex", tolerance: float = 0.0001):
        """
        Attempts to fix the input topology to matched the desired output type.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology
        topologyType : str , optional
            The desired output topology type. This must be one of "vertex", "edge", "wire", "face", "shell", "cell", "cellcomplex", "cluster". It is case insensitive. The default is "CellComplex"
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.
        
        Returns
        -------
        topologic_core.Topology
            The output topology in the desired type.

        """
        from topologicpy.Vertex import Vertex
        from topologicpy.Edge import Edge
        from topologicpy.Wire import Wire
        from topologicpy.Face import Face
        from topologicpy.Shell import Shell
        from topologicpy.Cell import Cell
        from topologicpy.CellComplex import CellComplex
        from topologicpy.Cluster import Cluster

        topology = Cluster.ByTopologies([topology])
        a_type = Topology.TypeAsString(topology).lower()
        b_type = topologyType.lower()
        if b_type not in ["vertex", "edge", "wire", "face", "shell", "cell", "cellcomplex", "cluster"]:
            print("Topology.Fix - Error: The input topologyType parameter is not recognized. Returning original topology.")
            return topology
        if a_type == b_type:
            return topology
        if b_type == "cluster":
            topology = Topology.SelfMerge(topology, tolerance=tolerance)
            return Cluster.ByTopologies([topology])
        if b_type == "cellcomplex":
            topology = Topology.SelfMerge(topology, tolerance=tolerance)
            if Topology.TypeAsString(topology).lower() == "cellcomplex":
                return topology
            cells = Topology.Cells(topology)
            if len(cells) < 2:
                print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
                return topology
            return_topology = CellComplex.ByCells(cells)
            if return_topology == None:
                print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
                return topology
            if Topology.TypeAsString(topology).lower() == "cellcomplex":
                return return_topology
            faces = Topology.Faces(topology)
            if len(faces) < 3:
                print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
                return topology
            return_topology = CellComplex.ByFaces(faces, tolerance=tolerance)
            if return_topology == None:
                print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
                return topology
            if Topology.TypeAsString(return_topology).lower() == "cellcomplex":
                return return_topology
            print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
            return topology
        if b_type == "cell":
            topology = Topology.SelfMerge(topology, tolerance=tolerance)
            if Topology.TypeAsString(topology).lower() == "cell":
                return topology
            if Topology.TypeAsString(topology).lower() == "cellComplex":
                return CellComplex.ExternalBoundary(topology)
            faces = Topology.Faces(topology)
            if len(faces) < 3:
                print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
                return topology
            return_topology = Cell.ByFaces(faces, tolerance=tolerance)
            if return_topology == None:
                return_topology = CellComplex.ByFaces(faces, tolerance=tolerance)
                if return_topology == None:
                    print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
                    return topology
                elif len(Topology.Cells(return_topology)) < 1:
                    print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
                    return topology
                return_topology = CellComplex.ExternalBoundary(return_topology)
            if return_topology == None:
                print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
                return topology
            if Topology.TypeAsString(return_topology).lower() == "cell":
                return return_topology
            print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
            return topology
        if b_type == "shell":
            topology = Topology.SelfMerge(topology, tolerance=tolerance)
            if Topology.TypeAsString(topology).lower() == "shell":
                return topology
            faces = Topology.Faces(topology)
            if len(faces) < 2:
                print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
                return topology
            return_topology = Shell.ByFaces(faces)
            if Topology.TypeAsString(return_topology).lower() == "shell":
                return return_topology
            print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
            return topology
        if b_type == "face":
            topology = Topology.SelfMerge(topology, tolerance=tolerance)
            if Topology.TypeAsString(topology).lower() == "face":
                return topology
            wires = Topology.Wires(topology)
            if len(wires) < 1:
                print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
                return topology
            return_topology = Face.ByWire(wires[0], tolerance=tolerance)
            if Topology.TypeAsString(return_topology).lower() == "face":
                return return_topology
            print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
            return topology
        if b_type == "wire":
            topology = Topology.SelfMerge(topology, tolerance=tolerance)
            if Topology.TypeAsString(topology).lower() == "wire":
                return topology
            edges = Topology.Edges(topology)
            if len(edges) < 2:
                print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
                return topology
            return_topology = Wire.ByEdges(edges, tolerance=tolerance)
            if Topology.TypeAsString(return_topology).lower() == "wire":
                return return_topology
            print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
            return topology
        if b_type == "edge":
            topology = Topology.SelfMerge(topology, tolerance=tolerance)
            if Topology.TypeAsString(topology).lower() == "edge":
                return topology
            vertices = Topology.Vertices(topology)
            if len(vertices) < 2:
                print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
                return topology
            return_topology = Edge.ByVertices(vertices, tolerance=tolerance)
            if Topology.TypeAsString(return_topology).lower() == "edge":
                return return_topology
            print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
            return topology
        if b_type == "vertex":
            topology = Topology.SelfMerge(topology, tolerance=tolerance)
            if Topology.TypeAsString(topology).lower() == "vertex":
                return topology
            vertices = Topology.Vertices(topology)
            if len(vertices) < 1:
                print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
                return topology
            return_topology = vertices[0]
            if Topology.TypeAsString(return_topology).lower() == "vertex":
                return return_topology
            print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
            return topology
        return topology

    @staticmethod
    def JSONString(topologies, mantissa: int = 6):
        """
        Exports the input list of topologies to a JSON string

        Parameters
        ----------
        topologies : list or topologic_core.Topology
            The input list of topologies or a single topology.
        mantissa : int , optional
            The desired length of the mantissa. The default is 6.
        
        Returns
        -------
        bool
            The status of exporting the JSON file. If True, the operation was successful. Otherwise, it was unsuccesful.

        """

        from topologicpy.Vertex import Vertex
        from topologicpy.Edge import Edge
        from topologicpy.Face import Face
        from topologicpy.Cell import Cell
        from topologicpy.Dictionary import Dictionary
        from topologicpy.Aperture import Aperture

        def getUUID(topology, uuidKey="uuid"):
            d = Topology.Dictionary(topology)
            if uuidKey not in Dictionary.Keys(d):
                uuidOne = str(uuid.uuid1())
                d = Dictionary.SetValueAtKey(d, uuidKey, uuidOne)
                topology = Topology.SetDictionary(topology, d)
            else:
                uuidOne = Dictionary.ValueAtKey(d, uuidKey)
            return uuidOne

        def getVertex(topology, uuidKey="uuid"):
            returnDict = {}
            uuidOne = getUUID(topology, uuidKey=uuidKey)
            returnDict['type'] = "Vertex"
            returnDict['uuid'] = uuidOne
            returnDict['coordinates'] = Vertex.Coordinates(topology, mantissa=mantissa)
            returnDict['dictionary'] = Dictionary.PythonDictionary(Topology.Dictionary(topology))
            return returnDict

        def getEdge(topology, uuidKey="uuid"):
            returnDict = {}
            uuidOne = getUUID(topology, uuidKey=uuidKey)
            returnDict['type'] = "Edge"
            returnDict['uuid'] = uuidOne
            edge_vertices = Edge.Vertices(topology)
            returnDict['vertices'] = [getUUID(v, uuidKey=uuidKey) for v in Edge.Vertices(topology)]
            returnDict['dictionary'] = Dictionary.PythonDictionary(Topology.Dictionary(topology))
            return returnDict

        def getWire(topology, uuidKey="uuid"):
            returnDict = {}
            uuidOne = getUUID(topology, uuidKey="uuid")
            returnDict['type'] = "Wire"
            returnDict['uuid'] = uuidOne
            returnDict['edges'] = [getUUID(e, uuidKey=uuidKey) for e in Topology.Edges(topology)]
            returnDict['dictionary'] = Dictionary.PythonDictionary(Topology.Dictionary(topology))
            return returnDict

        def getFace(topology, uuidKey="uuid"):
            apertures = [Aperture.Topology(ap) for ap in Topology.Apertures(topology)]
            returnDict = {}
            uuidOne = getUUID(topology, uuidKey=uuidKey)
            returnDict['type'] = "Face"
            returnDict['uuid'] = uuidOne
            wires = []
            external_boundary = Face.ExternalBoundary(topology)
            wires.append(getUUID(Face.ExternalBoundary(topology), uuidKey=uuidKey))
            internal_boundaries = [getUUID(ib, uuidKey=uuidKey) for ib in Face.InternalBoundaries(topology)]
            wires += internal_boundaries
            returnDict['wires'] = wires
            dictionary = Dictionary.PythonDictionary(Topology.Dictionary(topology))
            returnDict['dictionary'] = dictionary
            return returnDict

        def getShell(topology, uuidKey="uuid"):
            returnDict = {}
            uuidOne = getUUID(topology, uuidKey=uuidKey)
            returnDict['type'] = "Shell"
            returnDict['uuid'] = uuidOne
            returnDict['faces'] = [getUUID(f, uuidKey=uuidKey) for f in Topology.Faces(topology)]
            returnDict['dictionary'] = Dictionary.PythonDictionary(Topology.Dictionary(topology))
            return returnDict

        def getCell(topology, uuidKey="uuid"):
            returnDict = {}
            uuidOne = getUUID(topology, uuidKey=uuidKey)
            returnDict['type'] = "Cell"
            returnDict['uuid'] = uuidOne
            shells = []
            external_boundary = Cell.ExternalBoundary(topology)
            shells.append(getUUID(external_boundary, uuidKey=uuidKey))
            internal_boundaries = [getUUID(ib, uuidKey=uuidKey) for ib in Cell.InternalBoundaries(topology)]
            shells += internal_boundaries
            returnDict['shells'] = shells
            dictionary = Dictionary.PythonDictionary(Topology.Dictionary(topology))
            returnDict['dictionary'] = dictionary
            return returnDict

        def getCellComplex(topology, uuidKey="uuid"):
            returnDict = {}
            uuidOne = getUUID(topology, uuidKey=uuidKey)
            returnDict['type'] = "CellComplex"
            returnDict['uuid'] = uuidOne
            returnDict['cells'] = [getUUID(c, uuidKey=uuidKey) for c in Topology.Cells(topology)]
            returnDict['dictionary'] = Dictionary.PythonDictionary(Topology.Dictionary(topology))
            return returnDict


        def getApertureData(topology, topLevel="False", uuidKey="uuid"):
            json_data = []
            if Topology.IsInstance(topology, "Vertex"):
                d = getVertex(topology, uuidKey=uuidKey)
            elif Topology.IsInstance(topology, "Edge"):
                d = getEdge(topology, uuidKey=uuidKey)
            elif Topology.IsInstance(topology, "Wire"):
                d = getWire(topology, uuidKey=uuidKey)
            elif Topology.IsInstance(topology, "Face"):
                d = getFace(topology, uuidKey=uuidKey)
            elif Topology.IsInstance(topology, "Shell"):
                d = getShell(topology, uuidKey=uuidKey)
            elif Topology.IsInstance(topology, "Cell"):
                d = getCell(topology, uuidKey=uuidKey)
            elif Topology.IsInstance(topology, "CellComplex"):
                d = getCellComplex(topology, uuidKey=uuidKey)
            d['dictionary']['toplevel'] = topLevel
            json_data += getSubTopologyData(topology, uuidKey=uuidKey)
            apertures = [Aperture.Topology(ap) for ap in Topology.Apertures(topology)]
            aperture_data = []
            for ap in apertures:
                aperture_data.append(getApertureData(ap, topLevel=False, uuidKey=uuidKey))
            d['apertures'] = aperture_data
            json_data.append(d)
            return json_data

        def getSubTopologyData(topology, uuidKey="uuid"):
            json_data = []
            vertices = Topology.Vertices(topology)
            for v in vertices:
                d = getVertex(v, uuidKey=uuidKey)
                d['dictionary']['toplevel'] = False
                apertures = [Aperture.Topology(ap) for ap in Topology.Apertures(v)]
                aperture_data = []
                for ap in apertures:
                    aperture_data.append(getApertureData(ap, topLevel=False, uuidKey=uuidKey))
                d['apertures'] = aperture_data
                json_data.append(d)
            edges = Topology.Edges(topology)
            for e in edges:
                d = getEdge(e, uuidKey=uuidKey)
                d['dictionary']['toplevel'] = False
                apertures = [Aperture.Topology(ap) for ap in Topology.Apertures(e)]
                aperture_data = []
                for ap in apertures:
                    aperture_data.append(getApertureData(ap, topLevel=False, uuidKey=uuidKey))
                d['apertures'] = aperture_data
                json_data.append(d)
            wires = Topology.Wires(topology)
            for w in wires:
                d = getWire(w, uuidKey=uuidKey)
                d['dictionary']['toplevel'] = False
                apertures = [Aperture.Topology(ap) for ap in Topology.Apertures(w)]
                aperture_data = []
                for ap in apertures:
                    aperture_data.append(getApertureData(ap, topLevel=False, uuidKey=uuidKey))
                d['apertures'] = aperture_data
                json_data.append(d)
            faces = Topology.Faces(topology)
            for f in faces:
                d = getFace(f, uuidKey=uuidKey)
                d['dictionary']['toplevel'] = False
                apertures = [Aperture.Topology(ap) for ap in Topology.Apertures(f)]
                aperture_data = []
                for ap in apertures:
                    aperture_data.append(getApertureData(ap, topLevel=False, uuidKey=uuidKey))
                d['apertures'] = aperture_data
                json_data.append(d)
            shells = Topology.Shells(topology)
            for s in shells:
                d = getShell(s, uuidKey=uuidKey)
                d['dictionary']['toplevel'] = False
                apertures = [Aperture.Topology(ap) for ap in Topology.Apertures(s)]
                aperture_data = []
                for ap in apertures:
                    aperture_data.append(getApertureData(ap, topLevel=False, uuidKey=uuidKey))
                d['apertures'] = aperture_data
                json_data.append(d)
            cells = Topology.Cells(topology)
            for c in cells:
                d = getCell(c, uuidKey=uuidKey)
                d['dictionary']['toplevel'] = False
                apertures = [Aperture.Topology(ap) for ap in Topology.Apertures(c)]
                aperture_data = []
                for ap in apertures:
                    aperture_data.append(getApertureData(ap, topLevel=False, uuidKey=uuidKey))
                d['apertures'] = aperture_data
                json_data.append(d)
            cellComplexes = Topology.CellComplexes(topology)
            for cc in cellComplexes:
                d = getCellComplex(cc, uuidKey=uuidKey)
                d['dictionary']['toplevel'] = False
                apertures = [Aperture.Topology(ap) for ap in Topology.Apertures(cc)]
                aperture_data = []
                for ap in apertures:
                    aperture_data.append(getApertureData(ap, topLevel=False, uuidKey=uuidKey))
                d['apertures'] = aperture_data
                json_data.append(d)
            return json_data

        def getJSONData(topology, topLevel=False, uuidKey="uuid"):
            json_data = []
            if Topology.IsInstance(topology, "Vertex"):
                d = getVertex(topology, uuidKey=uuidKey)
            elif Topology.IsInstance(topology, "Edge"):
                d = getEdge(topology, uuidKey=uuidKey)
            elif Topology.IsInstance(topology, "Wire"):
                d = getWire(topology, uuidKey=uuidKey)
            elif Topology.IsInstance(topology, "Face"):
                d = getFace(topology, uuidKey=uuidKey)
            elif Topology.IsInstance(topology, "Shell"):
                d = getShell(topology, uuidKey=uuidKey)
            elif Topology.IsInstance(topology, "Cell"):
                d = getCell(topology, uuidKey=uuidKey)
            elif Topology.IsInstance(topology, "CellComplex"):
                d = getCellComplex(topology, uuidKey=uuidKey)
            else:
                print("Topology.JSONString - Error: Unknown topology type:", topology, ". Returning None.")
                return None
            d['dictionary']['toplevel'] = topLevel
            json_data += getSubTopologyData(topology, uuidKey=uuidKey)
            apertures = [Aperture.Topology(ap) for ap in Topology.Apertures(topology)]
            aperture_data = []
            for ap in apertures:
                aperture_data.append(getApertureData(ap, topLevel=False, uuidKey=uuidKey))
            d['apertures'] = aperture_data
            json_data.append(d)
            return json_data
        json_data = []
        if not isinstance(topologies, list):
            topologies = [topologies]
        topologies = [x for x in topologies if Topology.IsInstance(x, "Topology")]
        for topology in topologies:
            json_data += getJSONData(topology, topLevel=True, uuidKey="uuid")
        json_string = json.dumps(json_data, indent=4, sort_keys=False)
        return json_string
    
    @staticmethod
    def OBJString(topology, transposeAxes: bool = True, mode: int = 0, meshSize: float = None, mantissa: int = 6, tolerance: float = 0.0001):
        """
        Returns the Wavefront string of the input topology. This is very experimental and outputs a simple solid topology.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        transposeAxes : bool , optional
            If set to True the Z and Y coordinates are transposed so that Y points "up"
        mode : int , optional
            The desired mode of meshing algorithm. Several options are available:
            0: Classic
            1: MeshAdapt
            3: Initial Mesh Only
            5: Delaunay
            6: Frontal-Delaunay
            7: BAMG
            8: Fontal-Delaunay for Quads
            9: Packing of Parallelograms
            All options other than 0 (Classic) use the gmsh library. See https://gmsh.info/doc/texinfo/gmsh.html#Mesh-options
            WARNING: The options that use gmsh can be very time consuming and can create very heavy geometry.
        meshSize : float , optional
            The desired size of the mesh when using the "mesh" option. If set to None, it will be
            calculated automatically and set to 10% of the overall size of the face.
        mantissa : int , optional
            The desired length of the mantissa. The default is 6.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        str
            The Wavefront OBJ string of the input topology

        """
        from topologicpy.Helper import Helper
        from topologicpy.Vertex import Vertex
        from topologicpy.Face import Face

        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.ExportToOBJ - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        
        lines = []
        version = Helper.Version()
        lines.append("# topologicpy "+version)
        topology = Topology.Triangulate(topology, mode=mode, meshSize=meshSize, tolerance=tolerance)
        d = Topology.Geometry(topology, mantissa=mantissa)
        vertices = d['vertices']
        faces = d['faces']
        tVertices = []
        if transposeAxes:
            for v in vertices:
                tVertices.append([v[0], v[2], v[1]])
            vertices = tVertices
        for v in vertices:
            lines.append("v "+str(v[0])+" "+str(v[1])+" "+str(v[2]))
        for f in faces:
            line = "f"
            for j in f:
                line = line+" "+str(j+1)
            lines.append(line)
        finalLines = lines[0]
        for i in range(1,len(lines)):
            finalLines = finalLines+"\n"+lines[i]
        return finalLines
    
    @staticmethod
    def ExportToOBJ(topology, path, transposeAxes: bool = True, mode: int = 0, meshSize: float = None, overwrite: bool = False, mantissa: int = 6, tolerance: float = 0.0001):
        """
        Exports the input topology to a Wavefront OBJ file. This is very experimental and outputs a simple solid topology.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        path : str
            The input file path.
        transposeAxes : bool , optional
            If set to True the Z and Y coordinates are transposed so that Y points "up"
        mode : int , optional
            The desired mode of meshing algorithm. Several options are available:
            0: Classic
            1: MeshAdapt
            3: Initial Mesh Only
            5: Delaunay
            6: Frontal-Delaunay
            7: BAMG
            8: Fontal-Delaunay for Quads
            9: Packing of Parallelograms
            All options other than 0 (Classic) use the gmsh library. See https://gmsh.info/doc/texinfo/gmsh.html#Mesh-options
            WARNING: The options that use gmsh can be very time consuming and can create very heavy geometry.
        meshSize : float , optional
            The desired size of the mesh when using the "mesh" option. If set to None, it will be
            calculated automatically and set to 10% of the overall size of the face.
        mantissa : int , optional
            The desired length of the mantissa. The default is 6.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.
        overwrite : bool , optional
            If set to True the ouptut file will overwrite any pre-existing file. Otherwise, it won't. The default is False.

        Returns
        -------
        bool
            True if the export operation is successful. False otherwise.

        """
        from os.path import exists

        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.ExportToOBJ - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        if not overwrite and exists(path):
            print("Topology.ExportToOBJ - Error: a file already exists at the specified path and overwrite is set to False. Returning None.")
            return None
        
        # Make sure the file extension is .obj
        ext = path[len(path)-4:len(path)]
        if ext.lower() != ".obj":
            path = path+".obj"
        status = False
        objString = Topology.OBJString(topology, transposeAxes=transposeAxes, mode=mode, meshSize=meshSize, mantissa=mantissa, tolerance=tolerance)
        with open(path, "w") as f:
            f.writelines(objString)
            f.close()
            status = True
        return status

    @staticmethod
    def Filter(topologies, topologyType="any", searchType="any", key=None, value=None):
        """
        Filters the input list of topologies based on the input parameters.

        Parameters
        ----------
        topologies : list
            The input list of topologies.
        topologyType : str , optional
            The type of topology to filter by. This can be one of "any", "vertex", "edge", "wire", "face", "shell", "cell", "cellcomplex", or "cluster". It is case insensitive. The default is "any".
        searchType : str , optional
            The type of search query to conduct in the topology's dictionary. This can be one of "any", "equal to", "contains", "starts with", "ends with", "not equal to", "does not contain". The default is "any".
        key : str , optional
            The dictionary key to search within. The default is None which means it will filter by topology type only.
        value : str , optional
            The value to search for at the specified key. The default is None which means it will filter by topology type only.

        Returns
        -------
        dict
            A dictionary of filtered and other elements. The dictionary has two keys:
            - "filtered" The filtered topologies.
            - "other" The other topologies that did not meet the filter criteria.

        """
        from topologicpy.Dictionary import Dictionary

        def listToString(item):
            returnString = ""
            if isinstance(item, list):
                if len(item) < 2:
                    return str(item[0])
                else:
                    returnString = item[0]
                    for i in range(1, len(item)):
                        returnString = returnString+str(item[i])
            return returnString
        
        filteredTopologies = []
        otherTopologies = []
        for aTopology in topologies:
            if not aTopology:
                continue
            if (topologyType.lower() == "any") or (Topology.TypeAsString(aTopology).lower() == topologyType.lower()):
                if value == "" or key == "":
                    filteredTopologies.append(aTopology)
                else:
                    if isinstance(value, list):
                        value.sort()
                        value = str(value)
                    value.replace("*",".+")
                    value = value.lower()
                    d = Topology.Dictionary(aTopology)
                    v = Dictionary.ValueAtKey(d, key)
                    if v != None:
                        v = v.lower()
                        if searchType.lower() == "equal to":
                            searchResult = (value == v)
                        elif searchType.lower() == "contains":
                            searchResult = (value in v)
                        elif searchType.lower() == "starts with":
                            searchResult = (value == v[0: len(value)])
                        elif searchType.lower() == "ends with":
                            searchResult = (value == v[len(v)-len(value):len(v)])
                        elif searchType.lower() == "not equal to":
                            searchResult = not (value == v)
                        elif searchType.lower() == "does not contain":
                            searchResult = not (value in v)
                        else:
                            searchResult = False
                        if searchResult:
                            filteredTopologies.append(aTopology)
                        else:
                            otherTopologies.append(aTopology)
                    else:
                        otherTopologies.append(aTopology)
            else:
                otherTopologies.append(aTopology)
        return {"filtered": filteredTopologies, "other": otherTopologies}

    @staticmethod
    def Flatten(topology, origin=None, direction=[0, 0, 1]):
        """
        Flattens the input topology such that the input origin is located at the world origin and the input topology is rotated such that the input vector is pointed in the Up direction (see Vector.Up()).

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        origin : topologic_core.Vertex , optional
            The input origin. If set to None, The object's centroid will be used to place the world origin. The default is None.
        vector : list , optional
            The input direction vector. The input topology will be rotated such that this vector is pointed in the positive Z axis.

        Returns
        -------
        topologic_core.Topology
            The flattened topology.

        """
        from topologicpy.Vertex import Vertex
        from topologicpy.Vector import Vector

        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.Flatten - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        if origin == None:
            origin = Topology.Centroid(topology)
        up = Vector.Up()
        flat_topology = Topology.Translate(topology, -Vertex.X(origin), -Vertex.Y(origin), -Vertex.Z(origin))
        tran_mat = Vector.TransformationMatrix(direction, up)
        flat_topology = Topology.Transform(flat_topology, tran_mat)
        return flat_topology
    
    @staticmethod
    def Geometry(topology, mantissa=6):
        """
        Returns the geometry (mesh data format) of the input topology as a dictionary of vertices, edges, and faces.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        mantissa : int , optional
            The desired length of the mantissa. The default is 6.

        Returns
        -------
        dict
            A dictionary containing the vertices, edges, and faces data. The keys found in the dictionary are "vertices", "edges", and "faces".

        """
        from topologicpy.Vertex import Vertex
        from topologicpy.Face import Face
        from topologicpy.Vector import Vector
        
        def getSubTopologies(topology, subTopologyClass):
            topologies = []
            if subTopologyClass == topologic.Vertex:
                _ = topology.Vertices(None, topologies)
            elif subTopologyClass == topologic.Edge:
                _ = topology.Edges(None, topologies)
            elif subTopologyClass == topologic.Wire:
                _ = topology.Wires(None, topologies)
            elif subTopologyClass == topologic.Face:
                _ = topology.Faces(None, topologies)
            elif subTopologyClass == topologic.Shell:
                _ = topology.Shells(None, topologies)
            elif subTopologyClass == topologic.Cell:
                _ = topology.Cells(None, topologies)
            elif subTopologyClass == topologic.CellComplex:
                _ = topology.CellComplexes(None, topologies)
            return topologies

        def triangulateFace(face):
            faceTriangles = []
            for i in range(0, 5, 1):
                try:
                    _ = topologic.FaceUtility.Triangulate(face, float(i)*0.1, faceTriangles)
                    return faceTriangles
                except:
                    continue
            faceTriangles.append(face)
            return faceTriangles

        vertices = []
        edges = []
        faces = []
        if topology == None:
            return [None, None, None]
        topVerts = []
        if Topology.Type(topology) == Topology.TypeID("Vertex"): #input is a vertex, just add it and process it
            topVerts.append(topology)
        else:
            _ = topology.Vertices(None, topVerts)
        for aVertex in topVerts:
            try:
                vertices.index(Vertex.Coordinates(aVertex, mantissa=mantissa)) # Vertex already in list
            except:
                vertices.append(Vertex.Coordinates(aVertex, mantissa=mantissa)) # Vertex not in list, add it.
        topEdges = []
        if (Topology.Type(topology) == Topology.TypeID("Edge")): #Input is an Edge, just add it and process it
            topEdges.append(topology)
        elif (Topology.Type(topology) > Topology.TypeID("Vertex")):
            _ = topology.Edges(None, topEdges)
        for anEdge in topEdges:
            e = []
            sv = anEdge.StartVertex()
            ev = anEdge.EndVertex()
            try:
                svIndex = vertices.index(Vertex.Coordinates(sv, mantissa=mantissa))
            except:
                vertices.append(Vertex.Coordinates(sv, mantissa=mantissa))
                svIndex = len(vertices)-1
            try:
                evIndex = vertices.index(Vertex.Coordinates(ev, mantissa=mantissa))
            except:
                vertices.append(Vertex.Coordinates(ev, mantissa=mantissa))
                evIndex = len(vertices)-1
            e.append(svIndex)
            e.append(evIndex)
            if ([e[0], e[1]] not in edges) and ([e[1], e[0]] not in edges):
                edges.append(e)
        topFaces = []
        if (Topology.Type(topology) == Topology.TypeID("Face")): # Input is a Face, just add it and process it
            topFaces.append(topology)
        elif (Topology.Type(topology) > Topology.TypeID("Face")):
            _ = topology.Faces(None, topFaces)
        for aFace in topFaces:
            f_dir = Face.Normal(aFace)
            ib = []
            _ = aFace.InternalBoundaries(ib)
            if(len(ib) > 0):
                triFaces = triangulateFace(aFace)
                for aTriFace in triFaces:
                    wire = aTriFace.ExternalBoundary()
                    faceVertices = getSubTopologies(wire, topologic.Vertex)
                    temp_face = Face.ByWire(wire)
                    temp_dir = Face.Normal(temp_face)
                    if Vector.IsAntiParallel(f_dir, temp_dir):
                        faceVertices.reverse()
                    f = []
                    for aVertex in faceVertices:
                        try:
                            fVertexIndex = vertices.index(Vertex.Coordinates(aVertex, mantissa=mantissa))
                        except:
                            vertices.append(Vertex.Coordinates(aVertex, mantissa=mantissa))
                            fVertexIndex = len(vertices)-1
                        f.append(fVertexIndex)
                    faces.append(f)
            else:
                wire =  aFace.ExternalBoundary()
                #wire = topologic.WireUtility.RemoveCollinearEdges(wire, 0.1) #This is an angle Tolerance
                faceVertices = getSubTopologies(wire, topologic.Vertex)
                temp_face = Face.ByWire(wire)
                temp_dir = Face.Normal(temp_face)
                if Vector.IsAntiParallel(f_dir, temp_dir):
                    faceVertices.reverse()
                f = []
                for aVertex in faceVertices:
                    try:
                        fVertexIndex = vertices.index(Vertex.Coordinates(aVertex, mantissa=mantissa))
                    except:
                        vertices.append(Vertex.Coordinates(aVertex, mantissa=mantissa))
                        fVertexIndex = len(vertices)-1
                    f.append(fVertexIndex)
                faces.append(f)
        return {"vertices":vertices, "edges":edges, "faces":faces}

    @staticmethod
    def HighestType(topology):
        """
        Returns the highest topology type found in the input topology.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.

        Returns
        -------
        int
            The highest type found in the input topology.

        """
        from topologicpy.Cluster import Cluster

        if (Topology.Type(topology) == Topology.TypeID("Cluster")):
            return Cluster.HighestType(topology)
        else:
            return Topology.Type(topology)

    @staticmethod
    def InternalVertex(topology, tolerance: float = 0.0001):
        """
        Returns a vertex guaranteed to be inside the input topology.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        tolerance : float , ptional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        topologic_core.Vertex
            A vertex guaranteed to be inside the input topology.

        """
        from topologicpy.Vertex import Vertex
        from topologicpy.Edge import Edge
        from topologicpy.Face import Face
        from topologicpy.Cell import Cell
        from topologicpy.CellComplex import CellComplex
        from topologicpy.Aperture import Aperture

        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.InternalVertex - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        vst = None
        top = Topology.Copy(topology)
        if Topology.IsInstance(top, "CellComplex"): #CellComplex
            tempCell = Topology.Cells(top)[0]
            vst = Cell.InternalVertex(tempCell, tolerance=tolerance)
        elif Topology.IsInstance(top, "Cell"): #Cell
            vst = Cell.InternalVertex(top, tolerance=tolerance)
        elif Topology.IsInstance(top, "Shell"): #Shell
            tempFace = Topology.Faces(top)[0]
            vst = Face.InternalVertex(tempFace, tolerance=tolerance)
        elif Topology.IsInstance(top, "Face"): #Face
            vst = Face.InternalVertex(top, tolerance=tolerance)
        elif Topology.IsInstance(top, "Wire"): #Wire
            if top.IsClosed():
                internalBoundaries = []
                try:
                    tempFace = topologic.Face.ByExternalInternalBoundaries(top, internalBoundaries)
                    vst = Face.InternalVertex(tempFace, tolerance=tolerance)
                except:
                    vst = Topology.Centroid(top)
            else:
                tempEdge = Topology.Edges(top)[0]
                vst = Edge.VertexByParameter(tempEdge, 0.5)
        elif Topology.IsInstance(top, "Edge"): #Edge
            vst = Edge.VertexByParameter(top, 0.5)
        elif Topology.IsInstance(top, "Vertex"): #Vertex
            vst = top
        elif Topology.IsInstance(topology, "Aperture"): #Aperture
            vst = Face.InternalVertex(Aperture.Topology(top), tolerance)
        else:
            vst = Topology.Centroid(top)
        return vst

    @staticmethod
    def IsInstance(topology, type: str):
        """
        Returns True if the input topology is an instance of the class specified by the input type string.
        
        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        type : string
            The topology type. This can be one of:
            "Vertex"
            "Edge"
            "Wire"
            "Face"
            "Shell"
            "Cell"
            "CellComplex"
            "Cluster"
            "Topology"
            "Graph"
            "Aperture"
            "Dictionary"
            "Context"

        Returns
        -------
        bool
            True if the input topology is an instance of the class defined by the input type string. False otherwise.
        """

        if "vertex" in type.lower():
            return isinstance(topology, topologic.Vertex)
        elif "edge" in type.lower():
            return isinstance(topology, topologic.Edge)
        elif "wire" in type.lower():
            return isinstance(topology, topologic.Wire)
        elif "face" in type.lower():
            return isinstance(topology, topologic.Face)
        elif "shell" in type.lower():
            return isinstance(topology, topologic.Shell)
        elif "cellcomplex" in type.lower(): #Hack to test for cellcomplex before cell as they share the same prefix.
            return isinstance(topology, topologic.CellComplex)
        elif "cell" in type.lower():
            return isinstance(topology, topologic.Cell)
        elif "cluster" in type.lower():
            return isinstance(topology, topologic.Cluster)
        elif "topology" in type.lower():
            return isinstance(topology, topologic.Topology)
        elif "graph" in type.lower():
            return isinstance(topology, topologic.Graph)
        elif "aperture" in type.lower():
            return isinstance(topology, topologic.Aperture)
        elif "dictionary" in type.lower():
            return isinstance(topology, topologic.Dictionary)
        elif "context" in type.lower():
            return isinstance(topology, topologic.Context)
        else:
            print("Topology.IsInstance - Error: The type input string is not a known topology type. Returning None.")
            return None

    @staticmethod
    def IsPlanar(topology, tolerance=0.0001):
        """
        Returns True if all the vertices of the input topology are co-planar. Returns False otherwise.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        bool
            True if all the vertices of the input topology are co-planar. False otherwise.

        """
        
        def isOnPlane(v, plane, tolerance):
            x, y, z = v
            a, b, c, d = plane
            if math.fabs(a*x + b*y + c*z + d) <= tolerance:
                return True
            return False

        def plane(v1, v2, v3):
            a1 = v2.X() - v1.X() 
            b1 = v2.Y() - v1.Y() 
            c1 = v2.Z() - v1.Z() 
            a2 = v3.X() - v1.X() 
            b2 = v3.Y() - v1.Y() 
            c2 = v3.Z() - v1.Z() 
            a = b1 * c2 - b2 * c1 
            b = a2 * c1 - a1 * c2 
            c = a1 * b2 - b1 * a2 
            d = (- a * v1.X() - b * v1.Y() - c * v1.Z())
            return [a, b, c, d]

        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.IsPlanar - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        vertices = Topology.Vertices(topology)

        result = True
        if len(vertices) <= 3:
            result = True
        else:
            p = plane(vertices[0], vertices[1], vertices[2])
            for i in range(len(vertices)):
                if isOnPlane([vertices[i].X(), vertices[i].Y(), vertices[i].Z()], p, tolerance) == False:
                    result = False
                    break
        return result
    
    @staticmethod
    def IsSame(topologyA, topologyB):
        """
        Returns True if the input topologies are the same topology. Returns False otherwise.

        Parameters
        ----------
        topologyA : topologic_core.Topology
            The first input topology.
        topologyB : topologic_core.Topology
            The second input topology.

        Returns
        -------
        bool
            True of the input topologies are the same topology. False otherwise.

        """
        if not Topology.IsInstance(topologyA, "Topology"):
            print("Topology.IsSame - Error: the input topologyA parameter is not a valid topology. Returning None.")
            return None
        if not Topology.IsInstance(topologyB, "Topology"):
            print("Topology.IsSame - Error: the input topologyB parameter is not a valid topology. Returning None.")
            return None
        return topologic.Topology.IsSame(topologyA, topologyB)
    
    @staticmethod
    def MergeAll(topologies, tolerance=0.0001):
        """
        Merge all the input topologies.

        Parameters
        ----------
        topologies : list
            The list of input topologies.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        topologic_core.Topology
            The resulting merged Topology

        """

        from topologicpy.Cluster import Cluster

        if not isinstance(topologies, list):
            print("Topology.MergeAll - Error: the input topologies parameter is not a valid list. Returning None.")
            return None
        
        topologyList = [t for t in topologies if Topology.IsInstance(t, "Topology")]
        if len(topologyList) < 1:
            print("Topology.MergeAll - Error: the input topologyList does not contain any valid topologies. Returning None.")
            return None
        return Topology.SelfMerge(Cluster.ByTopologies(topologyList), tolerance=tolerance)
            
    @staticmethod
    def OCCTShape(topology):
        """
        Returns the occt shape of the input topology.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.

        Returns
        -------
        topologic_core.TopoDS_Shape
            The OCCT Shape.

        """
        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.OCCTShape - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        return topology.GetOcctShape()
    
    @staticmethod
    def Degree(topology, hostTopology):
        """
        Returns the number of immediate super topologies that use the input topology

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        hostTopology : topologic_core.Topology
            The input host topology to which the input topology belongs
        
        Returns
        -------
        int
            The degree of the topology (the number of immediate super topologies that use the input topology).
        
        """
        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.Degree - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        if not Topology.IsInstance(hostTopology, "Topology"):
            print("Topology.Degree - Error: the input hostTopology parameter is not a valid topology. Returning None.")
            return None
        
        hostTopologyType = Topology.TypeAsString(hostTopology).lower()
        type = Topology.TypeAsString(topology).lower()
        superType = ""
        if type == "vertex" and (hostTopologyType == "cellcomplex" or hostTopologyType == "cell" or hostTopologyType == "shell"):
            superType = "face"
        elif type == "vertex" and (hostTopologyType == "wire" or hostTopologyType == "edge"):
            superType = "edge"
        elif type == "edge" and (hostTopologyType == "cellcomplex" or hostTopologyType == "cell" or hostTopologyType == "shell"):
            superType = "face"
        elif type == "face" and (hostTopologyType == "cellcomplex"):
            superType = "cell"
        superTopologies = Topology.SuperTopologies(topology, hostTopology=hostTopology, topologyType=superType)
        if not superTopologies:
            return 0
        return len(superTopologies)

    @staticmethod
    def NonPlanarFaces(topology, tolerance=0.0001):
        """
        Returns any nonplanar faces in the input topology
        
        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.
        Returns
        -------
        list
            The list of nonplanar faces.
        
        """
        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.NonPlanarFaces - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        faces = Topology.SubTopologies(topology, subTopologyType="face")
        return [f for f in faces if not Topology.IsPlanar(f, tolerance=tolerance)]
    
    @staticmethod
    def OpenFaces(topology):
        """
        Returns the faces that border no cells.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        
        Returns
        -------
        list
            The list of open edges.
        
        """

        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.OpenFaces - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        
        return [f for f in Topology.SubTopologies(topology, subTopologyType="face") if Topology.Degree(f, hostTopology=topology) < 1]
    
    @staticmethod
    def OpenEdges(topology):
        """
        Returns the edges that border only one face.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        
        Returns
        -------
        list
            The list of open edges.
        
        """

        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.OpenEdges - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        
        return [e for e in Topology.SubTopologies(topology, subTopologyType="edge") if Topology.Degree(e, hostTopology=topology) < 2]
    
    @staticmethod
    def OpenVertices(topology):
        """
        Returns the vertices that border only one edge.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        
        Returns
        -------
        list
            The list of open edges.
        
        """

        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.OpenVertices - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        
        return [v for v in Topology.SubTopologies(topology, subTopologyType="vertex") if Topology.Degree(v, hostTopology=topology) < 2]
    
    @staticmethod
    def Orient(topology, origin=None, dirA=[0, 0, 1], dirB=[0, 0, 1], tolerance=0.0001):
        """
        Orients the input topology such that the input such that the input dirA vector is parallel to the input dirB vector.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        origin : topologic_core.Vertex , optional
            The input origin. If set to None, The object's centroid will be used to locate the input topology. The default is None.
        dirA : list , optional
            The first input direction vector. The input topology will be rotated such that this vector is parallel to the input dirB vector. The default is [0, 0, 1].
        dirB : list , optional
            The target direction vector. The input topology will be rotated such that the input dirA vector is parallel to this vector. The default is [0, 0, 1].
        tolerance : float , optional
            The desired tolerance. The default is 0.0001

        Returns
        -------
        topologic_core.Topology
            The flattened topology.

        """
        from topologicpy.Vertex import Vertex
        from topologicpy.Vector import Vector
        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.Orient - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        if not Topology.IsInstance(origin, "Vertex"):
            origin = Topology.Centroid(topology)
        return_topology = Topology.Place(topology, originA=origin, originB=Vertex.Origin())
        tran_mat = Vector.TransformationMatrix(dirA, dirB)
        return_topology = Topology.Transform(return_topology, tran_mat)
        return_topology = Topology.Place(return_topology, originA=Vertex.Origin(), originB=origin)
        return return_topology

    @staticmethod
    def Place(topology, originA=None, originB=None):
        """
        Places the input topology at the specified location.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        originA : topologic_core.Vertex , optional
            The old location to use as the origin of the movement. If set to None, the centroid of the input topology is used. The default is None.
        originB : topologic_core.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.

        Returns
        -------
        topologic_core.Topology
            The placed topology.

        """
        from topologicpy.Vertex import Vertex
        if not Topology.IsInstance(topology, "Topology"):
            return None
        if not Topology.IsInstance(originA, "Vertex"):
            originA = Topology.Centroid(topology)
        if not Topology.IsInstance(originA, "Vertex"):
            originA = Vertex.ByCoordinates(0, 0, 0)

        x = originB.X() - originA.X()
        y = originB.Y() - originA.Y()
        z = originB.Z() - originA.Z()
        newTopology = None
        try:
            newTopology = Topology.Translate(topology, x, y, z)
        except:
            print("Topology.Place - Error: (Topologic>TopologyUtility.Place) operation failed. Returning None.")
            newTopology = None
        return newTopology

    @staticmethod
    def RemoveCollinearEdges(topology, angTolerance=0.1, tolerance=0.0001):
        """
        Removes the collinear edges of the input topology

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        angTolerance : float , optional
            The desired angular tolerance. The default is 0.1.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        topologic_core.Topology
            The input topology with the collinear edges removed.

        """
        from topologicpy.Wire import Wire
        from topologicpy.Face import Face
        from topologicpy.Shell import Shell
        from topologicpy.Cell import Cell
        from topologicpy.CellComplex import CellComplex
        from topologicpy.Cluster import Cluster
        
        if not Topology.IsInstance(topology, "Topology"):
            return None
        return_topology = topology
        if Topology.IsInstance(topology, "Vertex") or Topology.IsInstance(topology, "Edge"): #Vertex or Edge or Cluster, return the original topology
            return return_topology
        elif Topology.IsInstance(topology, "Wire"):
            return_topology = Wire.RemoveCollinearEdges(topology, angTolerance=angTolerance, tolerance=tolerance)
            return return_topology
        elif Topology.IsInstance(topology, "Face"):
            return_topology = Face.RemoveCollinearEdges(topology, angTolerance=angTolerance, tolerance=tolerance)
            return return_topology
        elif Topology.IsInstance(topology, "Shell"):
            return_topology = Shell.RemoveCollinearEdges(topology, angTolerance=angTolerance, tolerance=tolerance)
            return return_topology
        elif Topology.IsInstance(topology, "Cell"):
            return_topology = Cell.RemoveCollinearEdges(topology, angTolerance=angTolerance, tolerance=tolerance)
            return return_topology
        elif Topology.IsInstance(topology, "CellComplex"):
            return_topology = CellComplex.RemoveCollinearEdges(topology, angTolerance=angTolerance, tolerance=tolerance)
            return return_topology
        elif Topology.IsInstance(topology, "Cluster"):
            topologies = []
            topologies += Cluster.FreeVertices(topology)
            topologies += Cluster.FreeEdges(topology)
            faces = Topology.Faces(topology)
            for face in faces:
                topologies.append(Face.RemoveCollinearEdges(topology, angTolerance=angTolerance, tolerance=tolerance))
            return_topology = Topology.SelfMerge(Cluster.ByTopologies(topologies), tolerance=tolerance)
        return return_topology

    @staticmethod
    def RemoveContent(topology, contents):
        """
        Removes the input content list from the input topology

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        contentList : list
            The input list of contents.

        Returns
        -------
        topologic_core.Topology
            The input topology with the input list of contents removed.

        """
        if isinstance(contents, list) == False:
            contents = [contents]
        return topology.RemoveContents(contents)
    
    @staticmethod
    def RemoveCoplanarFaces(topology, epsilon=0.01, tolerance=0.0001):
        """
        Removes coplanar faces in the input topology

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        angTolerance : float , optional
            The desired angular tolerance for removing coplanar faces. The default is 0.1.
        epsilon : float , optional
            The desired epsilon (another form of tolerance) for finding if two faces are coplanar. The default is 0.01.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        topologic_core.Topology
            The input topology with coplanar faces merged into one face.

        """
        from topologicpy.Vertex import Vertex
        from topologicpy.Face import Face
        from topologicpy.Shell import Shell
        from topologicpy.Cell import Cell
        from topologicpy.CellComplex import CellComplex
        from topologicpy.Cluster import Cluster

        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.RemoveCoplanarFaces - Error: The input topology parameter is not a valid topologic topology. Returning None.")
            return None
        t = Topology.Type(topology)
        if (t == Topology.TypeID("Vertex")) or (t == Topology.TypeID("Edge")) or (t == Topology.TypeID("Wire")) or (t == Topology.TypeID("Face")):
            return topology

        def faces_on_same_plane(face1, face2, epsilon=1e-6):
            vertices = Face.Vertices(face1)
            distances = []
            for v in vertices:
                distances.append(Vertex.PerpendicularDistance(v, face=face2, mantissa=6))
            d = sum(distances)/len(distances)
            return d <= epsilon

        def cluster_faces_on_planes(faces, epsilon=1e-6):

            # Create a dictionary to store bins based on plane equations
            bins = {}

            # Iterate through each face
            for i, face in enumerate(faces):
                # Check if a bin already exists for the plane equation
                found_bin = False
                for bin_face in bins.values():
                    if faces_on_same_plane(face, bin_face[0], epsilon=epsilon):
                        bin_face.append(face)
                        found_bin = True
                        break

                # If no bin is found, create a new bin
                if not found_bin:
                    bins[i] = [face]

            # Convert bins to a list of lists
            return list(bins.values())

        faces = Topology.Faces(topology)
        face_clusters = cluster_faces_on_planes(faces, epsilon=epsilon)
        final_faces = []
        for face_cluster in face_clusters:
            t = Topology.SelfMerge(Cluster.ByTopologies(face_cluster), tolerance=tolerance)
            if Topology.IsInstance(t, "Face"):
                #final_faces.append(Face.RemoveCollinearEdges(t))
                final_faces.append(t)
            elif Topology.IsInstance(t, "Shell"):
                    f = Face.ByShell(t)
                    if Topology.IsInstance(f, "Face"):
                        final_faces.append(f)
                    else:
                        print("Topology.RemoveCoplanarFaces - Warning: Could not remove some coplanar faces. Re-adding original faces.")
                        final_faces += Shell.Faces(shell)
            else: # It is a cluster
                shells = Topology.Shells(t)
                for shell in shells:
                    f = Face.ByShell(shell)
                    if Topology.IsInstance(f, "Face"):
                        final_faces.append(f)
                    else:
                        print("Topology.RemoveCoplanarFaces - Warning: Could not remove some coplanar faces. Re-adding original faces.")
                        final_faces += Shell.Faces(shell)
                if len(shells) == 0:
                    faces = Topology.Faces(t)
                    final_faces += faces
                faces = Cluster.FreeFaces(t)
                final_faces += faces
        return_topology = None
        if Topology.IsInstance(topology, "CellComplex"):
            return_topology = CellComplex.ByFaces(final_faces, tolerance=tolerance)
        elif Topology.IsInstance(topology, "Cell"):
            return_topology = Cell.ByFaces(final_faces, tolerance=tolerance)
        elif Topology.IsInstance(topology, "Shell"):
            if len(final_faces) == 1:
                return_topology = final_faces[0]
            else:
                return_topology = Shell.ByFaces(final_faces, tolerance=tolerance)
        if not Topology.IsInstance(return_topology, "Topology"):
            return_topology = Cluster.ByTopologies(final_faces)
        return return_topology

    @staticmethod
    def RemoveEdges(topology, edges=[], tolerance=0.0001):
        """
        Removes the input list of faces from the input topology

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        edges : list
            The input list of edges.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        topologic_core.Topology
            The input topology with the input list of edges removed.

        """

        from topologicpy.Cluster import Cluster
        if not Topology.IsInstance(topology, "Topology"):
            return None
        edges = [e for e in edges if Topology.IsInstance(e, "Edge")]
        if len(edges) < 1:
            return topology
        t_edges = Topology.Edges(topology)
        t_faces = Topology.Faces(topology)
        if len(t_edges) < 1:
            return topology
        if len(t_faces) > 0:
            remove_faces = []
            for t_e in t_edges:
                remove = False
                for i, e in enumerate(edges):
                    if Topology.IsSame(t_e, e):
                        remove = True
                        remove_faces += Topology.SuperTopologies(e, hostTopology=topology, topologyType="face")
                        edges = edges[:i]+ edges[i:]
                        break
            if len(remove_faces) > 0:
                return Topology.RemoveFaces(topology, remove_faces)
        else:
            remaining_edges = []
            for t_e in t_edges:
                remove = False
                for i, e in enumerate(edges):
                    if Topology.IsSame(t_e, e):
                        remove = True
                        edges = edges[:i]+ edges[i:]
                        break
                if not remove:
                    remaining_edges.append(t_e)
            if len(remaining_edges) < 1:
                return None
            elif len(remaining_edges) == 1:
                return remaining_edges[0]
            return Topology.SelfMerge(Cluster.ByTopologies(remaining_edges), tolerance=tolerance)

    @staticmethod
    def RemoveFaces(topology, faces=[], tolerance=0.0001):
        """
        Removes the input list of faces from the input topology

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        faces : list
            The input list of faces.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        topologic_core.Topology
            The input topology with the input list of faces removed.

        """

        from topologicpy.Cluster import Cluster
        if not Topology.IsInstance(topology, "Topology"):
            return None
        faces = [f for f in faces if Topology.IsInstance(f, "Face")]
        if len(faces) < 1:
            return topology
        t_faces = Topology.Faces(topology)
        if len(t_faces) < 1:
            return topology
        remaining_faces = []
        for t_f in t_faces:
            remove = False
            for i, f in enumerate(faces):
                if Topology.IsSame(t_f, f):
                    remove = True
                    faces = faces[:i]+ faces[i:]
                    break
            if not remove:
                remaining_faces.append(t_f)
        if len(remaining_faces) < 1:
            return None
        elif len(remaining_faces) == 1:
            return remaining_faces[0]
        return Topology.SelfMerge(Cluster.ByTopologies(remaining_faces), tolerance=tolerance)
    
    @staticmethod
    def RemoveFacesBySelectors(topology, selectors=[], tolerance = 0.0001):
        """
        Removes faces that contain the input list of selectors (vertices) from the input topology

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        selectors : list
            The input list of selectors (vertices).
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        topologic_core.Topology
            The input topology with the identified faces removed.

        """
        from topologicpy.Vertex import Vertex

        if not Topology.IsInstance(topology, "Topology"):
            return None
        selectors = [v for v in selectors if Topology.IsInstance(v, "Vertex")]
        if len(selectors) < 1:
            return topology
        t_faces = Topology.Faces(topology)
        to_remove = []
        for t_f in t_faces:
            remove = False
            for i, v in enumerate(selectors):
                if Vertex.IsInternal(v, t_f, tolerance=tolerance):
                    remove = True
                    selectors = selectors[:i]+ selectors[i:]
                    break
            if remove:
                to_remove.append(t_f)
        if len(to_remove) < 1:
            return topology
        return Topology.RemoveFaces(topology, faces = to_remove)

    @staticmethod
    def RemoveVertices(topology, vertices=[], tolerance=0.0001):
        """
        Removes the input list of vertices from the input topology

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        vertices : list
            The input list of vertices.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        topologic_core.Topology
            The input topology with the input list of vertices removed.

        """

        from topologicpy.Cluster import Cluster
        if not Topology.IsInstance(topology, "Topology"):
            return None
        vertices = [v for v in vertices if Topology.IsInstance(v, "Vertex")]
        if len(vertices) < 1:
            return topology
        t_vertices = Topology.Vertices(topology)
        t_edges = Topology.Edges(topology)
        if len(t_vertices) < 1:
            return topology
        if len(t_edges) > 0:
            remove_edges = []
            for t_v in t_vertices:
                remove = False
                for i, v in enumerate(vertices):
                    if Topology.IsSame(t_v, v):
                        remove = True
                        remove_edges += Topology.SuperTopologies(v, hostTopology=topology, topologyType="edge")
                        vertices = vertices[:i]+ vertices[i:]
                        break
            if len(remove_edges) > 0:
                return Topology.RemoveEdges(topology, remove_edges)
        else:
            remaining_vertices = []
            for t_v in t_vertices:
                remove = False
                for i, e in enumerate(vertices):
                    if Topology.IsSame(t_v, v):
                        remove = True
                        vertices = vertices[:i]+ vertices[i:]
                        break
                if not remove:
                    remaining_vertices.append(t_v)
            if len(remaining_vertices) < 1:
                return None
            elif len(remaining_vertices) == 1:
                return remaining_vertices[0]
            return Topology.SelfMerge(Cluster.ByTopologies(remaining_vertices), tolerance=tolerance)
    
    @staticmethod
    def Cleanup(topology=None):
        """
        Cleans up all resources in which are managed by topologic library. Use this to manage your application's memory consumption.
        USE WITH CARE. This methods deletes dictionaries, contents, and contexts

        Parameters
        ----------
        topology : topologic_core.Topology , optional
            If specified the resources used by the input topology will be deleted. If not, ALL resources will be deleted.
        
        Returns
        -------
        topologic_core.Topology
            The input topology, but with its resources deleted or None.
        """
        if not topology == None:
            if not Topology.IsInstance(topology, "Topology"):
                print("Topology.Cleanup - Error: The input topology parameter is not a valid topology. Returning None.")
                return None
        topologic.Topology.Cleanup(topology)
        return topology

    @staticmethod
    def ReplaceVertices(topology, verticesA=[], verticesB=[], mantissa=6, tolerance=0.0001):
        """
        Replaces the vertices in the first input list with the vertices in the second input list and rebuilds the input topology. The two lists must be of the same length.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        verticesA : list
            The first input list of vertices.
        verticesB : list
            The second input list of vertices.
        mantissa : int , optional
            The desired length of the mantissa. The default is 6.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        topologic_core.Topology
            The new topology.

        """
        if not len(verticesA) == len(verticesB):
            print("Topology.ReplaceVertices - Error: The input parameters verticesA and verticesB must be the same length")
            return None
        from topologicpy.Vertex import Vertex
        geom = Topology.Geometry(topology, mantissa=mantissa)
        g_verts = geom['vertices']
        g_edges = geom['edges']
        g_faces = geom['faces']
        verts = [Topology.Vertices(Topology.ByGeometry(vertices=[g_v]))[0] for g_v in g_verts]
        new_verts = [v for v in verts]
        for i, v in enumerate(verticesA):
            n = Vertex.Index(v, verts, tolerance=tolerance)
            if not n == None:
                new_verts[n] = verticesB[i]
        new_g_verts = [[Vertex.X(v),Vertex.Y(v),Vertex.Z(v)] for v in new_verts]
        new_topology = Topology.ByGeometry(vertices=new_g_verts, edges=g_edges, faces=g_faces)
        return new_topology

    @staticmethod
    def Rotate(topology, origin=None, axis: list = [0, 0, 1], angle: float = 0, angTolerance: float = 0.001, tolerance: float = 0.0001):
        """
        Rotates the input topology

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        origin : topologic_core.Vertex , optional
            The origin (center) of the rotation. If set to None, the world origin (0, 0, 0) is used. The default is None.
        axis : list , optional
            The vector representing the axis of rotation. The default is [0, 0, 1] which equates to the Z axis.
        angle : float , optional
            The angle of rotation in degrees. The default is 0.
        angTolerance : float , optional
            The angle tolerance in degrees under which no rotation is carried out. The default is 0.001 degrees.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        topologic_core.Topology
            The rotated topology.

        """
        from topologicpy.Vertex import Vertex
        from topologicpy.Dictionary import Dictionary

        def rotate_vertex_3d(vertex, axis, angle_degrees, origin):
            vertex = np.array(vertex)  # Vertex to be rotated
            axis = np.array(axis)    # Rotation axis (z-axis in this case)
            origin = np.array(origin)
            # Convert the angle from degrees to radians
            angle_radians = np.radians(angle_degrees)
            
            # Calculate the rotation matrix using the Rodrigues' formula
            axis = np.array(axis) / np.linalg.norm(axis)
            a = np.cos(angle_radians / 2)
            b, c, d = -axis * np.sin(angle_radians / 2)
            rotation_matrix = np.array([
                [a * a + b * b - c * c - d * d, 2 * (b * c - a * d), 2 * (b * d + a * c)],
                [2 * (b * c + a * d), a * a - b * b + c * c - d * d, 2 * (c * d - a * b)],
                [2 * (b * d - a * c), 2 * (c * d + a * b), a * a - b * b - c * c + d * d]
            ])
            
            # Translate the vertex to the origin, apply the rotation, and then translate it back
            translated_vertex = vertex - origin
            rotated_vertex = np.dot(rotation_matrix, translated_vertex) + origin
            
            rotated_vertex = [v for v in rotated_vertex] 
            return rotated_vertex

        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.Rotate - Error: The input topology parameter is not a valid topologic topology. Returning None.")
            return None
        if not origin:
            origin = Vertex.ByCoordinates(0, 0, 0)
        if not Topology.IsInstance(origin, "Vertex"):
            print("Topology.Rotate - Error: The input origin parameter is not a valid topologic vertex. Returning None.")
            return None
        returnTopology = topology
        if abs(angle) >= angTolerance:
            try:
                x, y, z = axis
                returnTopology = topologic.TopologyUtility.Rotate(topology, origin, x, y, z, angle)
            except:
                print("Topology.Rotate - Warning: (topologic.TopologyUtility.Rotate) operation failed. Trying a workaround.")
                vertices = [Vertex.Coordinates(v) for v in Topology.Vertices(topology)]
                origin = Vertex.Coordinates(origin)
                rot_vertices = []
                for v in vertices:
                    rot_vertices.append(rotate_vertex_3d(v, axis, angle, origin))
                rot_vertices = [Vertex.ByCoordinates(rot_v) for rot_v in rot_vertices]
                new_topology = Topology.ReplaceVertices(topology, verticesA=Topology.Vertices(topology), verticesB=rot_vertices)
                new_topology = Topology.SelfMerge(new_topology, tolerance=tolerance)
                return new_topology
        return returnTopology
    
    @staticmethod
    def Scale(topology, origin=None, x=1, y=1, z=1):
        """
        Scales the input topology

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        origin : topologic_core.Vertex , optional
            The origin (center) of the scaling. If set to None, the world origin (0, 0, 0) is used. The default is None.
        x : float , optional
            The 'x' component of the scaling factor. The default is 1.
        y : float , optional
            The 'y' component of the scaling factor. The default is 1.
        z : float , optional
            The 'z' component of the scaling factor. The default is 1..

        Returns
        -------
        topologic_core.Topology
            The scaled topology.

        """
        from topologicpy.Vertex import Vertex
        if not Topology.IsInstance(topology, "Topology"):
            return None
        if not origin:
            origin = Vertex.ByCoordinates(0, 0, 0)
        if not Topology.IsInstance(origin, "Vertex"):
            return None
        newTopology = None
        try:
            newTopology = topologic.TopologyUtility.Scale(topology, origin, x, y, z)
        except:
            print("Topology.Scale - ERROR: (Topologic>TopologyUtility.Scale) operation failed. Returning None.")
            newTopology = None
        return newTopology

    
    @staticmethod
    def SelectSubTopology(topology, selector, subTopologyType="vertex"):
        """
        Returns the subtopology within the input topology based on the input selector and the subTopologyType.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        selector : topologic_core.Vertex
            A vertex located on the desired subtopology.
        subTopologyType : str , optional.
            The desired subtopology type. This can be of "vertex", "edge", "wire", "face", "shell", "cell", or "cellcomplex". It is case insensitive. The default is "vertex".

        Returns
        -------
        topologic_core.Topology
            The selected subtopology.

        """
        if not Topology.IsInstance(topology, "Topology"):
            return None
        if not Topology.IsInstance(selector, "Vertex"):
            return None
        t = 1
        if subTopologyType.lower() == "vertex":
            t = 1
        elif subTopologyType.lower() == "edge":
            t = 2
        elif subTopologyType.lower() == "wire":
            t = 4
        elif subTopologyType.lower() == "face":
            t = 8
        elif subTopologyType.lower() == "shell":
            t = 16
        elif subTopologyType.lower() == "cell":
            t = 32
        elif subTopologyType.lower() == "cellcomplex":
            t = 64
        return topology.SelectSubtopology(selector, t)

    
    @staticmethod
    def SelfMerge(topology, transferDictionaries: bool = False, tolerance: float = 0.0001):
        """
        Self merges the input topology to return the most logical topology type given the input data.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001

        Returns
        -------
        topologic_core.Topology
            The self-merged topology.

        """
        from topologicpy.Cluster import Cluster

        if not Topology.IsInstance(topology, "Topology"):
            return None #return Silently
        if not Topology.Type(topology) == Topology.TypeID("Cluster"):
            topology = Cluster.ByTopologies([topology])
        resultingTopologies = []
        topCC = Topology.CellComplexes(topology)
        topCells = Topology.Cells(topology)
        topShells = Topology.Shells(topology)
        topFaces = Topology.Faces(topology)
        topWires = Topology.Wires(topology)
        topEdges = Topology.Edges(topology)
        topVertices = Topology.Vertices(topology)
        if len(topCC) == 1:
            cc = topCC[0]
            ccVertices = Topology.Vertices(cc)
            if len(topVertices) == len(ccVertices):
                resultingTopologies.append(cc)
        if len(topCC) == 0 and len(topCells) == 1:
            cell = topCells[0]
            ccVertices = Topology.Vertices(cell)
            if len(topVertices) == len(ccVertices):
                resultingTopologies.append(cell)
        if len(topCC) == 0 and len(topCells) == 0 and len(topShells) == 1:
            shell = topShells[0]
            ccVertices = Topology.Vertices(shell)
            if len(topVertices) == len(ccVertices):
                resultingTopologies.append(shell)
        if len(topCC) == 0 and len(topCells) == 0 and len(topShells) == 0 and len(topFaces) == 1:
            face = topFaces[0]
            ccVertices = Topology.Vertices(face)
            if len(topVertices) == len(ccVertices):
                resultingTopologies.append(face)
        if len(topCC) == 0 and len(topCells) == 0 and len(topShells) == 0 and len(topFaces) == 0 and len(topWires) == 1:
            wire = topWires[0]
            ccVertices = Topology.Vertices(wire)
            if len(topVertices) == len(ccVertices):
                resultingTopologies.append(wire)
        if len(topCC) == 0 and len(topCells) == 0 and len(topShells) == 0 and len(topFaces) == 0 and len(topWires) == 0 and len(topEdges) == 1:
            edge = topEdges[0]
            ccVertices = Topology.Vertices(edge)
            if len(topVertices) == len(ccVertices):
                resultingTopologies.append(edge)
        if len(topCC) == 0 and len(topCells) == 0 and len(topShells) == 0 and len(topFaces) == 0 and len(topWires) == 0 and len(topEdges) == 0 and len(topVertices) == 1:
            vertex = topVertices[0]
            resultingTopologies.append(vertex)
        if len(resultingTopologies) == 1:
            return resultingTopologies[0]
        try:
            return_topology = topology.SelfMerge()
        except:
            return_topology = None
        if Topology.IsInstance(return_topology, "CellComplex"):
            cells = Topology.Cells(return_topology)
            if isinstance(cells, list):
                if len(cells) > 1:
                    topA = cells[0]
                    topB = Cluster.ByTopologies(cells[1:])
                    return_topology = Topology.Merge(topA, topB, tolerance=tolerance)
                else:
                    return_topology = cells[0]
        return return_topology

    @staticmethod
    def SetDictionary(topology, dictionary):
        """
        Sets the input topology's dictionary to the input dictionary

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        dictionary : topologic_core.Dictionary
            The input dictionary.

        Returns
        -------
        topologic_core.Topology
            The input topology with the input dictionary set in it.

        """
        from topologicpy.Dictionary import Dictionary

        if not Topology.IsInstance(topology, "Topology") and not Topology.IsInstance(topology, "Graph"):
            print("Topology.SetDictionary - Error: the input topology parameter is not a valid topology or graph. Returning None.")
            return None
        if isinstance(dictionary, dict):
            dictionary = Dictionary.ByPythonDictionary(dictionary)
        if not Topology.IsInstance(dictionary, "Dictionary"):
            print("Topology.SetDictionary - Warning: the input dictionary parameter is not a valid dictionary. Returning original input.")
            return topology
        if len(dictionary.Keys()) < 1:
            print("Topology.SetDictionary - Warning: the input dictionary parameter is empty. Returning original input.")
            return topology
        _ = topology.SetDictionary(dictionary)
        return topology
    
    @staticmethod
    def SetSnapshot(topology, snapshot=None, timestamp=None, key="timestamp", silent=False):
        from topologicpy.Dictionary import Dictionary
        from datetime import datetime
        def is_valid_timestamp(timestamp):
            if isinstance(timestamp, datetime):
                return True
            elif isinstance(timestamp, str):
                try:
                    # Split the timestamp string into date and time parts
                    date_part, time_part = timestamp.split(' ')
                    # Parse the date part
                    date_obj = datetime.strptime(date_part, '%Y-%m-%d')
                    # Split the time part into hours, minutes, and seconds
                    hours, minutes, seconds = map(float, time_part.split(':'))
                    # Check if seconds are within valid range
                    if seconds < 0 or seconds >= 60:
                        return False
                    # Create a datetime object with the parsed date and time parts
                    datetime_obj = datetime(date_obj.year, date_obj.month, date_obj.day, int(hours), int(minutes), int(seconds))
                    return True
                except ValueError:
                    return False
            else:
                return False

        if not Topology.IsInstance(topology, "Topology"):
            if not silent:
                print("Topology.SetSnapshot - Error: The input topology parameter is not a valid topology. Returning None.")
                return None
        if not Topology.IsInstance(snapshot, "Topology"):
            snapshot = Topology.Copy(topology)
        if not Topology.IsInstance(snapshot, "Topology"):
            if not silent:
                print("Topology.SetSnapshot - Error: The input snapshot parameter is not a valid topology. Returning None.")
                return None
        if timestamp == None:
            timestamp = datetime.now()
        if not is_valid_timestamp(timestamp):
            if not silent:
                print("Topology.SetSnapshot - Error: The input timestamp parameter is not a valid timestamp. Returning None.")
                return None
        
        d = Topology.Dictionary(snapshot)
        d = Dictionary.SetValueAtKey(d, key, str(timestamp))
        snapshot = Topology.SetDictionary(snapshot, d)
        topology = Topology.AddContent(topology, snapshot)
        return topology
    
    @staticmethod
    def SharedTopologies(topologyA, topologyB):
        """
        Returns the shared topologies between the two input topologies

        Parameters
        ----------
        topologyA : topologic_core.Topology
            The first input topology.
        topologyB : topologic_core.Topology
            The second input topology.

        Returns
        -------
        dict
            A dictionary with the list of vertices, edges, wires, and faces. The keys are "vertices", "edges", "wires", and "faces".

        """

        if not Topology.IsInstance(topologyA, "Topology"):
            print("Topology.SharedTopologies - Error: the input topologyA parameter is not a valid topology. Returning None.")
            return None
        if not Topology.IsInstance(topologyB, "Topology"):
            print("Topology.SharedTopologies - Error: the input topologyB parameter is not a valid topology. Returning None.")
            return None
        vOutput = []
        eOutput = []
        wOutput = []
        fOutput = []
        _ = topologyA.SharedTopologies(topologyB, 1, vOutput)
        _ = topologyA.SharedTopologies(topologyB, 2, eOutput)
        _ = topologyA.SharedTopologies(topologyB, 4, wOutput)
        _ = topologyA.SharedTopologies(topologyB, 8, fOutput)
        return {"vertices":vOutput, "edges":eOutput, "wires":wOutput, "faces":fOutput}

    @staticmethod
    def SharedVertices(topologyA, topologyB):
        """
        Returns the shared vertices between the two input topologies

        Parameters
        ----------
        topologyA : topologic_core.Topology
            The first input topology.
        topologyB : topologic_core.Topology
            The second input topology.

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

        """
        d = Topology.SharedTopologies(topologyA, topologyB)
        l = None
        if isinstance(d, dict):
            try:
                l = d['vertices']
            except:
                l = None
        return l
    
    
    @staticmethod
    def SharedEdges(topologyA, topologyB):
        """
        Returns the shared edges between the two input topologies

        Parameters
        ----------
        topologyA : topologic_core.Topology
            The first input topology.
        topologyB : topologic_core.Topology
            The second input topology.

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

        """
        d = Topology.SharedTopologies(topologyA, topologyB)
        l = None
        if isinstance(d, dict):
            try:
                l = d['edges']
            except:
                l = None
        return l
    
    @staticmethod
    def SharedWires(topologyA, topologyB):
        """
        Returns the shared wires between the two input topologies

        Parameters
        ----------
        topologyA : topologic_core.Topology
            The first input topology.
        topologyB : topologic_core.Topology
            The second input topology.

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

        """
        d = Topology.SharedTopologies(topologyA, topologyB)
        l = None
        if isinstance(d, dict):
            try:
                l = d['wires']
            except:
                l = None
        return l
    
    
    @staticmethod
    def SharedFaces(topologyA, topologyB):
        """
        Returns the shared faces between the two input topologies

        Parameters
        ----------
        topologyA : topologic_core.Topology
            The first input topology.
        topologyB : topologic_core.Topology
            The second input topology.

        Returns
        -------
        list
            The list of shared faces.

        """
        d = Topology.SharedTopologies(topologyA, topologyB)
        l = None
        if isinstance(d, dict):
            try:
                l = d['faces']
            except:
                l = None
        return l
    
    
    @staticmethod
    def Show(*topologies,
             showVertices=True, vertexSize=1.1, vertexColor="black", 
             vertexLabelKey=None, vertexGroupKey=None, vertexGroups=[], 
             vertexMinGroup=None, vertexMaxGroup=None, 
             showVertexLegend=False, vertexLegendLabel="Topology Vertices", vertexLegendRank=1, 
             vertexLegendGroup=1, 

             showEdges=True, edgeWidth=1, edgeColor="black", 
             edgeLabelKey=None, edgeGroupKey=None, edgeGroups=[], 
             edgeMinGroup=None, edgeMaxGroup=None, 
             showEdgeLegend=False, edgeLegendLabel="Topology Edges", edgeLegendRank=2, 
             edgeLegendGroup=2, 

             showFaces=True, faceOpacity=0.5, faceColor="#FAFAFA",
             faceLabelKey=None, faceGroupKey=None, faceGroups=[], 
             faceMinGroup=None, faceMaxGroup=None, 
             showFaceLegend=False, faceLegendLabel="Topology Faces", faceLegendRank=3,
             faceLegendGroup=3, 
             intensityKey=None,
             intensities=[],
             
             width=950, height=500,
             xAxis=False, yAxis=False, zAxis=False, axisSize=1, backgroundColor='rgba(0,0,0,0)',
             marginLeft=0, marginRight=0, marginTop=20, marginBottom=0, camera=[-1.25, -1.25, 1.25],
             center=[0, 0, 0], up=[0, 0, 1], projection="perspective", renderer="notebook", showScale=False,
             
             cbValues=[], cbTicks=5, cbX=-0.15, cbWidth=15, cbOutlineWidth=0, cbTitle="",
             cbSubTitle="", cbUnits="", colorScale="Viridis", mantissa=6, tolerance=0.0001):
        """
            Shows the input topology on screen.

        Parameters
        ----------
        topologies : topologic_core.Topology or list
            The input topology. This must contain faces and or edges. If the input is a list, a cluster is first created

        showVertices : bool , optional
            If set to True the vertices will be drawn. Otherwise, they will not be drawn. The default is True.
        vertexSize : float , optional
            The desired size of the vertices. The default is 1.1.
        vertexColor : str , optional
            The desired color of the output vertices. This can be any plotly color string and may be specified as:
            - A hex string (e.g. '#ff0000')
            - An rgb/rgba string (e.g. 'rgb(255,0,0)')
            - An hsl/hsla string (e.g. 'hsl(0,100%,50%)')
            - An hsv/hsva string (e.g. 'hsv(0,100%,100%)')
            - A named CSS color.
            The default is "black".
        vertexLabelKey : str , optional
            The dictionary key to use to display the vertex label. The default is None.
        vertexGroupKey : str , optional
            The dictionary key to use to display the vertex group. The default is None.
        vertexGroups : list , optional
            The list of vertex groups against which to index the color of the vertex. The default is [].
        vertexMinGroup : int or float , optional
            For numeric vertexGroups, vertexMinGroup is the desired minimum value for the scaling of colors. This should match the type of value associated with the vertexGroupKey. If set to None, it is set to the minimum value in vertexGroups. The default is None.
        edgeMaxGroup : int or float , optional
            For numeric vertexGroups, vertexMaxGroup is the desired maximum value for the scaling of colors. This should match the type of value associated with the vertexGroupKey. If set to None, it is set to the maximum value in vertexGroups. The default is None.
        showVertexLegend : bool, optional
            If set to True, the legend for the vertices of this topology is shown. Otherwise, it isn't. The default is False.
        vertexLegendLabel : str , optional
            The legend label string used to identify vertices. The default is "Topology Vertices".
        vertexLegendRank : int , optional
            The legend rank order of the vertices of this topology. The default is 1.
        vertexLegendGroup : int , optional
            The number of the vertex legend group to which the vertices of this topology belong. The default is 1.
        
        showEdges : bool , optional
            If set to True the edges will be drawn. Otherwise, they will not be drawn. The default is True.
        edgeWidth : float , optional
            The desired thickness of the output edges. The default is 1.
        edgeColor : str , optional
            The desired color of the output edges. This can be any plotly color string and may be specified as:
            - A hex string (e.g. '#ff0000')
            - An rgb/rgba string (e.g. 'rgb(255,0,0)')
            - An hsl/hsla string (e.g. 'hsl(0,100%,50%)')
            - An hsv/hsva string (e.g. 'hsv(0,100%,100%)')
            - A named CSS color.
            The default is "black".
        edgeLabelKey : str , optional
            The dictionary key to use to display the edge label. The default is None.
        edgeGroupKey : str , optional
            The dictionary key to use to display the edge group. The default is None.
        edgeGroups : list , optional
            The list of edge groups against which to index the color of the edge. The default is [].
        edgeMinGroup : int or float , optional
            For numeric edgeGroups, edgeMinGroup is the desired minimum value for the scaling of colors. This should match the type of value associated with the edgeGroupKey. If set to None, it is set to the minimum value in edgeGroups. The default is None.
        edgeMaxGroup : int or float , optional
            For numeric edgeGroups, edgeMaxGroup is the desired maximum value for the scaling of colors. This should match the type of value associated with the edgeGroupKey. If set to None, it is set to the maximum value in edgeGroups. The default is None.
        showEdgeLegend : bool, optional
            If set to True, the legend for the edges of this topology is shown. Otherwise, it isn't. The default is False.
        edgeLegendLabel : str , optional
            The legend label string used to identify edges. The default is "Topology Edges".
        edgeLegendRank : int , optional
            The legend rank order of the edges of this topology. The default is 2.
        edgeLegendGroup : int , optional
            The number of the edge legend group to which the edges of this topology belong. The default is 2.
        
        showFaces : bool , optional
            If set to True the faces will be drawn. Otherwise, they will not be drawn. The default is True.
        faceOpacity : float , optional
            The desired opacity of the output faces (0=transparent, 1=opaque). The default is 0.5.
        faceColor : str , optional
            The desired color of the output faces. This can be any plotly color string and may be specified as:
            - A hex string (e.g. '#ff0000')
            - An rgb/rgba string (e.g. 'rgb(255,0,0)')
            - An hsl/hsla string (e.g. 'hsl(0,100%,50%)')
            - An hsv/hsva string (e.g. 'hsv(0,100%,100%)')
            - A named CSS color.
            The default is "#FAFAFA".
        faceLabelKey : str , optional
            The dictionary key to use to display the face label. The default is None.
        faceGroupKey : str , optional
            The dictionary key to use to display the face group. The default is None.
        faceGroups : list , optional
            The list of face groups against which to index the color of the face. This can bhave numeric or string values. This should match the type of value associated with the faceGroupKey. The default is [].
        faceMinGroup : int or float , optional
            For numeric faceGroups, minGroup is the desired minimum value for the scaling of colors. This should match the type of value associated with the faceGroupKey. If set to None, it is set to the minimum value in faceGroups. The default is None.
        faceMaxGroup : int or float , optional
            For numeric faceGroups, maxGroup is the desired maximum value for the scaling of colors. This should match the type of value associated with the faceGroupKey. If set to None, it is set to the maximum value in faceGroups. The default is None.
        showFaceLegend : bool, optional
            If set to True, the legend for the faces of this topology is shown. Otherwise, it isn't. The default is False.
        faceLegendLabel : str , optional
            The legend label string used to idenitfy edges. The default is "Topology Faces".
        faceLegendRank : int , optional
            The legend rank order of the faces of this topology. The default is 3.
        faceLegendGroup : int , optional
            The number of the face legend group to which the faces of this topology belong. The default is 3.
        width : int , optional
            The width in pixels of the figure. The default value is 950.
        height : int , optional
            The height in pixels of the figure. The default value is 950.
        xAxis : bool , optional
            If set to True the x axis is drawn. Otherwise it is not drawn. The default is False.
        yAxis : bool , optional
            If set to True the y axis is drawn. Otherwise it is not drawn. The default is False.
        zAxis : bool , optional
            If set to True the z axis is drawn. Otherwise it is not drawn. The default is False.
        backgroundColor : str , optional
            The desired color of the background. This can be any plotly color string and may be specified as:
            - A hex string (e.g. '#ff0000')
            - An rgb/rgba string (e.g. 'rgb(255,0,0)')
            - An hsl/hsla string (e.g. 'hsl(0,100%,50%)')
            - An hsv/hsva string (e.g. 'hsv(0,100%,100%)')
            - A named CSS color.
            The default is "rgba(0,0,0,0)".
        marginLeft : int , optional
            The size in pixels of the left margin. The default value is 0.
        marginRight : int , optional
            The size in pixels of the right margin. The default value is 0.
        marginTop : int , optional
            The size in pixels of the top margin. The default value is 20.
        marginBottom : int , optional
            The size in pixels of the bottom margin. The default value is 0.
        camera : list , optional
            The desired location of the camera). The default is [-1.25, -1.25, 1.25].
        center : list , optional
            The desired center (camera target). The default is [0, 0, 0].
        up : list , optional
            The desired up vector. The default is [0, 0, 1].
        projection : str , optional
            The desired type of projection. The options are "orthographic" or "perspective". It is case insensitive. The default is "perspective"
        renderer : str , optional
            The desired renderer. See Plotly.Renderers(). If set to None, the code will attempt to discover the most suitable renderer. The default is None.
        intensityKey : str , optional
            If not None, the dictionary of each vertex is searched for the value associated with the intensity key. This value is then used to color-code the vertex based on the colorScale. The default is None.
        intensities : list , optional
            The list of intensities against which to index the intensity of the vertex. The default is [].
        showScale : bool , optional
            If set to True, the colorbar is shown. The default is False.
        cbValues : list , optional
            The input list of values to use for the colorbar. The default is [].
        cbTicks : int , optional
            The number of ticks to use on the colorbar. The default is 5.
        cbX : float , optional
            The x location of the colorbar. The default is -0.15.
        cbWidth : int , optional
            The width in pixels of the colorbar. The default is 15
        cbOutlineWidth : int , optional
            The width in pixels of the outline of the colorbar. The default is 0.
        cbTitle : str , optional
            The title of the colorbar. The default is "".
        cbSubTitle : str , optional
            The subtitle of the colorbar. The default is "".
        cbUnits: str , optional
            The units used in the colorbar. The default is ""
        colorScale : str , optional
            The desired type of plotly color scales to use (e.g. "viridis", "plasma"). The default is "viridis". For a full list of names, see https://plotly.com/python/builtin-colorscales/.
        mantissa : int , optional
            The desired length of the mantissa for the values listed on the colorbar. The default is 6.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        None

        """

        from topologicpy.Cluster import Cluster
        from topologicpy.Plotly import Plotly
        from topologicpy.Helper import Helper
        from topologicpy.Graph import Graph
        
        if isinstance(topologies, tuple):
            topologies = Helper.Flatten(list(topologies))
        if isinstance(topologies, list):
            new_topologies = [t for t in topologies if Topology.IsInstance(t, "Topology")]
            graphs = [Graph.Topology(g) for g in topologies if Topology.IsInstance(g, "Graph")]
            new_topologies += graphs
        if len(new_topologies) == 0:
            print("Topology.Show - Error: the input topologies parameter does not contain any valid topology. Returning None.")
            return None
        if len(new_topologies) == 1:
            topology = new_topologies[0]
        else:
            topology = Cluster.ByTopologies(new_topologies)
        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.Show - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        data = Plotly.DataByTopology(topology=topology,
                       showVertices=showVertices, vertexSize=vertexSize, vertexColor=vertexColor, 
                       vertexLabelKey=vertexLabelKey, vertexGroupKey=vertexGroupKey, vertexGroups=vertexGroups, 
                       vertexMinGroup=vertexMinGroup, vertexMaxGroup=vertexMaxGroup, 
                       showVertexLegend=showVertexLegend, vertexLegendLabel=vertexLegendLabel, vertexLegendRank=vertexLegendRank,
                       vertexLegendGroup=vertexLegendGroup,
                       showEdges=showEdges, edgeWidth=edgeWidth, edgeColor=edgeColor, 
                       edgeLabelKey=edgeLabelKey, edgeGroupKey=edgeGroupKey, edgeGroups=edgeGroups, 
                       edgeMinGroup=edgeMinGroup, edgeMaxGroup=edgeMaxGroup, 
                       showEdgeLegend=showEdgeLegend, edgeLegendLabel=edgeLegendLabel, edgeLegendRank=edgeLegendRank, 
                       edgeLegendGroup=edgeLegendGroup,
                       showFaces=showFaces, faceOpacity=faceOpacity, faceColor=faceColor,
                       faceLabelKey=faceLabelKey, faceGroupKey=faceGroupKey, faceGroups=faceGroups, 
                       faceMinGroup=faceMinGroup, faceMaxGroup=faceMaxGroup, 
                       showFaceLegend=showFaceLegend, faceLegendLabel=faceLegendLabel, faceLegendRank=faceLegendRank,
                       faceLegendGroup=faceLegendGroup, 
                       intensityKey=intensityKey, intensities=intensities, colorScale=colorScale, mantissa=mantissa, tolerance=tolerance)
        figure = Plotly.FigureByData(data=data, width=width, height=height,
                                     xAxis=xAxis, yAxis=yAxis, zAxis=zAxis, axisSize=axisSize,
                                     backgroundColor=backgroundColor,
                                     marginLeft=marginLeft, marginRight=marginRight,
                                     marginTop=marginTop, marginBottom=marginBottom,
                                     tolerance=tolerance)
        if showScale:
            figure = Plotly.AddColorBar(figure, values=cbValues, nTicks=cbTicks, xPosition=cbX, width=cbWidth, outlineWidth=cbOutlineWidth, title=cbTitle, subTitle=cbSubTitle, units=cbUnits, colorScale=colorScale, mantissa=mantissa)
        Plotly.Show(figure=figure, renderer=renderer, camera=camera, center=center, up=up, projection=projection)

    @staticmethod
    def SortBySelectors(topologies, selectors, exclusive=False, tolerance=0.0001):
        """
        Sorts the input list of topologies according to the input list of selectors.

        Parameters
        ----------
        topologies : list
            The input list of topologies.
        selectors : list
            The input list of selectors (vertices).
        exclusive : bool , optional
            If set to True only one selector can be used to select on topology. The default is False.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        dict
            A dictionary containing the list of sorted and unsorted topologies. The keys are "sorted" and "unsorted".

        """

        from topologicpy.Vertex import Vertex
        usedTopologies = []
        sortedTopologies = []
        unsortedTopologies = []
        for i in range(len(topologies)):
            usedTopologies.append(0)
        
        for i in range(len(selectors)):
            found = False
            for j in range(len(topologies)):
                if usedTopologies[j] == 0:
                    if Vertex.IsInternal( selectors[i], topologies[j], tolerance):
                        sortedTopologies.append(topologies[j])
                        if exclusive == True:
                            usedTopologies[j] = 1
                        found = True
                        break
            if found == False:
                sortedTopologies.append(None)
        for i in range(len(usedTopologies)):
            if usedTopologies[i] == 0:
                unsortedTopologies.append(topologies[i])
        return {"sorted":sortedTopologies, "unsorted":unsortedTopologies}
    
    @staticmethod
    def Snapshots(topology, key="timestamp", start=None, end=None, silent=False):
        from topologicpy.Dictionary import Dictionary
        from datetime import datetime
        def is_valid_timestamp(timestamp):
            if isinstance(timestamp, datetime):
                return True
            elif isinstance(timestamp, str):
                try:
                    # Split the timestamp string into date and time parts
                    date_part, time_part = timestamp.split(' ')
                    # Parse the date part
                    date_obj = datetime.strptime(date_part, '%Y-%m-%d')
                    # Split the time part into hours, minutes, and seconds
                    hours, minutes, seconds = map(float, time_part.split(':'))
                    # Check if seconds are within valid range
                    if seconds < 0 or seconds >= 60:
                        return False
                    # Create a datetime object with the parsed date and time parts
                    return datetime(date_obj.year, date_obj.month, date_obj.day, int(hours), int(minutes), int(seconds))
                except ValueError:
                    return False
            else:
                return False
        
        if not Topology.IsInstance(topology, "Topology"):
            if not silent:
                print("Topology.Snapshots - Error: The input topology parameter is not a valid topology. Returning None.")
                return None
        if start == None:
            start = datetime.datetime(year=1900, month=1, day=1) # Set the start date to a date in the distant past
        if end == None:
            end = datetime.now() # Set the end date to the present.
        if not is_valid_timestamp(start):
            if not silent:
                print("Topology.Snapshots - Error: The input start parameter is not a valid timestamp. Returning None.")
                return None
        if not is_valid_timestamp(end):
            if not silent:
                print("Topology.Snapshots - Error: The input end parameter is not a valid timestamp. Returning None.")
                return None
        contents = Topology.Contents(topology)    
        snapshots = []
        for content in contents:
            d = Topology.Dictionary(content)
            timestamp = Dictionary.ValueAtKey(d, key)
            timestamp = is_valid_timestamp(timestamp)
            if not timestamp == False:
                if start <= timestamp <= end:
                    snapshots.append(content)
        return snapshots

    @staticmethod
    def Spin(topology, origin=None, triangulate: bool = True, direction: list = [0, 0, 1], angle: float = 360, sides: int = 16,
                     tolerance: float = 0.0001, silent: bool = False):
        """
        Spins the input topology around an axis to create a new topology.See https://en.wikipedia.org/wiki/Solid_of_revolution.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        origin : topologic_core.Vertex
            The origin (center) of the spin.
        triangulate : bool , optional
            If set to True, the result will be triangulated. The default is True.
        direction : list , optional
            The vector representing the direction of the spin axis. The default is [0, 0, 1].
        angle : float , optional
            The angle in degrees for the spin. The default is 360.
        sides : int , optional
            The desired number of sides. The default is 16.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        topologic_core.Topology
            The spun topology.

        """
        from topologicpy.Vertex import Vertex
        from topologicpy.Wire import Wire
        from topologicpy.Shell import Shell
        from topologicpy.Cell import Cell
        from topologicpy.CellComplex import CellComplex
        from topologicpy.Cluster import Cluster

        if not origin:
            origin = Vertex.ByCoordinates(0, 0, 0)
        if not Topology.IsInstance(topology, "Topology"):
            if not silent:
                print("Topology.Spin - Error: the input topology parameter is not a valid topology. Returning None.")
                return None
        if not Topology.IsInstance(origin, "Vertex"):
            if not silent:
                print("Topology.Spin - Error: the input origin parameter is not a valid vertex. Returning None.")
                return None
        topologies = []
        unit_degree = angle / float(sides)
        for i in range(sides+1):
            tempTopology = Topology.Rotate(topology, origin=origin, axis=direction, angle=unit_degree*i)
            if tempTopology:
                topologies.append(tempTopology)
        returnTopology = None
        if Topology.Type(topology) == Topology.TypeID("Vertex"):
            returnTopology = Wire.ByVertices(topologies, False)
        elif Topology.Type(topology) == Topology.TypeID("Edge"):
            try:
                returnTopology = Shell.ByWires(topologies,triangulate=triangulate, tolerance=tolerance, silent=True)
            except:
                try:
                    returnTopology = Cluster.ByTopologies(topologies)
                except:
                    returnTopology = None
        elif Topology.Type(topology) == Topology.TypeID("Wire"):
            if topology.IsClosed():
                #returnTopology = Cell.ByWires(topologies, triangulate=triangulate, tolerance=tolerance, silent=True)
                try:
                    returnTopology = Cell.ByWires(topologies, triangulate=triangulate, tolerance=tolerance, silent=True)
                    returnTopology = Cell.ExternalBoundary(returnTopology)
                    returnTopology = Cell.ByShell(returnTopology)
                except:
                    try:
                        returnTopology = CellComplex.ByWires(topologies, tolerance)
                        try:
                            returnTopology = returnTopology.ExternalBoundary()
                        except:
                            pass
                    except:
                        try:
                            returnTopology = Shell.ByWires(topologies, triangulate=triangulate, tolerance=tolerance, silent=True)
                        except:
                            try:
                                returnTopology = Cluster.ByTopologies(topologies)
                            except:
                                returnTopology = None
            else:
                Shell.ByWires(topologies, triangulate=triangulate, tolerance=tolerance, silent=True)
                try:
                    returnTopology = Shell.ByWires(topologies, triangulate=triangulate, tolerance=tolerance, silent=True)
                except:
                    try:
                        returnTopology = Cluster.ByTopologies(topologies)
                    except:
                        returnTopology = None
        elif Topology.IsInstance(topology, "Face"):
            external_wires = []
            for t in topologies:
                external_wires.append(topologic.Face.ExternalBoundary(t))
            try:
                returnTopology = CellComplex.ByWires(external_wires, tolerance)
            except:
                try:
                    returnTopology = Shell.ByWires(external_wires, triangulate=triangulate, tolerance=tolerance, silent=True)
                except:
                    try:
                        returnTopology = Cluster.ByTopologies(topologies)
                    except:
                        returnTopology = None
        else:
            returnTopology = Topology.SelfMerge(Cluster.ByTopologies(topologies), tolerance=tolerance)
        if not returnTopology:
            return Cluster.ByTopologies(topologies)
        if Topology.Type(returnTopology) == Topology.TypeID("Shell"):
            try:
                new_t = Cell.ByShell(returnTopology)
                if new_t:
                    returnTopology = new_t
            except:
                pass
        return returnTopology
    
    @staticmethod
    def Taper(topology, origin=None, ratioRange=[0, 1], triangulate=False, tolerance=0.0001):
        """
        Tapers the input topology. This method tapers the input geometry along its Z-axis based on the ratio range input.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        origin : topologic_core.Vertex , optional
            The desired origin for tapering. If not specified, the centroid of the input topology is used. The tapering will use the X, Y coordinates of the specified origin, but will use the Z of the point being tapered. The default is None.
        ratioRange : list , optional
            The desired ratio range. This will specify a linear range from bottom to top for tapering the vertices. 0 means no tapering, and 1 means maximum (inward) tapering. Negative numbers mean that tapering will be outwards.
        triangulate : bool , optional
            If set to true, the input topology is triangulated before tapering. Otherwise, it will not be traingulated. The default is False.
        tolerance : float , optional
            The desired tolerance. Vertices will not be moved if the calculated distance is at or less than this tolerance.

        Returns
        -------
        topologic_core.Topology
            The tapered topology.

        """
        from topologicpy.Vertex import Vertex
        ratioRange = [min(1,ratioRange[0]), min(1,ratioRange[1])]
        if ratioRange == [0, 0]:
            return topology
        if ratioRange == [1, 1]:
            print("Topology.Taper - Error: Degenerate result. Returning original topology.")
            return topology
        if triangulate == True:
            topology = Topology.Triangulate(topology)
        if origin == None:
            origin = Topology.Centroid(topology)
        vertices = Topology.Vertices(topology)
        zList = [Vertex.Z(v) for v in vertices]
        minZ = min(zList)
        maxZ = max(zList)
        new_vertices = []
        for v in vertices:
            ht = (Vertex.Z(v)-minZ)/(maxZ - minZ)
            rt = ratioRange[0] + ht*(ratioRange[1] - ratioRange[0])
            new_origin = Vertex.ByCoordinates(Vertex.X(origin), Vertex.Y(origin), Vertex.Z(v))
            new_dist = Vertex.Distance(new_origin, v)*rt
            c_a = Vertex.Coordinates(new_origin)
            c_b = Vertex.Coordinates(v)
            new_dir = [(c_a[0]-c_b[0]), (c_a[1]-c_b[1]), 0]
            if abs(new_dist) > tolerance:
                new_v = Topology.TranslateByDirectionDistance(v, direction=new_dir, distance=new_dist)
            else:
                new_v = v
            new_vertices.append(new_v)
        return_topology = Topology.ReplaceVertices(topology, vertices, new_vertices)
        return return_topology
    
    @staticmethod
    def Twist(topology, origin=None, angleRange=[45, 90], triangulate=False):
        """
        Twists the input topology. This method twists the input geometry along its Z-axis based on the degree range input.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        origin : topologic_core.Vertex , optional
            The desired origin for tapering. If not specified, the centroid of the input topology is used. The tapering will use the X, Y coordinates of the specified origin, but will use the Z of the point being tapered. The default is None.
        angleRange : list , optional
            The desired angle range in degrees. This will specify a linear range from bottom to top for twisting the vertices. positive numbers mean a clockwise rotation.
        triangulate : bool , optional
            If set to true, the input topology is triangulated before tapering. Otherwise, it will not be traingulated. The default is False.
        
        Returns
        -------
        topologic_core.Topology
            The twisted topology.

        """
        from topologicpy.Vertex import Vertex

        if angleRange == [0, 0]:
            return topology
        if triangulate == True:
            topology = Topology.Triangulate(topology)
        if origin == None:
            origin = Topology.Centroid(topology)
            
        vertices = Topology.Vertices(topology)
        zList = [Vertex.Z(v) for v in vertices]
        minZ = min(zList)
        maxZ = max(zList)
        h = maxZ - minZ
        new_vertices = []
        for v in vertices:
            ht = (Vertex.Z(v)-minZ)/(maxZ - minZ)
            new_rot = angleRange[0] + ht*(angleRange[1] - angleRange[0])
            orig = Vertex.ByCoordinates(Vertex.X(origin), Vertex.Y(origin), Vertex.Z(v))
            new_vertices.append(Topology.Rotate(v, origin=orig, axis=[0, 0, 1], angle=new_rot))
        return_topology = Topology.ReplaceVertices(topology, vertices, new_vertices)
        return_topology = Topology.Fix(return_topology, topologyType=Topology.TypeAsString(topology))
        return return_topology
    
    @staticmethod
    def Unflatten(topology, origin=None, direction=[0, 0, 1]):
        """
        Unflattens the input topology such that the world origin is translated to the input origin and the input topology is rotated such that the Up direction (see Vector.Up()) is aligned with the input vector.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        origin : topologic_core.Vertex , optional
            The input origin. If set to None, The object's centroid will be used to translate the world origin. The default is None.
        vector : list , optional
            The input direction vector. The input topology will be rotated such that this vector is pointed in the positive Z axis.

        Returns
        -------
        topologic_core.Topology
            The flattened topology.

        """
        from topologicpy.Vertex import Vertex
        from topologicpy.Vector import Vector

        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.Unflatten - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        if origin == None:
            origin = Vertex.Origin()
        up = Vector.Up()
        tran_mat = Vector.TransformationMatrix(up, direction)
        unflat_topology = Topology.Transform(topology, tran_mat)
        unflat_topology = Topology.Translate(unflat_topology, Vertex.X(origin), Vertex.Y(origin), Vertex.Z(origin))
        return unflat_topology
    
    @staticmethod
    def Vertices(topology):
        """
        Returns the vertices of the input topology.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.

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

        """
        return Topology.SubTopologies(topology=topology, subTopologyType="vertex")
    
    @staticmethod
    def Edges(topology):
        """
        Returns the edges of the input topology.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.

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

        """
        if Topology.IsInstance(topology, "Edge") or Topology.IsInstance(topology, "Vertex"):
            return []
        return Topology.SubTopologies(topology=topology, subTopologyType="edge")
    
    @staticmethod
    def Wires(topology):
        """
        Returns the wires of the input topology.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.

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

        """
        if Topology.IsInstance(topology, "Wire") or Topology.IsInstance(topology, "Edge") or Topology.IsInstance(topology, "Vertex"):
            return []
        return Topology.SubTopologies(topology=topology, subTopologyType="wire")
    
    @staticmethod
    def Faces(topology):
        """
        Returns the faces of the input topology.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.

        Returns
        -------
        list
            The list of faces.

        """
        if Topology.IsInstance(topology, "Face") or Topology.IsInstance(topology, "Wire") or Topology.IsInstance(topology, "Edge") or Topology.IsInstance(topology, "Vertex"):
            return []
        return Topology.SubTopologies(topology=topology, subTopologyType="face")
    
    @staticmethod
    def Shells(topology):
        """
        Returns the shells of the input topology.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.

        Returns
        -------
        list
            The list of shells.

        """
        if Topology.IsInstance(topology, "Shell") or Topology.IsInstance(topology, "Face") or Topology.IsInstance(topology, "Wire") or Topology.IsInstance(topology, "Edge") or Topology.IsInstance(topology, "Vertex"):
            return []
        return Topology.SubTopologies(topology=topology, subTopologyType="shell")
    
    @staticmethod
    def Cells(topology):
        """
        Returns the cells of the input topology.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.

        Returns
        -------
        list
            The list of cells.

        """
        if Topology.IsInstance(topology, "Cell") or Topology.IsInstance(topology, "Shell") or Topology.IsInstance(topology, "Face") or Topology.IsInstance(topology, "Wire") or Topology.IsInstance(topology, "Edge") or Topology.IsInstance(topology, "Vertex"):
            return []
        return Topology.SubTopologies(topology=topology, subTopologyType="cell")
    
    @staticmethod
    def CellComplexes(topology):
        """
        Returns the cellcomplexes of the input topology.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.

        Returns
        -------
        list
            The list of cellcomplexes.

        """
        if Topology.IsInstance(topology, "CellComplex") or Topology.IsInstance(topology, "Cell") or Topology.IsInstance(topology, "Shell") or Topology.IsInstance(topology, "Face") or Topology.IsInstance(topology, "Wire") or Topology.IsInstance(topology, "Edge") or Topology.IsInstance(topology, "Vertex"):
            return []
        return Topology.SubTopologies(topology=topology, subTopologyType="cellcomplex")
    
    @staticmethod
    def Clusters(topology):
        """
        Returns the clusters of the input topology.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.

        Returns
        -------
        list
            The list of clusters.

        """
        if not Topology.IsInstance(topology, "Cluster"):
            return []
        return Topology.SubTopologies(topology=topology, subTopologyType="cluster")
    
    @staticmethod
    def SubTopologies(topology, subTopologyType="vertex"):
        """
        Returns the subtopologies of the input topology as specified by the subTopologyType input string.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        subTopologyType : str , optional
            The requested subtopology type. This can be one of "vertex", "edge", "wire", "face", "shell", "cell", "cellcomplex", "cluster". It is case insensitive. The default is "vertex".

        Returns
        -------
        list
            The list of subtopologies.

        """
        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.SubTopologies - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        if Topology.TypeAsString(topology).lower() == subTopologyType.lower():
            return [topology]
        subTopologies = []
        if subTopologyType.lower() == "vertex":
            _ = topology.Vertices(None, subTopologies)
        elif subTopologyType.lower() == "edge":
            _ = topology.Edges(None, subTopologies)
        elif subTopologyType.lower() == "wire":
            _ = topology.Wires(None, subTopologies)
        elif subTopologyType.lower() == "face":
            _ = topology.Faces(None, subTopologies)
        elif subTopologyType.lower() == "shell":
            _ = topology.Shells(None, subTopologies)
        elif subTopologyType.lower() == "cell":
            _ = topology.Cells(None, subTopologies)
        elif subTopologyType.lower() == "cellcomplex":
            _ = topology.CellComplexes(None, subTopologies)
        elif subTopologyType.lower() == "cluster":
            _ = topology.Clusters(None, subTopologies)
        elif subTopologyType.lower() == "aperture":
            _ = topology.Apertures(subTopologies)
        if not subTopologies:
            return [] # Make sure to return an empty list instead of None
        return subTopologies

    
    @staticmethod
    def SuperTopologies(topology, hostTopology, topologyType: str = None) -> list:
        """
        Returns the supertopologies connected to the input topology.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        hostTopology : topologic_core.Topology
            The host to topology in which to search for ther supertopologies.
        topologyType : str , optional
            The topology type to search for. This can be any of "edge", "wire", "face", "shell", "cell", "cellcomplex", "cluster". It is case insensitive. If set to None, the immediate supertopology type is searched for. The default is None.

        Returns
        -------
        list
            The list of supertopologies connected to the input topology.

        """
        
        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.SuperTopologies - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        if not Topology.IsInstance(hostTopology, "Topology"):
            print("Topology.SuperTopologies - Error: the input hostTopology parameter is not a valid topology. Returning None.")
            return None

        superTopologies = []

        if topologyType == None:
            typeID = 2*Topology.TypeID(topology)
        else:
            typeID = Topology.TypeID(topologyType)
        if Topology.Type(topology) >= typeID:
            print("Topology.SuperTopologies - Error: The input topologyType parameter is not a valid type for a super topology of the input topology. Returning None.")
            return None #The user has asked for a topology type lower than the input topology
        elif typeID == Topology.TypeID("Edge"):
            topology.Edges(hostTopology, superTopologies)
        elif typeID == Topology.TypeID("Wire"):
            topology.Wires(hostTopology, superTopologies)
        elif typeID == Topology.TypeID("Face"):
            topology.Faces(hostTopology, superTopologies)
        elif typeID == Topology.TypeID("Shell"):
            topology.Shells(hostTopology, superTopologies)
        elif typeID == Topology.TypeID("Cell"):
            topology.Cells(hostTopology, superTopologies)
        elif typeID == Topology.TypeID("CellComplex"):
            topology.CellComplexes(hostTopology, superTopologies)
        elif typeID == Topology.TypeID("Cluster"):
            topology.Cluster(hostTopology, superTopologies)
        else:
            print("Topology.SuperTopologies - Error: The input topologyType parameter is not a valid type for a super topology of the input topology. Returning None.")
            return None
        if not superTopologies:
            return [] # Make sure you return an empty list instead of None
        return superTopologies
    
    @staticmethod
    def TransferDictionaries(sources, sinks, tolerance=0.0001, numWorkers=None):
        """
        Transfers the dictionaries from the list of sources to the list of sinks.

        Parameters
        ----------
        sources : list
            The list of topologies from which to transfer the dictionaries.
        sinks : list
            The list of topologies to which to transfer the dictionaries.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.
        numWorkers : int, optional
            Number of workers run in parallel to process.

        Returns
        -------
        dict
            Returns a dictionary with the lists of sources and sinks. The keys are "sinks" and "sources".

        """
        from topologicpy.Dictionary import Dictionary
        if not isinstance(sources, list):
            print("Topology.TransferDictionaries - Error: The input sources parameter is not a valid list. Returning None.")
            return None
        if not isinstance(sinks, list):
            print("Topology.TransferDictionaries - Error: The input sinks parameter is not a valid list. Returning None.")
            return None
        if numWorkers == None:
            import multiprocessing
            numWorkers = multiprocessing.cpu_count()*2
        sources = [x for x in sources if Topology.IsInstance(x, "Topology")]
        sinks = [x for x in sinks if Topology.IsInstance(x, "Topology")]
        so_dicts = [Dictionary.PythonDictionary(Topology.Dictionary(s)) for s in sources]
        if len(sources) < 1:
            print("Topology.TransferDictionaries - Error: The input sources does not contain any valid topologies. Returning None.")
            return None
        if len(sinks) < 1:
            print("Topology.TransferDictionaries - Error: The input sinks does not contain any valid topologies. Returning None.")
            return None

        queue = Queue()
        sources_str = [Topology.BREPString(s) for s in sources]
        sink_items = [SinkItem(id(s), Topology.BREPString(s)) for s in sinks]
        mergingProcess = MergingProcess(queue, sources_str, sink_items, so_dicts)
        mergingProcess.start()

        workerProcessPool = WorkerProcessPool(numWorkers, queue, sources_str, sink_items, so_dicts, tolerance)
        workerProcessPool.startProcesses()
        workerProcessPool.join()

        queue.put_nowait(None)
        sinkMap = queue.get()
        mergingProcess.join()

        for i, sink in enumerate(sink_items):
            mapItem = sinkMap[sink.ID]
            newDict = Dictionary.ByKeysValues(mapItem.sinkKeys, mapItem.sinkValues)
            _ = sinks[i].SetDictionary(newDict)
        return {"sources": sources, "sinks": sinks}

    
    @staticmethod
    def TransferDictionariesBySelectors(topology, selectors, tranVertices=False, tranEdges=False, tranFaces=False, tranCells=False, tolerance=0.0001, numWorkers=None):
        """
        Transfers the dictionaries of the list of selectors to the subtopologies of the input topology based on the input parameters.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        selectors : list
            The list of input selectors from which to transfer the dictionaries.
        tranVertices : bool , optional
            If True transfer dictionaries to the vertices of the input topology.
        tranEdges : bool , optional
            If True transfer dictionaries to the edges of the input topology.
        tranFaces : bool , optional
            If True transfer dictionaries to the faces of the input topology.
        tranCells : bool , optional
            If True transfer dictionaries to the cells of the input topology.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.
        numWorkers : int , optional
            Number of workers run in parallel to process. The default is None which causes the algorithm to use twice the number of cpu cores in the host computer.

        Returns
        -------
        Topology
            The input topology with the dictionaries transferred to its subtopologies.

        """

        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.TransferDictionariesBySelectors - Error: The input topology parameter is not a valid topology. Returning None.")
            return None
        if not isinstance(selectors, list):
            print("Topology.TransferDictionariesBySelectors - Error: The input selectors parameter is not a valid list. Returning None.")
            return None
        if numWorkers == None:
            import multiprocessing
            numWorkers = multiprocessing.cpu_count()*2
        selectors_tmp = [x for x in selectors if Topology.IsInstance(x, "Vertex")]
        if len(selectors_tmp) < 1:
            print("Topology.TransferDictionariesBySelectors - Error: The input selectors do not contain any valid topologies. Returning None.")
            return None
        sinkEdges = []
        sinkFaces = []
        sinkCells = []
        hidimSink = Topology.HighestType(topology)
        if tranVertices == True:
            sinkVertices = []
            if Topology.Type(topology) == Topology.TypeID("Vertex"):
                sinkVertices.append(topology)
            elif hidimSink >= Topology.TypeID("Vertex"):
                topology.Vertices(None, sinkVertices)
            _ = Topology.TransferDictionaries(selectors, sinkVertices, tolerance=tolerance, numWorkers=numWorkers)
        if tranEdges == True:
            sinkEdges = []
            if Topology.Type(topology) == Topology.TypeID("Edge"):
                sinkEdges.append(topology)
            elif hidimSink >= Topology.TypeID("Edge"):
                topology.Edges(None, sinkEdges)
                _ = Topology.TransferDictionaries(selectors, sinkEdges, tolerance=tolerance, numWorkers=numWorkers)
        if tranFaces == True:
            sinkFaces = []
            if Topology.Type(topology) == Topology.TypeID("Face"):
                sinkFaces.append(topology)
            elif hidimSink >= Topology.TypeID("Face"):
                topology.Faces(None, sinkFaces)
            _ = Topology.TransferDictionaries(selectors, sinkFaces, tolerance=tolerance, numWorkers=numWorkers)
        if tranCells == True:
            sinkCells = []
            if Topology.Type(topology) == Topology.TypeID("Cell"):
                sinkCells.append(topology)
            elif hidimSink >= Topology.TypeID("Cell"):
                topology.Cells(None, sinkCells)
            _ = Topology.TransferDictionaries(selectors, sinkCells, tolerance=tolerance, numWorkers=numWorkers)
        return topology

    
    @staticmethod
    def Transform(topology, matrix):
        """
        Transforms the input topology by the input 4X4 transformation matrix.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        matrix : list
            The input 4x4 transformation matrix.

        Returns
        -------
        topologic_core.Topology
            The transformed topology.

        """
        kTranslationX = 0.0
        kTranslationY = 0.0
        kTranslationZ = 0.0
        kRotation11 = 1.0
        kRotation12 = 0.0
        kRotation13 = 0.0
        kRotation21 = 0.0
        kRotation22 = 1.0
        kRotation23 = 0.0
        kRotation31 = 0.0
        kRotation32 = 0.0
        kRotation33 = 1.0

        
        kRotation11 = matrix[0][0]
        kRotation12 = matrix[0][1]
        kRotation13 = matrix[0][2]
        kRotation21 = matrix[1][0]
        kRotation22 = matrix[1][1]
        kRotation23 = matrix[1][2]
        kRotation31 = matrix[2][0]
        kRotation32 = matrix[2][1]
        kRotation33 = matrix[2][2]
        kTranslationX = matrix[3][0]
        kTranslationY = matrix[3][1]
        kTranslationZ = matrix[3][2]

        return topologic.TopologyUtility.Transform(topology, kTranslationX, kTranslationY, kTranslationZ, kRotation11, kRotation12, kRotation13, kRotation21, kRotation22, kRotation23, kRotation31, kRotation32, kRotation33)

    @staticmethod
    def Translate(topology, x=0, y=0, z=0):
        """
        Translates (moves) the input topology.

        Parameters
        ----------
        topology : topologic_core.topology
            The input topology.
        x : float , optional
            The x translation value. The default is 0.
        y : float , optional
            The y translation value. The default is 0.
        z : float , optional
            The z translation value. The default is 0.

        Returns
        -------
        topologic_core.Topology
            The translated topology.

        """
        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.Translate - Error: The input topology parameter is not a valid topology. Returning None.")
            return None
        try:
            return topologic.TopologyUtility.Translate(topology, x, y, z)
        except:
            return topology
    
    @staticmethod
    def TranslateByDirectionDistance(topology, direction: list = [0, 0, 0], distance: float = 0):
        """
        Translates (moves) the input topology along the input direction by the specified distance.

        Parameters
        ----------
        topology : topologic_core.topology
            The input topology.
        direction : list , optional
            The direction vector in which the topology should be moved. The default is [0, 0, 0]
        distance : float , optional
            The distance by which the toplogy should be moved. The default is 0.

        Returns
        -------
        topologic_core.Topology
            The translated topology.

        """
        from topologicpy.Vector import Vector
        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.TranslateByDirectionDistance - Error: The input topology parameter is not a valid topology. Returning None.")
            return None
        v = Vector.SetMagnitude(direction, distance)
        return Topology.Translate(topology, v[0], v[1], v[2])

    
    @staticmethod
    def Triangulate(topology, transferDictionaries: bool = False, mode: int = 0, meshSize: float = None, tolerance: float = 0.0001):
        """
        Triangulates the input topology.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topologgy.
        transferDictionaries : bool , optional
            If set to True, the dictionaries of the faces in the input topology will be transferred to the created triangular faces. The default is False.
        mode : int , optional
            The desired mode of meshing algorithm. Several options are available:
            0: Classic
            1: MeshAdapt
            3: Initial Mesh Only
            5: Delaunay
            6: Frontal-Delaunay
            7: BAMG
            8: Fontal-Delaunay for Quads
            9: Packing of Parallelograms
            All options other than 0 (Classic) use the gmsh library. See https://gmsh.info/doc/texinfo/gmsh.html#Mesh-options
            WARNING: The options that use gmsh can be very time consuming and can create very heavy geometry.
        meshSize : float , optional
            The desired size of the mesh when using the "mesh" option. If set to None, it will be
            calculated automatically and set to 10% of the overall size of the face.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        topologic_core.Topology
            The triangulated topology.

        """
        from topologicpy.Face import Face
        from topologicpy.Shell import Shell
        from topologicpy.Cell import Cell
        from topologicpy.CellComplex import CellComplex
        from topologicpy.Cluster import Cluster

        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.Triangulate - Error: The input parameter is not a valid topology. Returning None.")
            return None
        t = Topology.Type(topology)
        if (t == Topology.TypeID("Vertex")) or (t == Topology.TypeID("Edge")) or (t == Topology.TypeID("Wire")):
            return topology
        elif t == Topology.TypeID("Cluster"):
            temp_topologies = []
            cellComplexes = Topology.SubTopologies(topology, subTopologyType="cellcomplex") or []
            for cc in cellComplexes:
                temp_topologies.append(Topology.Triangulate(cc, transferDictionaries=transferDictionaries, mode=mode, meshSize=meshSize, tolerance=tolerance))
            cells = Cluster.FreeCells(topology, tolerance=tolerance) or []
            for c in cells:
                temp_topologies.append(Topology.Triangulate(c, transferDictionaries=transferDictionaries, mode=mode, meshSize=meshSize, tolerance=tolerance))
            shells = Cluster.FreeShells(topology, tolerance=tolerance) or []
            for s in shells:
                temp_topologies.append(Topology.Triangulate(s, transferDictionaries=transferDictionaries, mode=mode, meshSize=meshSize, tolerance=tolerance))
            faces = Cluster.FreeFaces(topology, tolerance=tolerance) or []
            for f in faces:
                temp_topologies.append(Topology.Triangulate(f, transferDictionaries=transferDictionaries, mode=mode, meshSize=meshSize, tolerance=tolerance))
            if len(temp_topologies) > 0:
                return Cluster.ByTopologies(temp_topologies)
            else:
                return topology
        topologyFaces = []
        _ = topology.Faces(None, topologyFaces)
        faceTriangles = []
        selectors = []
        for aFace in topologyFaces:
            if len(Topology.Vertices(aFace)) > 3:
                triFaces = Face.Triangulate(aFace, mode=mode, meshSize=meshSize, tolerance=tolerance)
            else:
                triFaces = [aFace]
            for triFace in triFaces:
                if transferDictionaries:
                    selectors.append(Topology.SetDictionary(Face.Centroid(triFace), Topology.Dictionary(aFace)))
                faceTriangles.append(triFace)
        if t == Topology.TypeID("Face") or t == Topology.TypeID("Shell"): # Face or Shell
            return_topology = Shell.ByFaces(faceTriangles, tolerance=tolerance)
            if transferDictionaries and not return_topology == None:
                return_topology = Topology.TransferDictionariesBySelectors(return_topology, selectors, tranFaces=True, tolerance=tolerance)
        elif t == Topology.TypeID("Cell"): # Cell
            return_topology = Cell.ByFaces(faceTriangles, tolerance=tolerance)
            if transferDictionaries and not return_topology == None:
                return_topology = Topology.TransferDictionariesBySelectors(return_topology, selectors, tranFaces=True, tolerance=tolerance)
        elif t == Topology.TypeID("CellComplex"): #CellComplex
            return_topology = CellComplex.ByFaces(faceTriangles, tolerance=tolerance)
            if transferDictionaries and not return_topology == None:
                return_topology = Topology.TransferDictionariesBySelectors(return_topology, selectors, tranFaces=True, tolerance=tolerance)
        
        if return_topology == None:
            return_topology = Topology.SelfMerge(Cluster.ByTopologies(faceTriangles), tolerance=tolerance)
            if transferDictionaries == True:
                return_topology = Topology.TransferDictionariesBySelectors(return_topology, selectors, tranFaces=True, tolerance=tolerance)
        
        return return_topology

    
    @staticmethod
    def Type(topology):
        """
        Returns the type of the input topology.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.

        Returns
        -------
        int
            The type of the input topology.

        """
        #if not Topology.IsInstance(topology, "Topology"):
            #print("Topology.Type - Error: The input topology parameter is not a valid topology. Returning None.")
            #return None
        return topology.Type()
    
    @staticmethod
    def TypeAsString(topology):
        """
        Returns the type of the input topology as a string.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.

        Returns
        -------
        str
            The type of the topology as a string.

        """
        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.TypeAsString - Error: The input topology parameter is not a valid topology. Returning None.")
            return None
        return topology.GetTypeAsString()
    
    @staticmethod
    def TypeID(name : str = None) -> int:
        """
        Returns the type id of the input name string.

        Parameters
        ----------
        name : str , optional
            The input class name string. This could be one of:
                "vertex",
                "edge",
                "wire",
                "face",
                "shell",
                "cell",
                "cellcomplex",
                "cluster",
                "aperture",
                "context",
                "dictionary",
                "graph",
                "topology"
            
            It is case insensitive. The default is None.

        Returns
        -------
        int
            The type id of the input topologyType string.

        """

        if not isinstance(name, str):
            print("Topology.TypeID - Error: The input topologyType parameter is not a valid string. Returning None.")
            return None
        name = name.lower()
        if not name in ["vertex", "edge", "wire",
                                "face", "shell", "cell",
                                "cellcomplex", "cluster", "aperture",
                                "context", "dictionary", "graph", "topology"]:
            print("Topology.TypeID - Error: The input name parameter is not a recognized string. Returning None.")
            return None
        typeID = None
        if name == "vertex":
            typeID = 1
        elif name == "edge":
            typeID = 2
        elif name == "wire":
            typeID = 4
        elif name == "face":
            typeID = 8
        elif name == "shell":
            typeID = 16
        elif name == "cell":
            typeID = 32
        elif name == "cellComplex":
            typeID = 64
        elif name == "cluster":
            typeID = 128
        elif name == "aperture":
            typeID = 256
        elif name == "context":
            typeID = 512
        elif name == "dictionary":
            typeID = 1024
        elif name == "graph":
            typeID = 2048
        elif name == "topology":
            typeID = 4096
        return typeID

Classes

class MergingProcess (message_queue, sources, sinks, so_dicts)

Receive message from other processes and merging the result

Expand source code
class MergingProcess(Process):
    """
    Receive message from other processes and merging the result
    """
    def __init__(self, message_queue, sources, sinks, so_dicts):
        Process.__init__(self, target=self.wait_message)
        self.message_queue = message_queue
        self.sources = sources
        self.sinks = sinks
        self.so_dicts = so_dicts
        self.sinkMap = self._init_sink_map()

    def _init_sink_map(self):
        sinkMap = {}
        for sink in self.sinks:
            sinkMap[sink.ID] = QueueItem(sink.ID, [], [])
        return sinkMap

    def wait_message(self):
        while True:
            try:
                item = self.message_queue.get()
                if item is None:
                    self.message_queue.put(self.sinkMap)
                    break
                mapItem = self.sinkMap[item.ID]
                mapItem.sinkKeys.extend(item.sinkKeys)
                mapItem.sinkValues.extend(item.sinkValues)
            except Exception as e:
                print(str(e))

Ancestors

  • multiprocessing.context.Process
  • multiprocessing.process.BaseProcess

Methods

def wait_message(self)
Expand source code
def wait_message(self):
    while True:
        try:
            item = self.message_queue.get()
            if item is None:
                self.message_queue.put(self.sinkMap)
                break
            mapItem = self.sinkMap[item.ID]
            mapItem.sinkKeys.extend(item.sinkKeys)
            mapItem.sinkValues.extend(item.sinkValues)
        except Exception as e:
            print(str(e))
class QueueItem (ID, sinkKeys, sinkValues)

QueueItem(ID, sinkKeys, sinkValues)

Ancestors

  • builtins.tuple

Instance variables

var ID

Alias for field number 0

var sinkKeys

Alias for field number 1

var sinkValues

Alias for field number 2

class SinkItem (ID, sink_str)

SinkItem(ID, sink_str)

Ancestors

  • builtins.tuple

Instance variables

var ID

Alias for field number 0

var sink_str

Alias for field number 1

class Topology
Expand source code
class Topology():
    @staticmethod
    def AddApertures(topology, apertures, exclusive=False, subTopologyType=None, tolerance=0.001):
        """
        Adds the input list of apertures to the input topology or to its subtpologies based on the input subTopologyType.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        apertures : list
            The input list of apertures.
        exclusive : bool , optional
            If set to True, one (sub)topology will accept only one aperture. Otherwise, one (sub)topology can accept multiple apertures. The default is False.
        subTopologyType : string , optional
            The subtopology type to which to add the apertures. This can be "cell", "face", "edge", or "vertex". It is case insensitive. If set to None, the apertures will be added to the input topology. The default is None.
        tolerance : float , optional
            The desired tolerance. The default is 0.001. This is larger than the usual 0.0001 as it seems to work better.

        Returns
        -------
        topologic_core.Topology
            The input topology with the apertures added to it.

        """
        from topologicpy.Vertex import Vertex
        from topologicpy.Cluster import Cluster
        from topologicpy.Aperture import Aperture
        def processApertures(subTopologies, apertures, exclusive=False, tolerance=0.001):
            usedTopologies = []
            for subTopology in subTopologies:
                    usedTopologies.append(0)
            ap = 1
            for aperture in apertures:
                apCenter = Topology.InternalVertex(aperture, tolerance)
                for i in range(len(subTopologies)):
                    subTopology = subTopologies[i]
                    if exclusive == True and usedTopologies[i] == 1:
                        continue
                    if Vertex.Distance(apCenter, subTopology) < tolerance:
                        context = topologic.Context.ByTopologyParameters(subTopology, 0.5, 0.5, 0.5)
                        _ = Aperture.ByTopologyContext(aperture, context)
                        if exclusive == True:
                            usedTopologies[i] = 1
                ap = ap + 1
            return None

        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.AddApertures - Error: The input topology parameter is not a valid topology. Returning None.")
            return None
        if not apertures:
            return topology
        if not isinstance(apertures, list):
            print("Topology.AddApertures - Error: the input apertures parameter is not a list. Returning None.")
            return None
        apertures = [x for x in apertures if Topology.IsInstance(x , "Topology")]
        if len(apertures) < 1:
            return topology
        if not subTopologyType:
            subTopologyType = "self"
        if not subTopologyType.lower() in ["self", "cell", "face", "edge", "vertex"]:
            print("Topology.AddApertures - Error: the input subtopology type parameter is not a recognized type. Returning None.")
            return None
        if subTopologyType.lower() == "self":
            subTopologies = [topology]
        else:
            subTopologies = Topology.SubTopologies(topology, subTopologyType)
        processApertures(subTopologies, apertures, exclusive, tolerance)
        return topology
    
    @staticmethod
    def AddContent(topology, contents, subTopologyType=None, tolerance=0.0001):
        """
        Adds the input list of contents to the input topology or to its subtpologies based on the input subTopologyType.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        contents : list or topologic_core.Topology
            The input list of contents (of type topologic_core.Topology). A single topology is also accepted as input.
        subTopologyType : string , optional
            The subtopology type to which to add the contents. This can be "cellcomplex", "cell", "shell", "face", "wire", "edge", or "vertex". It is case insensitive. If set to None, the contents will be added to the input topology. The default is None.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        topologic_core.Topology
            The input topology with the contents added to it.

        """
        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.AddContent - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        if not contents:
            return topology
        if not isinstance(contents, list):
            contents = [contents]
        if not isinstance(contents, list):
            print("Topology.AddContent - Error: the input contents parameter is not a list. Returning None.")
            return None
        contents = [x for x in contents if Topology.IsInstance(x, "Topology")]
        if len(contents) < 1:
            return topology
        if not subTopologyType:
            subTopologyType = "self"
        if not subTopologyType.lower() in ["self", "cellcomplex", "cell", "shell", "face", "wire", "edge", "vertex"]:
            print("Topology.AddContent - Error: the input subtopology type parameter is not a recognized type. Returning None.")
            return None
        if subTopologyType.lower() == "self":
            t = 0
        else:
            t = Topology.TypeID(subTopologyType)
        return topology.AddContents(contents, t)
    
    @staticmethod
    def AddDictionary(topology, dictionary):
        """
        Adds the input dictionary to the input topology.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        dictionary : topologic_core.Dictionary
            The input dictionary.

        Returns
        -------
        topologic_core.Topology
            The input topology with the input dictionary added to it.

        """
        
        from topologicpy.Dictionary import Dictionary
        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.AddDictionary - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        if not Topology.IsInstance(dictionary, "Dictionary"):
            print("Topology.AddDictionary - Error: the input dictionary parameter is not a dictionary. Returning None.")
            return None
        tDict = Topology.Dictionary(topology)
        if len(tDict.Keys()) < 1:
            _ = topology.SetDictionary(dictionary)
        else:
            newDict = Dictionary.ByMergedDictionaries([tDict, dictionary])
            if newDict:
                _ = topology.SetDictionary(newDict)
        return topology
    
    @staticmethod
    def AdjacentTopologies(topology, hostTopology, topologyType=None):
        """
        Returns the topologies, as specified by the input topology type, adjacent to the input topology witin the input host topology.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        hostTopology : topologic_core.Topology
            The host topology in which to search.
        topologyType : str
            The type of topology for which to search. This can be one of "vertex", "edge", "wire", "face", "shell", "cell", "cellcomplex". It is case-insensitive. If it is set to None, the type will be set to the same type as the input topology. The default is None.

        Returns
        -------
        adjacentTopologies : list
            The list of adjacent topologies.

        """
        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.AdjacentTopologies - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        if not Topology.IsInstance(hostTopology, "Topology"):
            print("Topology.AdjacentTopologies - Error: the input hostTopology parameter is not a valid topology. Returning None.")
            return None
        if not topologyType:
            topologyType = Topology.TypeAsString(topology).lower()
        if not isinstance(topologyType, str):
            print("Topology.AdjacentTopologies - Error: the input topologyType parameter is not a string. Returning None.")
            return None
        if not topologyType.lower() in ["vertex", "edge", "wire", "face", "shell", "cell", "cellcomplex"]:
            print("Topology.AdjacentTopologies - Error: the input topologyType parameter is not a recognized type. Returning None.")
            return None
        adjacentTopologies = []
        error = False
        if Topology.IsInstance(topology, "Vertex"):
            if topologyType.lower() == "vertex":
                try:
                    _ = topology.AdjacentVertices(hostTopology, adjacentTopologies)
                except:
                    try:
                        _ = topology.Vertices(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "edge":
                try:
                    _ = topologic.VertexUtility.AdjacentEdges(topology, hostTopology, adjacentTopologies)
                except:
                    try:
                        _ = topology.Edges(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "wire":
                try:
                    _ = topologic.VertexUtility.AdjacentWires(topology, hostTopology, adjacentTopologies)
                except:
                    try:
                        _ = topology.Wires(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "face":
                try:
                    _ = topologic.VertexUtility.AdjacentFaces(topology, hostTopology, adjacentTopologies)
                except:
                    try:
                        _ = topology.Faces(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "shell":
                try:
                    _ = topologic.VertexUtility.AdjacentShells(topology, hostTopology, adjacentTopologies)
                except:
                    try:
                        _ = topology.Shells(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "cell":
                try:
                    _ = topologic.VertexUtility.AdjacentCells(topology, hostTopology, adjacentTopologies)
                except:
                    try:
                        _ = topology.Cells(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "cellcomplex":
                try:
                    _ = topologic.VertexUtility.AdjacentCellComplexes(topology, hostTopology, adjacentTopologies)
                except:
                    try:
                        _ = topology.CellComplexes(hostTopology, adjacentTopologies)
                    except:
                        error = True
        elif Topology.IsInstance(topology, "Edge"):
            if topologyType.lower() == "vertex":
                try:
                    _ = topology.Vertices(hostTopology, adjacentTopologies)
                except:
                    error = True
            elif topologyType.lower() == "edge":
                try:
                    _ = topology.AdjacentEdges(hostTopology, adjacentTopologies)
                except:
                    error = True
            elif topologyType.lower() == "wire":
                try:
                    _ = topologic.EdgeUtility.AdjacentWires(topology, adjacentTopologies)
                except:
                    try:
                        _ = topology.Wires(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "face":
                try:
                    _ = topologic.EdgeUtility.AdjacentFaces(topology, adjacentTopologies)
                except:
                    try:
                        _ = topology.Faces(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "shell":
                try:
                    _ = topologic.EdgeUtility.AdjacentShells(adjacentTopologies)
                except:
                    try:
                        _ = topology.Shells(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "cell":
                try:
                    _ = topologic.EdgeUtility.AdjacentCells(adjacentTopologies)
                except:
                    try:
                        _ = topology.Cells(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "cellcomplex":
                try:
                    _ = topologic.EdgeUtility.AdjacentCellComplexes(adjacentTopologies)
                except:
                    try:
                        _ = topology.CellComplexes(hostTopology, adjacentTopologies)
                    except:
                        error = True
        elif Topology.IsInstance(topology, "Wire"):
            if topologyType.lower() == "vertex":
                try:
                    _ = topologic.WireUtility.AdjacentVertices(topology, adjacentTopologies)
                except:
                    try:
                        _ = topology.Vertices(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "edge":
                try:
                    _ = topologic.WireUtility.AdjacentEdges(topology, adjacentTopologies)
                except:
                    try:
                        _ = topology.Edges(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "wire":
                try:
                    _ = topologic.WireUtility.AdjacentWires(topology, adjacentTopologies)
                except:
                    try:
                        _ = topology.Wires(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "face":
                try:
                    _ = topologic.WireUtility.AdjacentFaces(topology, adjacentTopologies)
                except:
                    try:
                        _ = topology.Faces(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "shell":
                try:
                    _ = topologic.WireUtility.AdjacentShells(adjacentTopologies)
                except:
                    try:
                        _ = topology.Shells(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "cell":
                try:
                    _ = topologic.WireUtility.AdjacentCells(adjacentTopologies)
                except:
                    try:
                        _ = topology.Cells(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "cellcomplex":
                try:
                    _ = topologic.WireUtility.AdjacentCellComplexes(adjacentTopologies)
                except:
                    try:
                        _ = topology.CellComplexes(hostTopology, adjacentTopologies)
                    except:
                        error = True
        elif Topology.IsInstance(topology, "Face"):
            if topologyType.lower() == "vertex":
                try:
                    _ = topologic.FaceUtility.AdjacentVertices(topology, adjacentTopologies)
                except:
                    try:
                        _ = topology.Vertices(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "edge":
                try:
                    _ = topologic.FaceUtility.AdjacentEdges(topology, adjacentTopologies)
                except:
                    try:
                        _ = topology.Edges(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "wire":
                try:
                    _ = topologic.FaceUtility.AdjacentWires(topology, adjacentTopologies)
                except:
                    try:
                        _ = topology.Wires(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "face":
                _ = topology.AdjacentFaces(hostTopology, adjacentTopologies)
            elif topologyType.lower() == "shell":
                try:
                    _ = topologic.FaceUtility.AdjacentShells(adjacentTopologies)
                except:
                    try:
                        _ = topology.Shells(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "cell":
                try:
                    _ = topologic.FaceUtility.AdjacentCells(adjacentTopologies)
                except:
                    try:
                        _ = topology.Cells(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "cellcomplex":
                try:
                    _ = topologic.FaceUtility.AdjacentCellComplexes(adjacentTopologies)
                except:
                    try:
                        _ = topology.CellComplexes(hostTopology, adjacentTopologies)
                    except:
                        error = True
        elif Topology.IsInstance(topology, "Shell"):
            if topologyType.lower() == "vertex":
                try:
                    _ = topologic.ShellUtility.AdjacentVertices(topology, adjacentTopologies)
                except:
                    try:
                        _ = topology.Vertices(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "edge":
                try:
                    _ = topologic.ShellUtility.AdjacentEdges(topology, adjacentTopologies)
                except:
                    try:
                        _ = topology.Edges(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "wire":
                try:
                    _ = topologic.ShellUtility.AdjacentWires(topology, adjacentTopologies)
                except:
                    try:
                        _ = topology.Wires(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "face":
                try:
                    _ = topologic.ShellUtility.AdjacentFaces(topology, adjacentTopologies)
                except:
                    try:
                        _ = topology.Faces(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "shell":
                try:
                    _ = topologic.ShellUtility.AdjacentShells(adjacentTopologies)
                except:
                    try:
                        _ = topology.Shells(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "cell":
                try:
                    _ = topologic.ShellUtility.AdjacentCells(adjacentTopologies)
                except:
                    try:
                        _ = topology.Cells(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "cellcomplex":
                try:
                    _ = topologic.ShellUtility.AdjacentCellComplexes(adjacentTopologies)
                except:
                    try:
                        _ = topology.CellComplexes(hostTopology, adjacentTopologies)
                    except:
                        error = True
        elif Topology.IsInstance(topology, "Cell"):
            if topologyType.lower() == "vertex":
                try:
                    _ = topologic.CellUtility.AdjacentVertices(topology, adjacentTopologies)
                except:
                    try:
                        _ = topology.Vertices(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "edge":
                try:
                    _ = topologic.CellUtility.AdjacentEdges(topology, adjacentTopologies)
                except:
                    try:
                        _ = topology.Edges(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "wire":
                try:
                    _ = topologic.CellUtility.AdjacentWires(topology, adjacentTopologies)
                except:
                    try:
                        _ = topology.Wires(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "face":
                try:
                    _ = topologic.CellUtility.AdjacentFaces(topology, adjacentTopologies)
                except:
                    try:
                        _ = topology.Faces(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "shell":
                try:
                    _ = topologic.CellUtility.AdjacentShells(adjacentTopologies)
                except:
                    try:
                        _ = topology.Shells(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "cell":
                try:
                    _ = topology.AdjacentCells(hostTopology, adjacentTopologies)
                except:
                    try:
                        _ = topology.Cells(hostTopology, adjacentTopologies)
                    except:
                        error = True
            elif topologyType.lower() == "cellcomplex":
                try:
                    _ = topologic.CellUtility.AdjacentCellComplexes(adjacentTopologies)
                except:
                    try:
                        _ = topology.CellComplexes(hostTopology, adjacentTopologies)
                    except:
                        error = True
        elif Topology.IsInstance(topology, "CellComplex"):
            if topologyType.lower() == "vertex":
                try:
                    _ = topology.Vertices(hostTopology, adjacentTopologies)
                except:
                    error = True
            elif topologyType.lower() == "edge":
                try:
                    _ = topology.Edges(hostTopology, adjacentTopologies)
                except:
                    error = True
            elif topologyType.lower() == "wire":
                try:
                    _ = topology.Wires(hostTopology, adjacentTopologies)
                except:
                    error = True
            elif topologyType.lower() == "face":
                try:
                    _ = topology.Faces(hostTopology, adjacentTopologies)
                except:
                    error = True
            elif topologyType.lower() == "shell":
                try:
                    _ = topology.Shells(hostTopology, adjacentTopologies)
                except:
                    error = True
            elif topologyType.lower() == "cell":
                try:
                    _ = topology.Cells(hostTopology, adjacentTopologies)
                except:
                    error = True
            elif topologyType.lower() == "cellcomplex":
                raise Exception("Topology.AdjacentTopologies - Error: Cannot search for adjacent topologies of a CellComplex")
        elif Topology.IsInstance(topology, "Cluster"):
            raise Exception("Topology.AdjacentTopologies - Error: Cannot search for adjacent topologies of a Cluster")
        if error:
            raise Exception("Topology.AdjacentTopologies - Error: Failure in search for adjacent topologies of type "+topologyType)
        return adjacentTopologies

    @staticmethod
    def Analyze(topology):
        """
        Returns an analysis string that describes the input topology.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.

        Returns
        -------
        str
            The analysis string.

        """
        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.Analyze - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        return topologic.Topology.Analyze(topology)
    
    @staticmethod
    def Apertures(topology, subTopologyType=None):
        """
        Returns the apertures of the input topology.
        
        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        subTopologyType : string , optional
            The subtopology type from which to retrieve the apertures. This can be "cell", "face", "edge", or "vertex" or "all". It is case insensitive. If set to "all", then all apertures will be returned. If set to None, the apertures will be retrieved only from the input topology. The default is None.
        
        Returns
        -------
        list
            The list of apertures belonging to the input topology.

        """
        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.Apertures - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        
        apertures = []
        subTopologies = []
        if not subTopologyType:
            _ = topology.Apertures(apertures)
        elif subTopologyType.lower() == "vertex":
            subTopologies = Topology.Vertices(topology)
        elif subTopologyType.lower() == "edge":
            subTopologies = Topology.Edges(topology)
        elif subTopologyType.lower() == "face":
            subTopologies = Topology.Faces(topology)
        elif subTopologyType.lower() == "cell":
            subTopologies = Topology.Cells(topology)
        elif subTopologyType.lower() == "all":
            _ = topology.Apertures(apertures)
            subTopologies = Topology.Vertices(topology)
            subTopologies += Topology.Edges(topology)
            subTopologies += Topology.Faces(topology)
            subTopologies += Topology.Cells(topology)
        else:
            print("Topology.Apertures - Error: the input subtopologyType parameter is not a recognized type. Returning None.")
            return None
        for subTopology in subTopologies:
            apertures += Topology.Apertures(subTopology, subTopologyType=None)
        return apertures


    @staticmethod
    def ApertureTopologies(topology, subTopologyType=None):
        """
        Returns the aperture topologies of the input topology.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        subTopologyType : string , optional
            The subtopology type from which to retrieve the apertures. This can be "cell", "face", "edge", or "vertex" or "all". It is case insensitive. If set to "all", then all apertures will be returned. If set to None, the apertures will be retrieved only from the input topology. The default is None.
       
        Returns
        -------
        list
            The list of aperture topologies found in the input topology.

        """
        from topologicpy.Aperture import Aperture
        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.ApertureTopologies - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        apertures = Topology.Apertures(topology=topology, subTopologyType=subTopologyType)
        apTopologies = []
        for aperture in apertures:
            apTopologies.append(Aperture.Topology(aperture))
        return apTopologies
    
    @staticmethod
    def Union(topologyA, topologyB, tranDict=False, tolerance=0.0001):
        """
        See Topology.Boolean()

        """
        return Topology.Boolean(topologyA, topologyB, operation="union", tranDict=tranDict, tolerance=tolerance)
    
    @staticmethod
    def Difference(topologyA, topologyB, tranDict=False, tolerance=0.0001):
        """
        See Topology.Boolean().

        """
        return Topology.Boolean(topologyA=topologyA, topologyB=topologyB, operation="difference", tranDict=tranDict, tolerance=tolerance)
    
    @staticmethod
    def Intersect(topologyA, topologyB, tranDict=False, tolerance=0.0001):
        """
        See Topology.Boolean().

        """

        if topologyA == None:
            return None
        if topologyB == None:
            return None
        
        from topologicpy.Vertex import Vertex
        # Sort the two topologies by their type from lower to higher so comparison can be eased.
        if Topology.Type(topologyB) < Topology.Type(topologyA):
            temp = topologyA
            topologyA = topologyB
            topologyB = temp
        
        if Topology.IsInstance(topologyB, "CellComplex") or Topology.IsInstance(topologyB, "Cluster"):
            merge = Topology.Merge(topologyA, topologyB)
            symdif = Topology.SymDif(topologyA, topologyB)
            return Topology.Difference(merge, symdif)
        else:
        # Vertex:
            if Topology.IsInstance(topologyA, "Vertex"):
                # Vertex:
                if Topology.IsInstance(topologyB, "Vertex"):
                    if Vertex.Distance(topologyA, topologyB) < tolerance:
                        return topologyA
                    else:
                        return None
                # Edge/Wire/Face/Shell/Cell:
                else:
                    if Vertex.IsInternal(topologyA, topologyB):
                        return topologyA
                    else:
                        return None
            else:
                return topologyA.Intersect(topologyB)
    
    @staticmethod
    def SymmetricDifference(topologyA, topologyB, tranDict=False, tolerance=0.0001):
        """
        See Topology.Boolean().

        """
        return Topology.Boolean(topologyA=topologyA, topologyB=topologyB, operation="symdif", tranDict=tranDict, tolerance=tolerance)
    
    @staticmethod
    def SymDif(topologyA, topologyB, tranDict=False, tolerance=0.0001):
        """
        See Topology.Boolean().

        """
        return Topology.Boolean(topologyA=topologyA, topologyB=topologyB, operation="symdif", tranDict=tranDict, tolerance=tolerance)
    
    @staticmethod
    def XOR(topologyA, topologyB, tranDict=False, tolerance=0.0001):
        """
        See Topology.Boolean().

        """
        return Topology.Boolean(topologyA=topologyA, topologyB=topologyB, operation="symdif", tranDict=tranDict, tolerance=tolerance)
    
    @staticmethod
    def Merge(topologyA, topologyB, tranDict=False, tolerance=0.0001):
        """
        See Topology.Boolean().

        """
        return Topology.Boolean(topologyA=topologyA, topologyB=topologyB, operation="merge", tranDict=tranDict, tolerance=tolerance)
    
    @staticmethod
    def Slice(topologyA, topologyB, tranDict=False, tolerance=0.0001):
        """
        See Topology.Boolean().

        """
        return Topology.Boolean(topologyA=topologyA, topologyB=topologyB, operation="slice", tranDict=tranDict, tolerance=tolerance)
    
    @staticmethod
    def Merge(topologyA, topologyB, tranDict=False, tolerance=0.0001):
        """
        See Topology.Boolean().

        """
        return Topology.Boolean(topologyA=topologyA, topologyB=topologyB, operation="merge", tranDict=tranDict, tolerance=tolerance)
    
    @staticmethod
    def Impose(topologyA, topologyB, tranDict=False, tolerance=0.0001):
        """
        See Topology.Boolean().

        """
        return Topology.Boolean(topologyA=topologyA, topologyB=topologyB, operation="impose", tranDict=tranDict, tolerance=tolerance)
    
    @staticmethod
    def Imprint(topologyA, topologyB, tranDict=False, tolerance=0.0001):
        """
        See Topology.Boolean().

        """
        return Topology.Boolean(topologyA=topologyA, topologyB=topologyB, operation="imprint", tranDict=tranDict, tolerance=tolerance)
    
    @staticmethod
    def Boolean(topologyA, topologyB, operation="union", tranDict=False, tolerance=0.0001):
        """
        Execute the input boolean operation type on the input operand topologies and return the result. See https://en.wikipedia.org/wiki/Boolean_operation.

        Parameters
        ----------
        topologyA : topologic_core.Topology
            The first input topology.
        topologyB : topologic_core.Topology
            The second input topology.
        operation : str , optional
            The boolean operation. This can be one of "union", "difference", "intersect", "symdif", "merge", "slice", "impose", "imprint". It is case insensitive. The default is "union".
        tranDict : bool , optional
            If set to True the dictionaries of the operands are merged and transferred to the result. The default is False.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        topologic_core.Topology
            the resultant topology.

        """
        from topologicpy.Dictionary import Dictionary
        if not Topology.IsInstance(topologyA, "Topology"):
            print("Topology.Boolean - Error: the input topologyA parameter is not a valid topology. Returning None.")
            return None
        if not Topology.IsInstance(topologyB, "Topology"):
            print("Topology.Boolean - Error: the input topologyB parameter is not a valid topology. Returning None.")
            return None
        if not isinstance(operation, str):
            print("Topology.Boolean - Error: the input operation parameter is not a valid string. Returning None.")
            return None
        if not operation.lower() in ["union", "difference", "intersect", "symdif", "merge", "slice", "impose", "imprint"]:
            print("Topology.Boolean - Error: the input operation parameter is not a recognized operation. Returning None.")
            return None
        if not isinstance(tranDict, bool):
            print("Topology.Boolean - Error: the input tranDict parameter is not a valid boolean. Returning None.")
            return None
        topologyC = None
        #topologyC = Topology.Intersect(topologyA, topologyB)
        #try:
        if operation.lower() == "union":
            topologyC = topologyA.Union(topologyB, False)
        elif operation.lower() == "difference":
            topologyC = topologyA.Difference(topologyB, False)
        elif operation.lower() == "intersect": #Intersect in Topologic (Core) is faulty. This is a workaround.
            #topologyC = topologyA.Intersect(topologyB, False)
            
            topologyC = Topology.Intersect(topologyA, topologyB)
        elif operation.lower() == "symdif":
            topologyC = topologyA.XOR(topologyB, False)
        elif operation.lower() == "merge":
            topologyC = topologyA.Merge(topologyB, False)
        elif operation.lower() == "slice":
            topologyC = topologyA.Slice(topologyB, False)
        elif operation.lower() == "impose":
            topologyC = topologyA.Impose(topologyB, False)
        elif operation.lower() == "imprint":
            topologyC = topologyA.Imprint(topologyB, False)
        else:
            print("1. Topology.Boolean - Error: the boolean operation failed. Returning None.")
            return None
        #except:
            #print("2. Topology.Boolean - Error: the boolean operation failed. Returning None.")
            #return None
        if tranDict == True:
            sourceVertices = []
            sourceEdges = []
            sourceFaces = []
            sourceCells = []
            sinkVertices = []
            sinkEdges = []
            sinkFaces = []
            sinkCells = []
            hidimA = Topology.HighestType(topologyA)
            hidimB = Topology.HighestType(topologyB)
            hidimC = Topology.HighestType(topologyC)

            if Topology.Type(topologyA) == Topology.TypeID("Vertex"):
                sourceVertices += [topologyA]
            elif hidimA >= Topology.TypeID("Vertex"):
                sourceVertices += Topology.Vertices(topologyA)
            if Topology.Type(topologyB) == Topology.TypeID("Vertex"):
                sourceVertices += [topologyB]
            elif hidimB >= Topology.TypeID("Vertex"):
                sourceVertices += Topology.Vertices(topologyB)
            if Topology.Type(topologyC) == Topology.TypeID("Vertex"):
                sinkVertices = [topologyC]
            elif hidimC >= Topology.TypeID("Vertex"):
                sinkVertices = Topology.Vertices(topologyC)
            if len(sourceVertices) > 0 and len(sinkVertices) > 0:
                _ = Topology.TransferDictionaries(sourceVertices, sinkVertices, tolerance=tolerance)

            if Topology.Type(topologyA) == Topology.TypeID("Edge"):
                sourceEdges += [topologyA]
            elif hidimA >= Topology.TypeID("Edge"):
                sourceEdges += Topology.Edges(topologyA)
            if Topology.Type(topologyB) == Topology.TypeID("Edge"):
                sourceEdges += [topologyB]
            elif hidimB >= Topology.TypeID("Edge"):
                sourceEdges += Topology.Edges(topologyB)
            if Topology.Type(topologyC) == Topology.TypeID("Edge"):
                sinkEdges = [topologyC]
            elif hidimC >= Topology.TypeID("Edge"):
                sinkEdges = Topology.Edges(topologyC)
            if len(sourceEdges) > 0 and len(sinkEdges) > 0:
                _ = Topology.TransferDictionaries(sourceEdges, sinkEdges, tolerance=tolerance)

            if Topology.Type(topologyA) == Topology.TypeID("Face"):
                sourceFaces += [topologyA]
            elif hidimA >= Topology.TypeID("Face"):
                sourceFaces += Topology.Faces(topologyA)
            if Topology.Type(topologyB) == Topology.TypeID("Face"):
                sourceFaces += [topologyB]
            elif hidimB >= Topology.TypeID("Face"):
                sourceFaces += Topology.Faces(topologyB)
            if Topology.Type(topologyC) == Topology.TypeID("Face"):
                sinkFaces += [topologyC]
            elif hidimC >= Topology.TypeID("Face"):
                sinkFaces += Topology.Faces(topologyC)
            if len(sourceFaces) > 0 and len(sinkFaces) > 0:
                _ = Topology.TransferDictionaries(sourceFaces, sinkFaces, tolerance=tolerance)

            if Topology.Type(topologyA) == Topology.TypeID("Cell"):
                sourceCells += [topologyA]
            elif hidimA >= Topology.TypeID("Cell"):
                sourceCells += Topology.Cells(topologyA)
            if Topology.Type(topologyB) == Topology.TypeID("Cell"):
                sourceCells += [topologyB]
            elif hidimB >= Topology.TypeID("Cell"):
                sourceCells += Topology.Cells(topologyB)
            if Topology.Type(topologyC) == Topology.TypeID("Cell"):
                sinkCells = [topologyC]
            elif hidimC >= Topology.TypeID("Cell"):
                sinkCells = Topology.Cells(topologyC)
            if len(sourceCells) > 0 and len(sinkCells) > 0:
                _ = Topology.TransferDictionaries(sourceCells, sinkCells, tolerance=tolerance)
        return topologyC

    
    @staticmethod
    def BoundingBox(topology, optimize=0, axes="xyz", tolerance=0.0001):
        """
        Returns a cell representing a bounding box of the input topology. The returned cell contains a dictionary with keys "xrot", "yrot", and "zrot" that represents rotations around the X, Y, and Z axes. If applied in the order of Z, Y, X, the resulting box will become axis-aligned.

        Parameters
        ----------
        topology : topologic_core.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 box so that it reduces its surface area. The default is 0 which will result in an axis-aligned bounding box. The default is 0.
        axes : str , optional
            Sets what axes are to be used for rotating the bounding box. This can be any permutation or substring of "xyz". It is not case sensitive. The default is "xyz".
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.
        
        Returns
        -------
        topologic_core.Cell or topologic_core.Face
            The bounding box of the input topology.

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

        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.BoundingBox - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        if not isinstance(axes, str):
            print("Topology.BoundingBox - Error: the input axes parameter is not a valid string. Returning None.")
            return None
        axes = axes.lower()
        x_flag = "x" in axes
        y_flag = "y" in axes
        z_flag = "z" in axes
        if not x_flag and not y_flag and not z_flag:
            print("Topology.BoundingBox - Error: the input axes parameter is not a recognized string. Returning None.")
            return None
        vertices = Topology.SubTopologies(topology, subTopologyType="vertex")
        topology = Cluster.ByTopologies(vertices)
        boundingBox = bb(topology)
        minX = boundingBox[0]
        minY = boundingBox[1]
        minZ = boundingBox[2]
        maxX = boundingBox[3]
        maxY = boundingBox[4]
        maxZ = boundingBox[5]
        w = abs(maxX - minX)
        l = abs(maxY - minY)
        h = abs(maxZ - minZ)
        best_area = 2*l*w + 2*l*h + 2*w*h
        orig_area = best_area
        best_x = 0
        best_y = 0
        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
                if x_flag:
                    xa = n
                    xb = 90+n
                    xc = n
                else:
                    xa = 0
                    xb = 1
                    xc = 1
                if y_flag:
                    ya = n
                    yb = 90+n
                    yc = n
                else:
                    ya = 0
                    yb = 1
                    yc = 1
                if z_flag:
                    za = n
                    zb = 90+n
                    zc = n
                else:
                    za = 0
                    zb = 1
                    zc = 1
                for x in range(xa,xb,xc):
                    if flag:
                        break
                    for y in range(ya,yb,yc):
                        if flag:
                            break
                        for z in range(za,zb,zc):
                            if flag:
                                break
                            t = Topology.Rotate(topology, origin=origin, axis=[0, 0, 1], angle=z)
                            t = Topology.Rotate(t, origin=origin, axis=[0, 1, 0], angle=y)
                            t = Topology.Rotate(t, origin=origin, axis=[1, 0, 0], angle=x)
                            minX, minY, minZ, maxX, maxY, maxZ = bb(t)
                            w = abs(maxX - minX)
                            l = abs(maxY - minY)
                            h = abs(maxZ - minZ)
                            area = 2*l*w + 2*l*h + 2*w*h
                            if area < orig_area*factor:
                                best_area = area
                                best_x = x
                                best_y = y
                                best_z = z
                                best_bb = [minX, minY, minZ, maxX, maxY, maxZ]
                                flag = True
                                break
                            if area < best_area:
                                best_area = area
                                best_x = x
                                best_y = y
                                best_z = z
                                best_bb = [minX, minY, minZ, maxX, maxY, maxZ]
                        
        else:
            best_bb = boundingBox

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

        baseWire = Wire.ByVertices([vb1, vb2, vb3, vb4], close=True)
        baseFace = Face.ByWire(baseWire, tolerance=tolerance)
        if abs(maxZ-minZ) < tolerance:
            box = baseFace
        else:
            box = Cell.ByThickenedFace(baseFace, planarize=False, thickness=abs(maxZ-minZ), bothSides=False)
        box = Topology.Rotate(box, origin=origin, axis=[1, 0, 0], angle=-best_x)
        box = Topology.Rotate(box, origin=origin, axis=[0, 1, 0], angle=-best_y)
        box = Topology.Rotate(box, origin=origin, axis=[0, 0, 1], angle=-best_z)
        dictionary = Dictionary.ByKeysValues(["xrot","yrot","zrot", "minx", "miny", "minz", "maxx", "maxy", "maxz", "width", "length", "height"], [best_x, best_y, best_z, minX, minY, minZ, maxX, maxY, maxZ, (maxX-minX), (maxY-minY), (maxZ-minZ)])
        box = Topology.SetDictionary(box, dictionary)
        return box

    @staticmethod
    def BREPString(topology, version=3):
        """
        Returns the BRep string of the input topology.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        version : int , optional
            The desired BRep version number. The default is 3.

        Returns
        -------
        str
            The BREP string.

        """
        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.BREPString - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        st = None
        try:
            st = topologic.Topology.String(topology, version)
        except:
            try:
                st = topologic.Topology.BREPString(topology, version)
            except:
                st = None
        return st
    
    @staticmethod
    def ByGeometry(vertices=[], edges=[], faces=[], color=[1.0, 1.0, 1.0, 1.0], id=None, name=None, lengthUnit="METERS", outputMode="default", tolerance=0.0001):
        """
        Create a topology by the input lists of vertices, edges, and faces.

        Parameters
        ----------
        vertices : list
            The input list of vertices in the form of [x, y, z]
        edges : list , optional
            The input list of edges in the form of [i, j] where i and j are vertex indices.
        faces : list , optional
            The input list of faces in the form of [i, j, k, l, ...] where the items in the list are vertex indices. The face is assumed to be closed to the last vertex is connected to the first vertex automatically.
        color : list , optional
            The desired color of the object in the form of [r, g, b, a] where the components are between 0 and 1 and represent red, blue, green, and alpha (transparency) repsectively. The default is [1.0, 1.0, 1.0, 1.0].
        id : str , optional
            The desired ID of the object. If set to None, an automatic uuid4 will be assigned to the object. The default is None.
        name : str , optional
            The desired name of the object. If set to None, a default name "Topologic_[topology_type]" will be assigned to the object. The default is None.
        lengthUnit : str , optional
            The length unit used for the object. The default is "METERS"
        outputMode : str , optional
            The desired otuput mode of the object. This can be "wire", "shell", "cell", "cellcomplex", or "default". It is case insensitive. The default is "default".
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        topology : topologic_core.Topology
            The created topology. The topology will have a dictionary embedded in it that records the input attributes (color, id, lengthUnit, name, type)

        """
        def topologyByFaces(faces, outputMode, tolerance):
            output = None
            if len(faces) == 1:
                return faces[0]
            if outputMode.lower() == "cell":
                output = Cell.ByFaces(faces, tolerance=tolerance)
                if output:
                    return output
                else:
                    return None
            if outputMode.lower() == "cellcomplex":
                output = CellComplex.ByFaces(faces, tolerance=tolerance)
                if output:
                    return output
                else:
                    return None
            if outputMode.lower() == "shell":
                output = Shell.ByFaces(faces, tolerance=tolerance) # This can return a list
                if Topology.IsInstance(output, "Shell"):
                    return output
                else:
                    return None
            if outputMode.lower() == "default":
                output = Cluster.ByTopologies(faces)
                if output:
                    return output
            return output
        def topologyByEdges(edges, outputMode):
            output = None
            if len(edges) == 1:
                return edges[0]
            output = Cluster.ByTopologies(edges)
            if outputMode.lower() == "wire":
                output = Topology.SelfMerge(output, tolerance=tolerance)
                if Topology.IsInstance(output, "Wire"):
                    return output
                else:
                    return None
            return output
        def edgesByVertices(vertices, topVerts):
            if len(vertices) < 2:
                return []
            edges = []
            for i in range(len(vertices)-1):
                v1 = vertices[i]
                v2 = vertices[i+1]
                e1 = Edge.ByVertices([topVerts[v1], topVerts[v2]], tolerance=tolerance)
                edges.append(e1)
            # connect the last vertex to the first one
            v1 = vertices[-1]
            v2 = vertices[0]
            e1 = Edge.ByVertices([topVerts[v1], topVerts[v2]], tolerance=tolerance)
            edges.append(e1)
            return edges
        from topologicpy.Vertex import Vertex
        from topologicpy.Edge import Edge
        from topologicpy.Wire import Wire
        from topologicpy.Face import Face
        from topologicpy.Shell import Shell
        from topologicpy.Cell import Cell
        from topologicpy.CellComplex import CellComplex
        from topologicpy.Cluster import Cluster
        from topologicpy.Dictionary import Dictionary
        import uuid
        returnTopology = None
        topVerts = []
        topEdges = []
        topFaces = []
        vertices = [v for v in vertices if not len(v) == 0]
        edges = [e for e in edges if not len(e) == 0]
        faces = [f for f in faces if not len(f) == 0]
        if len(vertices) > 0:
            for aVertex in vertices:
                v = Vertex.ByCoordinates(aVertex[0], aVertex[1], aVertex[2])
                topVerts.append(v)
        else:
            return None
        if (outputMode.lower == "wire") and (len(edges) > 0):
            for anEdge in edges:
                topEdge = Edge.ByVertices([topVerts[anEdge[0]], topVerts[anEdge[1]]], tolerance=tolerance)
                topEdges.append(topEdge)
            if len(topEdges) > 0:
                returnTopology = topologyByEdges(topEdges)
        elif len(faces) > 0:
            for aFace in faces:
                faceEdges = edgesByVertices(aFace, topVerts)
                if len(faceEdges) > 2:
                    faceWire = Wire.ByEdges(faceEdges, tolerance=tolerance)
                    try:
                        topFace = Face.ByWire(faceWire, tolerance=tolerance, silent=True)
                        if Topology.IsInstance(topFace, "Face"):
                            topFaces.append(topFace)
                        elif isinstance(topFace, list):
                            topFaces += topFace
                    except:
                        pass
            if len(topFaces) > 0:
                returnTopology = topologyByFaces(topFaces, outputMode=outputMode, tolerance=tolerance)
        elif len(edges) > 0:
            for anEdge in edges:
                topEdge = Edge.ByVertices([topVerts[anEdge[0]], topVerts[anEdge[1]]], tolerance=tolerance)
                topEdges.append(topEdge)
            if len(topEdges) > 0:
                returnTopology = topologyByEdges(topEdges, outputMode=outputMode)
        else:
            returnTopology = Cluster.ByTopologies(topVerts)
        if returnTopology:
            keys = []
            values = []
            keys.append("TOPOLOGIC_color")
            keys.append("TOPOLOGIC_id")
            keys.append("TOPOLOGIC_name")
            keys.append("TOPOLOGIC_type")
            keys.append("TOPOLOGIC_length_unit")
            if color:
                if isinstance(color, tuple):
                    color = list(color)
                elif isinstance(color, list):
                    if isinstance(color[0], tuple):
                        color = list(color[0])
                values.append(color)
            else:
                values.append([1.0, 1.0, 1.0, 1.0])
            if id:
                values.append(id)
            else:
                values.append(str(uuid.uuid4()))
            if name:
                values.append(name)
            else:
                values.append("Topologic_"+Topology.TypeAsString(returnTopology))
            values.append(Topology.TypeAsString(returnTopology))
            values.append(lengthUnit)
            topDict = Dictionary.ByKeysValues(keys, values)
            Topology.SetDictionary(returnTopology, topDict)
        return returnTopology

    @staticmethod
    def ByBREPFile(file):
        """
        Imports a topology from a BREP file.

        Parameters
        ----------
        file : file object
            The BREP file.

        Returns
        -------
        topologic_core.Topology
            The imported topology.

        """
        topology = None
        if not file:
            print("Topology.ByBREPFile - Error: the input file parameter is not a valid file. Returning None.")
            return None
        brep_string = file.read()
        topology = Topology.ByBREPString(brep_string)
        file.close()
        return topology
    
    @staticmethod
    def ByBREPPath(path):
        """
        Imports a topology from a BREP file path.

        Parameters
        ----------
        path : str
            The path to the BREP file.

        Returns
        -------
        topologic_core.Topology
            The imported topology.

        """
        if not path:
            print("Topology.ByBREPPath - Error: the input path parameter is not a valid path. Returning None.")
            return None
        try:
            file = open(path)
        except:
            print("Topology.ByBREPPath - Error: the BREP file is not a valid file. Returning None.")
            return None
        return Topology.ByBREPFile(file)

    @staticmethod
    def ByDXFFile(file):
        """
        Imports a list of topologies from a DXF file.
        This is an experimental method with limited capabilities.

        Parameters
        ----------
        file : a DXF file object
            The DXF file object.

        Returns
        -------
        list
            The list of imported topologies.

        """
        from topologicpy.Vertex import Vertex
        from topologicpy.Edge import Edge
        from topologicpy.Wire import Wire
        from topologicpy.Shell import Shell
        from topologicpy.Cell import Cell
        from topologicpy.CellComplex import CellComplex
        from topologicpy.Topology import Topology

        try:
            import ezdxf
        except:
            print("Topology.ByDXFFile - Information: Installing required ezdxf library.")
            try:
                os.system("pip install ezdxf")
            except:
                os.system("pip install ezdxf --user")
            try:
                import ezdxf
                print("Topology.ByDXFFile - Information: ezdxf library installed successfully.")
            except:
                warnings.warn("Topology.ByDXFFile - Error: Could not import ezdxf library. Please install it manually. Returning None.")
                return None

        if not file:
            print("Topology.ByDXFFile - Error: the input file parameter is not a valid file. Returning None.")
            return None
        
        def convert_entity(entity):
            entity_type = entity.dxftype()
            converted_entity = None

            if entity_type == 'POINT':
                x, y, z = entity.dxf.location.x, entity.dxf.location.y, entity.dxf.location.z
                converted_entity = Vertex.ByCoordinates(x, y, z)

            elif entity_type == 'LINE':
                start = Vertex.ByCoordinates(entity.dxf.start.x, entity.dxf.start.y, entity.dxf.start.z)
                end = Vertex.ByCoordinates(entity.dxf.end.x, entity.dxf.end.y, entity.dxf.end.z)
                converted_entity = Edge.ByVertices(start, end)

            elif entity_type == 'CIRCLE':
                origin = Vertex.ByCoordinates(entity.dxf.center.x, entity.dxf.center.y, entity.dxf.center.z)
                radius = entity.dxf.radius
                converted_entity = Wire.Circle(origin, radius)

            elif entity_type in ['POLYLINE', 'LWPOLYLINE']:
                vertices = [Vertex.ByCoordinates(p[0], p[1], p[2]) for p in entity.points()]
                converted_entity = Wire.ByVertices(vertices)

            elif entity_type == 'ARC':
                vertices = [Vertex.ByCoordinates(p.x, p.y, p.z) for p in entity.vertices()]
                converted_entity = Wire.ByVertices(vertices)

            elif entity_type == 'MESH':
                vertices = [list(v) for v in entity.vertices]
                faces = [list(face) for face in entity.faces]
                converted_entity = Topology.SelfMerge(Topology.ByGeometry(vertices=vertices, faces=faces))
                # Try Cell
                temp = Cell.ByFaces(Topology.Faces(converted_entity), silent=True)
                if not Topology.IsInstance(temp, "Cell"):
                    temp = CellComplex.ByFaces(Topology.Faces(converted_entity))
                    if not Topology.IsInstance(temp, "CellComplex"):
                        temp = Shell.ByFaces(Topology.Faces(converted_entity))
                        if not Topology.IsInstance(temp, "Shell"):
                            temp = converted_entity
                converted_entity = temp
            return converted_entity

        def convert_insert(entity, file):
            block_name = entity.dxf.name
            block = file.blocks.get(block_name)
            converted_entities = []

            for block_entity in block:
                converted_entity = convert_entity(block_entity)
                if converted_entity is not None:
                    converted_entities.append(converted_entity)

            x, y, z = [entity.dxf.insert.x, entity.dxf.insert.y, entity.dxf.insert.z]
            return [Topology.Translate(obj, x, y, z) for obj in converted_entities]

        def convert_dxf_to_custom_types(file):
            # Read the DXF file
            msp = file.modelspace()

            # Store the converted entities
            converted_entities = []

            # Process each entity in the model space
            for entity in msp:
                entity_type = entity.dxftype()
                if entity_type in ['TEXT', 'MTEXT']:
                    continue  # Ignore TEXT and MTEXT

                if entity_type == 'INSERT':
                    converted_entities.extend(convert_insert(entity, file))
                else:
                    converted_entity = convert_entity(entity)
                    if converted_entity is not None:
                        converted_entities.append(converted_entity)

            return converted_entities
        converted_entities = convert_dxf_to_custom_types(file)
        return converted_entities

    @staticmethod
    def ByDXFPath(path):
        """
        Imports a list of topologies from a DXF file path.
        This is an experimental method with limited capabilities.

        Parameters
        ----------
        path : str
            The path to the DXF file.

        Returns
        -------
        list
            The list of imported topologies.

        """
        try:
            import ezdxf
        except:
            print("Topology.ExportToDXF - Information: Installing required ezdxf library.")
            try:
                os.system("pip install ezdxf")
            except:
                os.system("pip install ezdxf --user")
            try:
                import ezdxf
                print("Topology.ByDXFPath - Information: ezdxf library installed successfully.")
            except:
                warnings.warn("Topology.ByDXFPath - Error: Could not import ezdxf library. Please install it manually. Returning None.")
                return None
        if not path:
            print("Topology.ByDXFPath - Error: the input path parameter is not a valid path. Returning None.")
            return None
        try:
            file = ezdxf.readfile(path)
        except:
            file = None
        if not file:
            print("Topology.ByDXFPath - Error: the input file parameter is not a valid file. Returning None.")
            return None
        return Topology.ByDXFFile(file)

    @staticmethod
    def ByIFCFile(file, transferDictionaries=False, includeTypes=[], excludeTypes=[]):
        """
        Create a topology by importing it from an IFC file.

        Parameters
        ----------
        file : file object
            The input IFC file.
        transferDictionaries : bool , optional
            If set to True, the dictionaries from the IFC file will be transfered to the topology. Otherwise, they won't. The default is False.
        includeTypes : list , optional
            The list of IFC object types to include. It is case insensitive. If set to an empty list, all types are included. The default is [].
        excludeTypes : list , optional
            The list of IFC object types to exclude. It is case insensitive. If set to an empty list, no types are excluded. The default is [].
        
        Returns
        -------
        list
            The created list of topologies.
        
        """
        import multiprocessing
        from topologicpy.Cluster import Cluster
        from topologicpy.Dictionary import Dictionary
        import uuid
        
        try:
            import ifcopenshell
            import ifcopenshell.geom
        except:
            print("Topology.ByIFCFile - Warning: Installing required ifcopenshell library.")
            try:
                os.system("pip install ifcopenshell")
            except:
                os.system("pip install ifcopenshell --user")
            try:
                import ifcopenshell
                import ifcopenshell.geom
                print("Topology.ByIFCFile - Warning: ifcopenshell library installed correctly.")
            except:
                warnings.warn("Topology.ByIFCFile - Error: Could not import ifcopenshell. Please try to install ifcopenshell manually. Returning None.")
                return None
        if not file:
            print("Topology.ByIFCFile - Error: the input file parameter is not a valid file. Returning None.")
            return None
        includeTypes = [s.lower() for s in includeTypes]
        excludeTypes = [s.lower() for s in excludeTypes]
        topologies = []
        settings = ifcopenshell.geom.settings()
        settings.set(settings.DISABLE_TRIANGULATION, True)
        settings.set(settings.USE_BREP_DATA, True)
        settings.set(settings.USE_WORLD_COORDS, True)
        settings.set(settings.SEW_SHELLS, True)
        iterator = ifcopenshell.geom.iterator(settings, file, multiprocessing.cpu_count())
        if iterator.initialize():
            while True:
                shape = iterator.get()
                is_a = shape.type.lower()
                if (is_a in includeTypes or len(includeTypes) == 0) and (not is_a in excludeTypes):
                    try:
                        brep = shape.geometry.brep_data
                        topology = Topology.SelfMerge(Topology.ByBREPString(brep))
                        if transferDictionaries:
                            keys = []
                            values = []
                            keys.append("TOPOLOGIC_color")
                            values.append([1.0, 1.0, 1.0, 1.0])
                            keys.append("TOPOLOGIC_id")
                            values.append(str(uuid.uuid4()))
                            keys.append("TOPOLOGIC_name")
                            values.append(shape.name)
                            keys.append("TOPOLOGIC_type")
                            values.append(Topology.TypeAsString(topology))
                            keys.append("IFC_id")
                            values.append(str(shape.id))
                            keys.append("IFC_guid")
                            values.append(str(shape.guid))
                            keys.append("IFC_unique_id")
                            values.append(str(shape.unique_id))
                            keys.append("IFC_name")
                            values.append(shape.name)
                            keys.append("IFC_type")
                            values.append(shape.type)
                            d = Dictionary.ByKeysValues(keys, values)
                            topology = Topology.SetDictionary(topology, d)
                        topologies.append(topology)
                    except:
                        pass
                if not iterator.next():
                    break
        return topologies

    @staticmethod
    def ByIFCPath(path, transferDictionaries=False, includeTypes=[], excludeTypes=[]):
        """
        Create a topology by importing it from an IFC file path.

        Parameters
        ----------
        path : str
            The path to the IFC file.
        transferDictionaries : bool , optional
            If set to True, the dictionaries from the IFC file will be transfered to the topology. Otherwise, they won't. The default is False.
        includeTypes : list , optional
            The list of IFC object types to include. It is case insensitive. If set to an empty list, all types are included. The default is [].
        excludeTypes : list , optional
            The list of IFC object types to exclude. It is case insensitive. If set to an empty list, no types are excluded. The default is [].
        Returns
        -------
        list
            The created list of topologies.
        
        """
        import ifcopenshell

        if not path:
            print("Topology.ByIFCPath - Error: the input path parameter is not a valid path. Returning None.")
            return None
        try:
            file = ifcopenshell.open(path)
        except:
            file = None
        if not file:
            print("Topology.ByIFCPath - Error: the input file parameter is not a valid file. Returning None.")
            return None
        return Topology.ByIFCFile(file, transferDictionaries=transferDictionaries, includeTypes=includeTypes, excludeTypes=excludeTypes)
    
    '''
    @staticmethod
    def ByImportedIPFS(hash_, url, port):
        """
        NOT DONE YET.

        Parameters
        ----------
        hash : TYPE
            DESCRIPTION.
        url : TYPE
            DESCRIPTION.
        port : TYPE
            DESCRIPTION.

        Returns
        -------
        topology : TYPE
            DESCRIPTION.

        """
        # hash, url, port = item
        url = url.replace('http://','')
        url = '/dns/'+url+'/tcp/'+port+'/https'
        client = ipfshttpclient.connect(url)
        brepString = client.cat(hash_).decode("utf-8")
        topology = Topology.ByBREPString(brepString)
        return topology
    '''
    @staticmethod
    def ByJSONFile(file, tolerance=0.0001):
        """
        Imports the topology from a JSON file.

        Parameters
        ----------
        file : file object
            The input JSON file.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        list
            The list of imported topologies.

        """
        if not file:
            print("Topology.ByJSONFile - Error: the input file parameter is not a valid file. Returning None.")
            return None
        jsonData = json.load(file)
        jsonString = json.dumps(jsonData)
        return Topology.ByJSONString(jsonString, tolerance=tolerance)
    
    @staticmethod
    def ByJSONString(string, progressBar=False, tolerance=0.0001):
        """
        Imports the topology from a JSON string.

        Parameters
        ----------
        string : str
            The input JSON string.
        progressBar : bool , optional
            If set to True a tqdm progress bar is shown. If not, it will not be shown. The default is False.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        list or topologicpy.Topology
            The list of imported topologies. If the list only contains one element, it returns that element.

        """

        from topologicpy.Dictionary import Dictionary
        from topologicpy.Context import Context
        from topologicpy.Vertex import Vertex
        from topologicpy.Edge import Edge
        from topologicpy.Wire import Wire
        from topologicpy.Face import Face
        from topologicpy.Shell import Shell
        from topologicpy.Cell import Cell
        from topologicpy.CellComplex import CellComplex
        from topologicpy.Cluster import Cluster
        from topologicpy.Aperture import Aperture
        from topologicpy.Helper import Helper
        from topologicpy.Topology import Topology
        from tqdm.auto import tqdm
        import time

        def getUUID(topology, uuidKey="uuid"):
            d = Topology.Dictionary(topology)
            if d == None:
                uuidOne = str(uuid.uuid1())
                d = Dictionary.ByKeyValue(uuidKey, uuidOne)
            elif uuidKey not in Dictionary.Keys(d):
                uuidOne = str(uuid.uuid1())
                d = Dictionary.SetValueAtKey(d, uuidKey, uuidOne)
                topology = Topology.SetDictionary(topology, d)
            else:
                uuidOne = Dictionary.ValueAtKey(d, uuidKey)
            return uuidOne
            
        def find_json_item(json_list, key, value):
            for item in json_list:
                if key in item and item[key] == value:
                    return item
            return None

        def buildAperture(j_aperture):
            j_vertices = []
            j_edges = []
            j_wires = []
            j_faces = []
            j_shells = []
            j_cells = []
            j_cellComplexes = []
            for jsonItem in j_aperture:
                topology_type = jsonItem['type']
                if topology_type.lower() == "vertex":
                    j_vertices.append(jsonItem)
                elif topology_type.lower() == "edge":
                    j_edges.append(jsonItem)
                elif topology_type.lower() == "wire":
                    j_wires.append(jsonItem)
                elif topology_type.lower() == "face":
                    j_faces.append(jsonItem)
                elif topology_type.lower() == "shell":
                    j_shells.append(jsonItem)
                elif topology_type.lower() == "cell":
                    j_cells.append(jsonItem)
                elif topology_type.lower() == "cellcomplex":
                    j_cellComplexes.append(jsonItem)

            vertices = [buildVertex(j_v) for j_v in j_vertices]
            edges = [buildEdge(j_e, j_vertices, uuidKey="uuid") for j_e in j_edges]
            wires = [buildWire(j_w, j_edges, j_vertices, uuidKey="uuid") for j_w in j_wires]
            faces = [buildFace(j_f, j_wires, j_edges, j_vertices, uuidKey="uuid") for j_f in j_faces]
            faces = Helper.Flatten(faces)
            shells = [buildShell(j_s, j_faces, j_wires, j_edges, j_vertices, uuidKey="uuid") for j_s in j_shells]
            cells = [buildCell(j_c, j_shells, j_faces, j_wires, j_edges, j_vertices, uuidKey="uuid") for j_c in j_cells]
            cellComplexes = [buildCellComplex(j_cc, j_cells, j_shells, j_faces, j_wires, j_edges, j_vertices, uuidKey="uuid") for j_cc in j_cellComplexes]
            if len(cellComplexes) > 0:
                everything = cellComplexes
            elif len(cells) > 0:
                everything = cells
            elif len(shells) > 0:
                everything = shells
            elif len(faces) > 0:
                everything = faces
            elif len(wires) > 0:
                everything = wires
            elif len(edges) > 0:
                everything = edges
            elif len(vertices) > 0:
                everything = vertices
            else:
                return None
            if len(everything) == 1:
                aperture = everything[0]
            else:
                aperture = Topology.SelfMerge(Cluster.ByTopologies(everything), tolerance=tolerance)
            return aperture

        def buildVertex(json_item):
            x, y, z = json_item['coordinates']
            d = json_item['dictionary']
            v = Vertex.ByCoordinates(x, y, z)
            if v == None:
                print("Topology.ByJSONString - Error: Could not build a vertex. Returning None.")
                return None
            v = Topology.SetDictionary(v, Dictionary.ByPythonDictionary(d))
            apertures = [buildAperture(j_ap) for j_ap in json_item['apertures']]
            context = Context.ByTopologyParameters(v, u=0.5, v=0.5, w=0.5)
            for ap in apertures:
                _ = Aperture.ByTopologyContext(ap, context)
            return v

        def buildEdge(json_item, j_vertices, uuidKey="uuid", tolerance=0.0001):
            edge_vertices = json_item['vertices']
            vertices = []
            for j_v in edge_vertices:
                vertices.append(buildVertex(find_json_item(j_vertices, uuidKey, j_v)))
            e = Edge.ByVertices(vertices, tolerance=tolerance)
            if e == None:
                print("Topology.ByJSONString - Error: Could not build an edge. Returning None.")
                return None
            d = json_item['dictionary']
            e = Topology.SetDictionary(e, Dictionary.ByPythonDictionary(d))
            apertures = [buildAperture(j_ap) for j_ap in json_item['apertures']]
            context = Context.ByTopologyParameters(e, u=0.5, v=0.5, w=0.5)
            for ap in apertures:
                _ = Aperture.ByTopologyContext(ap, context)
            return e

        def buildWire(json_item, j_edges, j_vertices, uuidKey="uuid", tolerance=0.0001):
            wire_edges = json_item['edges']
            edges = []
            for j_e in wire_edges:
                edges.append(buildEdge(find_json_item(j_edges, uuidKey, j_e), j_vertices, uuidKey=uuidKey, tolerance=tolerance))
            w = Wire.ByEdges(edges, tolerance=tolerance)
            if w == None:
                print("Topology.ByJSONString - Error: Could not build a wire. Returning None.")
                return None
            d = json_item['dictionary']
            w = Topology.SetDictionary(w, Dictionary.ByPythonDictionary(d))
            apertures = [buildAperture(j_ap) for j_ap in json_item['apertures']]
            context = Context.ByTopologyParameters(w, u=0.5, v=0.5, w=0.5)
            for ap in apertures:
                _ = Aperture.ByTopologyContext(ap, context)
            return w

        def buildFace(json_item, j_wires, j_edges, j_vertices, uuidKey="uuid", tolerance=0.0001):
            face_wires = json_item['wires']
            external_boundary = buildWire(find_json_item(j_wires, uuidKey, face_wires[0]), j_edges, j_vertices, uuidKey=uuidKey, tolerance=tolerance)
            if not Topology.IsInstance(external_boundary, "Wire"):
                print("Topology.ByJSONString - ERROR: Something went wrong with original external boundary. Returning None.")
                return None
            if not Topology.IsPlanar(external_boundary, tolerance=tolerance):
                temp_boundary = Wire.Planarize(external_boundary, tolerance=tolerance)
                if temp_boundary == None or not Topology.IsInstance(temp_boundary, "Wire"):
                    print("Topology.ByJSONString - Error: Something went wrong with external boundary. Returning None.")
                    return None
                else:
                    external_boundary = temp_boundary
            if not Wire.IsClosed(external_boundary):
                external_boundary = Wire.Close(external_boundary)
            internal_boundaries = []
            for j_w in face_wires[1:]:
                ib = buildWire(find_json_item(j_wires, uuidKey, j_w),j_edges, j_vertices, uuidKey=uuidKey)
                if not Topology.IsPlanar(external_boundary):
                    ib = Wire.Planarize(ib)
                    if not Topology.IsInstance(ib, "Wire"):
                        print("Topology.ByJSONString - ERROR: Something went wrong with original internal boundary. Returning None.")
                        return None
                if not Wire.IsClosed(ib):
                    ib = Wire.Close(ib)
                internal_boundaries.append(ib)

            f = Face.ByWires(external_boundary, internal_boundaries, tolerance=tolerance)
            if not Topology.IsInstance(f, "Face"):
                print("Topology.ByJSONString - Error: Could not build a face. Returning None.", f, "Ex Bound:", external_boundary)
                return None
            area = Face.Area(f)
            if area == None:
                print("Topology.ByJSONString - Error: Could not compute the area of the built face. Returning None.")
                return None
            if Face.Area(f) < 0:
                external_boundary = Wire.Invert(external_boundary)
            f = Face.ByWires(external_boundary, internal_boundaries, tolerance=tolerance)
            if f == None:
                print("Topology.ByJSONString - Error: Could not build a face. Returning None.")
                return None
            d = json_item['dictionary']
            f = Topology.SetDictionary(f, Dictionary.ByPythonDictionary(d))
            apertures = [buildAperture(j_ap) for j_ap in json_item['apertures']]
            if len(apertures) > 0:
                context = Context.ByTopologyParameters(f, u=0.5, v=0.5, w=0.5)
                for ap in apertures:
                    _ = Aperture.ByTopologyContext(ap, context)
            return f

        def buildShell(json_item, j_faces, j_wires, j_edges, j_vertices, uuidKey="uuid", tolerance=0.0001):
            shell_faces = json_item['faces']
            faces = []
            for j_f in shell_faces:
                faces.append(buildFace(find_json_item(j_faces, uuidKey, j_f), j_wires, j_edges, j_vertices, uuidKey=uuidKey))
            faces = Helper.Flatten(faces)
            s = Shell.ByFaces(faces, tolerance=tolerance) # This can return a list
            if not Topology.IsInstance(s, "Shell"):
                print("Topology.ByJSONString - Error: Could not build a shell. Returning None.")
                return None
            d = json_item['dictionary']
            s = Topology.SetDictionary(s, Dictionary.ByPythonDictionary(d))
            apertures = [buildAperture(j_ap) for j_ap in json_item['apertures']]
            if len(apertures) > 0:
                context = Context.ByTopologyParameters(s, u=0.5, v=0.5, w=0.5)
                for ap in apertures:
                    _ = Aperture.ByTopologyContext(ap, context)
            return s

        def buildCell(json_item, j_shells, j_faces, j_wires, j_edges, j_vertices, uuidKey="uuid", tolerance=0.0001):
            cell_shells = json_item['shells']
            shells = []
            external_boundary = buildShell(find_json_item(j_shells, uuidKey, cell_shells[0]), j_faces, j_wires, j_edges, j_vertices, uuidKey=uuidKey, tolerance=tolerance)
            internal_boundaries = []
            for j_s in cell_shells[1:]:
                internal_boundaries.append(buildShell(find_json_item(j_shells, uuidKey, j_s), j_faces, j_wires, j_edges, j_vertices, uuidKey=uuidKey, tolerance=tolerance))
            c = Cell.ByShell(external_boundary)
            if c == None:
                print("Topology.ByJSONString - Error: Could not build a cell. Returning None.")
                return None
            for ib in internal_boundaries:
                ib_c = Cell.ByShell(ib)
                c = Topology.Difference(c, ib_c, tolerance=tolerance)
            d = json_item['dictionary']
            c = Topology.SetDictionary(c, Dictionary.ByPythonDictionary(d))
            apertures = [buildAperture(j_ap) for j_ap in json_item['apertures']]
            context = Context.ByTopologyParameters(c, u=0.5, v=0.5, w=0.5)
            for ap in apertures:
                _ = Aperture.ByTopologyContext(ap, context)
            return c

        def buildCellComplex(json_item, j_cells, j_shells, j_faces, j_wires, j_edges, j_vertices, uuidKey="uuid", tolerance=0.0001):
            cc_cells = json_item['cells']
            cells = []
            for j_c in cc_cells:
                cells.append(buildCell(find_json_item(j_cells, uuidKey, j_c), j_shells, j_faces, j_wires, j_edges, j_vertices, uuidKey=uuidKey, tolerance=tolerance))
            cc = CellComplex.ByCells(cells, tolerance=tolerance)
            if cc == None:
                print("Topology.ByJSONString - Error: Could not build a cellcomplex. Returning None.")
                return None
            d = json_item['dictionary']
            cc = Topology.SetDictionary(cc, Dictionary.ByPythonDictionary(d))
            apertures = [buildAperture(j_ap) for j_ap in json_item['apertures']]
            context = Context.ByTopologyParameters(cc, u=0.5, v=0.5, w=0.5)
            for ap in apertures:
                _ = Aperture.ByTopologyContext(ap, context)
            return cc

        def addAperturesUUID(topology, uuidKey="uuid"):
            topology_apertures = [Aperture.Topology(ap) for ap in Topology.Apertures(topology)]
            apertures_uuid = []
            for top_a in topology_apertures:
                uuid = getUUID(top_a, uuidKey=uuidKey)
                apertures_uuid.append(uuid)
            d = Topology.Dictionary(topology)
            d = Dictionary.SetValueAtKey(d, 'apertures', apertures_uuid)
            topology = Topology.SetDictionary(topology, d)
            s = Topology.InternalVertex(topology, tolerance=tolerance)
            s = Topology.SetDictionary(s, d)
            return topology, s, topology_apertures

        def findAperture(uuid, apertures, uuidKey="uuid"):
            for ap in apertures:
                d = Topology.Dictionary(ap)
                ap_uuid = Dictionary.ValueAtKey(d, uuidKey)
                if uuid == ap_uuid:
                    return ap
            return None

        def setApertures(topology, allApertures, uuidKey="uuid"):
            apertures = []
            d = Topology.Dictionary(topology)
            apertures_uuid = Dictionary.ValueAtKey(d, 'apertures')
            if not isinstance(apertures_uuid, list):
                apertures_uuid = [apertures_uuid]
            for ap_uuid in apertures_uuid:
                ap = findAperture(ap_uuid, allApertures, uuidKey=uuidKey)
                if ap != None:
                    apertures.append(ap)
            context = Context.ByTopologyParameters(topology, u=0.5, v=0.5, w=0.5)
            for ap in apertures:
                _ = Aperture.ByTopologyContext(ap, context)
            return topology
                    
                
        jsondata = json.loads(string)
        if not isinstance(jsondata, list):
            jsondata = [jsondata]

        j_vertices = []
        j_edges = []
        j_wires = []
        j_faces = []
        j_shells = []
        j_cells = []
        j_cellComplexes = []

        vertices = []
        edges = []
        wires = []
        faces = []
        shells = []
        cells = []
        cellComplexes = []

        if progressBar:
            for jsonItem in tqdm(jsondata):
                try:
                    topology_type = jsonItem['type']
                    if topology_type.lower() == "vertex":
                        j_vertices.append(jsonItem)
                    elif topology_type.lower() == "edge":
                        j_edges.append(jsonItem)
                    elif topology_type.lower() == "wire":
                        j_wires.append(jsonItem)
                    elif topology_type.lower() == "face":
                        j_faces.append(jsonItem)
                    elif topology_type.lower() == "shell":
                        j_shells.append(jsonItem)
                    elif topology_type.lower() == "cell":
                        j_cells.append(jsonItem)
                    elif topology_type.lower() == "cellcomplex":
                        j_cellComplexes.append(jsonItem)
                except:
                    continue
        else:
            for jsonItem in jsondata:
                try:
                    topology_type = jsonItem['type']
                    if topology_type.lower() == "vertex":
                        j_vertices.append(jsonItem)
                    elif topology_type.lower() == "edge":
                        j_edges.append(jsonItem)
                    elif topology_type.lower() == "wire":
                        j_wires.append(jsonItem)
                    elif topology_type.lower() == "face":
                        j_faces.append(jsonItem)
                    elif topology_type.lower() == "shell":
                        j_shells.append(jsonItem)
                    elif topology_type.lower() == "cell":
                        j_cells.append(jsonItem)
                    elif topology_type.lower() == "cellcomplex":
                        j_cellComplexes.append(jsonItem)
                except:
                    continue

        vertices = [buildVertex(j_v) for j_v in j_vertices]
        vertex_selectors = []
        all_vertex_apertures = []
        for v in vertices:
            v, s, vertex_apertures = addAperturesUUID(v, uuidKey="uuid")
            all_vertex_apertures += vertex_apertures
            vertex_selectors.append(s)

        edges = [buildEdge(j_e, j_vertices, uuidKey="uuid") for j_e in j_edges]
        edge_selectors = []
        all_edge_apertures = []
        for e in edges:
            e, s, edge_apertures = addAperturesUUID(e, uuidKey="uuid")
            all_edge_apertures += edge_apertures
            edge_selectors.append(s)

        wires = [buildWire(j_w, j_edges, j_vertices, uuidKey="uuid") for j_w in j_wires]
        wire_selectors = []
        all_wire_apertures = []
        for w in wires:
            w, s, wire_apertures = addAperturesUUID(w, uuidKey="uuid")
            all_wire_apertures += wire_apertures
            wire_selectors.append(s)

        faces = []
        for j_f in j_faces:
            f = buildFace(j_f, j_wires, j_edges, j_vertices, uuidKey="uuid")
            faces.append(f)
       
        faces = Helper.Flatten(faces)
        face_selectors = []
        all_face_apertures = []
        for f in faces:
            f, s, face_apertures = addAperturesUUID(f, uuidKey="uuid")
            all_face_apertures += face_apertures
            face_selectors.append(s)

        shells = [buildShell(j_s, j_faces, j_wires, j_edges, j_vertices, uuidKey="uuid") for j_s in j_shells]
        shell_selectors = []
        all_shell_apertures = []
        for sh in shells:
            sh, s, shell_apertures = addAperturesUUID(sh, uuidKey="uuid")
            all_shell_apertures += shell_apertures
            shell_selectors.append(s)

        cells = [buildCell(j_c, j_shells, j_faces, j_wires, j_edges, j_vertices, uuidKey="uuid") for j_c in j_cells]
        cell_selectors = []
        all_cell_apertures = []
        for c in cells:
            c, s, cell_apertures = addAperturesUUID(c, uuidKey="uuid")
            all_cell_apertures += cell_apertures
            cell_selectors.append(s)

        cellComplexes = [buildCellComplex(j_cc, j_cells, j_shells, j_faces, j_wires, j_edges, j_vertices, uuidKey="uuid") for j_cc in j_cellComplexes]
        cellComplex_selectors = []
        all_cellComplex_apertures = []
        for cc in cellComplexes:
            cc, s, cellComplex_apertures = addAperturesUUID(cc, uuidKey="uuid")
            all_cellComplex_apertures += cellComplex_apertures
            cellComplex_selectors.append(s)

        everything = vertices+edges+wires+faces+shells+cells+cellComplexes
        toplevelTopologies = []
        for ev in everything:
            d = Topology.Dictionary(ev)
            if Dictionary.ValueAtKey(d,'toplevel') == True:
                toplevelTopologies.append(ev)

        for tp in toplevelTopologies:
            # This is a hack because sometimes the imported topologies get weird. I think it is an opencascade bug.
            tp = Topology.ByBREPString(Topology.BREPString(tp))
            if len(vertex_selectors) > 0:
                _ = Topology.TransferDictionariesBySelectors(tp, vertex_selectors, tranVertices=True, tolerance=tolerance)
            if len(edge_selectors) > 0:
                _ = Topology.TransferDictionariesBySelectors(tp, edge_selectors, tranEdges=True, tolerance=tolerance)
            if len(face_selectors) > 0:
                _ = Topology.TransferDictionariesBySelectors(tp, face_selectors, tranFaces=True, tolerance=tolerance)
            if len(cell_selectors) > 0:
                _ = Topology.TransferDictionariesBySelectors(tp, cell_selectors, tranCells=True, tolerance=tolerance)
            if len(all_vertex_apertures) > 0:
                tp_vertices = Topology.Vertices(tp)
                for tp_vertex in tp_vertices:
                    tp_vertex = setApertures(tp_vertex, all_vertex_apertures, uuidKey="uuid")
            if len(all_edge_apertures) > 0:
                tp_edges = Topology.Edges(tp)
                for tp_edge in tp_edges:
                    tp_edge = setApertures(tp_edge, all_edge_apertures, uuidKey="uuid")
            if len(all_face_apertures) > 0:
                tp_faces = Topology.Faces(tp)
                for tp_face in tp_faces:
                    tp_face = setApertures(tp_face, all_face_apertures, uuidKey="uuid")
            if len(all_cell_apertures) > 0:
                tp_cells = Topology.Cells(tp)
                for tp_cell in tp_cells:
                    tp_cell = setApertures(tp_cell, all_cell_apertures, uuidKey="uuid")
    
        if len(toplevelTopologies) == 1:
            return toplevelTopologies[0]
        else:
            return toplevelTopologies
    
    @staticmethod
    def ByJSONPath(path, tolerance=0.0001):
        """
        Imports the topology from a JSON file.

        Parameters
        ----------
        path : str
            The file path to the json file.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        list
            The list of imported topologies.

        """
        if not path:
            print("Topology.ByJSONPath - Error: the input path parameter is not a valid path. Returning None.")
            return None
        data = None
        with open(path) as file:
            data = Topology.ByJSONFile(file=file, tolerance=tolerance)
        return data

    @staticmethod
    def ByOBJString(string, transposeAxes = True, progressBar=False, tolerance=0.0001):
        """
        Creates a topology from the input Waverfront OBJ string. This is a very experimental method and only works with simple planar solids. Materials and Colors are ignored.

        Parameters
        ----------
        string : str
            The input OBJ string.
        transposeAxes : bool , optional
            If set to True the Z and Y coordinates are transposed so that Y points "up" 
        progressBar : bool , optional
            If set to True a tqdm progress bar is shown. If not, it will not be shown. The default is False.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        topology
            The created topology.

        """
        from topologicpy.Vertex import Vertex
        from tqdm.auto import tqdm

        def parse(lines):
            vertices = []
            faces = []
            for i in range(len(lines)):
                l = lines[i].replace(",", " ")
                s = l.split()
                if isinstance(s, list):
                    if len(s) > 3:
                        if s[0].lower() == "v":
                            vertices.append([float(s[1]), float(s[2]), float(s[3])])
                        elif s[0].lower() == "f":
                            temp_faces = []
                            for j in range(1,len(s)):
                                f = s[j].split("/")[0]
                                temp_faces.append(int(f)-1)
                            faces.append(temp_faces)
            return [vertices, faces]
        
        def parsetqdm(lines):
            vertices = []
            faces = []
            for i in tqdm(range(len(lines))):
                s = lines[i].split()
                if isinstance(s, list):
                    if len(s) > 3:
                        if s[0].lower() == "v":
                                vertices.append([float(s[1]), float(s[2]), float(s[3])])
                        elif s[0].lower() == "f":
                            temp_faces = []
                            for j in range(1,len(s)):
                                f = s[j].split("/")[0]
                                temp_faces.append(int(f)-1)
                            faces.append(temp_faces)
            return [vertices, faces]
        
        
        lines = string.split("\n")
        if lines:
            if progressBar:
                vertices, faces = parsetqdm(lines)
            else:
                vertices, faces = parse(lines)
        if vertices or faces:
            topology = Topology.ByGeometry(vertices = vertices, faces = faces, outputMode="default", tolerance=tolerance)
            if transposeAxes == True:
                topology = Topology.Rotate(topology, origin=Vertex.Origin(), axis=[1, 0, 0], angle=90)
            return Topology.SelfMerge(topology)
        print("Topology.ByOBJString - Error: Could not find vertices or faces. Returning None.")
        return None

    @staticmethod
    def ByOBJFile(file, transposeAxes=True, progressBar=False, tolerance=0.0001):
        """
        Imports the topology from a Weverfront OBJ file. This is a very experimental method and only works with simple planar solids. Materials and Colors are ignored.

        Parameters
        ----------
        file : file object
            The input OBJ file.
        transposeAxes : bool , optional
            If set to True the Z and Y coordinates are transposed so that Y points "up"
        progressBar : bool , optional
            If set to True a tqdm progress bar is shown. If not, it will not be shown. The default is False.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        topology
            The imported topology.

        """
        if not file:
            print("Topology.ByOBJFile - Error: the input file parameter is not a valid file. Returning None.")
            return None
        obj_string = file.read()
        topology = Topology.ByOBJString(obj_string, transposeAxes=transposeAxes, progressBar=progressBar, tolerance=tolerance)
        file.close()
        return topology
    
    @staticmethod
    def ByOBJPath(path, transposeAxes=True, progressBar=False, tolerance=0.0001):
        """
        Imports the topology from a Weverfront OBJ file path. This is a very experimental method and only works with simple planar solids. Materials and Colors are ignored.

        Parameters
        ----------
        path : str
            The file path to the OBJ file.
        transposeAxes : bool , optional
            If set to True the Z and Y coordinates are transposed so that Y points "up".
        progressBar : bool , optional
            If set to True a tqdm progress bar is shown. If not, it will not be shown. The default is False.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        topology
            The imported topology.

        """
        if not path:
            print("Topology.ByOBJPath - Error: the input path parameter is not a valid path. Returning None.")
            return None
        try:
            file = open(path)
        except:
            print("Topology.ByOBJPath - Error: the OBJ file is not a valid file. Returning None.")
            return None
        return Topology.ByOBJFile(file, transposeAxes=transposeAxes, progressBar=progressBar, tolerance=tolerance)

    @staticmethod
    def ByOCCTShape(occtShape):
        """
        Creates a topology from the input OCCT shape. See https://dev.opencascade.org/doc/overview/html/occt_user_guides__modeling_data.html.

        Parameters
        ----------
        occtShape : topologic_core.TopoDS_Shape
            The inoput OCCT Shape.

        Returns
        -------
        topologic_core.Topology
            The created topology.

        """
        return topologic.Topology.ByOcctShape(occtShape, "")
    
    @staticmethod
    def ByBREPString(string):
        """
        Creates a topology from the input brep string

        Parameters
        ----------
        string : str
            The input brep string.

        Returns
        -------
        topologic_core.Topology
            The created topology.

        """
        if not isinstance(string, str):
            print("Topology.ByBREPString - Error: the input string parameter is not a valid string. Returning None.")
            return None
        returnTopology = None
        try:
            returnTopology = topologic.Topology.ByString(string)
        except:
            print("Topology.ByBREPString - Error: the input string parameter is not a valid string. Returning None.")
            returnTopology = None
        return returnTopology
    
    @staticmethod
    def ByXYZFile(file, frameIdKey="id", vertexIdKey="id"):
        """
        Imports the topology from an XYZ file path. This is a very experimental method. While variants of the format exist, topologicpy reads XYZ files that conform to the following:
        An XYZ file can be made out of one or more frames. Each frame will be stored in a sepatate topologic cluster.
        First line: total number of vertices in the frame. This must be an integer. No other words or characters are allowed on this line.
        Second line: frame label. This is free text and will be stored in the dictionary of each frame (topologic_core.Cluster)
        All other lines: vertex_label, x, y, and z coordinates, separated by spaces, tabs, or commas. The vertex label must be one word with no spaces. It is stored in the dictionary of each vertex.

        Example:
        3
        Frame 1
        A 5.67 -3.45 2.61
        B 3.91 -1.91 4
        A 3.2 1.2 -12.3
        4
        Frame 2
        B 5.47 -3.45 2.61
        B 3.91 -1.93 3.1
        A 3.2 1.2 -22.4
        A 3.2 1.2 -12.3
        3
        Frame 3
        A 5.67 -3.45 2.61
        B 3.91 -1.91 4
        C 3.2 1.2 -12.3

        Parameters
        ----------
        file : file object
            The input XYZ file.
        frameIdKey : str , optional
            The desired id key to use to store the ID of each frame in its dictionary. The default is "id".
        vertexIdKey : str , optional
            The desired id key to use to store the ID of each point in its dictionary. The default is "id".

        Returns
        -------
        list
            The list of frames (topologic_core.Cluster).

        """
        from topologicpy.Vertex import Vertex
        from topologicpy.Cluster import Cluster
        from topologicpy.Dictionary import Dictionary

        def parse(lines):
            frames = []
            line_index = 0
            while line_index < len(lines):
                try:
                    n_vertices = int(lines[line_index])
                except:
                    return frames
                frame_label = lines[line_index+1][:-1]
                vertices = []
                for i in range(n_vertices):
                    one_line = lines[line_index+2+i]
                    s = one_line.split()
                    vertex_label = s[0]
                    v = Vertex.ByCoordinates(float(s[1]), float(s[2]), float(s[3]))
                    vertex_dict = Dictionary.ByKeysValues([vertexIdKey], [vertex_label])
                    v = Topology.SetDictionary(v, vertex_dict)
                    vertices.append(v)
                frame = Cluster.ByTopologies(vertices)
                frame_dict = Dictionary.ByKeysValues([frameIdKey], [frame_label])
                frame = Topology.SetDictionary(frame, frame_dict)
                frames.append(frame)
                line_index = line_index + 2 + n_vertices
            return frames
        
        if not file:
            print("Topology.ByXYZFile - Error: the input file parameter is not a valid file. Returning None.")
            return None
        lines = []
        for lineo, line in enumerate(file):
                lines.append(line)
        if len(lines) > 0:
            frames = parse(lines)
        file.close()
        return frames
    
    @staticmethod
    def ByXYZPath(path, frameIdKey="id", vertexIdKey="id"):
        """
        Imports the topology from an XYZ file path. This is a very experimental method. While variants of the format exist, topologicpy reads XYZ files that conform to the following:
        An XYZ file can be made out of one or more frames. Each frame will be stored in a sepatate topologic cluster.
        First line: total number of vertices in the frame. This must be an integer. No other words or characters are allowed on this line.
        Second line: frame label. This is free text and will be stored in the dictionary of each frame (topologic_core.Cluster)
        All other lines: vertex_label, x, y, and z coordinates, separated by spaces, tabs, or commas. The vertex label must be one word with no spaces. It is stored in the dictionary of each vertex.

        Example:
        3
        Frame 1
        A 5.67 -3.45 2.61
        B 3.91 -1.91 4
        A 3.2 1.2 -12.3
        4
        Frame 2
        B 5.47 -3.45 2.61
        B 3.91 -1.93 3.1
        A 3.2 1.2 -22.4
        A 3.2 1.2 -12.3
        3
        Frame 3
        A 5.67 -3.45 2.61
        B 3.91 -1.91 4
        C 3.2 1.2 -12.3

        Parameters
        ----------
        path : str
            The input XYZ file path.
        frameIdKey : str , optional
            The desired id key to use to store the ID of each frame in its dictionary. The default is "id".
        vertexIdKey : str , optional
            The desired id key to use to store the ID of each point in its dictionary. The default is "id".

        Returns
        -------
        list
            The list of frames (topologic_core.Cluster).

        """
        if not path:
            print("Topology.ByXYZPath - Error: the input path parameter is not a valid path. Returning None.")
            return None
        try:
            file = open(path)
        except:
            print("Topology.ByXYZPath - Error: the XYZ file is not a valid file. Returning None.")
            return None
        return Topology.ByXYZFile(file, frameIdKey=frameIdKey, vertexIdKey=frameIdKey)
    
    @staticmethod
    def CenterOfMass(topology):
        """
        Returns the center of mass of the input topology. See https://en.wikipedia.org/wiki/Center_of_mass.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.

        Returns
        -------
        topologic_core.Vertex
            The center of mass of the input topology.

        """
        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.CenterofMass - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        return topology.CenterOfMass()
    
    @staticmethod
    def Centroid(topology):
        """
        Returns the centroid of the vertices of the input topology. See https://en.wikipedia.org/wiki/Centroid.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.

        Returns
        -------
        topologic_core.Vertex
            The centroid of the input topology.

        """
        from topologicpy.Aperture import Aperture

        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.Centroid - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        if Topology.IsInstance(topology, "Aperture"):
            return Aperture.Topology(topology).Centroid()
        return topology.Centroid()
    
    @staticmethod
    def ClusterFaces(topology, angTolerance=2, tolerance=0.0001):
        """
        Clusters the faces of the input topology by their direction.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        angTolerance : float , optional
            The desired angular tolerance. The default is 0.1.
        tolerance : float, optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        list
            The list of clusters of faces where faces in the same cluster have the same direction.

        """
        from topologicpy.Vector import Vector
        from topologicpy.Face import Face
        from topologicpy.Cluster import Cluster

        faces = Topology.SubTopologies(topology, subTopologyType="face")
        face_normals = []
        for face in faces:
            face_normals.append(Face.Normal(face))
        bins = []
        for face_normal in face_normals:
            minAngle = angTolerance * 100
            for bin in bins:
                ang = Vector.Angle(bin, face_normal)
                if ang < minAngle:
                    minAngle = ang
            if minAngle > angTolerance:
                bins.append(face_normal)
        num_bins = len(bins)
        # Convert face_normals to a numpy array for efficient computation
        face_normals_array = np.array(face_normals)

        # Compute the bounds for each bin along each dimension
        bin_bounds = [np.linspace(-1, 1, num_bins + 1) for _ in range(3)]

        # Assign each face to a bin based on its normal
        bin_indices = [np.digitize(face_normal, bounds) - 1 for face_normal, bounds in zip(face_normals_array.T, bin_bounds)]
        # Combine the indices along the three dimensions to get a single bin index for each face
        cluster_labels = bin_indices[0] * (num_bins**2) + bin_indices[1] * num_bins + bin_indices[2]
        cluster_labels = list(cluster_labels)
        bins = list(set(cluster_labels))
        clusters = []
        for bin in bins:
            clusters.append([])
        for i, face in enumerate(faces):
            ind = bins.index(cluster_labels[i])
            clusters[ind].append(face)
        final_clusters = []
        for cluster in clusters:
            final_clusters.append(Topology.SelfMerge(Cluster.ByTopologies(cluster), tolerance=tolerance))
        return final_clusters
    
    @staticmethod
    def ClusterFaces_orig(topology, angTolerance=0.1, tolerance=0.0001):
        """
        Clusters the faces of the input topology by their direction.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        angTolerance : float , optional
            The desired angular tolerance. The default is 0.1.
        tolerance : float, optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        list
            The list of clusters of faces where faces in the same cluster have the same direction.

        """
        from topologicpy.Face import Face
        from topologicpy.Cluster import Cluster

        def angle_between(v1, v2):
            u1 = v1 / norm(v1)
            u2 = v2 / norm(v2)
            y = u1 - u2
            x = u1 + u2
            if norm(x) == 0:
                return 0
            a0 = 2 * arctan(norm(y) / norm(x))
            if (not signbit(a0)) or signbit(pi - a0):
                return a0
            elif signbit(a0):
                return 0
            else:
                return pi

        def collinear(v1, v2, tol):
            ang = angle_between(v1, v2)
            if math.isnan(ang) or math.isinf(ang):
                raise Exception("Face.IsCollinear - Error: Could not determine the angle between the input faces")
            elif abs(ang) < tol or abs(pi - ang) < tol:
                return True
            return False
        
        def sumRow(matrix, i):
            return np.sum(matrix[i,:])
        
        def buildSimilarityMatrix(samples, tol):
            numOfSamples = len(samples)
            matrix = np.zeros(shape=(numOfSamples, numOfSamples))
            for i in range(len(matrix)):
                for j in range(len(matrix)):
                    if collinear(samples[i], samples[j], tol):
                        matrix[i, j] = 1
            return matrix

        def determineRow(matrix):
            maxNumOfOnes = -1
            row = -1
            for i in range(len(matrix)):
                if maxNumOfOnes < sumRow(matrix, i):
                    maxNumOfOnes = sumRow(matrix, i)
                    row = i
            return row

        def categorizeIntoClusters(matrix):
            groups = []
            while np.sum(matrix) > 0:
                group = []
                row = determineRow(matrix)
                indexes = addIntoGroup(matrix, row)
                groups.append(indexes)
                matrix = deleteChosenRowsAndCols(matrix, indexes)
            return groups

        def addIntoGroup(matrix, ind):
            change = True
            indexes = []
            for col in range(len(matrix)):
                if matrix[ind, col] == 1:
                    indexes.append(col)
            while change == True:
                change = False
                numIndexes = len(indexes)
                for i in indexes:
                    for col in range(len(matrix)):
                        if matrix[i, col] == 1:
                            if col not in indexes:
                                indexes.append(col)
                numIndexes2 = len(indexes)
                if numIndexes != numIndexes2:
                    change = True
            return indexes

        def deleteChosenRowsAndCols(matrix, indexes):
            for i in indexes:
                matrix[i, :] = 0
                matrix[:, i] = 0
            return matrix
        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.ClusterFaces - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        faces = []
        _ = topology.Faces(None, faces)
        normals = []
        for aFace in faces:
            normals.append(Face.NormalAtParameters(aFace, 0.5, 0.5, "XYZ", 3))
        # build a matrix of similarity
        mat = buildSimilarityMatrix(normals, angTolerance)
        categories = categorizeIntoClusters(mat)
        returnList = []
        for aCategory in categories:
            tempList = []
            if len(aCategory) > 0:
                for index in aCategory:
                    tempList.append(faces[index])
                returnList.append(Topology.SelfMerge(Cluster.ByTopologies(tempList), tolerance=tolerance))
        return returnList

    @staticmethod
    def Contents(topology):
        """
        Returns the contents of the input topology

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.

        Returns
        -------
        list
            The list of contents of the input topology.

        """
        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.Contents - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        contents = []
        _ = topology.Contents(contents)
        return contents
    
    @staticmethod
    def Contexts(topology):
        """
        Returns the list of contexts of the input topology

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.

        Returns
        -------
        list
            The list of contexts of the input topology.

        """
        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.Contexts - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        contexts = []
        _ = topology.Contexts(contexts)
        return contexts

    @staticmethod
    def ConvexHull(topology, tolerance=0.0001):
        """
        Creates a convex hull

        Parameters
        ----------
        topology : topologic_core.Topology
            The input Topology.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        topologic_core.Topology
            The created convex hull of the input topology.

        """
        from topologicpy.Vertex import Vertex
        from topologicpy.Edge import Edge
        from topologicpy.Wire import Wire
        from topologicpy.Face import Face
        from topologicpy.Shell import Shell
        from topologicpy.Cell import Cell
        from topologicpy.Cluster import Cluster        
        
        def convexHull3D(item, tolerance, option):
            if item:
                vertices = []
                _ = item.Vertices(None, vertices)
                pointList = []
                for v in vertices:
                    pointList.append([v.X(), v.Y(), v.Z()])
                points = np.array(pointList)
                if option:
                    hull = ConvexHull(points, qhull_options=option)
                else:
                    hull = ConvexHull(points)
                faces = []
                for simplex in hull.simplices:
                    edges = []
                    for i in range(len(simplex)-1):
                        sp = hull.points[simplex[i]]
                        ep = hull.points[simplex[i+1]]
                        sv = Vertex.ByCoordinates(sp[0], sp[1], sp[2])
                        ev = Vertex.ByCoordinates(ep[0], ep[1], ep[2])
                        edges.append(Edge.ByVertices([sv, ev], tolerance=tolerance))
                    sp = hull.points[simplex[-1]]
                    ep = hull.points[simplex[0]]
                    sv = Vertex.ByCoordinates(sp[0], sp[1], sp[2])
                    ev = Vertex.ByCoordinates(ep[0], ep[1], ep[2])
                    edges.append(Edge.ByVertices([sv, ev], tolerance=tolerance))
                    faces.append(Face.ByWire(Wire.ByEdges(edges, tolerance=tolerance), tolerance=tolerance))
            try:
                c = Cell.ByFaces(faces, tolerance=tolerance)
                return c
            except:
                returnTopology = Topology.SelfMerge(Cluster.ByTopologies(faces), tolerance=tolerance)
                if Topology.Type(returnTopology) == Topology.TypeID("Shell"):
                    return Shell.ExternalBoundary(returnTopology, tolerance=tolerance)
        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.ConvexHull - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        returnObject = None
        try:
            returnObject = convexHull3D(topology, tolerance, None)
        except:
            returnObject = convexHull3D(topology, tolerance, 'QJ')
        return returnObject
    
    @staticmethod
    def Copy(topology, deep=False):
        """
        Returns a copy of the input topology

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        deep : bool , optional
            If set to True, a deep copy will be performed (this is slow). Othwerwise, it will not. The default is False

        Returns
        -------
        topologic_core.Topology
            A copy of the input topology.

        """
        from topologicpy.Dictionary import Dictionary
        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.Copy - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        if deep:
            return Topology.ByJSONString(Topology.JSONString([topology]), progressBar=False)
        d = Topology.Dictionary(topology)
        return_topology = Topology.ByBREPString(Topology.BREPString(topology))
        keys = Dictionary.Keys(d)
        if len(keys) > 0:
            return_topology = Topology.SetDictionary(return_topology, d)
        return return_topology
    
    @staticmethod
    def Dictionary(topology):
        """
        Returns the dictionary of the input topology

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.

        Returns
        -------
        topologic_core.Dictionary
            The dictionary of the input topology.

        """
        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.Dictionary - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        return topology.GetDictionary()
    
    @staticmethod
    def Dimensionality(topology):
        """
        Returns the dimensionality of the input topology

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.

        Returns
        -------
        int
            The dimensionality of the input topology.

        """
        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.Dimensionality - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        return topology.Dimensionality()
    
    @staticmethod
    def Divide(topologyA, topologyB, transferDictionary=False, addNestingDepth=False):
        """
        Divides the input topology by the input tool and places the results in the contents of the input topology.

        Parameters
        ----------
        topologyA : topologic_core.Topology
            The input topology to be divided.
        topologyB : topologic_core.Topology
            the tool used to divide the input topology.
        transferDictionary : bool , optional
            If set to True the dictionary of the input topology is transferred to the divided topologies.
        addNestingDepth : bool , optional
            If set to True the nesting depth of the division is added to the dictionaries of the divided topologies.

        Returns
        -------
        topologic_core.Topology
            The input topology with the divided topologies added to it as contents.

        """
        
        from topologicpy.Dictionary import Dictionary
        if not Topology.IsInstance(topologyA, "Topology"):
            print("Topology.Divide - Error: the input topologyA parameter is not a valid topology. Returning None.")
            return None
        if not Topology.IsInstance(topologyB, "Topology"):
            print("Topology.Divide - Error: the input topologyB parameter is not a valid topology. Returning None.")
            return None
        try:
            _ = topologyA.Divide(topologyB, False) # Don't transfer dictionaries just yet
        except:
            raise Exception("TopologyDivide - Error: Divide operation failed.")
        nestingDepth = "1"
        keys = ["nesting_depth"]
        values = [nestingDepth]

        if not addNestingDepth and not transferDictionary:
            return topologyA

        contents = []
        _ = topologyA.Contents(contents)
        for i in range(len(contents)):
            if not addNestingDepth and transferDictionary:
                parentDictionary = Topology.Dictionary(topologyA)
                if parentDictionary != None:
                    _ = contents[i].SetDictionary(parentDictionary)
            if addNestingDepth and transferDictionary:
                parentDictionary = Topology.Dictionary(topologyA)
                if parentDictionary != None:
                    keys = Dictionary.Keys(parentDictionary)
                    values = Dictionary.Values(parentDictionary)
                    if ("nesting_depth" in keys):
                        nestingDepth = parentDictionary.ValueAtKey("nesting_depth").StringValue()
                    else:
                        keys.append("nesting_depth")
                        values.append(nestingDepth)
                    parentDictionary = Dictionary.ByKeysValues(keys, values)
                else:
                    keys = ["nesting_depth"]
                    values = [nestingDepth]
                parentDictionary = Dictionary.ByKeysValues(keys, values)
                _ = Topology.SetDictionary(topologyA, parentDictionary)
                values[keys.index("nesting_depth")] = nestingDepth+"_"+str(i+1)
                d = Dictionary.ByKeysValues(keys, values)
                _ = contents[i].SetDictionary(d)
            if addNestingDepth and  not transferDictionary:
                parentDictionary = Topology.Dictionary(topologyA)
                if parentDictionary != None:
                    keys, values = Dictionary.ByKeysValues(parentDictionary)
                    if ("nesting_depth" in keys):
                        nestingDepth = parentDictionary.ValueAtKey("nesting_depth").StringValue()
                    else:
                        keys.append("nesting_depth")
                        values.append(nestingDepth)
                    parentDictionary = Dictionary.ByKeysValues(keys, values)
                else:
                    keys = ["nesting_depth"]
                    values = [nestingDepth]
                parentDictionary = Dictionary.ByKeysValues(keys, values)
                _ = Topology.SetDictionary(topologyA, parentDictionary)
                keys = ["nesting_depth"]
                v = nestingDepth+"_"+str(i+1)
                values = [v]
                d = Dictionary.ByKeysValues(keys, values)
                _ = Topology.SetDictionary(contents[i], d)
        return topologyA
    
    @staticmethod
    def Explode(topology, origin=None, scale=1.25, typeFilter=None, axes="xyz", tolerance=0.0001):
        """
        Explodes the input topology. See https://en.wikipedia.org/wiki/Exploded-view_drawing.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        origin : topologic_core.Vertex , optional
            The origin of the explosion. If set to None, the centroid of the input topology will be used. The default is None.
        scale : float , optional
            The scale factor of the explosion. The default is 1.25.
        typeFilter : str , optional
            The type of the subtopologies to explode. This can be any of "vertex", "edge", "face", or "cell". If set to None, a subtopology one level below the type of the input topology will be used. The default is None.
        axes : str , optional
            Sets what axes are to be used for exploding the topology. This can be any permutation or substring of "xyz". It is not case sensitive. The default is "xyz".
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.
        
        Returns
        -------
        topologic_core.Cluster
            The exploded topology.

        """
        from topologicpy.Vertex import Vertex
        from topologicpy.Cluster import Cluster
        from topologicpy.Graph import Graph

        def processClusterTypeFilter(cluster):
            if len(Cluster.CellComplexes(cluster)) > 0:
                return "cell"
            elif len(Cluster.Cells(cluster)) > 0:
                return "face"
            elif len(Cluster.Shells(cluster)) > 0:
                return "face"
            elif len(Cluster.Faces(cluster)) > 0:
                return "edge"
            elif len(Cluster.Wires(cluster)) > 0:
                return "edge"
            elif len(Cluster.Edges(cluster)) > 0:
                return "vertex"
            else:
                return "self"

        def getTypeFilter(topology):
            typeFilter = "self"
            if Topology.IsInstance(topology, "Vertex"):
                typeFilter = "self"
            elif Topology.IsInstance(topology, "Edge"):
                typeFilter = "vertex"
            elif Topology.IsInstance(topology, "Wire"):
                typeFilter = "edge"
            elif Topology.IsInstance(topology, "Face"):
                typeFilter = "edge"
            elif Topology.IsInstance(topology, "Shell"):
                typeFilter = "face"
            elif Topology.IsInstance(topology, "Cell"):
                typeFilter = "face"
            elif Topology.IsInstance(topology, "CellComplex"):
                typeFilter = "cell"
            elif Topology.IsInstance(topology, "Cluster"):
                typeFilter = processClusterTypeFilter(topology)
            elif Topology.IsInstance(topology, "Graph"):
                typeFilter = "edge"
            return typeFilter
        
        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.Explode - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        if not Topology.IsInstance(origin, "Vertex"):
            origin = Topology.CenterOfMass(topology)
        if not typeFilter:
            typeFilter = getTypeFilter(topology)
        if not isinstance(typeFilter, str):
            print("Topology.Explode - Error: the input typeFilter parameter is not a valid string. Returning None.")
            return None
        if not isinstance(axes, str):
            print("Topology.Explode - Error: the input axes parameter is not a valid string. Returning None.")
            return None
        if Topology.IsInstance(topology, "Topology"):
            # Hack to fix a weird bug that seems to be a problem with OCCT memory handling.
            topology = Topology.ByBREPString(Topology.BREPString(topology))
        axes = axes.lower()
        x_flag = "x" in axes
        y_flag = "y" in axes
        z_flag = "z" in axes
        if not x_flag and not y_flag and not z_flag:
            print("Topology.Explode - Error: the input axes parameter is not a valid string. Returning None.")
            return None

        topologies = []
        newTopologies = []
        if Topology.IsInstance(topology, "Graph"):
            topology = Graph.Topology(topology)

        if typeFilter.lower() == "self":
            topologies = [topology]
        else:
            topologies = Topology.SubTopologies(topology, subTopologyType=typeFilter.lower())
        for aTopology in topologies:
            c = Topology.InternalVertex(aTopology, tolerance=tolerance)
            oldX = c.X()
            oldY = c.Y()
            oldZ = c.Z()
            if x_flag:
                newX = (oldX - origin.X())*scale + origin.X()
            else:
                newX = oldX
            if y_flag:
                newY = (oldY - origin.Y())*scale + origin.Y()
            else:
                newY = oldY
            if z_flag:
                newZ = (oldZ - origin.Z())*scale + origin.Z()
            else:
                newZ = oldZ
            xT = newX - oldX
            yT = newY - oldY
            zT = newZ - oldZ
            newTopology = Topology.Translate(aTopology, xT, yT, zT)
            newTopologies.append(newTopology)
        return Cluster.ByTopologies(newTopologies)
    
    @staticmethod
    def ExportToBREP(topology, path, overwrite=False, version=3):
        """
        Exports the input topology to a BREP file. See https://dev.opencascade.org/doc/occt-6.7.0/overview/html/occt_brep_format.html.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        path : str
            The input file path.
        overwrite : bool , optional
            If set to True the ouptut file will overwrite any pre-existing file. Otherwise, it won't. The default is False.
        version : int , optional
            The desired version number for the BREP file. The default is 3.

        Returns
        -------
        bool
            True if the export operation is successful. False otherwise.

        """
        from os.path import exists
        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.ExportToBREP - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        if not isinstance(path, str):
            print("Topology.ExportToBREP - Error: the input path parameter is not a valid string. Returning None.")
            return None
        # Make sure the file extension is .brep
        ext = path[len(path)-5:len(path)]
        if ext.lower() != ".brep":
            path = path+".brep"
        if not overwrite and exists(path):
            print("Topology.ExportToBREP - Error: a file already exists at the specified path and overwrite is set to False. Returning None.")
            return None
        f = None
        try:
            if overwrite == True:
                f = open(path, "w")
            else:
                f = open(path, "x") # Try to create a new File
        except:
            raise Exception("Error: Could not create a new file at the following location: "+path)
        if (f):
            s = Topology.BREPString(topology, version)
            f.write(s)
            f.close()    
            return True
        return False


    def ExportToDXF(topologies, path,  overwrite=False):
        """
        Exports the input topology to a DXF file. See https://en.wikipedia.org/wiki/AutoCAD_DXF.
        THe DXF version is 'R2010'
        This is experimental and only geometry is exported.

        Parameters
        ----------
        topologies : list or topologic_core.Topology
            The input list of topologies. This can also be a single topologic_core.Topology.
        path : str
            The input file path.
        overwrite : bool , optional
            If set to True the ouptut file will overwrite any pre-existing file. Otherwise, it won't. The default is False.

        Returns
        -------
        bool
            True if the export operation is successful. False otherwise.

        """
        import os
        import warnings

        try:
            import ezdxf
        except:
            print("Topology.ExportToDXF - Information: Installing required ezdxf library.")
            try:
                os.system("pip install ezdxf")
            except:
                os.system("pip install ezdxf --user")
            try:
                import ezdxf
                print("Topology.ExportToDXF - Information: ezdxf library installed successfully.")
            except:
                warnings.warn("Topology.ExportToDXF - Error: Could not import ezdxf library. Please install it manually. Returning None.")
                return None
        
        from topologicpy.Vertex import Vertex
        from topologicpy.Edge import Edge
        from topologicpy.Cluster import Cluster
        from topologicpy.Topology import Topology

        from os.path import exists
        if not isinstance(topologies, list):
            topologies = [topologies]
        topologies = [topology for topology in topologies if Topology.IsInstance(topology, "Topology")]
        if len(topologies) < 1:
            print("Topology.ExportToDXF - Error: The inupt list parameter topologies does not contain any valid topologies. Returning None.")
        # Make sure the file extension is .brep
        ext = path[len(path)-4:len(path)]
        if ext.lower() != ".dxf":
            path = path+".dxf"
        if not overwrite and exists(path):
            print("Topology.ExportToDXF - Error: a file already exists at the specified path and overwrite is set to False. Returning None.")
            return None
        
        def add_vertices(vertices, msp):
            for v in vertices:
                if Topology.IsInstance(v, "Vertex"):
                    msp.add_point((Vertex.X(v), Vertex.Y(v), Vertex.Z(v)))
        def add_edges(edges, msp):
            for e in edges:
                if Topology.IsInstance(e, "Edge"):
                    sv = Edge.StartVertex(e)
                    ev = Edge.EndVertex(e)
                    start = (Vertex.X(sv), Vertex.Y(sv), Vertex.Z(sv))
                    end = (Vertex.X(ev), Vertex.Y(ev), Vertex.Z(ev))
                    msp.add_line(start, end)
        def add_wires(wires, msp):
            for i, w in enumerate(wires):
                if Topology.IsInstance(w, "Wire"):
                    block_name = "Wire_"+str(i+1).zfill(3)
                    block = doc.blocks.new(name=block_name)
                    # Add edges to the block
                    edges = Topology.Edges(w)
                    for edge in edges:
                        sv = Edge.StartVertex(edge)
                        ev = Edge.EndVertex(edge)
                        start = (Vertex.X(sv), Vertex.Y(sv), Vertex.Z(sv))
                        end = (Vertex.X(ev), Vertex.Y(ev), Vertex.Z(ev))
                        block.add_line(start, end)
                    # Insert the block into the model space
                    msp.add_blockref(block_name, insert=(0, 0, 0))
        def add_meshes(meshes, msp):
            for m in meshes:
                data = Topology.Geometry(m)
                vertices = data['vertices']
                faces = data['faces']
                mesh = msp.add_mesh()
                mesh.dxf.subdivision_levels = 0
                with mesh.edit_data() as mesh_data:
                    mesh_data.vertices = vertices
                    mesh_data.faces = faces
        # Create a new DXF document
        doc = ezdxf.new(dxfversion='R2010')
        msp = doc.modelspace()
        i = 1
        for topology in topologies:
            if Topology.IsInstance(topology, "Vertex"):
                add_vertices([topology], msp)
            elif Topology.IsInstance(topology, "Edge"):
                add_edges([topology], msp)
            elif Topology.IsInstance(topology, "Wire"):
                add_wires([topology], msp)
            elif Topology.IsInstance(topology, "Face"):
                add_meshes([topology], msp)
            elif Topology.IsInstance(topology, "Shell"):
                add_meshes([topology], msp)
            elif Topology.IsInstance(topology, "Cell"):
                add_meshes([topology], msp)
            elif Topology.IsInstance(topology, "CellComplex"):
                add_meshes([topology], msp)
            elif Topology.IsInstance(topology, "Cluster"):
                cellComplexes = Topology.CellComplexes(topology)
                add_meshes(cellComplexes, msp)
                cells = Cluster.FreeCells(topology)
                add_meshes(cells, msp)
                shells = Cluster.FreeShells(topology)
                add_meshes(shells, msp)
                faces = Cluster.FreeFaces(topology)
                add_meshes(faces, msp)
                wires = Cluster.FreeWires(topology)
                add_wires(wires, msp)
                edges = Cluster.FreeEdges(topology)
                add_edges(edges, msp)
                vertices = Cluster.FreeVertices(topology)
                add_vertices(vertices, msp)
        
        # Save the DXF document
        status = False
        try:
            doc.saveas(path)
            status = True
        except:
            status = False
        return status

    '''
    @staticmethod
    def ExportToIPFS(topology, url, port, user, password):
        """
        NOT DONE YET

        Parameters
        ----------
        topology : TYPE
            DESCRIPTION.
        url : TYPE
            DESCRIPTION.
        port : TYPE
            DESCRIPTION.
        user : TYPE
            DESCRIPTION.
        password : TYPE
            DESCRIPTION.

        Returns
        -------
        TYPE
            DESCRIPTION.

        """
        # topology, url, port, user, password = item
        
        def exportToBREP(topology, path, overwrite):
            # Make sure the file extension is .brep
            ext = path[len(path)-5:len(path)]
            if ext.lower() != ".brep":
                path = path+".brep"
            f = None
            try:
                if overwrite == True:
                    f = open(path, "w")
                else:
                    f = open(path, "x") # Try to create a new File
            except:
                raise Exception("Error: Could not create a new file at the following location: "+path)
            if (f):
                topString = topology.BREPString()
                f.write(topString)
                f.close()       
                return True
            return False
        
        path = os.path.expanduser('~')+"/tempFile.brep"
        if exportToBREP(topology, path, True):
            url = url.replace('http://','')
            url = '/dns/'+url+'/tcp/'+port+'/https'
            client = ipfshttpclient.connect(url, auth=(user, password))
            newfile = client.add(path)
            os.remove(path)
            return newfile['Hash']
        return ''
    '''
    @staticmethod
    def ExportToJSON(topologies, path, overwrite=False):
        """
        Exports the input list of topologies to a JSON file.

        Parameters
        ----------
        topologies : list
            The input list of topologies.
        path : str
            The path to the JSON file.
        overwrite : bool , optional
            If set to True the ouptut file will overwrite any pre-existing file. Otherwise, it won't. The default is False.

        Returns
        -------
        bool
            The status of exporting the JSON file. If True, the operation was successful. Otherwise, it was unsuccesful.

        """
        from os.path import exists
        # Make sure the file extension is .json
        ext = path[len(path)-5:len(path)]
        if ext.lower() != ".json":
            path = path+".json"
        if not overwrite and exists(path):
            print("Topology.ExportToJSON - Error: a file already exists at the specified path and overwrite is set to False. Returning None.")
            return None
        f = None
        try:
            if overwrite == True:
                f = open(path, "w")
            else:
                f = open(path, "x") # Try to create a new File
        except:
            raise Exception("Error: Could not create a new file at the following location: "+path)
        if (f):
            jsondata = json.loads(Topology.JSONString(topologies))
            if jsondata != None:
                json.dump(jsondata, f, indent=4, sort_keys=True)
                f.close()
                return True
            else:
                f.close()
                return False
        return False

    @staticmethod
    def Fix(topology, topologyType: str = "CellComplex", tolerance: float = 0.0001):
        """
        Attempts to fix the input topology to matched the desired output type.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology
        topologyType : str , optional
            The desired output topology type. This must be one of "vertex", "edge", "wire", "face", "shell", "cell", "cellcomplex", "cluster". It is case insensitive. The default is "CellComplex"
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.
        
        Returns
        -------
        topologic_core.Topology
            The output topology in the desired type.

        """
        from topologicpy.Vertex import Vertex
        from topologicpy.Edge import Edge
        from topologicpy.Wire import Wire
        from topologicpy.Face import Face
        from topologicpy.Shell import Shell
        from topologicpy.Cell import Cell
        from topologicpy.CellComplex import CellComplex
        from topologicpy.Cluster import Cluster

        topology = Cluster.ByTopologies([topology])
        a_type = Topology.TypeAsString(topology).lower()
        b_type = topologyType.lower()
        if b_type not in ["vertex", "edge", "wire", "face", "shell", "cell", "cellcomplex", "cluster"]:
            print("Topology.Fix - Error: The input topologyType parameter is not recognized. Returning original topology.")
            return topology
        if a_type == b_type:
            return topology
        if b_type == "cluster":
            topology = Topology.SelfMerge(topology, tolerance=tolerance)
            return Cluster.ByTopologies([topology])
        if b_type == "cellcomplex":
            topology = Topology.SelfMerge(topology, tolerance=tolerance)
            if Topology.TypeAsString(topology).lower() == "cellcomplex":
                return topology
            cells = Topology.Cells(topology)
            if len(cells) < 2:
                print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
                return topology
            return_topology = CellComplex.ByCells(cells)
            if return_topology == None:
                print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
                return topology
            if Topology.TypeAsString(topology).lower() == "cellcomplex":
                return return_topology
            faces = Topology.Faces(topology)
            if len(faces) < 3:
                print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
                return topology
            return_topology = CellComplex.ByFaces(faces, tolerance=tolerance)
            if return_topology == None:
                print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
                return topology
            if Topology.TypeAsString(return_topology).lower() == "cellcomplex":
                return return_topology
            print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
            return topology
        if b_type == "cell":
            topology = Topology.SelfMerge(topology, tolerance=tolerance)
            if Topology.TypeAsString(topology).lower() == "cell":
                return topology
            if Topology.TypeAsString(topology).lower() == "cellComplex":
                return CellComplex.ExternalBoundary(topology)
            faces = Topology.Faces(topology)
            if len(faces) < 3:
                print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
                return topology
            return_topology = Cell.ByFaces(faces, tolerance=tolerance)
            if return_topology == None:
                return_topology = CellComplex.ByFaces(faces, tolerance=tolerance)
                if return_topology == None:
                    print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
                    return topology
                elif len(Topology.Cells(return_topology)) < 1:
                    print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
                    return topology
                return_topology = CellComplex.ExternalBoundary(return_topology)
            if return_topology == None:
                print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
                return topology
            if Topology.TypeAsString(return_topology).lower() == "cell":
                return return_topology
            print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
            return topology
        if b_type == "shell":
            topology = Topology.SelfMerge(topology, tolerance=tolerance)
            if Topology.TypeAsString(topology).lower() == "shell":
                return topology
            faces = Topology.Faces(topology)
            if len(faces) < 2:
                print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
                return topology
            return_topology = Shell.ByFaces(faces)
            if Topology.TypeAsString(return_topology).lower() == "shell":
                return return_topology
            print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
            return topology
        if b_type == "face":
            topology = Topology.SelfMerge(topology, tolerance=tolerance)
            if Topology.TypeAsString(topology).lower() == "face":
                return topology
            wires = Topology.Wires(topology)
            if len(wires) < 1:
                print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
                return topology
            return_topology = Face.ByWire(wires[0], tolerance=tolerance)
            if Topology.TypeAsString(return_topology).lower() == "face":
                return return_topology
            print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
            return topology
        if b_type == "wire":
            topology = Topology.SelfMerge(topology, tolerance=tolerance)
            if Topology.TypeAsString(topology).lower() == "wire":
                return topology
            edges = Topology.Edges(topology)
            if len(edges) < 2:
                print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
                return topology
            return_topology = Wire.ByEdges(edges, tolerance=tolerance)
            if Topology.TypeAsString(return_topology).lower() == "wire":
                return return_topology
            print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
            return topology
        if b_type == "edge":
            topology = Topology.SelfMerge(topology, tolerance=tolerance)
            if Topology.TypeAsString(topology).lower() == "edge":
                return topology
            vertices = Topology.Vertices(topology)
            if len(vertices) < 2:
                print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
                return topology
            return_topology = Edge.ByVertices(vertices, tolerance=tolerance)
            if Topology.TypeAsString(return_topology).lower() == "edge":
                return return_topology
            print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
            return topology
        if b_type == "vertex":
            topology = Topology.SelfMerge(topology, tolerance=tolerance)
            if Topology.TypeAsString(topology).lower() == "vertex":
                return topology
            vertices = Topology.Vertices(topology)
            if len(vertices) < 1:
                print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
                return topology
            return_topology = vertices[0]
            if Topology.TypeAsString(return_topology).lower() == "vertex":
                return return_topology
            print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
            return topology
        return topology

    @staticmethod
    def JSONString(topologies, mantissa: int = 6):
        """
        Exports the input list of topologies to a JSON string

        Parameters
        ----------
        topologies : list or topologic_core.Topology
            The input list of topologies or a single topology.
        mantissa : int , optional
            The desired length of the mantissa. The default is 6.
        
        Returns
        -------
        bool
            The status of exporting the JSON file. If True, the operation was successful. Otherwise, it was unsuccesful.

        """

        from topologicpy.Vertex import Vertex
        from topologicpy.Edge import Edge
        from topologicpy.Face import Face
        from topologicpy.Cell import Cell
        from topologicpy.Dictionary import Dictionary
        from topologicpy.Aperture import Aperture

        def getUUID(topology, uuidKey="uuid"):
            d = Topology.Dictionary(topology)
            if uuidKey not in Dictionary.Keys(d):
                uuidOne = str(uuid.uuid1())
                d = Dictionary.SetValueAtKey(d, uuidKey, uuidOne)
                topology = Topology.SetDictionary(topology, d)
            else:
                uuidOne = Dictionary.ValueAtKey(d, uuidKey)
            return uuidOne

        def getVertex(topology, uuidKey="uuid"):
            returnDict = {}
            uuidOne = getUUID(topology, uuidKey=uuidKey)
            returnDict['type'] = "Vertex"
            returnDict['uuid'] = uuidOne
            returnDict['coordinates'] = Vertex.Coordinates(topology, mantissa=mantissa)
            returnDict['dictionary'] = Dictionary.PythonDictionary(Topology.Dictionary(topology))
            return returnDict

        def getEdge(topology, uuidKey="uuid"):
            returnDict = {}
            uuidOne = getUUID(topology, uuidKey=uuidKey)
            returnDict['type'] = "Edge"
            returnDict['uuid'] = uuidOne
            edge_vertices = Edge.Vertices(topology)
            returnDict['vertices'] = [getUUID(v, uuidKey=uuidKey) for v in Edge.Vertices(topology)]
            returnDict['dictionary'] = Dictionary.PythonDictionary(Topology.Dictionary(topology))
            return returnDict

        def getWire(topology, uuidKey="uuid"):
            returnDict = {}
            uuidOne = getUUID(topology, uuidKey="uuid")
            returnDict['type'] = "Wire"
            returnDict['uuid'] = uuidOne
            returnDict['edges'] = [getUUID(e, uuidKey=uuidKey) for e in Topology.Edges(topology)]
            returnDict['dictionary'] = Dictionary.PythonDictionary(Topology.Dictionary(topology))
            return returnDict

        def getFace(topology, uuidKey="uuid"):
            apertures = [Aperture.Topology(ap) for ap in Topology.Apertures(topology)]
            returnDict = {}
            uuidOne = getUUID(topology, uuidKey=uuidKey)
            returnDict['type'] = "Face"
            returnDict['uuid'] = uuidOne
            wires = []
            external_boundary = Face.ExternalBoundary(topology)
            wires.append(getUUID(Face.ExternalBoundary(topology), uuidKey=uuidKey))
            internal_boundaries = [getUUID(ib, uuidKey=uuidKey) for ib in Face.InternalBoundaries(topology)]
            wires += internal_boundaries
            returnDict['wires'] = wires
            dictionary = Dictionary.PythonDictionary(Topology.Dictionary(topology))
            returnDict['dictionary'] = dictionary
            return returnDict

        def getShell(topology, uuidKey="uuid"):
            returnDict = {}
            uuidOne = getUUID(topology, uuidKey=uuidKey)
            returnDict['type'] = "Shell"
            returnDict['uuid'] = uuidOne
            returnDict['faces'] = [getUUID(f, uuidKey=uuidKey) for f in Topology.Faces(topology)]
            returnDict['dictionary'] = Dictionary.PythonDictionary(Topology.Dictionary(topology))
            return returnDict

        def getCell(topology, uuidKey="uuid"):
            returnDict = {}
            uuidOne = getUUID(topology, uuidKey=uuidKey)
            returnDict['type'] = "Cell"
            returnDict['uuid'] = uuidOne
            shells = []
            external_boundary = Cell.ExternalBoundary(topology)
            shells.append(getUUID(external_boundary, uuidKey=uuidKey))
            internal_boundaries = [getUUID(ib, uuidKey=uuidKey) for ib in Cell.InternalBoundaries(topology)]
            shells += internal_boundaries
            returnDict['shells'] = shells
            dictionary = Dictionary.PythonDictionary(Topology.Dictionary(topology))
            returnDict['dictionary'] = dictionary
            return returnDict

        def getCellComplex(topology, uuidKey="uuid"):
            returnDict = {}
            uuidOne = getUUID(topology, uuidKey=uuidKey)
            returnDict['type'] = "CellComplex"
            returnDict['uuid'] = uuidOne
            returnDict['cells'] = [getUUID(c, uuidKey=uuidKey) for c in Topology.Cells(topology)]
            returnDict['dictionary'] = Dictionary.PythonDictionary(Topology.Dictionary(topology))
            return returnDict


        def getApertureData(topology, topLevel="False", uuidKey="uuid"):
            json_data = []
            if Topology.IsInstance(topology, "Vertex"):
                d = getVertex(topology, uuidKey=uuidKey)
            elif Topology.IsInstance(topology, "Edge"):
                d = getEdge(topology, uuidKey=uuidKey)
            elif Topology.IsInstance(topology, "Wire"):
                d = getWire(topology, uuidKey=uuidKey)
            elif Topology.IsInstance(topology, "Face"):
                d = getFace(topology, uuidKey=uuidKey)
            elif Topology.IsInstance(topology, "Shell"):
                d = getShell(topology, uuidKey=uuidKey)
            elif Topology.IsInstance(topology, "Cell"):
                d = getCell(topology, uuidKey=uuidKey)
            elif Topology.IsInstance(topology, "CellComplex"):
                d = getCellComplex(topology, uuidKey=uuidKey)
            d['dictionary']['toplevel'] = topLevel
            json_data += getSubTopologyData(topology, uuidKey=uuidKey)
            apertures = [Aperture.Topology(ap) for ap in Topology.Apertures(topology)]
            aperture_data = []
            for ap in apertures:
                aperture_data.append(getApertureData(ap, topLevel=False, uuidKey=uuidKey))
            d['apertures'] = aperture_data
            json_data.append(d)
            return json_data

        def getSubTopologyData(topology, uuidKey="uuid"):
            json_data = []
            vertices = Topology.Vertices(topology)
            for v in vertices:
                d = getVertex(v, uuidKey=uuidKey)
                d['dictionary']['toplevel'] = False
                apertures = [Aperture.Topology(ap) for ap in Topology.Apertures(v)]
                aperture_data = []
                for ap in apertures:
                    aperture_data.append(getApertureData(ap, topLevel=False, uuidKey=uuidKey))
                d['apertures'] = aperture_data
                json_data.append(d)
            edges = Topology.Edges(topology)
            for e in edges:
                d = getEdge(e, uuidKey=uuidKey)
                d['dictionary']['toplevel'] = False
                apertures = [Aperture.Topology(ap) for ap in Topology.Apertures(e)]
                aperture_data = []
                for ap in apertures:
                    aperture_data.append(getApertureData(ap, topLevel=False, uuidKey=uuidKey))
                d['apertures'] = aperture_data
                json_data.append(d)
            wires = Topology.Wires(topology)
            for w in wires:
                d = getWire(w, uuidKey=uuidKey)
                d['dictionary']['toplevel'] = False
                apertures = [Aperture.Topology(ap) for ap in Topology.Apertures(w)]
                aperture_data = []
                for ap in apertures:
                    aperture_data.append(getApertureData(ap, topLevel=False, uuidKey=uuidKey))
                d['apertures'] = aperture_data
                json_data.append(d)
            faces = Topology.Faces(topology)
            for f in faces:
                d = getFace(f, uuidKey=uuidKey)
                d['dictionary']['toplevel'] = False
                apertures = [Aperture.Topology(ap) for ap in Topology.Apertures(f)]
                aperture_data = []
                for ap in apertures:
                    aperture_data.append(getApertureData(ap, topLevel=False, uuidKey=uuidKey))
                d['apertures'] = aperture_data
                json_data.append(d)
            shells = Topology.Shells(topology)
            for s in shells:
                d = getShell(s, uuidKey=uuidKey)
                d['dictionary']['toplevel'] = False
                apertures = [Aperture.Topology(ap) for ap in Topology.Apertures(s)]
                aperture_data = []
                for ap in apertures:
                    aperture_data.append(getApertureData(ap, topLevel=False, uuidKey=uuidKey))
                d['apertures'] = aperture_data
                json_data.append(d)
            cells = Topology.Cells(topology)
            for c in cells:
                d = getCell(c, uuidKey=uuidKey)
                d['dictionary']['toplevel'] = False
                apertures = [Aperture.Topology(ap) for ap in Topology.Apertures(c)]
                aperture_data = []
                for ap in apertures:
                    aperture_data.append(getApertureData(ap, topLevel=False, uuidKey=uuidKey))
                d['apertures'] = aperture_data
                json_data.append(d)
            cellComplexes = Topology.CellComplexes(topology)
            for cc in cellComplexes:
                d = getCellComplex(cc, uuidKey=uuidKey)
                d['dictionary']['toplevel'] = False
                apertures = [Aperture.Topology(ap) for ap in Topology.Apertures(cc)]
                aperture_data = []
                for ap in apertures:
                    aperture_data.append(getApertureData(ap, topLevel=False, uuidKey=uuidKey))
                d['apertures'] = aperture_data
                json_data.append(d)
            return json_data

        def getJSONData(topology, topLevel=False, uuidKey="uuid"):
            json_data = []
            if Topology.IsInstance(topology, "Vertex"):
                d = getVertex(topology, uuidKey=uuidKey)
            elif Topology.IsInstance(topology, "Edge"):
                d = getEdge(topology, uuidKey=uuidKey)
            elif Topology.IsInstance(topology, "Wire"):
                d = getWire(topology, uuidKey=uuidKey)
            elif Topology.IsInstance(topology, "Face"):
                d = getFace(topology, uuidKey=uuidKey)
            elif Topology.IsInstance(topology, "Shell"):
                d = getShell(topology, uuidKey=uuidKey)
            elif Topology.IsInstance(topology, "Cell"):
                d = getCell(topology, uuidKey=uuidKey)
            elif Topology.IsInstance(topology, "CellComplex"):
                d = getCellComplex(topology, uuidKey=uuidKey)
            else:
                print("Topology.JSONString - Error: Unknown topology type:", topology, ". Returning None.")
                return None
            d['dictionary']['toplevel'] = topLevel
            json_data += getSubTopologyData(topology, uuidKey=uuidKey)
            apertures = [Aperture.Topology(ap) for ap in Topology.Apertures(topology)]
            aperture_data = []
            for ap in apertures:
                aperture_data.append(getApertureData(ap, topLevel=False, uuidKey=uuidKey))
            d['apertures'] = aperture_data
            json_data.append(d)
            return json_data
        json_data = []
        if not isinstance(topologies, list):
            topologies = [topologies]
        topologies = [x for x in topologies if Topology.IsInstance(x, "Topology")]
        for topology in topologies:
            json_data += getJSONData(topology, topLevel=True, uuidKey="uuid")
        json_string = json.dumps(json_data, indent=4, sort_keys=False)
        return json_string
    
    @staticmethod
    def OBJString(topology, transposeAxes: bool = True, mode: int = 0, meshSize: float = None, mantissa: int = 6, tolerance: float = 0.0001):
        """
        Returns the Wavefront string of the input topology. This is very experimental and outputs a simple solid topology.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        transposeAxes : bool , optional
            If set to True the Z and Y coordinates are transposed so that Y points "up"
        mode : int , optional
            The desired mode of meshing algorithm. Several options are available:
            0: Classic
            1: MeshAdapt
            3: Initial Mesh Only
            5: Delaunay
            6: Frontal-Delaunay
            7: BAMG
            8: Fontal-Delaunay for Quads
            9: Packing of Parallelograms
            All options other than 0 (Classic) use the gmsh library. See https://gmsh.info/doc/texinfo/gmsh.html#Mesh-options
            WARNING: The options that use gmsh can be very time consuming and can create very heavy geometry.
        meshSize : float , optional
            The desired size of the mesh when using the "mesh" option. If set to None, it will be
            calculated automatically and set to 10% of the overall size of the face.
        mantissa : int , optional
            The desired length of the mantissa. The default is 6.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        str
            The Wavefront OBJ string of the input topology

        """
        from topologicpy.Helper import Helper
        from topologicpy.Vertex import Vertex
        from topologicpy.Face import Face

        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.ExportToOBJ - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        
        lines = []
        version = Helper.Version()
        lines.append("# topologicpy "+version)
        topology = Topology.Triangulate(topology, mode=mode, meshSize=meshSize, tolerance=tolerance)
        d = Topology.Geometry(topology, mantissa=mantissa)
        vertices = d['vertices']
        faces = d['faces']
        tVertices = []
        if transposeAxes:
            for v in vertices:
                tVertices.append([v[0], v[2], v[1]])
            vertices = tVertices
        for v in vertices:
            lines.append("v "+str(v[0])+" "+str(v[1])+" "+str(v[2]))
        for f in faces:
            line = "f"
            for j in f:
                line = line+" "+str(j+1)
            lines.append(line)
        finalLines = lines[0]
        for i in range(1,len(lines)):
            finalLines = finalLines+"\n"+lines[i]
        return finalLines
    
    @staticmethod
    def ExportToOBJ(topology, path, transposeAxes: bool = True, mode: int = 0, meshSize: float = None, overwrite: bool = False, mantissa: int = 6, tolerance: float = 0.0001):
        """
        Exports the input topology to a Wavefront OBJ file. This is very experimental and outputs a simple solid topology.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        path : str
            The input file path.
        transposeAxes : bool , optional
            If set to True the Z and Y coordinates are transposed so that Y points "up"
        mode : int , optional
            The desired mode of meshing algorithm. Several options are available:
            0: Classic
            1: MeshAdapt
            3: Initial Mesh Only
            5: Delaunay
            6: Frontal-Delaunay
            7: BAMG
            8: Fontal-Delaunay for Quads
            9: Packing of Parallelograms
            All options other than 0 (Classic) use the gmsh library. See https://gmsh.info/doc/texinfo/gmsh.html#Mesh-options
            WARNING: The options that use gmsh can be very time consuming and can create very heavy geometry.
        meshSize : float , optional
            The desired size of the mesh when using the "mesh" option. If set to None, it will be
            calculated automatically and set to 10% of the overall size of the face.
        mantissa : int , optional
            The desired length of the mantissa. The default is 6.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.
        overwrite : bool , optional
            If set to True the ouptut file will overwrite any pre-existing file. Otherwise, it won't. The default is False.

        Returns
        -------
        bool
            True if the export operation is successful. False otherwise.

        """
        from os.path import exists

        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.ExportToOBJ - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        if not overwrite and exists(path):
            print("Topology.ExportToOBJ - Error: a file already exists at the specified path and overwrite is set to False. Returning None.")
            return None
        
        # Make sure the file extension is .obj
        ext = path[len(path)-4:len(path)]
        if ext.lower() != ".obj":
            path = path+".obj"
        status = False
        objString = Topology.OBJString(topology, transposeAxes=transposeAxes, mode=mode, meshSize=meshSize, mantissa=mantissa, tolerance=tolerance)
        with open(path, "w") as f:
            f.writelines(objString)
            f.close()
            status = True
        return status

    @staticmethod
    def Filter(topologies, topologyType="any", searchType="any", key=None, value=None):
        """
        Filters the input list of topologies based on the input parameters.

        Parameters
        ----------
        topologies : list
            The input list of topologies.
        topologyType : str , optional
            The type of topology to filter by. This can be one of "any", "vertex", "edge", "wire", "face", "shell", "cell", "cellcomplex", or "cluster". It is case insensitive. The default is "any".
        searchType : str , optional
            The type of search query to conduct in the topology's dictionary. This can be one of "any", "equal to", "contains", "starts with", "ends with", "not equal to", "does not contain". The default is "any".
        key : str , optional
            The dictionary key to search within. The default is None which means it will filter by topology type only.
        value : str , optional
            The value to search for at the specified key. The default is None which means it will filter by topology type only.

        Returns
        -------
        dict
            A dictionary of filtered and other elements. The dictionary has two keys:
            - "filtered" The filtered topologies.
            - "other" The other topologies that did not meet the filter criteria.

        """
        from topologicpy.Dictionary import Dictionary

        def listToString(item):
            returnString = ""
            if isinstance(item, list):
                if len(item) < 2:
                    return str(item[0])
                else:
                    returnString = item[0]
                    for i in range(1, len(item)):
                        returnString = returnString+str(item[i])
            return returnString
        
        filteredTopologies = []
        otherTopologies = []
        for aTopology in topologies:
            if not aTopology:
                continue
            if (topologyType.lower() == "any") or (Topology.TypeAsString(aTopology).lower() == topologyType.lower()):
                if value == "" or key == "":
                    filteredTopologies.append(aTopology)
                else:
                    if isinstance(value, list):
                        value.sort()
                        value = str(value)
                    value.replace("*",".+")
                    value = value.lower()
                    d = Topology.Dictionary(aTopology)
                    v = Dictionary.ValueAtKey(d, key)
                    if v != None:
                        v = v.lower()
                        if searchType.lower() == "equal to":
                            searchResult = (value == v)
                        elif searchType.lower() == "contains":
                            searchResult = (value in v)
                        elif searchType.lower() == "starts with":
                            searchResult = (value == v[0: len(value)])
                        elif searchType.lower() == "ends with":
                            searchResult = (value == v[len(v)-len(value):len(v)])
                        elif searchType.lower() == "not equal to":
                            searchResult = not (value == v)
                        elif searchType.lower() == "does not contain":
                            searchResult = not (value in v)
                        else:
                            searchResult = False
                        if searchResult:
                            filteredTopologies.append(aTopology)
                        else:
                            otherTopologies.append(aTopology)
                    else:
                        otherTopologies.append(aTopology)
            else:
                otherTopologies.append(aTopology)
        return {"filtered": filteredTopologies, "other": otherTopologies}

    @staticmethod
    def Flatten(topology, origin=None, direction=[0, 0, 1]):
        """
        Flattens the input topology such that the input origin is located at the world origin and the input topology is rotated such that the input vector is pointed in the Up direction (see Vector.Up()).

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        origin : topologic_core.Vertex , optional
            The input origin. If set to None, The object's centroid will be used to place the world origin. The default is None.
        vector : list , optional
            The input direction vector. The input topology will be rotated such that this vector is pointed in the positive Z axis.

        Returns
        -------
        topologic_core.Topology
            The flattened topology.

        """
        from topologicpy.Vertex import Vertex
        from topologicpy.Vector import Vector

        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.Flatten - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        if origin == None:
            origin = Topology.Centroid(topology)
        up = Vector.Up()
        flat_topology = Topology.Translate(topology, -Vertex.X(origin), -Vertex.Y(origin), -Vertex.Z(origin))
        tran_mat = Vector.TransformationMatrix(direction, up)
        flat_topology = Topology.Transform(flat_topology, tran_mat)
        return flat_topology
    
    @staticmethod
    def Geometry(topology, mantissa=6):
        """
        Returns the geometry (mesh data format) of the input topology as a dictionary of vertices, edges, and faces.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        mantissa : int , optional
            The desired length of the mantissa. The default is 6.

        Returns
        -------
        dict
            A dictionary containing the vertices, edges, and faces data. The keys found in the dictionary are "vertices", "edges", and "faces".

        """
        from topologicpy.Vertex import Vertex
        from topologicpy.Face import Face
        from topologicpy.Vector import Vector
        
        def getSubTopologies(topology, subTopologyClass):
            topologies = []
            if subTopologyClass == topologic.Vertex:
                _ = topology.Vertices(None, topologies)
            elif subTopologyClass == topologic.Edge:
                _ = topology.Edges(None, topologies)
            elif subTopologyClass == topologic.Wire:
                _ = topology.Wires(None, topologies)
            elif subTopologyClass == topologic.Face:
                _ = topology.Faces(None, topologies)
            elif subTopologyClass == topologic.Shell:
                _ = topology.Shells(None, topologies)
            elif subTopologyClass == topologic.Cell:
                _ = topology.Cells(None, topologies)
            elif subTopologyClass == topologic.CellComplex:
                _ = topology.CellComplexes(None, topologies)
            return topologies

        def triangulateFace(face):
            faceTriangles = []
            for i in range(0, 5, 1):
                try:
                    _ = topologic.FaceUtility.Triangulate(face, float(i)*0.1, faceTriangles)
                    return faceTriangles
                except:
                    continue
            faceTriangles.append(face)
            return faceTriangles

        vertices = []
        edges = []
        faces = []
        if topology == None:
            return [None, None, None]
        topVerts = []
        if Topology.Type(topology) == Topology.TypeID("Vertex"): #input is a vertex, just add it and process it
            topVerts.append(topology)
        else:
            _ = topology.Vertices(None, topVerts)
        for aVertex in topVerts:
            try:
                vertices.index(Vertex.Coordinates(aVertex, mantissa=mantissa)) # Vertex already in list
            except:
                vertices.append(Vertex.Coordinates(aVertex, mantissa=mantissa)) # Vertex not in list, add it.
        topEdges = []
        if (Topology.Type(topology) == Topology.TypeID("Edge")): #Input is an Edge, just add it and process it
            topEdges.append(topology)
        elif (Topology.Type(topology) > Topology.TypeID("Vertex")):
            _ = topology.Edges(None, topEdges)
        for anEdge in topEdges:
            e = []
            sv = anEdge.StartVertex()
            ev = anEdge.EndVertex()
            try:
                svIndex = vertices.index(Vertex.Coordinates(sv, mantissa=mantissa))
            except:
                vertices.append(Vertex.Coordinates(sv, mantissa=mantissa))
                svIndex = len(vertices)-1
            try:
                evIndex = vertices.index(Vertex.Coordinates(ev, mantissa=mantissa))
            except:
                vertices.append(Vertex.Coordinates(ev, mantissa=mantissa))
                evIndex = len(vertices)-1
            e.append(svIndex)
            e.append(evIndex)
            if ([e[0], e[1]] not in edges) and ([e[1], e[0]] not in edges):
                edges.append(e)
        topFaces = []
        if (Topology.Type(topology) == Topology.TypeID("Face")): # Input is a Face, just add it and process it
            topFaces.append(topology)
        elif (Topology.Type(topology) > Topology.TypeID("Face")):
            _ = topology.Faces(None, topFaces)
        for aFace in topFaces:
            f_dir = Face.Normal(aFace)
            ib = []
            _ = aFace.InternalBoundaries(ib)
            if(len(ib) > 0):
                triFaces = triangulateFace(aFace)
                for aTriFace in triFaces:
                    wire = aTriFace.ExternalBoundary()
                    faceVertices = getSubTopologies(wire, topologic.Vertex)
                    temp_face = Face.ByWire(wire)
                    temp_dir = Face.Normal(temp_face)
                    if Vector.IsAntiParallel(f_dir, temp_dir):
                        faceVertices.reverse()
                    f = []
                    for aVertex in faceVertices:
                        try:
                            fVertexIndex = vertices.index(Vertex.Coordinates(aVertex, mantissa=mantissa))
                        except:
                            vertices.append(Vertex.Coordinates(aVertex, mantissa=mantissa))
                            fVertexIndex = len(vertices)-1
                        f.append(fVertexIndex)
                    faces.append(f)
            else:
                wire =  aFace.ExternalBoundary()
                #wire = topologic.WireUtility.RemoveCollinearEdges(wire, 0.1) #This is an angle Tolerance
                faceVertices = getSubTopologies(wire, topologic.Vertex)
                temp_face = Face.ByWire(wire)
                temp_dir = Face.Normal(temp_face)
                if Vector.IsAntiParallel(f_dir, temp_dir):
                    faceVertices.reverse()
                f = []
                for aVertex in faceVertices:
                    try:
                        fVertexIndex = vertices.index(Vertex.Coordinates(aVertex, mantissa=mantissa))
                    except:
                        vertices.append(Vertex.Coordinates(aVertex, mantissa=mantissa))
                        fVertexIndex = len(vertices)-1
                    f.append(fVertexIndex)
                faces.append(f)
        return {"vertices":vertices, "edges":edges, "faces":faces}

    @staticmethod
    def HighestType(topology):
        """
        Returns the highest topology type found in the input topology.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.

        Returns
        -------
        int
            The highest type found in the input topology.

        """
        from topologicpy.Cluster import Cluster

        if (Topology.Type(topology) == Topology.TypeID("Cluster")):
            return Cluster.HighestType(topology)
        else:
            return Topology.Type(topology)

    @staticmethod
    def InternalVertex(topology, tolerance: float = 0.0001):
        """
        Returns a vertex guaranteed to be inside the input topology.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        tolerance : float , ptional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        topologic_core.Vertex
            A vertex guaranteed to be inside the input topology.

        """
        from topologicpy.Vertex import Vertex
        from topologicpy.Edge import Edge
        from topologicpy.Face import Face
        from topologicpy.Cell import Cell
        from topologicpy.CellComplex import CellComplex
        from topologicpy.Aperture import Aperture

        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.InternalVertex - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        vst = None
        top = Topology.Copy(topology)
        if Topology.IsInstance(top, "CellComplex"): #CellComplex
            tempCell = Topology.Cells(top)[0]
            vst = Cell.InternalVertex(tempCell, tolerance=tolerance)
        elif Topology.IsInstance(top, "Cell"): #Cell
            vst = Cell.InternalVertex(top, tolerance=tolerance)
        elif Topology.IsInstance(top, "Shell"): #Shell
            tempFace = Topology.Faces(top)[0]
            vst = Face.InternalVertex(tempFace, tolerance=tolerance)
        elif Topology.IsInstance(top, "Face"): #Face
            vst = Face.InternalVertex(top, tolerance=tolerance)
        elif Topology.IsInstance(top, "Wire"): #Wire
            if top.IsClosed():
                internalBoundaries = []
                try:
                    tempFace = topologic.Face.ByExternalInternalBoundaries(top, internalBoundaries)
                    vst = Face.InternalVertex(tempFace, tolerance=tolerance)
                except:
                    vst = Topology.Centroid(top)
            else:
                tempEdge = Topology.Edges(top)[0]
                vst = Edge.VertexByParameter(tempEdge, 0.5)
        elif Topology.IsInstance(top, "Edge"): #Edge
            vst = Edge.VertexByParameter(top, 0.5)
        elif Topology.IsInstance(top, "Vertex"): #Vertex
            vst = top
        elif Topology.IsInstance(topology, "Aperture"): #Aperture
            vst = Face.InternalVertex(Aperture.Topology(top), tolerance)
        else:
            vst = Topology.Centroid(top)
        return vst

    @staticmethod
    def IsInstance(topology, type: str):
        """
        Returns True if the input topology is an instance of the class specified by the input type string.
        
        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        type : string
            The topology type. This can be one of:
            "Vertex"
            "Edge"
            "Wire"
            "Face"
            "Shell"
            "Cell"
            "CellComplex"
            "Cluster"
            "Topology"
            "Graph"
            "Aperture"
            "Dictionary"
            "Context"

        Returns
        -------
        bool
            True if the input topology is an instance of the class defined by the input type string. False otherwise.
        """

        if "vertex" in type.lower():
            return isinstance(topology, topologic.Vertex)
        elif "edge" in type.lower():
            return isinstance(topology, topologic.Edge)
        elif "wire" in type.lower():
            return isinstance(topology, topologic.Wire)
        elif "face" in type.lower():
            return isinstance(topology, topologic.Face)
        elif "shell" in type.lower():
            return isinstance(topology, topologic.Shell)
        elif "cellcomplex" in type.lower(): #Hack to test for cellcomplex before cell as they share the same prefix.
            return isinstance(topology, topologic.CellComplex)
        elif "cell" in type.lower():
            return isinstance(topology, topologic.Cell)
        elif "cluster" in type.lower():
            return isinstance(topology, topologic.Cluster)
        elif "topology" in type.lower():
            return isinstance(topology, topologic.Topology)
        elif "graph" in type.lower():
            return isinstance(topology, topologic.Graph)
        elif "aperture" in type.lower():
            return isinstance(topology, topologic.Aperture)
        elif "dictionary" in type.lower():
            return isinstance(topology, topologic.Dictionary)
        elif "context" in type.lower():
            return isinstance(topology, topologic.Context)
        else:
            print("Topology.IsInstance - Error: The type input string is not a known topology type. Returning None.")
            return None

    @staticmethod
    def IsPlanar(topology, tolerance=0.0001):
        """
        Returns True if all the vertices of the input topology are co-planar. Returns False otherwise.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        bool
            True if all the vertices of the input topology are co-planar. False otherwise.

        """
        
        def isOnPlane(v, plane, tolerance):
            x, y, z = v
            a, b, c, d = plane
            if math.fabs(a*x + b*y + c*z + d) <= tolerance:
                return True
            return False

        def plane(v1, v2, v3):
            a1 = v2.X() - v1.X() 
            b1 = v2.Y() - v1.Y() 
            c1 = v2.Z() - v1.Z() 
            a2 = v3.X() - v1.X() 
            b2 = v3.Y() - v1.Y() 
            c2 = v3.Z() - v1.Z() 
            a = b1 * c2 - b2 * c1 
            b = a2 * c1 - a1 * c2 
            c = a1 * b2 - b1 * a2 
            d = (- a * v1.X() - b * v1.Y() - c * v1.Z())
            return [a, b, c, d]

        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.IsPlanar - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        vertices = Topology.Vertices(topology)

        result = True
        if len(vertices) <= 3:
            result = True
        else:
            p = plane(vertices[0], vertices[1], vertices[2])
            for i in range(len(vertices)):
                if isOnPlane([vertices[i].X(), vertices[i].Y(), vertices[i].Z()], p, tolerance) == False:
                    result = False
                    break
        return result
    
    @staticmethod
    def IsSame(topologyA, topologyB):
        """
        Returns True if the input topologies are the same topology. Returns False otherwise.

        Parameters
        ----------
        topologyA : topologic_core.Topology
            The first input topology.
        topologyB : topologic_core.Topology
            The second input topology.

        Returns
        -------
        bool
            True of the input topologies are the same topology. False otherwise.

        """
        if not Topology.IsInstance(topologyA, "Topology"):
            print("Topology.IsSame - Error: the input topologyA parameter is not a valid topology. Returning None.")
            return None
        if not Topology.IsInstance(topologyB, "Topology"):
            print("Topology.IsSame - Error: the input topologyB parameter is not a valid topology. Returning None.")
            return None
        return topologic.Topology.IsSame(topologyA, topologyB)
    
    @staticmethod
    def MergeAll(topologies, tolerance=0.0001):
        """
        Merge all the input topologies.

        Parameters
        ----------
        topologies : list
            The list of input topologies.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        topologic_core.Topology
            The resulting merged Topology

        """

        from topologicpy.Cluster import Cluster

        if not isinstance(topologies, list):
            print("Topology.MergeAll - Error: the input topologies parameter is not a valid list. Returning None.")
            return None
        
        topologyList = [t for t in topologies if Topology.IsInstance(t, "Topology")]
        if len(topologyList) < 1:
            print("Topology.MergeAll - Error: the input topologyList does not contain any valid topologies. Returning None.")
            return None
        return Topology.SelfMerge(Cluster.ByTopologies(topologyList), tolerance=tolerance)
            
    @staticmethod
    def OCCTShape(topology):
        """
        Returns the occt shape of the input topology.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.

        Returns
        -------
        topologic_core.TopoDS_Shape
            The OCCT Shape.

        """
        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.OCCTShape - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        return topology.GetOcctShape()
    
    @staticmethod
    def Degree(topology, hostTopology):
        """
        Returns the number of immediate super topologies that use the input topology

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        hostTopology : topologic_core.Topology
            The input host topology to which the input topology belongs
        
        Returns
        -------
        int
            The degree of the topology (the number of immediate super topologies that use the input topology).
        
        """
        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.Degree - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        if not Topology.IsInstance(hostTopology, "Topology"):
            print("Topology.Degree - Error: the input hostTopology parameter is not a valid topology. Returning None.")
            return None
        
        hostTopologyType = Topology.TypeAsString(hostTopology).lower()
        type = Topology.TypeAsString(topology).lower()
        superType = ""
        if type == "vertex" and (hostTopologyType == "cellcomplex" or hostTopologyType == "cell" or hostTopologyType == "shell"):
            superType = "face"
        elif type == "vertex" and (hostTopologyType == "wire" or hostTopologyType == "edge"):
            superType = "edge"
        elif type == "edge" and (hostTopologyType == "cellcomplex" or hostTopologyType == "cell" or hostTopologyType == "shell"):
            superType = "face"
        elif type == "face" and (hostTopologyType == "cellcomplex"):
            superType = "cell"
        superTopologies = Topology.SuperTopologies(topology, hostTopology=hostTopology, topologyType=superType)
        if not superTopologies:
            return 0
        return len(superTopologies)

    @staticmethod
    def NonPlanarFaces(topology, tolerance=0.0001):
        """
        Returns any nonplanar faces in the input topology
        
        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.
        Returns
        -------
        list
            The list of nonplanar faces.
        
        """
        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.NonPlanarFaces - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        faces = Topology.SubTopologies(topology, subTopologyType="face")
        return [f for f in faces if not Topology.IsPlanar(f, tolerance=tolerance)]
    
    @staticmethod
    def OpenFaces(topology):
        """
        Returns the faces that border no cells.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        
        Returns
        -------
        list
            The list of open edges.
        
        """

        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.OpenFaces - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        
        return [f for f in Topology.SubTopologies(topology, subTopologyType="face") if Topology.Degree(f, hostTopology=topology) < 1]
    
    @staticmethod
    def OpenEdges(topology):
        """
        Returns the edges that border only one face.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        
        Returns
        -------
        list
            The list of open edges.
        
        """

        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.OpenEdges - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        
        return [e for e in Topology.SubTopologies(topology, subTopologyType="edge") if Topology.Degree(e, hostTopology=topology) < 2]
    
    @staticmethod
    def OpenVertices(topology):
        """
        Returns the vertices that border only one edge.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        
        Returns
        -------
        list
            The list of open edges.
        
        """

        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.OpenVertices - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        
        return [v for v in Topology.SubTopologies(topology, subTopologyType="vertex") if Topology.Degree(v, hostTopology=topology) < 2]
    
    @staticmethod
    def Orient(topology, origin=None, dirA=[0, 0, 1], dirB=[0, 0, 1], tolerance=0.0001):
        """
        Orients the input topology such that the input such that the input dirA vector is parallel to the input dirB vector.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        origin : topologic_core.Vertex , optional
            The input origin. If set to None, The object's centroid will be used to locate the input topology. The default is None.
        dirA : list , optional
            The first input direction vector. The input topology will be rotated such that this vector is parallel to the input dirB vector. The default is [0, 0, 1].
        dirB : list , optional
            The target direction vector. The input topology will be rotated such that the input dirA vector is parallel to this vector. The default is [0, 0, 1].
        tolerance : float , optional
            The desired tolerance. The default is 0.0001

        Returns
        -------
        topologic_core.Topology
            The flattened topology.

        """
        from topologicpy.Vertex import Vertex
        from topologicpy.Vector import Vector
        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.Orient - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        if not Topology.IsInstance(origin, "Vertex"):
            origin = Topology.Centroid(topology)
        return_topology = Topology.Place(topology, originA=origin, originB=Vertex.Origin())
        tran_mat = Vector.TransformationMatrix(dirA, dirB)
        return_topology = Topology.Transform(return_topology, tran_mat)
        return_topology = Topology.Place(return_topology, originA=Vertex.Origin(), originB=origin)
        return return_topology

    @staticmethod
    def Place(topology, originA=None, originB=None):
        """
        Places the input topology at the specified location.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        originA : topologic_core.Vertex , optional
            The old location to use as the origin of the movement. If set to None, the centroid of the input topology is used. The default is None.
        originB : topologic_core.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.

        Returns
        -------
        topologic_core.Topology
            The placed topology.

        """
        from topologicpy.Vertex import Vertex
        if not Topology.IsInstance(topology, "Topology"):
            return None
        if not Topology.IsInstance(originA, "Vertex"):
            originA = Topology.Centroid(topology)
        if not Topology.IsInstance(originA, "Vertex"):
            originA = Vertex.ByCoordinates(0, 0, 0)

        x = originB.X() - originA.X()
        y = originB.Y() - originA.Y()
        z = originB.Z() - originA.Z()
        newTopology = None
        try:
            newTopology = Topology.Translate(topology, x, y, z)
        except:
            print("Topology.Place - Error: (Topologic>TopologyUtility.Place) operation failed. Returning None.")
            newTopology = None
        return newTopology

    @staticmethod
    def RemoveCollinearEdges(topology, angTolerance=0.1, tolerance=0.0001):
        """
        Removes the collinear edges of the input topology

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        angTolerance : float , optional
            The desired angular tolerance. The default is 0.1.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        topologic_core.Topology
            The input topology with the collinear edges removed.

        """
        from topologicpy.Wire import Wire
        from topologicpy.Face import Face
        from topologicpy.Shell import Shell
        from topologicpy.Cell import Cell
        from topologicpy.CellComplex import CellComplex
        from topologicpy.Cluster import Cluster
        
        if not Topology.IsInstance(topology, "Topology"):
            return None
        return_topology = topology
        if Topology.IsInstance(topology, "Vertex") or Topology.IsInstance(topology, "Edge"): #Vertex or Edge or Cluster, return the original topology
            return return_topology
        elif Topology.IsInstance(topology, "Wire"):
            return_topology = Wire.RemoveCollinearEdges(topology, angTolerance=angTolerance, tolerance=tolerance)
            return return_topology
        elif Topology.IsInstance(topology, "Face"):
            return_topology = Face.RemoveCollinearEdges(topology, angTolerance=angTolerance, tolerance=tolerance)
            return return_topology
        elif Topology.IsInstance(topology, "Shell"):
            return_topology = Shell.RemoveCollinearEdges(topology, angTolerance=angTolerance, tolerance=tolerance)
            return return_topology
        elif Topology.IsInstance(topology, "Cell"):
            return_topology = Cell.RemoveCollinearEdges(topology, angTolerance=angTolerance, tolerance=tolerance)
            return return_topology
        elif Topology.IsInstance(topology, "CellComplex"):
            return_topology = CellComplex.RemoveCollinearEdges(topology, angTolerance=angTolerance, tolerance=tolerance)
            return return_topology
        elif Topology.IsInstance(topology, "Cluster"):
            topologies = []
            topologies += Cluster.FreeVertices(topology)
            topologies += Cluster.FreeEdges(topology)
            faces = Topology.Faces(topology)
            for face in faces:
                topologies.append(Face.RemoveCollinearEdges(topology, angTolerance=angTolerance, tolerance=tolerance))
            return_topology = Topology.SelfMerge(Cluster.ByTopologies(topologies), tolerance=tolerance)
        return return_topology

    @staticmethod
    def RemoveContent(topology, contents):
        """
        Removes the input content list from the input topology

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        contentList : list
            The input list of contents.

        Returns
        -------
        topologic_core.Topology
            The input topology with the input list of contents removed.

        """
        if isinstance(contents, list) == False:
            contents = [contents]
        return topology.RemoveContents(contents)
    
    @staticmethod
    def RemoveCoplanarFaces(topology, epsilon=0.01, tolerance=0.0001):
        """
        Removes coplanar faces in the input topology

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        angTolerance : float , optional
            The desired angular tolerance for removing coplanar faces. The default is 0.1.
        epsilon : float , optional
            The desired epsilon (another form of tolerance) for finding if two faces are coplanar. The default is 0.01.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        topologic_core.Topology
            The input topology with coplanar faces merged into one face.

        """
        from topologicpy.Vertex import Vertex
        from topologicpy.Face import Face
        from topologicpy.Shell import Shell
        from topologicpy.Cell import Cell
        from topologicpy.CellComplex import CellComplex
        from topologicpy.Cluster import Cluster

        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.RemoveCoplanarFaces - Error: The input topology parameter is not a valid topologic topology. Returning None.")
            return None
        t = Topology.Type(topology)
        if (t == Topology.TypeID("Vertex")) or (t == Topology.TypeID("Edge")) or (t == Topology.TypeID("Wire")) or (t == Topology.TypeID("Face")):
            return topology

        def faces_on_same_plane(face1, face2, epsilon=1e-6):
            vertices = Face.Vertices(face1)
            distances = []
            for v in vertices:
                distances.append(Vertex.PerpendicularDistance(v, face=face2, mantissa=6))
            d = sum(distances)/len(distances)
            return d <= epsilon

        def cluster_faces_on_planes(faces, epsilon=1e-6):

            # Create a dictionary to store bins based on plane equations
            bins = {}

            # Iterate through each face
            for i, face in enumerate(faces):
                # Check if a bin already exists for the plane equation
                found_bin = False
                for bin_face in bins.values():
                    if faces_on_same_plane(face, bin_face[0], epsilon=epsilon):
                        bin_face.append(face)
                        found_bin = True
                        break

                # If no bin is found, create a new bin
                if not found_bin:
                    bins[i] = [face]

            # Convert bins to a list of lists
            return list(bins.values())

        faces = Topology.Faces(topology)
        face_clusters = cluster_faces_on_planes(faces, epsilon=epsilon)
        final_faces = []
        for face_cluster in face_clusters:
            t = Topology.SelfMerge(Cluster.ByTopologies(face_cluster), tolerance=tolerance)
            if Topology.IsInstance(t, "Face"):
                #final_faces.append(Face.RemoveCollinearEdges(t))
                final_faces.append(t)
            elif Topology.IsInstance(t, "Shell"):
                    f = Face.ByShell(t)
                    if Topology.IsInstance(f, "Face"):
                        final_faces.append(f)
                    else:
                        print("Topology.RemoveCoplanarFaces - Warning: Could not remove some coplanar faces. Re-adding original faces.")
                        final_faces += Shell.Faces(shell)
            else: # It is a cluster
                shells = Topology.Shells(t)
                for shell in shells:
                    f = Face.ByShell(shell)
                    if Topology.IsInstance(f, "Face"):
                        final_faces.append(f)
                    else:
                        print("Topology.RemoveCoplanarFaces - Warning: Could not remove some coplanar faces. Re-adding original faces.")
                        final_faces += Shell.Faces(shell)
                if len(shells) == 0:
                    faces = Topology.Faces(t)
                    final_faces += faces
                faces = Cluster.FreeFaces(t)
                final_faces += faces
        return_topology = None
        if Topology.IsInstance(topology, "CellComplex"):
            return_topology = CellComplex.ByFaces(final_faces, tolerance=tolerance)
        elif Topology.IsInstance(topology, "Cell"):
            return_topology = Cell.ByFaces(final_faces, tolerance=tolerance)
        elif Topology.IsInstance(topology, "Shell"):
            if len(final_faces) == 1:
                return_topology = final_faces[0]
            else:
                return_topology = Shell.ByFaces(final_faces, tolerance=tolerance)
        if not Topology.IsInstance(return_topology, "Topology"):
            return_topology = Cluster.ByTopologies(final_faces)
        return return_topology

    @staticmethod
    def RemoveEdges(topology, edges=[], tolerance=0.0001):
        """
        Removes the input list of faces from the input topology

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        edges : list
            The input list of edges.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        topologic_core.Topology
            The input topology with the input list of edges removed.

        """

        from topologicpy.Cluster import Cluster
        if not Topology.IsInstance(topology, "Topology"):
            return None
        edges = [e for e in edges if Topology.IsInstance(e, "Edge")]
        if len(edges) < 1:
            return topology
        t_edges = Topology.Edges(topology)
        t_faces = Topology.Faces(topology)
        if len(t_edges) < 1:
            return topology
        if len(t_faces) > 0:
            remove_faces = []
            for t_e in t_edges:
                remove = False
                for i, e in enumerate(edges):
                    if Topology.IsSame(t_e, e):
                        remove = True
                        remove_faces += Topology.SuperTopologies(e, hostTopology=topology, topologyType="face")
                        edges = edges[:i]+ edges[i:]
                        break
            if len(remove_faces) > 0:
                return Topology.RemoveFaces(topology, remove_faces)
        else:
            remaining_edges = []
            for t_e in t_edges:
                remove = False
                for i, e in enumerate(edges):
                    if Topology.IsSame(t_e, e):
                        remove = True
                        edges = edges[:i]+ edges[i:]
                        break
                if not remove:
                    remaining_edges.append(t_e)
            if len(remaining_edges) < 1:
                return None
            elif len(remaining_edges) == 1:
                return remaining_edges[0]
            return Topology.SelfMerge(Cluster.ByTopologies(remaining_edges), tolerance=tolerance)

    @staticmethod
    def RemoveFaces(topology, faces=[], tolerance=0.0001):
        """
        Removes the input list of faces from the input topology

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        faces : list
            The input list of faces.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        topologic_core.Topology
            The input topology with the input list of faces removed.

        """

        from topologicpy.Cluster import Cluster
        if not Topology.IsInstance(topology, "Topology"):
            return None
        faces = [f for f in faces if Topology.IsInstance(f, "Face")]
        if len(faces) < 1:
            return topology
        t_faces = Topology.Faces(topology)
        if len(t_faces) < 1:
            return topology
        remaining_faces = []
        for t_f in t_faces:
            remove = False
            for i, f in enumerate(faces):
                if Topology.IsSame(t_f, f):
                    remove = True
                    faces = faces[:i]+ faces[i:]
                    break
            if not remove:
                remaining_faces.append(t_f)
        if len(remaining_faces) < 1:
            return None
        elif len(remaining_faces) == 1:
            return remaining_faces[0]
        return Topology.SelfMerge(Cluster.ByTopologies(remaining_faces), tolerance=tolerance)
    
    @staticmethod
    def RemoveFacesBySelectors(topology, selectors=[], tolerance = 0.0001):
        """
        Removes faces that contain the input list of selectors (vertices) from the input topology

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        selectors : list
            The input list of selectors (vertices).
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        topologic_core.Topology
            The input topology with the identified faces removed.

        """
        from topologicpy.Vertex import Vertex

        if not Topology.IsInstance(topology, "Topology"):
            return None
        selectors = [v for v in selectors if Topology.IsInstance(v, "Vertex")]
        if len(selectors) < 1:
            return topology
        t_faces = Topology.Faces(topology)
        to_remove = []
        for t_f in t_faces:
            remove = False
            for i, v in enumerate(selectors):
                if Vertex.IsInternal(v, t_f, tolerance=tolerance):
                    remove = True
                    selectors = selectors[:i]+ selectors[i:]
                    break
            if remove:
                to_remove.append(t_f)
        if len(to_remove) < 1:
            return topology
        return Topology.RemoveFaces(topology, faces = to_remove)

    @staticmethod
    def RemoveVertices(topology, vertices=[], tolerance=0.0001):
        """
        Removes the input list of vertices from the input topology

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        vertices : list
            The input list of vertices.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        topologic_core.Topology
            The input topology with the input list of vertices removed.

        """

        from topologicpy.Cluster import Cluster
        if not Topology.IsInstance(topology, "Topology"):
            return None
        vertices = [v for v in vertices if Topology.IsInstance(v, "Vertex")]
        if len(vertices) < 1:
            return topology
        t_vertices = Topology.Vertices(topology)
        t_edges = Topology.Edges(topology)
        if len(t_vertices) < 1:
            return topology
        if len(t_edges) > 0:
            remove_edges = []
            for t_v in t_vertices:
                remove = False
                for i, v in enumerate(vertices):
                    if Topology.IsSame(t_v, v):
                        remove = True
                        remove_edges += Topology.SuperTopologies(v, hostTopology=topology, topologyType="edge")
                        vertices = vertices[:i]+ vertices[i:]
                        break
            if len(remove_edges) > 0:
                return Topology.RemoveEdges(topology, remove_edges)
        else:
            remaining_vertices = []
            for t_v in t_vertices:
                remove = False
                for i, e in enumerate(vertices):
                    if Topology.IsSame(t_v, v):
                        remove = True
                        vertices = vertices[:i]+ vertices[i:]
                        break
                if not remove:
                    remaining_vertices.append(t_v)
            if len(remaining_vertices) < 1:
                return None
            elif len(remaining_vertices) == 1:
                return remaining_vertices[0]
            return Topology.SelfMerge(Cluster.ByTopologies(remaining_vertices), tolerance=tolerance)
    
    @staticmethod
    def Cleanup(topology=None):
        """
        Cleans up all resources in which are managed by topologic library. Use this to manage your application's memory consumption.
        USE WITH CARE. This methods deletes dictionaries, contents, and contexts

        Parameters
        ----------
        topology : topologic_core.Topology , optional
            If specified the resources used by the input topology will be deleted. If not, ALL resources will be deleted.
        
        Returns
        -------
        topologic_core.Topology
            The input topology, but with its resources deleted or None.
        """
        if not topology == None:
            if not Topology.IsInstance(topology, "Topology"):
                print("Topology.Cleanup - Error: The input topology parameter is not a valid topology. Returning None.")
                return None
        topologic.Topology.Cleanup(topology)
        return topology

    @staticmethod
    def ReplaceVertices(topology, verticesA=[], verticesB=[], mantissa=6, tolerance=0.0001):
        """
        Replaces the vertices in the first input list with the vertices in the second input list and rebuilds the input topology. The two lists must be of the same length.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        verticesA : list
            The first input list of vertices.
        verticesB : list
            The second input list of vertices.
        mantissa : int , optional
            The desired length of the mantissa. The default is 6.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        topologic_core.Topology
            The new topology.

        """
        if not len(verticesA) == len(verticesB):
            print("Topology.ReplaceVertices - Error: The input parameters verticesA and verticesB must be the same length")
            return None
        from topologicpy.Vertex import Vertex
        geom = Topology.Geometry(topology, mantissa=mantissa)
        g_verts = geom['vertices']
        g_edges = geom['edges']
        g_faces = geom['faces']
        verts = [Topology.Vertices(Topology.ByGeometry(vertices=[g_v]))[0] for g_v in g_verts]
        new_verts = [v for v in verts]
        for i, v in enumerate(verticesA):
            n = Vertex.Index(v, verts, tolerance=tolerance)
            if not n == None:
                new_verts[n] = verticesB[i]
        new_g_verts = [[Vertex.X(v),Vertex.Y(v),Vertex.Z(v)] for v in new_verts]
        new_topology = Topology.ByGeometry(vertices=new_g_verts, edges=g_edges, faces=g_faces)
        return new_topology

    @staticmethod
    def Rotate(topology, origin=None, axis: list = [0, 0, 1], angle: float = 0, angTolerance: float = 0.001, tolerance: float = 0.0001):
        """
        Rotates the input topology

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        origin : topologic_core.Vertex , optional
            The origin (center) of the rotation. If set to None, the world origin (0, 0, 0) is used. The default is None.
        axis : list , optional
            The vector representing the axis of rotation. The default is [0, 0, 1] which equates to the Z axis.
        angle : float , optional
            The angle of rotation in degrees. The default is 0.
        angTolerance : float , optional
            The angle tolerance in degrees under which no rotation is carried out. The default is 0.001 degrees.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        topologic_core.Topology
            The rotated topology.

        """
        from topologicpy.Vertex import Vertex
        from topologicpy.Dictionary import Dictionary

        def rotate_vertex_3d(vertex, axis, angle_degrees, origin):
            vertex = np.array(vertex)  # Vertex to be rotated
            axis = np.array(axis)    # Rotation axis (z-axis in this case)
            origin = np.array(origin)
            # Convert the angle from degrees to radians
            angle_radians = np.radians(angle_degrees)
            
            # Calculate the rotation matrix using the Rodrigues' formula
            axis = np.array(axis) / np.linalg.norm(axis)
            a = np.cos(angle_radians / 2)
            b, c, d = -axis * np.sin(angle_radians / 2)
            rotation_matrix = np.array([
                [a * a + b * b - c * c - d * d, 2 * (b * c - a * d), 2 * (b * d + a * c)],
                [2 * (b * c + a * d), a * a - b * b + c * c - d * d, 2 * (c * d - a * b)],
                [2 * (b * d - a * c), 2 * (c * d + a * b), a * a - b * b - c * c + d * d]
            ])
            
            # Translate the vertex to the origin, apply the rotation, and then translate it back
            translated_vertex = vertex - origin
            rotated_vertex = np.dot(rotation_matrix, translated_vertex) + origin
            
            rotated_vertex = [v for v in rotated_vertex] 
            return rotated_vertex

        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.Rotate - Error: The input topology parameter is not a valid topologic topology. Returning None.")
            return None
        if not origin:
            origin = Vertex.ByCoordinates(0, 0, 0)
        if not Topology.IsInstance(origin, "Vertex"):
            print("Topology.Rotate - Error: The input origin parameter is not a valid topologic vertex. Returning None.")
            return None
        returnTopology = topology
        if abs(angle) >= angTolerance:
            try:
                x, y, z = axis
                returnTopology = topologic.TopologyUtility.Rotate(topology, origin, x, y, z, angle)
            except:
                print("Topology.Rotate - Warning: (topologic.TopologyUtility.Rotate) operation failed. Trying a workaround.")
                vertices = [Vertex.Coordinates(v) for v in Topology.Vertices(topology)]
                origin = Vertex.Coordinates(origin)
                rot_vertices = []
                for v in vertices:
                    rot_vertices.append(rotate_vertex_3d(v, axis, angle, origin))
                rot_vertices = [Vertex.ByCoordinates(rot_v) for rot_v in rot_vertices]
                new_topology = Topology.ReplaceVertices(topology, verticesA=Topology.Vertices(topology), verticesB=rot_vertices)
                new_topology = Topology.SelfMerge(new_topology, tolerance=tolerance)
                return new_topology
        return returnTopology
    
    @staticmethod
    def Scale(topology, origin=None, x=1, y=1, z=1):
        """
        Scales the input topology

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        origin : topologic_core.Vertex , optional
            The origin (center) of the scaling. If set to None, the world origin (0, 0, 0) is used. The default is None.
        x : float , optional
            The 'x' component of the scaling factor. The default is 1.
        y : float , optional
            The 'y' component of the scaling factor. The default is 1.
        z : float , optional
            The 'z' component of the scaling factor. The default is 1..

        Returns
        -------
        topologic_core.Topology
            The scaled topology.

        """
        from topologicpy.Vertex import Vertex
        if not Topology.IsInstance(topology, "Topology"):
            return None
        if not origin:
            origin = Vertex.ByCoordinates(0, 0, 0)
        if not Topology.IsInstance(origin, "Vertex"):
            return None
        newTopology = None
        try:
            newTopology = topologic.TopologyUtility.Scale(topology, origin, x, y, z)
        except:
            print("Topology.Scale - ERROR: (Topologic>TopologyUtility.Scale) operation failed. Returning None.")
            newTopology = None
        return newTopology

    
    @staticmethod
    def SelectSubTopology(topology, selector, subTopologyType="vertex"):
        """
        Returns the subtopology within the input topology based on the input selector and the subTopologyType.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        selector : topologic_core.Vertex
            A vertex located on the desired subtopology.
        subTopologyType : str , optional.
            The desired subtopology type. This can be of "vertex", "edge", "wire", "face", "shell", "cell", or "cellcomplex". It is case insensitive. The default is "vertex".

        Returns
        -------
        topologic_core.Topology
            The selected subtopology.

        """
        if not Topology.IsInstance(topology, "Topology"):
            return None
        if not Topology.IsInstance(selector, "Vertex"):
            return None
        t = 1
        if subTopologyType.lower() == "vertex":
            t = 1
        elif subTopologyType.lower() == "edge":
            t = 2
        elif subTopologyType.lower() == "wire":
            t = 4
        elif subTopologyType.lower() == "face":
            t = 8
        elif subTopologyType.lower() == "shell":
            t = 16
        elif subTopologyType.lower() == "cell":
            t = 32
        elif subTopologyType.lower() == "cellcomplex":
            t = 64
        return topology.SelectSubtopology(selector, t)

    
    @staticmethod
    def SelfMerge(topology, transferDictionaries: bool = False, tolerance: float = 0.0001):
        """
        Self merges the input topology to return the most logical topology type given the input data.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001

        Returns
        -------
        topologic_core.Topology
            The self-merged topology.

        """
        from topologicpy.Cluster import Cluster

        if not Topology.IsInstance(topology, "Topology"):
            return None #return Silently
        if not Topology.Type(topology) == Topology.TypeID("Cluster"):
            topology = Cluster.ByTopologies([topology])
        resultingTopologies = []
        topCC = Topology.CellComplexes(topology)
        topCells = Topology.Cells(topology)
        topShells = Topology.Shells(topology)
        topFaces = Topology.Faces(topology)
        topWires = Topology.Wires(topology)
        topEdges = Topology.Edges(topology)
        topVertices = Topology.Vertices(topology)
        if len(topCC) == 1:
            cc = topCC[0]
            ccVertices = Topology.Vertices(cc)
            if len(topVertices) == len(ccVertices):
                resultingTopologies.append(cc)
        if len(topCC) == 0 and len(topCells) == 1:
            cell = topCells[0]
            ccVertices = Topology.Vertices(cell)
            if len(topVertices) == len(ccVertices):
                resultingTopologies.append(cell)
        if len(topCC) == 0 and len(topCells) == 0 and len(topShells) == 1:
            shell = topShells[0]
            ccVertices = Topology.Vertices(shell)
            if len(topVertices) == len(ccVertices):
                resultingTopologies.append(shell)
        if len(topCC) == 0 and len(topCells) == 0 and len(topShells) == 0 and len(topFaces) == 1:
            face = topFaces[0]
            ccVertices = Topology.Vertices(face)
            if len(topVertices) == len(ccVertices):
                resultingTopologies.append(face)
        if len(topCC) == 0 and len(topCells) == 0 and len(topShells) == 0 and len(topFaces) == 0 and len(topWires) == 1:
            wire = topWires[0]
            ccVertices = Topology.Vertices(wire)
            if len(topVertices) == len(ccVertices):
                resultingTopologies.append(wire)
        if len(topCC) == 0 and len(topCells) == 0 and len(topShells) == 0 and len(topFaces) == 0 and len(topWires) == 0 and len(topEdges) == 1:
            edge = topEdges[0]
            ccVertices = Topology.Vertices(edge)
            if len(topVertices) == len(ccVertices):
                resultingTopologies.append(edge)
        if len(topCC) == 0 and len(topCells) == 0 and len(topShells) == 0 and len(topFaces) == 0 and len(topWires) == 0 and len(topEdges) == 0 and len(topVertices) == 1:
            vertex = topVertices[0]
            resultingTopologies.append(vertex)
        if len(resultingTopologies) == 1:
            return resultingTopologies[0]
        try:
            return_topology = topology.SelfMerge()
        except:
            return_topology = None
        if Topology.IsInstance(return_topology, "CellComplex"):
            cells = Topology.Cells(return_topology)
            if isinstance(cells, list):
                if len(cells) > 1:
                    topA = cells[0]
                    topB = Cluster.ByTopologies(cells[1:])
                    return_topology = Topology.Merge(topA, topB, tolerance=tolerance)
                else:
                    return_topology = cells[0]
        return return_topology

    @staticmethod
    def SetDictionary(topology, dictionary):
        """
        Sets the input topology's dictionary to the input dictionary

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        dictionary : topologic_core.Dictionary
            The input dictionary.

        Returns
        -------
        topologic_core.Topology
            The input topology with the input dictionary set in it.

        """
        from topologicpy.Dictionary import Dictionary

        if not Topology.IsInstance(topology, "Topology") and not Topology.IsInstance(topology, "Graph"):
            print("Topology.SetDictionary - Error: the input topology parameter is not a valid topology or graph. Returning None.")
            return None
        if isinstance(dictionary, dict):
            dictionary = Dictionary.ByPythonDictionary(dictionary)
        if not Topology.IsInstance(dictionary, "Dictionary"):
            print("Topology.SetDictionary - Warning: the input dictionary parameter is not a valid dictionary. Returning original input.")
            return topology
        if len(dictionary.Keys()) < 1:
            print("Topology.SetDictionary - Warning: the input dictionary parameter is empty. Returning original input.")
            return topology
        _ = topology.SetDictionary(dictionary)
        return topology
    
    @staticmethod
    def SetSnapshot(topology, snapshot=None, timestamp=None, key="timestamp", silent=False):
        from topologicpy.Dictionary import Dictionary
        from datetime import datetime
        def is_valid_timestamp(timestamp):
            if isinstance(timestamp, datetime):
                return True
            elif isinstance(timestamp, str):
                try:
                    # Split the timestamp string into date and time parts
                    date_part, time_part = timestamp.split(' ')
                    # Parse the date part
                    date_obj = datetime.strptime(date_part, '%Y-%m-%d')
                    # Split the time part into hours, minutes, and seconds
                    hours, minutes, seconds = map(float, time_part.split(':'))
                    # Check if seconds are within valid range
                    if seconds < 0 or seconds >= 60:
                        return False
                    # Create a datetime object with the parsed date and time parts
                    datetime_obj = datetime(date_obj.year, date_obj.month, date_obj.day, int(hours), int(minutes), int(seconds))
                    return True
                except ValueError:
                    return False
            else:
                return False

        if not Topology.IsInstance(topology, "Topology"):
            if not silent:
                print("Topology.SetSnapshot - Error: The input topology parameter is not a valid topology. Returning None.")
                return None
        if not Topology.IsInstance(snapshot, "Topology"):
            snapshot = Topology.Copy(topology)
        if not Topology.IsInstance(snapshot, "Topology"):
            if not silent:
                print("Topology.SetSnapshot - Error: The input snapshot parameter is not a valid topology. Returning None.")
                return None
        if timestamp == None:
            timestamp = datetime.now()
        if not is_valid_timestamp(timestamp):
            if not silent:
                print("Topology.SetSnapshot - Error: The input timestamp parameter is not a valid timestamp. Returning None.")
                return None
        
        d = Topology.Dictionary(snapshot)
        d = Dictionary.SetValueAtKey(d, key, str(timestamp))
        snapshot = Topology.SetDictionary(snapshot, d)
        topology = Topology.AddContent(topology, snapshot)
        return topology
    
    @staticmethod
    def SharedTopologies(topologyA, topologyB):
        """
        Returns the shared topologies between the two input topologies

        Parameters
        ----------
        topologyA : topologic_core.Topology
            The first input topology.
        topologyB : topologic_core.Topology
            The second input topology.

        Returns
        -------
        dict
            A dictionary with the list of vertices, edges, wires, and faces. The keys are "vertices", "edges", "wires", and "faces".

        """

        if not Topology.IsInstance(topologyA, "Topology"):
            print("Topology.SharedTopologies - Error: the input topologyA parameter is not a valid topology. Returning None.")
            return None
        if not Topology.IsInstance(topologyB, "Topology"):
            print("Topology.SharedTopologies - Error: the input topologyB parameter is not a valid topology. Returning None.")
            return None
        vOutput = []
        eOutput = []
        wOutput = []
        fOutput = []
        _ = topologyA.SharedTopologies(topologyB, 1, vOutput)
        _ = topologyA.SharedTopologies(topologyB, 2, eOutput)
        _ = topologyA.SharedTopologies(topologyB, 4, wOutput)
        _ = topologyA.SharedTopologies(topologyB, 8, fOutput)
        return {"vertices":vOutput, "edges":eOutput, "wires":wOutput, "faces":fOutput}

    @staticmethod
    def SharedVertices(topologyA, topologyB):
        """
        Returns the shared vertices between the two input topologies

        Parameters
        ----------
        topologyA : topologic_core.Topology
            The first input topology.
        topologyB : topologic_core.Topology
            The second input topology.

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

        """
        d = Topology.SharedTopologies(topologyA, topologyB)
        l = None
        if isinstance(d, dict):
            try:
                l = d['vertices']
            except:
                l = None
        return l
    
    
    @staticmethod
    def SharedEdges(topologyA, topologyB):
        """
        Returns the shared edges between the two input topologies

        Parameters
        ----------
        topologyA : topologic_core.Topology
            The first input topology.
        topologyB : topologic_core.Topology
            The second input topology.

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

        """
        d = Topology.SharedTopologies(topologyA, topologyB)
        l = None
        if isinstance(d, dict):
            try:
                l = d['edges']
            except:
                l = None
        return l
    
    @staticmethod
    def SharedWires(topologyA, topologyB):
        """
        Returns the shared wires between the two input topologies

        Parameters
        ----------
        topologyA : topologic_core.Topology
            The first input topology.
        topologyB : topologic_core.Topology
            The second input topology.

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

        """
        d = Topology.SharedTopologies(topologyA, topologyB)
        l = None
        if isinstance(d, dict):
            try:
                l = d['wires']
            except:
                l = None
        return l
    
    
    @staticmethod
    def SharedFaces(topologyA, topologyB):
        """
        Returns the shared faces between the two input topologies

        Parameters
        ----------
        topologyA : topologic_core.Topology
            The first input topology.
        topologyB : topologic_core.Topology
            The second input topology.

        Returns
        -------
        list
            The list of shared faces.

        """
        d = Topology.SharedTopologies(topologyA, topologyB)
        l = None
        if isinstance(d, dict):
            try:
                l = d['faces']
            except:
                l = None
        return l
    
    
    @staticmethod
    def Show(*topologies,
             showVertices=True, vertexSize=1.1, vertexColor="black", 
             vertexLabelKey=None, vertexGroupKey=None, vertexGroups=[], 
             vertexMinGroup=None, vertexMaxGroup=None, 
             showVertexLegend=False, vertexLegendLabel="Topology Vertices", vertexLegendRank=1, 
             vertexLegendGroup=1, 

             showEdges=True, edgeWidth=1, edgeColor="black", 
             edgeLabelKey=None, edgeGroupKey=None, edgeGroups=[], 
             edgeMinGroup=None, edgeMaxGroup=None, 
             showEdgeLegend=False, edgeLegendLabel="Topology Edges", edgeLegendRank=2, 
             edgeLegendGroup=2, 

             showFaces=True, faceOpacity=0.5, faceColor="#FAFAFA",
             faceLabelKey=None, faceGroupKey=None, faceGroups=[], 
             faceMinGroup=None, faceMaxGroup=None, 
             showFaceLegend=False, faceLegendLabel="Topology Faces", faceLegendRank=3,
             faceLegendGroup=3, 
             intensityKey=None,
             intensities=[],
             
             width=950, height=500,
             xAxis=False, yAxis=False, zAxis=False, axisSize=1, backgroundColor='rgba(0,0,0,0)',
             marginLeft=0, marginRight=0, marginTop=20, marginBottom=0, camera=[-1.25, -1.25, 1.25],
             center=[0, 0, 0], up=[0, 0, 1], projection="perspective", renderer="notebook", showScale=False,
             
             cbValues=[], cbTicks=5, cbX=-0.15, cbWidth=15, cbOutlineWidth=0, cbTitle="",
             cbSubTitle="", cbUnits="", colorScale="Viridis", mantissa=6, tolerance=0.0001):
        """
            Shows the input topology on screen.

        Parameters
        ----------
        topologies : topologic_core.Topology or list
            The input topology. This must contain faces and or edges. If the input is a list, a cluster is first created

        showVertices : bool , optional
            If set to True the vertices will be drawn. Otherwise, they will not be drawn. The default is True.
        vertexSize : float , optional
            The desired size of the vertices. The default is 1.1.
        vertexColor : str , optional
            The desired color of the output vertices. This can be any plotly color string and may be specified as:
            - A hex string (e.g. '#ff0000')
            - An rgb/rgba string (e.g. 'rgb(255,0,0)')
            - An hsl/hsla string (e.g. 'hsl(0,100%,50%)')
            - An hsv/hsva string (e.g. 'hsv(0,100%,100%)')
            - A named CSS color.
            The default is "black".
        vertexLabelKey : str , optional
            The dictionary key to use to display the vertex label. The default is None.
        vertexGroupKey : str , optional
            The dictionary key to use to display the vertex group. The default is None.
        vertexGroups : list , optional
            The list of vertex groups against which to index the color of the vertex. The default is [].
        vertexMinGroup : int or float , optional
            For numeric vertexGroups, vertexMinGroup is the desired minimum value for the scaling of colors. This should match the type of value associated with the vertexGroupKey. If set to None, it is set to the minimum value in vertexGroups. The default is None.
        edgeMaxGroup : int or float , optional
            For numeric vertexGroups, vertexMaxGroup is the desired maximum value for the scaling of colors. This should match the type of value associated with the vertexGroupKey. If set to None, it is set to the maximum value in vertexGroups. The default is None.
        showVertexLegend : bool, optional
            If set to True, the legend for the vertices of this topology is shown. Otherwise, it isn't. The default is False.
        vertexLegendLabel : str , optional
            The legend label string used to identify vertices. The default is "Topology Vertices".
        vertexLegendRank : int , optional
            The legend rank order of the vertices of this topology. The default is 1.
        vertexLegendGroup : int , optional
            The number of the vertex legend group to which the vertices of this topology belong. The default is 1.
        
        showEdges : bool , optional
            If set to True the edges will be drawn. Otherwise, they will not be drawn. The default is True.
        edgeWidth : float , optional
            The desired thickness of the output edges. The default is 1.
        edgeColor : str , optional
            The desired color of the output edges. This can be any plotly color string and may be specified as:
            - A hex string (e.g. '#ff0000')
            - An rgb/rgba string (e.g. 'rgb(255,0,0)')
            - An hsl/hsla string (e.g. 'hsl(0,100%,50%)')
            - An hsv/hsva string (e.g. 'hsv(0,100%,100%)')
            - A named CSS color.
            The default is "black".
        edgeLabelKey : str , optional
            The dictionary key to use to display the edge label. The default is None.
        edgeGroupKey : str , optional
            The dictionary key to use to display the edge group. The default is None.
        edgeGroups : list , optional
            The list of edge groups against which to index the color of the edge. The default is [].
        edgeMinGroup : int or float , optional
            For numeric edgeGroups, edgeMinGroup is the desired minimum value for the scaling of colors. This should match the type of value associated with the edgeGroupKey. If set to None, it is set to the minimum value in edgeGroups. The default is None.
        edgeMaxGroup : int or float , optional
            For numeric edgeGroups, edgeMaxGroup is the desired maximum value for the scaling of colors. This should match the type of value associated with the edgeGroupKey. If set to None, it is set to the maximum value in edgeGroups. The default is None.
        showEdgeLegend : bool, optional
            If set to True, the legend for the edges of this topology is shown. Otherwise, it isn't. The default is False.
        edgeLegendLabel : str , optional
            The legend label string used to identify edges. The default is "Topology Edges".
        edgeLegendRank : int , optional
            The legend rank order of the edges of this topology. The default is 2.
        edgeLegendGroup : int , optional
            The number of the edge legend group to which the edges of this topology belong. The default is 2.
        
        showFaces : bool , optional
            If set to True the faces will be drawn. Otherwise, they will not be drawn. The default is True.
        faceOpacity : float , optional
            The desired opacity of the output faces (0=transparent, 1=opaque). The default is 0.5.
        faceColor : str , optional
            The desired color of the output faces. This can be any plotly color string and may be specified as:
            - A hex string (e.g. '#ff0000')
            - An rgb/rgba string (e.g. 'rgb(255,0,0)')
            - An hsl/hsla string (e.g. 'hsl(0,100%,50%)')
            - An hsv/hsva string (e.g. 'hsv(0,100%,100%)')
            - A named CSS color.
            The default is "#FAFAFA".
        faceLabelKey : str , optional
            The dictionary key to use to display the face label. The default is None.
        faceGroupKey : str , optional
            The dictionary key to use to display the face group. The default is None.
        faceGroups : list , optional
            The list of face groups against which to index the color of the face. This can bhave numeric or string values. This should match the type of value associated with the faceGroupKey. The default is [].
        faceMinGroup : int or float , optional
            For numeric faceGroups, minGroup is the desired minimum value for the scaling of colors. This should match the type of value associated with the faceGroupKey. If set to None, it is set to the minimum value in faceGroups. The default is None.
        faceMaxGroup : int or float , optional
            For numeric faceGroups, maxGroup is the desired maximum value for the scaling of colors. This should match the type of value associated with the faceGroupKey. If set to None, it is set to the maximum value in faceGroups. The default is None.
        showFaceLegend : bool, optional
            If set to True, the legend for the faces of this topology is shown. Otherwise, it isn't. The default is False.
        faceLegendLabel : str , optional
            The legend label string used to idenitfy edges. The default is "Topology Faces".
        faceLegendRank : int , optional
            The legend rank order of the faces of this topology. The default is 3.
        faceLegendGroup : int , optional
            The number of the face legend group to which the faces of this topology belong. The default is 3.
        width : int , optional
            The width in pixels of the figure. The default value is 950.
        height : int , optional
            The height in pixels of the figure. The default value is 950.
        xAxis : bool , optional
            If set to True the x axis is drawn. Otherwise it is not drawn. The default is False.
        yAxis : bool , optional
            If set to True the y axis is drawn. Otherwise it is not drawn. The default is False.
        zAxis : bool , optional
            If set to True the z axis is drawn. Otherwise it is not drawn. The default is False.
        backgroundColor : str , optional
            The desired color of the background. This can be any plotly color string and may be specified as:
            - A hex string (e.g. '#ff0000')
            - An rgb/rgba string (e.g. 'rgb(255,0,0)')
            - An hsl/hsla string (e.g. 'hsl(0,100%,50%)')
            - An hsv/hsva string (e.g. 'hsv(0,100%,100%)')
            - A named CSS color.
            The default is "rgba(0,0,0,0)".
        marginLeft : int , optional
            The size in pixels of the left margin. The default value is 0.
        marginRight : int , optional
            The size in pixels of the right margin. The default value is 0.
        marginTop : int , optional
            The size in pixels of the top margin. The default value is 20.
        marginBottom : int , optional
            The size in pixels of the bottom margin. The default value is 0.
        camera : list , optional
            The desired location of the camera). The default is [-1.25, -1.25, 1.25].
        center : list , optional
            The desired center (camera target). The default is [0, 0, 0].
        up : list , optional
            The desired up vector. The default is [0, 0, 1].
        projection : str , optional
            The desired type of projection. The options are "orthographic" or "perspective". It is case insensitive. The default is "perspective"
        renderer : str , optional
            The desired renderer. See Plotly.Renderers(). If set to None, the code will attempt to discover the most suitable renderer. The default is None.
        intensityKey : str , optional
            If not None, the dictionary of each vertex is searched for the value associated with the intensity key. This value is then used to color-code the vertex based on the colorScale. The default is None.
        intensities : list , optional
            The list of intensities against which to index the intensity of the vertex. The default is [].
        showScale : bool , optional
            If set to True, the colorbar is shown. The default is False.
        cbValues : list , optional
            The input list of values to use for the colorbar. The default is [].
        cbTicks : int , optional
            The number of ticks to use on the colorbar. The default is 5.
        cbX : float , optional
            The x location of the colorbar. The default is -0.15.
        cbWidth : int , optional
            The width in pixels of the colorbar. The default is 15
        cbOutlineWidth : int , optional
            The width in pixels of the outline of the colorbar. The default is 0.
        cbTitle : str , optional
            The title of the colorbar. The default is "".
        cbSubTitle : str , optional
            The subtitle of the colorbar. The default is "".
        cbUnits: str , optional
            The units used in the colorbar. The default is ""
        colorScale : str , optional
            The desired type of plotly color scales to use (e.g. "viridis", "plasma"). The default is "viridis". For a full list of names, see https://plotly.com/python/builtin-colorscales/.
        mantissa : int , optional
            The desired length of the mantissa for the values listed on the colorbar. The default is 6.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        None

        """

        from topologicpy.Cluster import Cluster
        from topologicpy.Plotly import Plotly
        from topologicpy.Helper import Helper
        from topologicpy.Graph import Graph
        
        if isinstance(topologies, tuple):
            topologies = Helper.Flatten(list(topologies))
        if isinstance(topologies, list):
            new_topologies = [t for t in topologies if Topology.IsInstance(t, "Topology")]
            graphs = [Graph.Topology(g) for g in topologies if Topology.IsInstance(g, "Graph")]
            new_topologies += graphs
        if len(new_topologies) == 0:
            print("Topology.Show - Error: the input topologies parameter does not contain any valid topology. Returning None.")
            return None
        if len(new_topologies) == 1:
            topology = new_topologies[0]
        else:
            topology = Cluster.ByTopologies(new_topologies)
        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.Show - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        data = Plotly.DataByTopology(topology=topology,
                       showVertices=showVertices, vertexSize=vertexSize, vertexColor=vertexColor, 
                       vertexLabelKey=vertexLabelKey, vertexGroupKey=vertexGroupKey, vertexGroups=vertexGroups, 
                       vertexMinGroup=vertexMinGroup, vertexMaxGroup=vertexMaxGroup, 
                       showVertexLegend=showVertexLegend, vertexLegendLabel=vertexLegendLabel, vertexLegendRank=vertexLegendRank,
                       vertexLegendGroup=vertexLegendGroup,
                       showEdges=showEdges, edgeWidth=edgeWidth, edgeColor=edgeColor, 
                       edgeLabelKey=edgeLabelKey, edgeGroupKey=edgeGroupKey, edgeGroups=edgeGroups, 
                       edgeMinGroup=edgeMinGroup, edgeMaxGroup=edgeMaxGroup, 
                       showEdgeLegend=showEdgeLegend, edgeLegendLabel=edgeLegendLabel, edgeLegendRank=edgeLegendRank, 
                       edgeLegendGroup=edgeLegendGroup,
                       showFaces=showFaces, faceOpacity=faceOpacity, faceColor=faceColor,
                       faceLabelKey=faceLabelKey, faceGroupKey=faceGroupKey, faceGroups=faceGroups, 
                       faceMinGroup=faceMinGroup, faceMaxGroup=faceMaxGroup, 
                       showFaceLegend=showFaceLegend, faceLegendLabel=faceLegendLabel, faceLegendRank=faceLegendRank,
                       faceLegendGroup=faceLegendGroup, 
                       intensityKey=intensityKey, intensities=intensities, colorScale=colorScale, mantissa=mantissa, tolerance=tolerance)
        figure = Plotly.FigureByData(data=data, width=width, height=height,
                                     xAxis=xAxis, yAxis=yAxis, zAxis=zAxis, axisSize=axisSize,
                                     backgroundColor=backgroundColor,
                                     marginLeft=marginLeft, marginRight=marginRight,
                                     marginTop=marginTop, marginBottom=marginBottom,
                                     tolerance=tolerance)
        if showScale:
            figure = Plotly.AddColorBar(figure, values=cbValues, nTicks=cbTicks, xPosition=cbX, width=cbWidth, outlineWidth=cbOutlineWidth, title=cbTitle, subTitle=cbSubTitle, units=cbUnits, colorScale=colorScale, mantissa=mantissa)
        Plotly.Show(figure=figure, renderer=renderer, camera=camera, center=center, up=up, projection=projection)

    @staticmethod
    def SortBySelectors(topologies, selectors, exclusive=False, tolerance=0.0001):
        """
        Sorts the input list of topologies according to the input list of selectors.

        Parameters
        ----------
        topologies : list
            The input list of topologies.
        selectors : list
            The input list of selectors (vertices).
        exclusive : bool , optional
            If set to True only one selector can be used to select on topology. The default is False.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        dict
            A dictionary containing the list of sorted and unsorted topologies. The keys are "sorted" and "unsorted".

        """

        from topologicpy.Vertex import Vertex
        usedTopologies = []
        sortedTopologies = []
        unsortedTopologies = []
        for i in range(len(topologies)):
            usedTopologies.append(0)
        
        for i in range(len(selectors)):
            found = False
            for j in range(len(topologies)):
                if usedTopologies[j] == 0:
                    if Vertex.IsInternal( selectors[i], topologies[j], tolerance):
                        sortedTopologies.append(topologies[j])
                        if exclusive == True:
                            usedTopologies[j] = 1
                        found = True
                        break
            if found == False:
                sortedTopologies.append(None)
        for i in range(len(usedTopologies)):
            if usedTopologies[i] == 0:
                unsortedTopologies.append(topologies[i])
        return {"sorted":sortedTopologies, "unsorted":unsortedTopologies}
    
    @staticmethod
    def Snapshots(topology, key="timestamp", start=None, end=None, silent=False):
        from topologicpy.Dictionary import Dictionary
        from datetime import datetime
        def is_valid_timestamp(timestamp):
            if isinstance(timestamp, datetime):
                return True
            elif isinstance(timestamp, str):
                try:
                    # Split the timestamp string into date and time parts
                    date_part, time_part = timestamp.split(' ')
                    # Parse the date part
                    date_obj = datetime.strptime(date_part, '%Y-%m-%d')
                    # Split the time part into hours, minutes, and seconds
                    hours, minutes, seconds = map(float, time_part.split(':'))
                    # Check if seconds are within valid range
                    if seconds < 0 or seconds >= 60:
                        return False
                    # Create a datetime object with the parsed date and time parts
                    return datetime(date_obj.year, date_obj.month, date_obj.day, int(hours), int(minutes), int(seconds))
                except ValueError:
                    return False
            else:
                return False
        
        if not Topology.IsInstance(topology, "Topology"):
            if not silent:
                print("Topology.Snapshots - Error: The input topology parameter is not a valid topology. Returning None.")
                return None
        if start == None:
            start = datetime.datetime(year=1900, month=1, day=1) # Set the start date to a date in the distant past
        if end == None:
            end = datetime.now() # Set the end date to the present.
        if not is_valid_timestamp(start):
            if not silent:
                print("Topology.Snapshots - Error: The input start parameter is not a valid timestamp. Returning None.")
                return None
        if not is_valid_timestamp(end):
            if not silent:
                print("Topology.Snapshots - Error: The input end parameter is not a valid timestamp. Returning None.")
                return None
        contents = Topology.Contents(topology)    
        snapshots = []
        for content in contents:
            d = Topology.Dictionary(content)
            timestamp = Dictionary.ValueAtKey(d, key)
            timestamp = is_valid_timestamp(timestamp)
            if not timestamp == False:
                if start <= timestamp <= end:
                    snapshots.append(content)
        return snapshots

    @staticmethod
    def Spin(topology, origin=None, triangulate: bool = True, direction: list = [0, 0, 1], angle: float = 360, sides: int = 16,
                     tolerance: float = 0.0001, silent: bool = False):
        """
        Spins the input topology around an axis to create a new topology.See https://en.wikipedia.org/wiki/Solid_of_revolution.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        origin : topologic_core.Vertex
            The origin (center) of the spin.
        triangulate : bool , optional
            If set to True, the result will be triangulated. The default is True.
        direction : list , optional
            The vector representing the direction of the spin axis. The default is [0, 0, 1].
        angle : float , optional
            The angle in degrees for the spin. The default is 360.
        sides : int , optional
            The desired number of sides. The default is 16.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        topologic_core.Topology
            The spun topology.

        """
        from topologicpy.Vertex import Vertex
        from topologicpy.Wire import Wire
        from topologicpy.Shell import Shell
        from topologicpy.Cell import Cell
        from topologicpy.CellComplex import CellComplex
        from topologicpy.Cluster import Cluster

        if not origin:
            origin = Vertex.ByCoordinates(0, 0, 0)
        if not Topology.IsInstance(topology, "Topology"):
            if not silent:
                print("Topology.Spin - Error: the input topology parameter is not a valid topology. Returning None.")
                return None
        if not Topology.IsInstance(origin, "Vertex"):
            if not silent:
                print("Topology.Spin - Error: the input origin parameter is not a valid vertex. Returning None.")
                return None
        topologies = []
        unit_degree = angle / float(sides)
        for i in range(sides+1):
            tempTopology = Topology.Rotate(topology, origin=origin, axis=direction, angle=unit_degree*i)
            if tempTopology:
                topologies.append(tempTopology)
        returnTopology = None
        if Topology.Type(topology) == Topology.TypeID("Vertex"):
            returnTopology = Wire.ByVertices(topologies, False)
        elif Topology.Type(topology) == Topology.TypeID("Edge"):
            try:
                returnTopology = Shell.ByWires(topologies,triangulate=triangulate, tolerance=tolerance, silent=True)
            except:
                try:
                    returnTopology = Cluster.ByTopologies(topologies)
                except:
                    returnTopology = None
        elif Topology.Type(topology) == Topology.TypeID("Wire"):
            if topology.IsClosed():
                #returnTopology = Cell.ByWires(topologies, triangulate=triangulate, tolerance=tolerance, silent=True)
                try:
                    returnTopology = Cell.ByWires(topologies, triangulate=triangulate, tolerance=tolerance, silent=True)
                    returnTopology = Cell.ExternalBoundary(returnTopology)
                    returnTopology = Cell.ByShell(returnTopology)
                except:
                    try:
                        returnTopology = CellComplex.ByWires(topologies, tolerance)
                        try:
                            returnTopology = returnTopology.ExternalBoundary()
                        except:
                            pass
                    except:
                        try:
                            returnTopology = Shell.ByWires(topologies, triangulate=triangulate, tolerance=tolerance, silent=True)
                        except:
                            try:
                                returnTopology = Cluster.ByTopologies(topologies)
                            except:
                                returnTopology = None
            else:
                Shell.ByWires(topologies, triangulate=triangulate, tolerance=tolerance, silent=True)
                try:
                    returnTopology = Shell.ByWires(topologies, triangulate=triangulate, tolerance=tolerance, silent=True)
                except:
                    try:
                        returnTopology = Cluster.ByTopologies(topologies)
                    except:
                        returnTopology = None
        elif Topology.IsInstance(topology, "Face"):
            external_wires = []
            for t in topologies:
                external_wires.append(topologic.Face.ExternalBoundary(t))
            try:
                returnTopology = CellComplex.ByWires(external_wires, tolerance)
            except:
                try:
                    returnTopology = Shell.ByWires(external_wires, triangulate=triangulate, tolerance=tolerance, silent=True)
                except:
                    try:
                        returnTopology = Cluster.ByTopologies(topologies)
                    except:
                        returnTopology = None
        else:
            returnTopology = Topology.SelfMerge(Cluster.ByTopologies(topologies), tolerance=tolerance)
        if not returnTopology:
            return Cluster.ByTopologies(topologies)
        if Topology.Type(returnTopology) == Topology.TypeID("Shell"):
            try:
                new_t = Cell.ByShell(returnTopology)
                if new_t:
                    returnTopology = new_t
            except:
                pass
        return returnTopology
    
    @staticmethod
    def Taper(topology, origin=None, ratioRange=[0, 1], triangulate=False, tolerance=0.0001):
        """
        Tapers the input topology. This method tapers the input geometry along its Z-axis based on the ratio range input.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        origin : topologic_core.Vertex , optional
            The desired origin for tapering. If not specified, the centroid of the input topology is used. The tapering will use the X, Y coordinates of the specified origin, but will use the Z of the point being tapered. The default is None.
        ratioRange : list , optional
            The desired ratio range. This will specify a linear range from bottom to top for tapering the vertices. 0 means no tapering, and 1 means maximum (inward) tapering. Negative numbers mean that tapering will be outwards.
        triangulate : bool , optional
            If set to true, the input topology is triangulated before tapering. Otherwise, it will not be traingulated. The default is False.
        tolerance : float , optional
            The desired tolerance. Vertices will not be moved if the calculated distance is at or less than this tolerance.

        Returns
        -------
        topologic_core.Topology
            The tapered topology.

        """
        from topologicpy.Vertex import Vertex
        ratioRange = [min(1,ratioRange[0]), min(1,ratioRange[1])]
        if ratioRange == [0, 0]:
            return topology
        if ratioRange == [1, 1]:
            print("Topology.Taper - Error: Degenerate result. Returning original topology.")
            return topology
        if triangulate == True:
            topology = Topology.Triangulate(topology)
        if origin == None:
            origin = Topology.Centroid(topology)
        vertices = Topology.Vertices(topology)
        zList = [Vertex.Z(v) for v in vertices]
        minZ = min(zList)
        maxZ = max(zList)
        new_vertices = []
        for v in vertices:
            ht = (Vertex.Z(v)-minZ)/(maxZ - minZ)
            rt = ratioRange[0] + ht*(ratioRange[1] - ratioRange[0])
            new_origin = Vertex.ByCoordinates(Vertex.X(origin), Vertex.Y(origin), Vertex.Z(v))
            new_dist = Vertex.Distance(new_origin, v)*rt
            c_a = Vertex.Coordinates(new_origin)
            c_b = Vertex.Coordinates(v)
            new_dir = [(c_a[0]-c_b[0]), (c_a[1]-c_b[1]), 0]
            if abs(new_dist) > tolerance:
                new_v = Topology.TranslateByDirectionDistance(v, direction=new_dir, distance=new_dist)
            else:
                new_v = v
            new_vertices.append(new_v)
        return_topology = Topology.ReplaceVertices(topology, vertices, new_vertices)
        return return_topology
    
    @staticmethod
    def Twist(topology, origin=None, angleRange=[45, 90], triangulate=False):
        """
        Twists the input topology. This method twists the input geometry along its Z-axis based on the degree range input.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        origin : topologic_core.Vertex , optional
            The desired origin for tapering. If not specified, the centroid of the input topology is used. The tapering will use the X, Y coordinates of the specified origin, but will use the Z of the point being tapered. The default is None.
        angleRange : list , optional
            The desired angle range in degrees. This will specify a linear range from bottom to top for twisting the vertices. positive numbers mean a clockwise rotation.
        triangulate : bool , optional
            If set to true, the input topology is triangulated before tapering. Otherwise, it will not be traingulated. The default is False.
        
        Returns
        -------
        topologic_core.Topology
            The twisted topology.

        """
        from topologicpy.Vertex import Vertex

        if angleRange == [0, 0]:
            return topology
        if triangulate == True:
            topology = Topology.Triangulate(topology)
        if origin == None:
            origin = Topology.Centroid(topology)
            
        vertices = Topology.Vertices(topology)
        zList = [Vertex.Z(v) for v in vertices]
        minZ = min(zList)
        maxZ = max(zList)
        h = maxZ - minZ
        new_vertices = []
        for v in vertices:
            ht = (Vertex.Z(v)-minZ)/(maxZ - minZ)
            new_rot = angleRange[0] + ht*(angleRange[1] - angleRange[0])
            orig = Vertex.ByCoordinates(Vertex.X(origin), Vertex.Y(origin), Vertex.Z(v))
            new_vertices.append(Topology.Rotate(v, origin=orig, axis=[0, 0, 1], angle=new_rot))
        return_topology = Topology.ReplaceVertices(topology, vertices, new_vertices)
        return_topology = Topology.Fix(return_topology, topologyType=Topology.TypeAsString(topology))
        return return_topology
    
    @staticmethod
    def Unflatten(topology, origin=None, direction=[0, 0, 1]):
        """
        Unflattens the input topology such that the world origin is translated to the input origin and the input topology is rotated such that the Up direction (see Vector.Up()) is aligned with the input vector.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        origin : topologic_core.Vertex , optional
            The input origin. If set to None, The object's centroid will be used to translate the world origin. The default is None.
        vector : list , optional
            The input direction vector. The input topology will be rotated such that this vector is pointed in the positive Z axis.

        Returns
        -------
        topologic_core.Topology
            The flattened topology.

        """
        from topologicpy.Vertex import Vertex
        from topologicpy.Vector import Vector

        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.Unflatten - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        if origin == None:
            origin = Vertex.Origin()
        up = Vector.Up()
        tran_mat = Vector.TransformationMatrix(up, direction)
        unflat_topology = Topology.Transform(topology, tran_mat)
        unflat_topology = Topology.Translate(unflat_topology, Vertex.X(origin), Vertex.Y(origin), Vertex.Z(origin))
        return unflat_topology
    
    @staticmethod
    def Vertices(topology):
        """
        Returns the vertices of the input topology.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.

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

        """
        return Topology.SubTopologies(topology=topology, subTopologyType="vertex")
    
    @staticmethod
    def Edges(topology):
        """
        Returns the edges of the input topology.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.

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

        """
        if Topology.IsInstance(topology, "Edge") or Topology.IsInstance(topology, "Vertex"):
            return []
        return Topology.SubTopologies(topology=topology, subTopologyType="edge")
    
    @staticmethod
    def Wires(topology):
        """
        Returns the wires of the input topology.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.

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

        """
        if Topology.IsInstance(topology, "Wire") or Topology.IsInstance(topology, "Edge") or Topology.IsInstance(topology, "Vertex"):
            return []
        return Topology.SubTopologies(topology=topology, subTopologyType="wire")
    
    @staticmethod
    def Faces(topology):
        """
        Returns the faces of the input topology.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.

        Returns
        -------
        list
            The list of faces.

        """
        if Topology.IsInstance(topology, "Face") or Topology.IsInstance(topology, "Wire") or Topology.IsInstance(topology, "Edge") or Topology.IsInstance(topology, "Vertex"):
            return []
        return Topology.SubTopologies(topology=topology, subTopologyType="face")
    
    @staticmethod
    def Shells(topology):
        """
        Returns the shells of the input topology.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.

        Returns
        -------
        list
            The list of shells.

        """
        if Topology.IsInstance(topology, "Shell") or Topology.IsInstance(topology, "Face") or Topology.IsInstance(topology, "Wire") or Topology.IsInstance(topology, "Edge") or Topology.IsInstance(topology, "Vertex"):
            return []
        return Topology.SubTopologies(topology=topology, subTopologyType="shell")
    
    @staticmethod
    def Cells(topology):
        """
        Returns the cells of the input topology.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.

        Returns
        -------
        list
            The list of cells.

        """
        if Topology.IsInstance(topology, "Cell") or Topology.IsInstance(topology, "Shell") or Topology.IsInstance(topology, "Face") or Topology.IsInstance(topology, "Wire") or Topology.IsInstance(topology, "Edge") or Topology.IsInstance(topology, "Vertex"):
            return []
        return Topology.SubTopologies(topology=topology, subTopologyType="cell")
    
    @staticmethod
    def CellComplexes(topology):
        """
        Returns the cellcomplexes of the input topology.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.

        Returns
        -------
        list
            The list of cellcomplexes.

        """
        if Topology.IsInstance(topology, "CellComplex") or Topology.IsInstance(topology, "Cell") or Topology.IsInstance(topology, "Shell") or Topology.IsInstance(topology, "Face") or Topology.IsInstance(topology, "Wire") or Topology.IsInstance(topology, "Edge") or Topology.IsInstance(topology, "Vertex"):
            return []
        return Topology.SubTopologies(topology=topology, subTopologyType="cellcomplex")
    
    @staticmethod
    def Clusters(topology):
        """
        Returns the clusters of the input topology.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.

        Returns
        -------
        list
            The list of clusters.

        """
        if not Topology.IsInstance(topology, "Cluster"):
            return []
        return Topology.SubTopologies(topology=topology, subTopologyType="cluster")
    
    @staticmethod
    def SubTopologies(topology, subTopologyType="vertex"):
        """
        Returns the subtopologies of the input topology as specified by the subTopologyType input string.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        subTopologyType : str , optional
            The requested subtopology type. This can be one of "vertex", "edge", "wire", "face", "shell", "cell", "cellcomplex", "cluster". It is case insensitive. The default is "vertex".

        Returns
        -------
        list
            The list of subtopologies.

        """
        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.SubTopologies - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        if Topology.TypeAsString(topology).lower() == subTopologyType.lower():
            return [topology]
        subTopologies = []
        if subTopologyType.lower() == "vertex":
            _ = topology.Vertices(None, subTopologies)
        elif subTopologyType.lower() == "edge":
            _ = topology.Edges(None, subTopologies)
        elif subTopologyType.lower() == "wire":
            _ = topology.Wires(None, subTopologies)
        elif subTopologyType.lower() == "face":
            _ = topology.Faces(None, subTopologies)
        elif subTopologyType.lower() == "shell":
            _ = topology.Shells(None, subTopologies)
        elif subTopologyType.lower() == "cell":
            _ = topology.Cells(None, subTopologies)
        elif subTopologyType.lower() == "cellcomplex":
            _ = topology.CellComplexes(None, subTopologies)
        elif subTopologyType.lower() == "cluster":
            _ = topology.Clusters(None, subTopologies)
        elif subTopologyType.lower() == "aperture":
            _ = topology.Apertures(subTopologies)
        if not subTopologies:
            return [] # Make sure to return an empty list instead of None
        return subTopologies

    
    @staticmethod
    def SuperTopologies(topology, hostTopology, topologyType: str = None) -> list:
        """
        Returns the supertopologies connected to the input topology.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        hostTopology : topologic_core.Topology
            The host to topology in which to search for ther supertopologies.
        topologyType : str , optional
            The topology type to search for. This can be any of "edge", "wire", "face", "shell", "cell", "cellcomplex", "cluster". It is case insensitive. If set to None, the immediate supertopology type is searched for. The default is None.

        Returns
        -------
        list
            The list of supertopologies connected to the input topology.

        """
        
        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.SuperTopologies - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
        if not Topology.IsInstance(hostTopology, "Topology"):
            print("Topology.SuperTopologies - Error: the input hostTopology parameter is not a valid topology. Returning None.")
            return None

        superTopologies = []

        if topologyType == None:
            typeID = 2*Topology.TypeID(topology)
        else:
            typeID = Topology.TypeID(topologyType)
        if Topology.Type(topology) >= typeID:
            print("Topology.SuperTopologies - Error: The input topologyType parameter is not a valid type for a super topology of the input topology. Returning None.")
            return None #The user has asked for a topology type lower than the input topology
        elif typeID == Topology.TypeID("Edge"):
            topology.Edges(hostTopology, superTopologies)
        elif typeID == Topology.TypeID("Wire"):
            topology.Wires(hostTopology, superTopologies)
        elif typeID == Topology.TypeID("Face"):
            topology.Faces(hostTopology, superTopologies)
        elif typeID == Topology.TypeID("Shell"):
            topology.Shells(hostTopology, superTopologies)
        elif typeID == Topology.TypeID("Cell"):
            topology.Cells(hostTopology, superTopologies)
        elif typeID == Topology.TypeID("CellComplex"):
            topology.CellComplexes(hostTopology, superTopologies)
        elif typeID == Topology.TypeID("Cluster"):
            topology.Cluster(hostTopology, superTopologies)
        else:
            print("Topology.SuperTopologies - Error: The input topologyType parameter is not a valid type for a super topology of the input topology. Returning None.")
            return None
        if not superTopologies:
            return [] # Make sure you return an empty list instead of None
        return superTopologies
    
    @staticmethod
    def TransferDictionaries(sources, sinks, tolerance=0.0001, numWorkers=None):
        """
        Transfers the dictionaries from the list of sources to the list of sinks.

        Parameters
        ----------
        sources : list
            The list of topologies from which to transfer the dictionaries.
        sinks : list
            The list of topologies to which to transfer the dictionaries.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.
        numWorkers : int, optional
            Number of workers run in parallel to process.

        Returns
        -------
        dict
            Returns a dictionary with the lists of sources and sinks. The keys are "sinks" and "sources".

        """
        from topologicpy.Dictionary import Dictionary
        if not isinstance(sources, list):
            print("Topology.TransferDictionaries - Error: The input sources parameter is not a valid list. Returning None.")
            return None
        if not isinstance(sinks, list):
            print("Topology.TransferDictionaries - Error: The input sinks parameter is not a valid list. Returning None.")
            return None
        if numWorkers == None:
            import multiprocessing
            numWorkers = multiprocessing.cpu_count()*2
        sources = [x for x in sources if Topology.IsInstance(x, "Topology")]
        sinks = [x for x in sinks if Topology.IsInstance(x, "Topology")]
        so_dicts = [Dictionary.PythonDictionary(Topology.Dictionary(s)) for s in sources]
        if len(sources) < 1:
            print("Topology.TransferDictionaries - Error: The input sources does not contain any valid topologies. Returning None.")
            return None
        if len(sinks) < 1:
            print("Topology.TransferDictionaries - Error: The input sinks does not contain any valid topologies. Returning None.")
            return None

        queue = Queue()
        sources_str = [Topology.BREPString(s) for s in sources]
        sink_items = [SinkItem(id(s), Topology.BREPString(s)) for s in sinks]
        mergingProcess = MergingProcess(queue, sources_str, sink_items, so_dicts)
        mergingProcess.start()

        workerProcessPool = WorkerProcessPool(numWorkers, queue, sources_str, sink_items, so_dicts, tolerance)
        workerProcessPool.startProcesses()
        workerProcessPool.join()

        queue.put_nowait(None)
        sinkMap = queue.get()
        mergingProcess.join()

        for i, sink in enumerate(sink_items):
            mapItem = sinkMap[sink.ID]
            newDict = Dictionary.ByKeysValues(mapItem.sinkKeys, mapItem.sinkValues)
            _ = sinks[i].SetDictionary(newDict)
        return {"sources": sources, "sinks": sinks}

    
    @staticmethod
    def TransferDictionariesBySelectors(topology, selectors, tranVertices=False, tranEdges=False, tranFaces=False, tranCells=False, tolerance=0.0001, numWorkers=None):
        """
        Transfers the dictionaries of the list of selectors to the subtopologies of the input topology based on the input parameters.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        selectors : list
            The list of input selectors from which to transfer the dictionaries.
        tranVertices : bool , optional
            If True transfer dictionaries to the vertices of the input topology.
        tranEdges : bool , optional
            If True transfer dictionaries to the edges of the input topology.
        tranFaces : bool , optional
            If True transfer dictionaries to the faces of the input topology.
        tranCells : bool , optional
            If True transfer dictionaries to the cells of the input topology.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.
        numWorkers : int , optional
            Number of workers run in parallel to process. The default is None which causes the algorithm to use twice the number of cpu cores in the host computer.

        Returns
        -------
        Topology
            The input topology with the dictionaries transferred to its subtopologies.

        """

        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.TransferDictionariesBySelectors - Error: The input topology parameter is not a valid topology. Returning None.")
            return None
        if not isinstance(selectors, list):
            print("Topology.TransferDictionariesBySelectors - Error: The input selectors parameter is not a valid list. Returning None.")
            return None
        if numWorkers == None:
            import multiprocessing
            numWorkers = multiprocessing.cpu_count()*2
        selectors_tmp = [x for x in selectors if Topology.IsInstance(x, "Vertex")]
        if len(selectors_tmp) < 1:
            print("Topology.TransferDictionariesBySelectors - Error: The input selectors do not contain any valid topologies. Returning None.")
            return None
        sinkEdges = []
        sinkFaces = []
        sinkCells = []
        hidimSink = Topology.HighestType(topology)
        if tranVertices == True:
            sinkVertices = []
            if Topology.Type(topology) == Topology.TypeID("Vertex"):
                sinkVertices.append(topology)
            elif hidimSink >= Topology.TypeID("Vertex"):
                topology.Vertices(None, sinkVertices)
            _ = Topology.TransferDictionaries(selectors, sinkVertices, tolerance=tolerance, numWorkers=numWorkers)
        if tranEdges == True:
            sinkEdges = []
            if Topology.Type(topology) == Topology.TypeID("Edge"):
                sinkEdges.append(topology)
            elif hidimSink >= Topology.TypeID("Edge"):
                topology.Edges(None, sinkEdges)
                _ = Topology.TransferDictionaries(selectors, sinkEdges, tolerance=tolerance, numWorkers=numWorkers)
        if tranFaces == True:
            sinkFaces = []
            if Topology.Type(topology) == Topology.TypeID("Face"):
                sinkFaces.append(topology)
            elif hidimSink >= Topology.TypeID("Face"):
                topology.Faces(None, sinkFaces)
            _ = Topology.TransferDictionaries(selectors, sinkFaces, tolerance=tolerance, numWorkers=numWorkers)
        if tranCells == True:
            sinkCells = []
            if Topology.Type(topology) == Topology.TypeID("Cell"):
                sinkCells.append(topology)
            elif hidimSink >= Topology.TypeID("Cell"):
                topology.Cells(None, sinkCells)
            _ = Topology.TransferDictionaries(selectors, sinkCells, tolerance=tolerance, numWorkers=numWorkers)
        return topology

    
    @staticmethod
    def Transform(topology, matrix):
        """
        Transforms the input topology by the input 4X4 transformation matrix.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.
        matrix : list
            The input 4x4 transformation matrix.

        Returns
        -------
        topologic_core.Topology
            The transformed topology.

        """
        kTranslationX = 0.0
        kTranslationY = 0.0
        kTranslationZ = 0.0
        kRotation11 = 1.0
        kRotation12 = 0.0
        kRotation13 = 0.0
        kRotation21 = 0.0
        kRotation22 = 1.0
        kRotation23 = 0.0
        kRotation31 = 0.0
        kRotation32 = 0.0
        kRotation33 = 1.0

        
        kRotation11 = matrix[0][0]
        kRotation12 = matrix[0][1]
        kRotation13 = matrix[0][2]
        kRotation21 = matrix[1][0]
        kRotation22 = matrix[1][1]
        kRotation23 = matrix[1][2]
        kRotation31 = matrix[2][0]
        kRotation32 = matrix[2][1]
        kRotation33 = matrix[2][2]
        kTranslationX = matrix[3][0]
        kTranslationY = matrix[3][1]
        kTranslationZ = matrix[3][2]

        return topologic.TopologyUtility.Transform(topology, kTranslationX, kTranslationY, kTranslationZ, kRotation11, kRotation12, kRotation13, kRotation21, kRotation22, kRotation23, kRotation31, kRotation32, kRotation33)

    @staticmethod
    def Translate(topology, x=0, y=0, z=0):
        """
        Translates (moves) the input topology.

        Parameters
        ----------
        topology : topologic_core.topology
            The input topology.
        x : float , optional
            The x translation value. The default is 0.
        y : float , optional
            The y translation value. The default is 0.
        z : float , optional
            The z translation value. The default is 0.

        Returns
        -------
        topologic_core.Topology
            The translated topology.

        """
        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.Translate - Error: The input topology parameter is not a valid topology. Returning None.")
            return None
        try:
            return topologic.TopologyUtility.Translate(topology, x, y, z)
        except:
            return topology
    
    @staticmethod
    def TranslateByDirectionDistance(topology, direction: list = [0, 0, 0], distance: float = 0):
        """
        Translates (moves) the input topology along the input direction by the specified distance.

        Parameters
        ----------
        topology : topologic_core.topology
            The input topology.
        direction : list , optional
            The direction vector in which the topology should be moved. The default is [0, 0, 0]
        distance : float , optional
            The distance by which the toplogy should be moved. The default is 0.

        Returns
        -------
        topologic_core.Topology
            The translated topology.

        """
        from topologicpy.Vector import Vector
        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.TranslateByDirectionDistance - Error: The input topology parameter is not a valid topology. Returning None.")
            return None
        v = Vector.SetMagnitude(direction, distance)
        return Topology.Translate(topology, v[0], v[1], v[2])

    
    @staticmethod
    def Triangulate(topology, transferDictionaries: bool = False, mode: int = 0, meshSize: float = None, tolerance: float = 0.0001):
        """
        Triangulates the input topology.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topologgy.
        transferDictionaries : bool , optional
            If set to True, the dictionaries of the faces in the input topology will be transferred to the created triangular faces. The default is False.
        mode : int , optional
            The desired mode of meshing algorithm. Several options are available:
            0: Classic
            1: MeshAdapt
            3: Initial Mesh Only
            5: Delaunay
            6: Frontal-Delaunay
            7: BAMG
            8: Fontal-Delaunay for Quads
            9: Packing of Parallelograms
            All options other than 0 (Classic) use the gmsh library. See https://gmsh.info/doc/texinfo/gmsh.html#Mesh-options
            WARNING: The options that use gmsh can be very time consuming and can create very heavy geometry.
        meshSize : float , optional
            The desired size of the mesh when using the "mesh" option. If set to None, it will be
            calculated automatically and set to 10% of the overall size of the face.
        tolerance : float , optional
            The desired tolerance. The default is 0.0001.

        Returns
        -------
        topologic_core.Topology
            The triangulated topology.

        """
        from topologicpy.Face import Face
        from topologicpy.Shell import Shell
        from topologicpy.Cell import Cell
        from topologicpy.CellComplex import CellComplex
        from topologicpy.Cluster import Cluster

        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.Triangulate - Error: The input parameter is not a valid topology. Returning None.")
            return None
        t = Topology.Type(topology)
        if (t == Topology.TypeID("Vertex")) or (t == Topology.TypeID("Edge")) or (t == Topology.TypeID("Wire")):
            return topology
        elif t == Topology.TypeID("Cluster"):
            temp_topologies = []
            cellComplexes = Topology.SubTopologies(topology, subTopologyType="cellcomplex") or []
            for cc in cellComplexes:
                temp_topologies.append(Topology.Triangulate(cc, transferDictionaries=transferDictionaries, mode=mode, meshSize=meshSize, tolerance=tolerance))
            cells = Cluster.FreeCells(topology, tolerance=tolerance) or []
            for c in cells:
                temp_topologies.append(Topology.Triangulate(c, transferDictionaries=transferDictionaries, mode=mode, meshSize=meshSize, tolerance=tolerance))
            shells = Cluster.FreeShells(topology, tolerance=tolerance) or []
            for s in shells:
                temp_topologies.append(Topology.Triangulate(s, transferDictionaries=transferDictionaries, mode=mode, meshSize=meshSize, tolerance=tolerance))
            faces = Cluster.FreeFaces(topology, tolerance=tolerance) or []
            for f in faces:
                temp_topologies.append(Topology.Triangulate(f, transferDictionaries=transferDictionaries, mode=mode, meshSize=meshSize, tolerance=tolerance))
            if len(temp_topologies) > 0:
                return Cluster.ByTopologies(temp_topologies)
            else:
                return topology
        topologyFaces = []
        _ = topology.Faces(None, topologyFaces)
        faceTriangles = []
        selectors = []
        for aFace in topologyFaces:
            if len(Topology.Vertices(aFace)) > 3:
                triFaces = Face.Triangulate(aFace, mode=mode, meshSize=meshSize, tolerance=tolerance)
            else:
                triFaces = [aFace]
            for triFace in triFaces:
                if transferDictionaries:
                    selectors.append(Topology.SetDictionary(Face.Centroid(triFace), Topology.Dictionary(aFace)))
                faceTriangles.append(triFace)
        if t == Topology.TypeID("Face") or t == Topology.TypeID("Shell"): # Face or Shell
            return_topology = Shell.ByFaces(faceTriangles, tolerance=tolerance)
            if transferDictionaries and not return_topology == None:
                return_topology = Topology.TransferDictionariesBySelectors(return_topology, selectors, tranFaces=True, tolerance=tolerance)
        elif t == Topology.TypeID("Cell"): # Cell
            return_topology = Cell.ByFaces(faceTriangles, tolerance=tolerance)
            if transferDictionaries and not return_topology == None:
                return_topology = Topology.TransferDictionariesBySelectors(return_topology, selectors, tranFaces=True, tolerance=tolerance)
        elif t == Topology.TypeID("CellComplex"): #CellComplex
            return_topology = CellComplex.ByFaces(faceTriangles, tolerance=tolerance)
            if transferDictionaries and not return_topology == None:
                return_topology = Topology.TransferDictionariesBySelectors(return_topology, selectors, tranFaces=True, tolerance=tolerance)
        
        if return_topology == None:
            return_topology = Topology.SelfMerge(Cluster.ByTopologies(faceTriangles), tolerance=tolerance)
            if transferDictionaries == True:
                return_topology = Topology.TransferDictionariesBySelectors(return_topology, selectors, tranFaces=True, tolerance=tolerance)
        
        return return_topology

    
    @staticmethod
    def Type(topology):
        """
        Returns the type of the input topology.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.

        Returns
        -------
        int
            The type of the input topology.

        """
        #if not Topology.IsInstance(topology, "Topology"):
            #print("Topology.Type - Error: The input topology parameter is not a valid topology. Returning None.")
            #return None
        return topology.Type()
    
    @staticmethod
    def TypeAsString(topology):
        """
        Returns the type of the input topology as a string.

        Parameters
        ----------
        topology : topologic_core.Topology
            The input topology.

        Returns
        -------
        str
            The type of the topology as a string.

        """
        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.TypeAsString - Error: The input topology parameter is not a valid topology. Returning None.")
            return None
        return topology.GetTypeAsString()
    
    @staticmethod
    def TypeID(name : str = None) -> int:
        """
        Returns the type id of the input name string.

        Parameters
        ----------
        name : str , optional
            The input class name string. This could be one of:
                "vertex",
                "edge",
                "wire",
                "face",
                "shell",
                "cell",
                "cellcomplex",
                "cluster",
                "aperture",
                "context",
                "dictionary",
                "graph",
                "topology"
            
            It is case insensitive. The default is None.

        Returns
        -------
        int
            The type id of the input topologyType string.

        """

        if not isinstance(name, str):
            print("Topology.TypeID - Error: The input topologyType parameter is not a valid string. Returning None.")
            return None
        name = name.lower()
        if not name in ["vertex", "edge", "wire",
                                "face", "shell", "cell",
                                "cellcomplex", "cluster", "aperture",
                                "context", "dictionary", "graph", "topology"]:
            print("Topology.TypeID - Error: The input name parameter is not a recognized string. Returning None.")
            return None
        typeID = None
        if name == "vertex":
            typeID = 1
        elif name == "edge":
            typeID = 2
        elif name == "wire":
            typeID = 4
        elif name == "face":
            typeID = 8
        elif name == "shell":
            typeID = 16
        elif name == "cell":
            typeID = 32
        elif name == "cellComplex":
            typeID = 64
        elif name == "cluster":
            typeID = 128
        elif name == "aperture":
            typeID = 256
        elif name == "context":
            typeID = 512
        elif name == "dictionary":
            typeID = 1024
        elif name == "graph":
            typeID = 2048
        elif name == "topology":
            typeID = 4096
        return typeID

Static methods

def AddApertures(topology, apertures, exclusive=False, subTopologyType=None, tolerance=0.001)

Adds the input list of apertures to the input topology or to its subtpologies based on the input subTopologyType.

Parameters

topology : topologic_core.Topology
The input topology.
apertures : list
The input list of apertures.
exclusive : bool , optional
If set to True, one (sub)topology will accept only one aperture. Otherwise, one (sub)topology can accept multiple apertures. The default is False.
subTopologyType : string , optional
The subtopology type to which to add the apertures. This can be "cell", "face", "edge", or "vertex". It is case insensitive. If set to None, the apertures will be added to the input topology. The default is None.
tolerance : float , optional
The desired tolerance. The default is 0.001. This is larger than the usual 0.0001 as it seems to work better.

Returns

topologic_core.Topology
The input topology with the apertures added to it.
Expand source code
@staticmethod
def AddApertures(topology, apertures, exclusive=False, subTopologyType=None, tolerance=0.001):
    """
    Adds the input list of apertures to the input topology or to its subtpologies based on the input subTopologyType.

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.
    apertures : list
        The input list of apertures.
    exclusive : bool , optional
        If set to True, one (sub)topology will accept only one aperture. Otherwise, one (sub)topology can accept multiple apertures. The default is False.
    subTopologyType : string , optional
        The subtopology type to which to add the apertures. This can be "cell", "face", "edge", or "vertex". It is case insensitive. If set to None, the apertures will be added to the input topology. The default is None.
    tolerance : float , optional
        The desired tolerance. The default is 0.001. This is larger than the usual 0.0001 as it seems to work better.

    Returns
    -------
    topologic_core.Topology
        The input topology with the apertures added to it.

    """
    from topologicpy.Vertex import Vertex
    from topologicpy.Cluster import Cluster
    from topologicpy.Aperture import Aperture
    def processApertures(subTopologies, apertures, exclusive=False, tolerance=0.001):
        usedTopologies = []
        for subTopology in subTopologies:
                usedTopologies.append(0)
        ap = 1
        for aperture in apertures:
            apCenter = Topology.InternalVertex(aperture, tolerance)
            for i in range(len(subTopologies)):
                subTopology = subTopologies[i]
                if exclusive == True and usedTopologies[i] == 1:
                    continue
                if Vertex.Distance(apCenter, subTopology) < tolerance:
                    context = topologic.Context.ByTopologyParameters(subTopology, 0.5, 0.5, 0.5)
                    _ = Aperture.ByTopologyContext(aperture, context)
                    if exclusive == True:
                        usedTopologies[i] = 1
            ap = ap + 1
        return None

    if not Topology.IsInstance(topology, "Topology"):
        print("Topology.AddApertures - Error: The input topology parameter is not a valid topology. Returning None.")
        return None
    if not apertures:
        return topology
    if not isinstance(apertures, list):
        print("Topology.AddApertures - Error: the input apertures parameter is not a list. Returning None.")
        return None
    apertures = [x for x in apertures if Topology.IsInstance(x , "Topology")]
    if len(apertures) < 1:
        return topology
    if not subTopologyType:
        subTopologyType = "self"
    if not subTopologyType.lower() in ["self", "cell", "face", "edge", "vertex"]:
        print("Topology.AddApertures - Error: the input subtopology type parameter is not a recognized type. Returning None.")
        return None
    if subTopologyType.lower() == "self":
        subTopologies = [topology]
    else:
        subTopologies = Topology.SubTopologies(topology, subTopologyType)
    processApertures(subTopologies, apertures, exclusive, tolerance)
    return topology
def AddContent(topology, contents, subTopologyType=None, tolerance=0.0001)

Adds the input list of contents to the input topology or to its subtpologies based on the input subTopologyType.

Parameters

topology : topologic_core.Topology
The input topology.
contents : list or topologic_core.Topology
The input list of contents (of type topologic_core.Topology). A single topology is also accepted as input.
subTopologyType : string , optional
The subtopology type to which to add the contents. This can be "cellcomplex", "cell", "shell", "face", "wire", "edge", or "vertex". It is case insensitive. If set to None, the contents will be added to the input topology. The default is None.
tolerance : float , optional
The desired tolerance. The default is 0.0001.

Returns

topologic_core.Topology
The input topology with the contents added to it.
Expand source code
@staticmethod
def AddContent(topology, contents, subTopologyType=None, tolerance=0.0001):
    """
    Adds the input list of contents to the input topology or to its subtpologies based on the input subTopologyType.

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.
    contents : list or topologic_core.Topology
        The input list of contents (of type topologic_core.Topology). A single topology is also accepted as input.
    subTopologyType : string , optional
        The subtopology type to which to add the contents. This can be "cellcomplex", "cell", "shell", "face", "wire", "edge", or "vertex". It is case insensitive. If set to None, the contents will be added to the input topology. The default is None.
    tolerance : float , optional
        The desired tolerance. The default is 0.0001.

    Returns
    -------
    topologic_core.Topology
        The input topology with the contents added to it.

    """
    if not Topology.IsInstance(topology, "Topology"):
        print("Topology.AddContent - Error: the input topology parameter is not a valid topology. Returning None.")
        return None
    if not contents:
        return topology
    if not isinstance(contents, list):
        contents = [contents]
    if not isinstance(contents, list):
        print("Topology.AddContent - Error: the input contents parameter is not a list. Returning None.")
        return None
    contents = [x for x in contents if Topology.IsInstance(x, "Topology")]
    if len(contents) < 1:
        return topology
    if not subTopologyType:
        subTopologyType = "self"
    if not subTopologyType.lower() in ["self", "cellcomplex", "cell", "shell", "face", "wire", "edge", "vertex"]:
        print("Topology.AddContent - Error: the input subtopology type parameter is not a recognized type. Returning None.")
        return None
    if subTopologyType.lower() == "self":
        t = 0
    else:
        t = Topology.TypeID(subTopologyType)
    return topology.AddContents(contents, t)
def AddDictionary(topology, dictionary)

Adds the input dictionary to the input topology.

Parameters

topology : topologic_core.Topology
The input topology.
dictionary : topologic_core.Dictionary
The input dictionary.

Returns

topologic_core.Topology
The input topology with the input dictionary added to it.
Expand source code
@staticmethod
def AddDictionary(topology, dictionary):
    """
    Adds the input dictionary to the input topology.

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.
    dictionary : topologic_core.Dictionary
        The input dictionary.

    Returns
    -------
    topologic_core.Topology
        The input topology with the input dictionary added to it.

    """
    
    from topologicpy.Dictionary import Dictionary
    if not Topology.IsInstance(topology, "Topology"):
        print("Topology.AddDictionary - Error: the input topology parameter is not a valid topology. Returning None.")
        return None
    if not Topology.IsInstance(dictionary, "Dictionary"):
        print("Topology.AddDictionary - Error: the input dictionary parameter is not a dictionary. Returning None.")
        return None
    tDict = Topology.Dictionary(topology)
    if len(tDict.Keys()) < 1:
        _ = topology.SetDictionary(dictionary)
    else:
        newDict = Dictionary.ByMergedDictionaries([tDict, dictionary])
        if newDict:
            _ = topology.SetDictionary(newDict)
    return topology
def AdjacentTopologies(topology, hostTopology, topologyType=None)

Returns the topologies, as specified by the input topology type, adjacent to the input topology witin the input host topology.

Parameters

topology : topologic_core.Topology
The input topology.
hostTopology : topologic_core.Topology
The host topology in which to search.
topologyType : str
The type of topology for which to search. This can be one of "vertex", "edge", "wire", "face", "shell", "cell", "cellcomplex". It is case-insensitive. If it is set to None, the type will be set to the same type as the input topology. The default is None.

Returns

adjacentTopologies : list
The list of adjacent topologies.
Expand source code
@staticmethod
def AdjacentTopologies(topology, hostTopology, topologyType=None):
    """
    Returns the topologies, as specified by the input topology type, adjacent to the input topology witin the input host topology.

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.
    hostTopology : topologic_core.Topology
        The host topology in which to search.
    topologyType : str
        The type of topology for which to search. This can be one of "vertex", "edge", "wire", "face", "shell", "cell", "cellcomplex". It is case-insensitive. If it is set to None, the type will be set to the same type as the input topology. The default is None.

    Returns
    -------
    adjacentTopologies : list
        The list of adjacent topologies.

    """
    if not Topology.IsInstance(topology, "Topology"):
        print("Topology.AdjacentTopologies - Error: the input topology parameter is not a valid topology. Returning None.")
        return None
    if not Topology.IsInstance(hostTopology, "Topology"):
        print("Topology.AdjacentTopologies - Error: the input hostTopology parameter is not a valid topology. Returning None.")
        return None
    if not topologyType:
        topologyType = Topology.TypeAsString(topology).lower()
    if not isinstance(topologyType, str):
        print("Topology.AdjacentTopologies - Error: the input topologyType parameter is not a string. Returning None.")
        return None
    if not topologyType.lower() in ["vertex", "edge", "wire", "face", "shell", "cell", "cellcomplex"]:
        print("Topology.AdjacentTopologies - Error: the input topologyType parameter is not a recognized type. Returning None.")
        return None
    adjacentTopologies = []
    error = False
    if Topology.IsInstance(topology, "Vertex"):
        if topologyType.lower() == "vertex":
            try:
                _ = topology.AdjacentVertices(hostTopology, adjacentTopologies)
            except:
                try:
                    _ = topology.Vertices(hostTopology, adjacentTopologies)
                except:
                    error = True
        elif topologyType.lower() == "edge":
            try:
                _ = topologic.VertexUtility.AdjacentEdges(topology, hostTopology, adjacentTopologies)
            except:
                try:
                    _ = topology.Edges(hostTopology, adjacentTopologies)
                except:
                    error = True
        elif topologyType.lower() == "wire":
            try:
                _ = topologic.VertexUtility.AdjacentWires(topology, hostTopology, adjacentTopologies)
            except:
                try:
                    _ = topology.Wires(hostTopology, adjacentTopologies)
                except:
                    error = True
        elif topologyType.lower() == "face":
            try:
                _ = topologic.VertexUtility.AdjacentFaces(topology, hostTopology, adjacentTopologies)
            except:
                try:
                    _ = topology.Faces(hostTopology, adjacentTopologies)
                except:
                    error = True
        elif topologyType.lower() == "shell":
            try:
                _ = topologic.VertexUtility.AdjacentShells(topology, hostTopology, adjacentTopologies)
            except:
                try:
                    _ = topology.Shells(hostTopology, adjacentTopologies)
                except:
                    error = True
        elif topologyType.lower() == "cell":
            try:
                _ = topologic.VertexUtility.AdjacentCells(topology, hostTopology, adjacentTopologies)
            except:
                try:
                    _ = topology.Cells(hostTopology, adjacentTopologies)
                except:
                    error = True
        elif topologyType.lower() == "cellcomplex":
            try:
                _ = topologic.VertexUtility.AdjacentCellComplexes(topology, hostTopology, adjacentTopologies)
            except:
                try:
                    _ = topology.CellComplexes(hostTopology, adjacentTopologies)
                except:
                    error = True
    elif Topology.IsInstance(topology, "Edge"):
        if topologyType.lower() == "vertex":
            try:
                _ = topology.Vertices(hostTopology, adjacentTopologies)
            except:
                error = True
        elif topologyType.lower() == "edge":
            try:
                _ = topology.AdjacentEdges(hostTopology, adjacentTopologies)
            except:
                error = True
        elif topologyType.lower() == "wire":
            try:
                _ = topologic.EdgeUtility.AdjacentWires(topology, adjacentTopologies)
            except:
                try:
                    _ = topology.Wires(hostTopology, adjacentTopologies)
                except:
                    error = True
        elif topologyType.lower() == "face":
            try:
                _ = topologic.EdgeUtility.AdjacentFaces(topology, adjacentTopologies)
            except:
                try:
                    _ = topology.Faces(hostTopology, adjacentTopologies)
                except:
                    error = True
        elif topologyType.lower() == "shell":
            try:
                _ = topologic.EdgeUtility.AdjacentShells(adjacentTopologies)
            except:
                try:
                    _ = topology.Shells(hostTopology, adjacentTopologies)
                except:
                    error = True
        elif topologyType.lower() == "cell":
            try:
                _ = topologic.EdgeUtility.AdjacentCells(adjacentTopologies)
            except:
                try:
                    _ = topology.Cells(hostTopology, adjacentTopologies)
                except:
                    error = True
        elif topologyType.lower() == "cellcomplex":
            try:
                _ = topologic.EdgeUtility.AdjacentCellComplexes(adjacentTopologies)
            except:
                try:
                    _ = topology.CellComplexes(hostTopology, adjacentTopologies)
                except:
                    error = True
    elif Topology.IsInstance(topology, "Wire"):
        if topologyType.lower() == "vertex":
            try:
                _ = topologic.WireUtility.AdjacentVertices(topology, adjacentTopologies)
            except:
                try:
                    _ = topology.Vertices(hostTopology, adjacentTopologies)
                except:
                    error = True
        elif topologyType.lower() == "edge":
            try:
                _ = topologic.WireUtility.AdjacentEdges(topology, adjacentTopologies)
            except:
                try:
                    _ = topology.Edges(hostTopology, adjacentTopologies)
                except:
                    error = True
        elif topologyType.lower() == "wire":
            try:
                _ = topologic.WireUtility.AdjacentWires(topology, adjacentTopologies)
            except:
                try:
                    _ = topology.Wires(hostTopology, adjacentTopologies)
                except:
                    error = True
        elif topologyType.lower() == "face":
            try:
                _ = topologic.WireUtility.AdjacentFaces(topology, adjacentTopologies)
            except:
                try:
                    _ = topology.Faces(hostTopology, adjacentTopologies)
                except:
                    error = True
        elif topologyType.lower() == "shell":
            try:
                _ = topologic.WireUtility.AdjacentShells(adjacentTopologies)
            except:
                try:
                    _ = topology.Shells(hostTopology, adjacentTopologies)
                except:
                    error = True
        elif topologyType.lower() == "cell":
            try:
                _ = topologic.WireUtility.AdjacentCells(adjacentTopologies)
            except:
                try:
                    _ = topology.Cells(hostTopology, adjacentTopologies)
                except:
                    error = True
        elif topologyType.lower() == "cellcomplex":
            try:
                _ = topologic.WireUtility.AdjacentCellComplexes(adjacentTopologies)
            except:
                try:
                    _ = topology.CellComplexes(hostTopology, adjacentTopologies)
                except:
                    error = True
    elif Topology.IsInstance(topology, "Face"):
        if topologyType.lower() == "vertex":
            try:
                _ = topologic.FaceUtility.AdjacentVertices(topology, adjacentTopologies)
            except:
                try:
                    _ = topology.Vertices(hostTopology, adjacentTopologies)
                except:
                    error = True
        elif topologyType.lower() == "edge":
            try:
                _ = topologic.FaceUtility.AdjacentEdges(topology, adjacentTopologies)
            except:
                try:
                    _ = topology.Edges(hostTopology, adjacentTopologies)
                except:
                    error = True
        elif topologyType.lower() == "wire":
            try:
                _ = topologic.FaceUtility.AdjacentWires(topology, adjacentTopologies)
            except:
                try:
                    _ = topology.Wires(hostTopology, adjacentTopologies)
                except:
                    error = True
        elif topologyType.lower() == "face":
            _ = topology.AdjacentFaces(hostTopology, adjacentTopologies)
        elif topologyType.lower() == "shell":
            try:
                _ = topologic.FaceUtility.AdjacentShells(adjacentTopologies)
            except:
                try:
                    _ = topology.Shells(hostTopology, adjacentTopologies)
                except:
                    error = True
        elif topologyType.lower() == "cell":
            try:
                _ = topologic.FaceUtility.AdjacentCells(adjacentTopologies)
            except:
                try:
                    _ = topology.Cells(hostTopology, adjacentTopologies)
                except:
                    error = True
        elif topologyType.lower() == "cellcomplex":
            try:
                _ = topologic.FaceUtility.AdjacentCellComplexes(adjacentTopologies)
            except:
                try:
                    _ = topology.CellComplexes(hostTopology, adjacentTopologies)
                except:
                    error = True
    elif Topology.IsInstance(topology, "Shell"):
        if topologyType.lower() == "vertex":
            try:
                _ = topologic.ShellUtility.AdjacentVertices(topology, adjacentTopologies)
            except:
                try:
                    _ = topology.Vertices(hostTopology, adjacentTopologies)
                except:
                    error = True
        elif topologyType.lower() == "edge":
            try:
                _ = topologic.ShellUtility.AdjacentEdges(topology, adjacentTopologies)
            except:
                try:
                    _ = topology.Edges(hostTopology, adjacentTopologies)
                except:
                    error = True
        elif topologyType.lower() == "wire":
            try:
                _ = topologic.ShellUtility.AdjacentWires(topology, adjacentTopologies)
            except:
                try:
                    _ = topology.Wires(hostTopology, adjacentTopologies)
                except:
                    error = True
        elif topologyType.lower() == "face":
            try:
                _ = topologic.ShellUtility.AdjacentFaces(topology, adjacentTopologies)
            except:
                try:
                    _ = topology.Faces(hostTopology, adjacentTopologies)
                except:
                    error = True
        elif topologyType.lower() == "shell":
            try:
                _ = topologic.ShellUtility.AdjacentShells(adjacentTopologies)
            except:
                try:
                    _ = topology.Shells(hostTopology, adjacentTopologies)
                except:
                    error = True
        elif topologyType.lower() == "cell":
            try:
                _ = topologic.ShellUtility.AdjacentCells(adjacentTopologies)
            except:
                try:
                    _ = topology.Cells(hostTopology, adjacentTopologies)
                except:
                    error = True
        elif topologyType.lower() == "cellcomplex":
            try:
                _ = topologic.ShellUtility.AdjacentCellComplexes(adjacentTopologies)
            except:
                try:
                    _ = topology.CellComplexes(hostTopology, adjacentTopologies)
                except:
                    error = True
    elif Topology.IsInstance(topology, "Cell"):
        if topologyType.lower() == "vertex":
            try:
                _ = topologic.CellUtility.AdjacentVertices(topology, adjacentTopologies)
            except:
                try:
                    _ = topology.Vertices(hostTopology, adjacentTopologies)
                except:
                    error = True
        elif topologyType.lower() == "edge":
            try:
                _ = topologic.CellUtility.AdjacentEdges(topology, adjacentTopologies)
            except:
                try:
                    _ = topology.Edges(hostTopology, adjacentTopologies)
                except:
                    error = True
        elif topologyType.lower() == "wire":
            try:
                _ = topologic.CellUtility.AdjacentWires(topology, adjacentTopologies)
            except:
                try:
                    _ = topology.Wires(hostTopology, adjacentTopologies)
                except:
                    error = True
        elif topologyType.lower() == "face":
            try:
                _ = topologic.CellUtility.AdjacentFaces(topology, adjacentTopologies)
            except:
                try:
                    _ = topology.Faces(hostTopology, adjacentTopologies)
                except:
                    error = True
        elif topologyType.lower() == "shell":
            try:
                _ = topologic.CellUtility.AdjacentShells(adjacentTopologies)
            except:
                try:
                    _ = topology.Shells(hostTopology, adjacentTopologies)
                except:
                    error = True
        elif topologyType.lower() == "cell":
            try:
                _ = topology.AdjacentCells(hostTopology, adjacentTopologies)
            except:
                try:
                    _ = topology.Cells(hostTopology, adjacentTopologies)
                except:
                    error = True
        elif topologyType.lower() == "cellcomplex":
            try:
                _ = topologic.CellUtility.AdjacentCellComplexes(adjacentTopologies)
            except:
                try:
                    _ = topology.CellComplexes(hostTopology, adjacentTopologies)
                except:
                    error = True
    elif Topology.IsInstance(topology, "CellComplex"):
        if topologyType.lower() == "vertex":
            try:
                _ = topology.Vertices(hostTopology, adjacentTopologies)
            except:
                error = True
        elif topologyType.lower() == "edge":
            try:
                _ = topology.Edges(hostTopology, adjacentTopologies)
            except:
                error = True
        elif topologyType.lower() == "wire":
            try:
                _ = topology.Wires(hostTopology, adjacentTopologies)
            except:
                error = True
        elif topologyType.lower() == "face":
            try:
                _ = topology.Faces(hostTopology, adjacentTopologies)
            except:
                error = True
        elif topologyType.lower() == "shell":
            try:
                _ = topology.Shells(hostTopology, adjacentTopologies)
            except:
                error = True
        elif topologyType.lower() == "cell":
            try:
                _ = topology.Cells(hostTopology, adjacentTopologies)
            except:
                error = True
        elif topologyType.lower() == "cellcomplex":
            raise Exception("Topology.AdjacentTopologies - Error: Cannot search for adjacent topologies of a CellComplex")
    elif Topology.IsInstance(topology, "Cluster"):
        raise Exception("Topology.AdjacentTopologies - Error: Cannot search for adjacent topologies of a Cluster")
    if error:
        raise Exception("Topology.AdjacentTopologies - Error: Failure in search for adjacent topologies of type "+topologyType)
    return adjacentTopologies
def Analyze(topology)

Returns an analysis string that describes the input topology.

Parameters

topology : topologic_core.Topology
The input topology.

Returns

str
The analysis string.
Expand source code
@staticmethod
def Analyze(topology):
    """
    Returns an analysis string that describes the input topology.

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.

    Returns
    -------
    str
        The analysis string.

    """
    if not Topology.IsInstance(topology, "Topology"):
        print("Topology.Analyze - Error: the input topology parameter is not a valid topology. Returning None.")
        return None
    return topologic.Topology.Analyze(topology)
def ApertureTopologies(topology, subTopologyType=None)

Returns the aperture topologies of the input topology.

Parameters

topology : topologic_core.Topology
The input topology.
subTopologyType : string , optional
The subtopology type from which to retrieve the apertures. This can be "cell", "face", "edge", or "vertex" or "all". It is case insensitive. If set to "all", then all apertures will be returned. If set to None, the apertures will be retrieved only from the input topology. The default is None.

Returns

list
The list of aperture topologies found in the input topology.
Expand source code
@staticmethod
def ApertureTopologies(topology, subTopologyType=None):
    """
    Returns the aperture topologies of the input topology.

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.
    subTopologyType : string , optional
        The subtopology type from which to retrieve the apertures. This can be "cell", "face", "edge", or "vertex" or "all". It is case insensitive. If set to "all", then all apertures will be returned. If set to None, the apertures will be retrieved only from the input topology. The default is None.
   
    Returns
    -------
    list
        The list of aperture topologies found in the input topology.

    """
    from topologicpy.Aperture import Aperture
    if not Topology.IsInstance(topology, "Topology"):
        print("Topology.ApertureTopologies - Error: the input topology parameter is not a valid topology. Returning None.")
        return None
    apertures = Topology.Apertures(topology=topology, subTopologyType=subTopologyType)
    apTopologies = []
    for aperture in apertures:
        apTopologies.append(Aperture.Topology(aperture))
    return apTopologies
def Apertures(topology, subTopologyType=None)

Returns the apertures of the input topology.

Parameters

topology : topologic_core.Topology
The input topology.
subTopologyType : string , optional
The subtopology type from which to retrieve the apertures. This can be "cell", "face", "edge", or "vertex" or "all". It is case insensitive. If set to "all", then all apertures will be returned. If set to None, the apertures will be retrieved only from the input topology. The default is None.

Returns

list
The list of apertures belonging to the input topology.
Expand source code
@staticmethod
def Apertures(topology, subTopologyType=None):
    """
    Returns the apertures of the input topology.
    
    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.
    subTopologyType : string , optional
        The subtopology type from which to retrieve the apertures. This can be "cell", "face", "edge", or "vertex" or "all". It is case insensitive. If set to "all", then all apertures will be returned. If set to None, the apertures will be retrieved only from the input topology. The default is None.
    
    Returns
    -------
    list
        The list of apertures belonging to the input topology.

    """
    if not Topology.IsInstance(topology, "Topology"):
        print("Topology.Apertures - Error: the input topology parameter is not a valid topology. Returning None.")
        return None
    
    apertures = []
    subTopologies = []
    if not subTopologyType:
        _ = topology.Apertures(apertures)
    elif subTopologyType.lower() == "vertex":
        subTopologies = Topology.Vertices(topology)
    elif subTopologyType.lower() == "edge":
        subTopologies = Topology.Edges(topology)
    elif subTopologyType.lower() == "face":
        subTopologies = Topology.Faces(topology)
    elif subTopologyType.lower() == "cell":
        subTopologies = Topology.Cells(topology)
    elif subTopologyType.lower() == "all":
        _ = topology.Apertures(apertures)
        subTopologies = Topology.Vertices(topology)
        subTopologies += Topology.Edges(topology)
        subTopologies += Topology.Faces(topology)
        subTopologies += Topology.Cells(topology)
    else:
        print("Topology.Apertures - Error: the input subtopologyType parameter is not a recognized type. Returning None.")
        return None
    for subTopology in subTopologies:
        apertures += Topology.Apertures(subTopology, subTopologyType=None)
    return apertures
def BREPString(topology, version=3)

Returns the BRep string of the input topology.

Parameters

topology : topologic_core.Topology
The input topology.
version : int , optional
The desired BRep version number. The default is 3.

Returns

str
The BREP string.
Expand source code
@staticmethod
def BREPString(topology, version=3):
    """
    Returns the BRep string of the input topology.

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.
    version : int , optional
        The desired BRep version number. The default is 3.

    Returns
    -------
    str
        The BREP string.

    """
    if not Topology.IsInstance(topology, "Topology"):
        print("Topology.BREPString - Error: the input topology parameter is not a valid topology. Returning None.")
        return None
    st = None
    try:
        st = topologic.Topology.String(topology, version)
    except:
        try:
            st = topologic.Topology.BREPString(topology, version)
        except:
            st = None
    return st
def Boolean(topologyA, topologyB, operation='union', tranDict=False, tolerance=0.0001)

Execute the input boolean operation type on the input operand topologies and return the result. See https://en.wikipedia.org/wiki/Boolean_operation.

Parameters

topologyA : topologic_core.Topology
The first input topology.
topologyB : topologic_core.Topology
The second input topology.
operation : str , optional
The boolean operation. This can be one of "union", "difference", "intersect", "symdif", "merge", "slice", "impose", "imprint". It is case insensitive. The default is "union".
tranDict : bool , optional
If set to True the dictionaries of the operands are merged and transferred to the result. The default is False.
tolerance : float , optional
The desired tolerance. The default is 0.0001.

Returns

topologic_core.Topology
the resultant topology.
Expand source code
@staticmethod
def Boolean(topologyA, topologyB, operation="union", tranDict=False, tolerance=0.0001):
    """
    Execute the input boolean operation type on the input operand topologies and return the result. See https://en.wikipedia.org/wiki/Boolean_operation.

    Parameters
    ----------
    topologyA : topologic_core.Topology
        The first input topology.
    topologyB : topologic_core.Topology
        The second input topology.
    operation : str , optional
        The boolean operation. This can be one of "union", "difference", "intersect", "symdif", "merge", "slice", "impose", "imprint". It is case insensitive. The default is "union".
    tranDict : bool , optional
        If set to True the dictionaries of the operands are merged and transferred to the result. The default is False.
    tolerance : float , optional
        The desired tolerance. The default is 0.0001.

    Returns
    -------
    topologic_core.Topology
        the resultant topology.

    """
    from topologicpy.Dictionary import Dictionary
    if not Topology.IsInstance(topologyA, "Topology"):
        print("Topology.Boolean - Error: the input topologyA parameter is not a valid topology. Returning None.")
        return None
    if not Topology.IsInstance(topologyB, "Topology"):
        print("Topology.Boolean - Error: the input topologyB parameter is not a valid topology. Returning None.")
        return None
    if not isinstance(operation, str):
        print("Topology.Boolean - Error: the input operation parameter is not a valid string. Returning None.")
        return None
    if not operation.lower() in ["union", "difference", "intersect", "symdif", "merge", "slice", "impose", "imprint"]:
        print("Topology.Boolean - Error: the input operation parameter is not a recognized operation. Returning None.")
        return None
    if not isinstance(tranDict, bool):
        print("Topology.Boolean - Error: the input tranDict parameter is not a valid boolean. Returning None.")
        return None
    topologyC = None
    #topologyC = Topology.Intersect(topologyA, topologyB)
    #try:
    if operation.lower() == "union":
        topologyC = topologyA.Union(topologyB, False)
    elif operation.lower() == "difference":
        topologyC = topologyA.Difference(topologyB, False)
    elif operation.lower() == "intersect": #Intersect in Topologic (Core) is faulty. This is a workaround.
        #topologyC = topologyA.Intersect(topologyB, False)
        
        topologyC = Topology.Intersect(topologyA, topologyB)
    elif operation.lower() == "symdif":
        topologyC = topologyA.XOR(topologyB, False)
    elif operation.lower() == "merge":
        topologyC = topologyA.Merge(topologyB, False)
    elif operation.lower() == "slice":
        topologyC = topologyA.Slice(topologyB, False)
    elif operation.lower() == "impose":
        topologyC = topologyA.Impose(topologyB, False)
    elif operation.lower() == "imprint":
        topologyC = topologyA.Imprint(topologyB, False)
    else:
        print("1. Topology.Boolean - Error: the boolean operation failed. Returning None.")
        return None
    #except:
        #print("2. Topology.Boolean - Error: the boolean operation failed. Returning None.")
        #return None
    if tranDict == True:
        sourceVertices = []
        sourceEdges = []
        sourceFaces = []
        sourceCells = []
        sinkVertices = []
        sinkEdges = []
        sinkFaces = []
        sinkCells = []
        hidimA = Topology.HighestType(topologyA)
        hidimB = Topology.HighestType(topologyB)
        hidimC = Topology.HighestType(topologyC)

        if Topology.Type(topologyA) == Topology.TypeID("Vertex"):
            sourceVertices += [topologyA]
        elif hidimA >= Topology.TypeID("Vertex"):
            sourceVertices += Topology.Vertices(topologyA)
        if Topology.Type(topologyB) == Topology.TypeID("Vertex"):
            sourceVertices += [topologyB]
        elif hidimB >= Topology.TypeID("Vertex"):
            sourceVertices += Topology.Vertices(topologyB)
        if Topology.Type(topologyC) == Topology.TypeID("Vertex"):
            sinkVertices = [topologyC]
        elif hidimC >= Topology.TypeID("Vertex"):
            sinkVertices = Topology.Vertices(topologyC)
        if len(sourceVertices) > 0 and len(sinkVertices) > 0:
            _ = Topology.TransferDictionaries(sourceVertices, sinkVertices, tolerance=tolerance)

        if Topology.Type(topologyA) == Topology.TypeID("Edge"):
            sourceEdges += [topologyA]
        elif hidimA >= Topology.TypeID("Edge"):
            sourceEdges += Topology.Edges(topologyA)
        if Topology.Type(topologyB) == Topology.TypeID("Edge"):
            sourceEdges += [topologyB]
        elif hidimB >= Topology.TypeID("Edge"):
            sourceEdges += Topology.Edges(topologyB)
        if Topology.Type(topologyC) == Topology.TypeID("Edge"):
            sinkEdges = [topologyC]
        elif hidimC >= Topology.TypeID("Edge"):
            sinkEdges = Topology.Edges(topologyC)
        if len(sourceEdges) > 0 and len(sinkEdges) > 0:
            _ = Topology.TransferDictionaries(sourceEdges, sinkEdges, tolerance=tolerance)

        if Topology.Type(topologyA) == Topology.TypeID("Face"):
            sourceFaces += [topologyA]
        elif hidimA >= Topology.TypeID("Face"):
            sourceFaces += Topology.Faces(topologyA)
        if Topology.Type(topologyB) == Topology.TypeID("Face"):
            sourceFaces += [topologyB]
        elif hidimB >= Topology.TypeID("Face"):
            sourceFaces += Topology.Faces(topologyB)
        if Topology.Type(topologyC) == Topology.TypeID("Face"):
            sinkFaces += [topologyC]
        elif hidimC >= Topology.TypeID("Face"):
            sinkFaces += Topology.Faces(topologyC)
        if len(sourceFaces) > 0 and len(sinkFaces) > 0:
            _ = Topology.TransferDictionaries(sourceFaces, sinkFaces, tolerance=tolerance)

        if Topology.Type(topologyA) == Topology.TypeID("Cell"):
            sourceCells += [topologyA]
        elif hidimA >= Topology.TypeID("Cell"):
            sourceCells += Topology.Cells(topologyA)
        if Topology.Type(topologyB) == Topology.TypeID("Cell"):
            sourceCells += [topologyB]
        elif hidimB >= Topology.TypeID("Cell"):
            sourceCells += Topology.Cells(topologyB)
        if Topology.Type(topologyC) == Topology.TypeID("Cell"):
            sinkCells = [topologyC]
        elif hidimC >= Topology.TypeID("Cell"):
            sinkCells = Topology.Cells(topologyC)
        if len(sourceCells) > 0 and len(sinkCells) > 0:
            _ = Topology.TransferDictionaries(sourceCells, sinkCells, tolerance=tolerance)
    return topologyC
def BoundingBox(topology, optimize=0, axes='xyz', tolerance=0.0001)

Returns a cell representing a bounding box of the input topology. The returned cell contains a dictionary with keys "xrot", "yrot", and "zrot" that represents rotations around the X, Y, and Z axes. If applied in the order of Z, Y, X, the resulting box will become axis-aligned.

Parameters

topology : topologic_core.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 box so that it reduces its surface area. The default is 0 which will result in an axis-aligned bounding box. The default is 0.
axes : str , optional
Sets what axes are to be used for rotating the bounding box. This can be any permutation or substring of "xyz". It is not case sensitive. The default is "xyz".
tolerance : float , optional
The desired tolerance. The default is 0.0001.

Returns

topologic_core.Cell or topologic_core.Face
The bounding box of the input topology.
Expand source code
@staticmethod
def BoundingBox(topology, optimize=0, axes="xyz", tolerance=0.0001):
    """
    Returns a cell representing a bounding box of the input topology. The returned cell contains a dictionary with keys "xrot", "yrot", and "zrot" that represents rotations around the X, Y, and Z axes. If applied in the order of Z, Y, X, the resulting box will become axis-aligned.

    Parameters
    ----------
    topology : topologic_core.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 box so that it reduces its surface area. The default is 0 which will result in an axis-aligned bounding box. The default is 0.
    axes : str , optional
        Sets what axes are to be used for rotating the bounding box. This can be any permutation or substring of "xyz". It is not case sensitive. The default is "xyz".
    tolerance : float , optional
        The desired tolerance. The default is 0.0001.
    
    Returns
    -------
    topologic_core.Cell or topologic_core.Face
        The bounding box of the input topology.

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

    if not Topology.IsInstance(topology, "Topology"):
        print("Topology.BoundingBox - Error: the input topology parameter is not a valid topology. Returning None.")
        return None
    if not isinstance(axes, str):
        print("Topology.BoundingBox - Error: the input axes parameter is not a valid string. Returning None.")
        return None
    axes = axes.lower()
    x_flag = "x" in axes
    y_flag = "y" in axes
    z_flag = "z" in axes
    if not x_flag and not y_flag and not z_flag:
        print("Topology.BoundingBox - Error: the input axes parameter is not a recognized string. Returning None.")
        return None
    vertices = Topology.SubTopologies(topology, subTopologyType="vertex")
    topology = Cluster.ByTopologies(vertices)
    boundingBox = bb(topology)
    minX = boundingBox[0]
    minY = boundingBox[1]
    minZ = boundingBox[2]
    maxX = boundingBox[3]
    maxY = boundingBox[4]
    maxZ = boundingBox[5]
    w = abs(maxX - minX)
    l = abs(maxY - minY)
    h = abs(maxZ - minZ)
    best_area = 2*l*w + 2*l*h + 2*w*h
    orig_area = best_area
    best_x = 0
    best_y = 0
    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
            if x_flag:
                xa = n
                xb = 90+n
                xc = n
            else:
                xa = 0
                xb = 1
                xc = 1
            if y_flag:
                ya = n
                yb = 90+n
                yc = n
            else:
                ya = 0
                yb = 1
                yc = 1
            if z_flag:
                za = n
                zb = 90+n
                zc = n
            else:
                za = 0
                zb = 1
                zc = 1
            for x in range(xa,xb,xc):
                if flag:
                    break
                for y in range(ya,yb,yc):
                    if flag:
                        break
                    for z in range(za,zb,zc):
                        if flag:
                            break
                        t = Topology.Rotate(topology, origin=origin, axis=[0, 0, 1], angle=z)
                        t = Topology.Rotate(t, origin=origin, axis=[0, 1, 0], angle=y)
                        t = Topology.Rotate(t, origin=origin, axis=[1, 0, 0], angle=x)
                        minX, minY, minZ, maxX, maxY, maxZ = bb(t)
                        w = abs(maxX - minX)
                        l = abs(maxY - minY)
                        h = abs(maxZ - minZ)
                        area = 2*l*w + 2*l*h + 2*w*h
                        if area < orig_area*factor:
                            best_area = area
                            best_x = x
                            best_y = y
                            best_z = z
                            best_bb = [minX, minY, minZ, maxX, maxY, maxZ]
                            flag = True
                            break
                        if area < best_area:
                            best_area = area
                            best_x = x
                            best_y = y
                            best_z = z
                            best_bb = [minX, minY, minZ, maxX, maxY, maxZ]
                    
    else:
        best_bb = boundingBox

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

    baseWire = Wire.ByVertices([vb1, vb2, vb3, vb4], close=True)
    baseFace = Face.ByWire(baseWire, tolerance=tolerance)
    if abs(maxZ-minZ) < tolerance:
        box = baseFace
    else:
        box = Cell.ByThickenedFace(baseFace, planarize=False, thickness=abs(maxZ-minZ), bothSides=False)
    box = Topology.Rotate(box, origin=origin, axis=[1, 0, 0], angle=-best_x)
    box = Topology.Rotate(box, origin=origin, axis=[0, 1, 0], angle=-best_y)
    box = Topology.Rotate(box, origin=origin, axis=[0, 0, 1], angle=-best_z)
    dictionary = Dictionary.ByKeysValues(["xrot","yrot","zrot", "minx", "miny", "minz", "maxx", "maxy", "maxz", "width", "length", "height"], [best_x, best_y, best_z, minX, minY, minZ, maxX, maxY, maxZ, (maxX-minX), (maxY-minY), (maxZ-minZ)])
    box = Topology.SetDictionary(box, dictionary)
    return box
def ByBREPFile(file)

Imports a topology from a BREP file.

Parameters

file : file object
The BREP file.

Returns

topologic_core.Topology
The imported topology.
Expand source code
@staticmethod
def ByBREPFile(file):
    """
    Imports a topology from a BREP file.

    Parameters
    ----------
    file : file object
        The BREP file.

    Returns
    -------
    topologic_core.Topology
        The imported topology.

    """
    topology = None
    if not file:
        print("Topology.ByBREPFile - Error: the input file parameter is not a valid file. Returning None.")
        return None
    brep_string = file.read()
    topology = Topology.ByBREPString(brep_string)
    file.close()
    return topology
def ByBREPPath(path)

Imports a topology from a BREP file path.

Parameters

path : str
The path to the BREP file.

Returns

topologic_core.Topology
The imported topology.
Expand source code
@staticmethod
def ByBREPPath(path):
    """
    Imports a topology from a BREP file path.

    Parameters
    ----------
    path : str
        The path to the BREP file.

    Returns
    -------
    topologic_core.Topology
        The imported topology.

    """
    if not path:
        print("Topology.ByBREPPath - Error: the input path parameter is not a valid path. Returning None.")
        return None
    try:
        file = open(path)
    except:
        print("Topology.ByBREPPath - Error: the BREP file is not a valid file. Returning None.")
        return None
    return Topology.ByBREPFile(file)
def ByBREPString(string)

Creates a topology from the input brep string

Parameters

string : str
The input brep string.

Returns

topologic_core.Topology
The created topology.
Expand source code
@staticmethod
def ByBREPString(string):
    """
    Creates a topology from the input brep string

    Parameters
    ----------
    string : str
        The input brep string.

    Returns
    -------
    topologic_core.Topology
        The created topology.

    """
    if not isinstance(string, str):
        print("Topology.ByBREPString - Error: the input string parameter is not a valid string. Returning None.")
        return None
    returnTopology = None
    try:
        returnTopology = topologic.Topology.ByString(string)
    except:
        print("Topology.ByBREPString - Error: the input string parameter is not a valid string. Returning None.")
        returnTopology = None
    return returnTopology
def ByDXFFile(file)

Imports a list of topologies from a DXF file. This is an experimental method with limited capabilities.

Parameters

file : a DXF file object
The DXF file object.

Returns

list
The list of imported topologies.
Expand source code
@staticmethod
def ByDXFFile(file):
    """
    Imports a list of topologies from a DXF file.
    This is an experimental method with limited capabilities.

    Parameters
    ----------
    file : a DXF file object
        The DXF file object.

    Returns
    -------
    list
        The list of imported topologies.

    """
    from topologicpy.Vertex import Vertex
    from topologicpy.Edge import Edge
    from topologicpy.Wire import Wire
    from topologicpy.Shell import Shell
    from topologicpy.Cell import Cell
    from topologicpy.CellComplex import CellComplex
    from topologicpy.Topology import Topology

    try:
        import ezdxf
    except:
        print("Topology.ByDXFFile - Information: Installing required ezdxf library.")
        try:
            os.system("pip install ezdxf")
        except:
            os.system("pip install ezdxf --user")
        try:
            import ezdxf
            print("Topology.ByDXFFile - Information: ezdxf library installed successfully.")
        except:
            warnings.warn("Topology.ByDXFFile - Error: Could not import ezdxf library. Please install it manually. Returning None.")
            return None

    if not file:
        print("Topology.ByDXFFile - Error: the input file parameter is not a valid file. Returning None.")
        return None
    
    def convert_entity(entity):
        entity_type = entity.dxftype()
        converted_entity = None

        if entity_type == 'POINT':
            x, y, z = entity.dxf.location.x, entity.dxf.location.y, entity.dxf.location.z
            converted_entity = Vertex.ByCoordinates(x, y, z)

        elif entity_type == 'LINE':
            start = Vertex.ByCoordinates(entity.dxf.start.x, entity.dxf.start.y, entity.dxf.start.z)
            end = Vertex.ByCoordinates(entity.dxf.end.x, entity.dxf.end.y, entity.dxf.end.z)
            converted_entity = Edge.ByVertices(start, end)

        elif entity_type == 'CIRCLE':
            origin = Vertex.ByCoordinates(entity.dxf.center.x, entity.dxf.center.y, entity.dxf.center.z)
            radius = entity.dxf.radius
            converted_entity = Wire.Circle(origin, radius)

        elif entity_type in ['POLYLINE', 'LWPOLYLINE']:
            vertices = [Vertex.ByCoordinates(p[0], p[1], p[2]) for p in entity.points()]
            converted_entity = Wire.ByVertices(vertices)

        elif entity_type == 'ARC':
            vertices = [Vertex.ByCoordinates(p.x, p.y, p.z) for p in entity.vertices()]
            converted_entity = Wire.ByVertices(vertices)

        elif entity_type == 'MESH':
            vertices = [list(v) for v in entity.vertices]
            faces = [list(face) for face in entity.faces]
            converted_entity = Topology.SelfMerge(Topology.ByGeometry(vertices=vertices, faces=faces))
            # Try Cell
            temp = Cell.ByFaces(Topology.Faces(converted_entity), silent=True)
            if not Topology.IsInstance(temp, "Cell"):
                temp = CellComplex.ByFaces(Topology.Faces(converted_entity))
                if not Topology.IsInstance(temp, "CellComplex"):
                    temp = Shell.ByFaces(Topology.Faces(converted_entity))
                    if not Topology.IsInstance(temp, "Shell"):
                        temp = converted_entity
            converted_entity = temp
        return converted_entity

    def convert_insert(entity, file):
        block_name = entity.dxf.name
        block = file.blocks.get(block_name)
        converted_entities = []

        for block_entity in block:
            converted_entity = convert_entity(block_entity)
            if converted_entity is not None:
                converted_entities.append(converted_entity)

        x, y, z = [entity.dxf.insert.x, entity.dxf.insert.y, entity.dxf.insert.z]
        return [Topology.Translate(obj, x, y, z) for obj in converted_entities]

    def convert_dxf_to_custom_types(file):
        # Read the DXF file
        msp = file.modelspace()

        # Store the converted entities
        converted_entities = []

        # Process each entity in the model space
        for entity in msp:
            entity_type = entity.dxftype()
            if entity_type in ['TEXT', 'MTEXT']:
                continue  # Ignore TEXT and MTEXT

            if entity_type == 'INSERT':
                converted_entities.extend(convert_insert(entity, file))
            else:
                converted_entity = convert_entity(entity)
                if converted_entity is not None:
                    converted_entities.append(converted_entity)

        return converted_entities
    converted_entities = convert_dxf_to_custom_types(file)
    return converted_entities
def ByDXFPath(path)

Imports a list of topologies from a DXF file path. This is an experimental method with limited capabilities.

Parameters

path : str
The path to the DXF file.

Returns

list
The list of imported topologies.
Expand source code
@staticmethod
def ByDXFPath(path):
    """
    Imports a list of topologies from a DXF file path.
    This is an experimental method with limited capabilities.

    Parameters
    ----------
    path : str
        The path to the DXF file.

    Returns
    -------
    list
        The list of imported topologies.

    """
    try:
        import ezdxf
    except:
        print("Topology.ExportToDXF - Information: Installing required ezdxf library.")
        try:
            os.system("pip install ezdxf")
        except:
            os.system("pip install ezdxf --user")
        try:
            import ezdxf
            print("Topology.ByDXFPath - Information: ezdxf library installed successfully.")
        except:
            warnings.warn("Topology.ByDXFPath - Error: Could not import ezdxf library. Please install it manually. Returning None.")
            return None
    if not path:
        print("Topology.ByDXFPath - Error: the input path parameter is not a valid path. Returning None.")
        return None
    try:
        file = ezdxf.readfile(path)
    except:
        file = None
    if not file:
        print("Topology.ByDXFPath - Error: the input file parameter is not a valid file. Returning None.")
        return None
    return Topology.ByDXFFile(file)
def ByGeometry(vertices=[], edges=[], faces=[], color=[1.0, 1.0, 1.0, 1.0], id=None, name=None, lengthUnit='METERS', outputMode='default', tolerance=0.0001)

Create a topology by the input lists of vertices, edges, and faces.

Parameters

vertices : list
The input list of vertices in the form of [x, y, z]
edges : list , optional
The input list of edges in the form of [i, j] where i and j are vertex indices.
faces : list , optional
The input list of faces in the form of [i, j, k, l, …] where the items in the list are vertex indices. The face is assumed to be closed to the last vertex is connected to the first vertex automatically.
color : list , optional
The desired color of the object in the form of [r, g, b, a] where the components are between 0 and 1 and represent red, blue, green, and alpha (transparency) repsectively. The default is [1.0, 1.0, 1.0, 1.0].
id : str , optional
The desired ID of the object. If set to None, an automatic uuid4 will be assigned to the object. The default is None.
name : str , optional
The desired name of the object. If set to None, a default name "Topologic_[topology_type]" will be assigned to the object. The default is None.
lengthUnit : str , optional
The length unit used for the object. The default is "METERS"
outputMode : str , optional
The desired otuput mode of the object. This can be "wire", "shell", "cell", "cellcomplex", or "default". It is case insensitive. The default is "default".
tolerance : float , optional
The desired tolerance. The default is 0.0001.

Returns

topology : topologic_core.Topology
The created topology. The topology will have a dictionary embedded in it that records the input attributes (color, id, lengthUnit, name, type)
Expand source code
@staticmethod
def ByGeometry(vertices=[], edges=[], faces=[], color=[1.0, 1.0, 1.0, 1.0], id=None, name=None, lengthUnit="METERS", outputMode="default", tolerance=0.0001):
    """
    Create a topology by the input lists of vertices, edges, and faces.

    Parameters
    ----------
    vertices : list
        The input list of vertices in the form of [x, y, z]
    edges : list , optional
        The input list of edges in the form of [i, j] where i and j are vertex indices.
    faces : list , optional
        The input list of faces in the form of [i, j, k, l, ...] where the items in the list are vertex indices. The face is assumed to be closed to the last vertex is connected to the first vertex automatically.
    color : list , optional
        The desired color of the object in the form of [r, g, b, a] where the components are between 0 and 1 and represent red, blue, green, and alpha (transparency) repsectively. The default is [1.0, 1.0, 1.0, 1.0].
    id : str , optional
        The desired ID of the object. If set to None, an automatic uuid4 will be assigned to the object. The default is None.
    name : str , optional
        The desired name of the object. If set to None, a default name "Topologic_[topology_type]" will be assigned to the object. The default is None.
    lengthUnit : str , optional
        The length unit used for the object. The default is "METERS"
    outputMode : str , optional
        The desired otuput mode of the object. This can be "wire", "shell", "cell", "cellcomplex", or "default". It is case insensitive. The default is "default".
    tolerance : float , optional
        The desired tolerance. The default is 0.0001.

    Returns
    -------
    topology : topologic_core.Topology
        The created topology. The topology will have a dictionary embedded in it that records the input attributes (color, id, lengthUnit, name, type)

    """
    def topologyByFaces(faces, outputMode, tolerance):
        output = None
        if len(faces) == 1:
            return faces[0]
        if outputMode.lower() == "cell":
            output = Cell.ByFaces(faces, tolerance=tolerance)
            if output:
                return output
            else:
                return None
        if outputMode.lower() == "cellcomplex":
            output = CellComplex.ByFaces(faces, tolerance=tolerance)
            if output:
                return output
            else:
                return None
        if outputMode.lower() == "shell":
            output = Shell.ByFaces(faces, tolerance=tolerance) # This can return a list
            if Topology.IsInstance(output, "Shell"):
                return output
            else:
                return None
        if outputMode.lower() == "default":
            output = Cluster.ByTopologies(faces)
            if output:
                return output
        return output
    def topologyByEdges(edges, outputMode):
        output = None
        if len(edges) == 1:
            return edges[0]
        output = Cluster.ByTopologies(edges)
        if outputMode.lower() == "wire":
            output = Topology.SelfMerge(output, tolerance=tolerance)
            if Topology.IsInstance(output, "Wire"):
                return output
            else:
                return None
        return output
    def edgesByVertices(vertices, topVerts):
        if len(vertices) < 2:
            return []
        edges = []
        for i in range(len(vertices)-1):
            v1 = vertices[i]
            v2 = vertices[i+1]
            e1 = Edge.ByVertices([topVerts[v1], topVerts[v2]], tolerance=tolerance)
            edges.append(e1)
        # connect the last vertex to the first one
        v1 = vertices[-1]
        v2 = vertices[0]
        e1 = Edge.ByVertices([topVerts[v1], topVerts[v2]], tolerance=tolerance)
        edges.append(e1)
        return edges
    from topologicpy.Vertex import Vertex
    from topologicpy.Edge import Edge
    from topologicpy.Wire import Wire
    from topologicpy.Face import Face
    from topologicpy.Shell import Shell
    from topologicpy.Cell import Cell
    from topologicpy.CellComplex import CellComplex
    from topologicpy.Cluster import Cluster
    from topologicpy.Dictionary import Dictionary
    import uuid
    returnTopology = None
    topVerts = []
    topEdges = []
    topFaces = []
    vertices = [v for v in vertices if not len(v) == 0]
    edges = [e for e in edges if not len(e) == 0]
    faces = [f for f in faces if not len(f) == 0]
    if len(vertices) > 0:
        for aVertex in vertices:
            v = Vertex.ByCoordinates(aVertex[0], aVertex[1], aVertex[2])
            topVerts.append(v)
    else:
        return None
    if (outputMode.lower == "wire") and (len(edges) > 0):
        for anEdge in edges:
            topEdge = Edge.ByVertices([topVerts[anEdge[0]], topVerts[anEdge[1]]], tolerance=tolerance)
            topEdges.append(topEdge)
        if len(topEdges) > 0:
            returnTopology = topologyByEdges(topEdges)
    elif len(faces) > 0:
        for aFace in faces:
            faceEdges = edgesByVertices(aFace, topVerts)
            if len(faceEdges) > 2:
                faceWire = Wire.ByEdges(faceEdges, tolerance=tolerance)
                try:
                    topFace = Face.ByWire(faceWire, tolerance=tolerance, silent=True)
                    if Topology.IsInstance(topFace, "Face"):
                        topFaces.append(topFace)
                    elif isinstance(topFace, list):
                        topFaces += topFace
                except:
                    pass
        if len(topFaces) > 0:
            returnTopology = topologyByFaces(topFaces, outputMode=outputMode, tolerance=tolerance)
    elif len(edges) > 0:
        for anEdge in edges:
            topEdge = Edge.ByVertices([topVerts[anEdge[0]], topVerts[anEdge[1]]], tolerance=tolerance)
            topEdges.append(topEdge)
        if len(topEdges) > 0:
            returnTopology = topologyByEdges(topEdges, outputMode=outputMode)
    else:
        returnTopology = Cluster.ByTopologies(topVerts)
    if returnTopology:
        keys = []
        values = []
        keys.append("TOPOLOGIC_color")
        keys.append("TOPOLOGIC_id")
        keys.append("TOPOLOGIC_name")
        keys.append("TOPOLOGIC_type")
        keys.append("TOPOLOGIC_length_unit")
        if color:
            if isinstance(color, tuple):
                color = list(color)
            elif isinstance(color, list):
                if isinstance(color[0], tuple):
                    color = list(color[0])
            values.append(color)
        else:
            values.append([1.0, 1.0, 1.0, 1.0])
        if id:
            values.append(id)
        else:
            values.append(str(uuid.uuid4()))
        if name:
            values.append(name)
        else:
            values.append("Topologic_"+Topology.TypeAsString(returnTopology))
        values.append(Topology.TypeAsString(returnTopology))
        values.append(lengthUnit)
        topDict = Dictionary.ByKeysValues(keys, values)
        Topology.SetDictionary(returnTopology, topDict)
    return returnTopology
def ByIFCFile(file, transferDictionaries=False, includeTypes=[], excludeTypes=[])

Create a topology by importing it from an IFC file.

Parameters

file : file object
The input IFC file.
transferDictionaries : bool , optional
If set to True, the dictionaries from the IFC file will be transfered to the topology. Otherwise, they won't. The default is False.
includeTypes : list , optional
The list of IFC object types to include. It is case insensitive. If set to an empty list, all types are included. The default is [].
excludeTypes : list , optional
The list of IFC object types to exclude. It is case insensitive. If set to an empty list, no types are excluded. The default is [].

Returns

list
The created list of topologies.
Expand source code
@staticmethod
def ByIFCFile(file, transferDictionaries=False, includeTypes=[], excludeTypes=[]):
    """
    Create a topology by importing it from an IFC file.

    Parameters
    ----------
    file : file object
        The input IFC file.
    transferDictionaries : bool , optional
        If set to True, the dictionaries from the IFC file will be transfered to the topology. Otherwise, they won't. The default is False.
    includeTypes : list , optional
        The list of IFC object types to include. It is case insensitive. If set to an empty list, all types are included. The default is [].
    excludeTypes : list , optional
        The list of IFC object types to exclude. It is case insensitive. If set to an empty list, no types are excluded. The default is [].
    
    Returns
    -------
    list
        The created list of topologies.
    
    """
    import multiprocessing
    from topologicpy.Cluster import Cluster
    from topologicpy.Dictionary import Dictionary
    import uuid
    
    try:
        import ifcopenshell
        import ifcopenshell.geom
    except:
        print("Topology.ByIFCFile - Warning: Installing required ifcopenshell library.")
        try:
            os.system("pip install ifcopenshell")
        except:
            os.system("pip install ifcopenshell --user")
        try:
            import ifcopenshell
            import ifcopenshell.geom
            print("Topology.ByIFCFile - Warning: ifcopenshell library installed correctly.")
        except:
            warnings.warn("Topology.ByIFCFile - Error: Could not import ifcopenshell. Please try to install ifcopenshell manually. Returning None.")
            return None
    if not file:
        print("Topology.ByIFCFile - Error: the input file parameter is not a valid file. Returning None.")
        return None
    includeTypes = [s.lower() for s in includeTypes]
    excludeTypes = [s.lower() for s in excludeTypes]
    topologies = []
    settings = ifcopenshell.geom.settings()
    settings.set(settings.DISABLE_TRIANGULATION, True)
    settings.set(settings.USE_BREP_DATA, True)
    settings.set(settings.USE_WORLD_COORDS, True)
    settings.set(settings.SEW_SHELLS, True)
    iterator = ifcopenshell.geom.iterator(settings, file, multiprocessing.cpu_count())
    if iterator.initialize():
        while True:
            shape = iterator.get()
            is_a = shape.type.lower()
            if (is_a in includeTypes or len(includeTypes) == 0) and (not is_a in excludeTypes):
                try:
                    brep = shape.geometry.brep_data
                    topology = Topology.SelfMerge(Topology.ByBREPString(brep))
                    if transferDictionaries:
                        keys = []
                        values = []
                        keys.append("TOPOLOGIC_color")
                        values.append([1.0, 1.0, 1.0, 1.0])
                        keys.append("TOPOLOGIC_id")
                        values.append(str(uuid.uuid4()))
                        keys.append("TOPOLOGIC_name")
                        values.append(shape.name)
                        keys.append("TOPOLOGIC_type")
                        values.append(Topology.TypeAsString(topology))
                        keys.append("IFC_id")
                        values.append(str(shape.id))
                        keys.append("IFC_guid")
                        values.append(str(shape.guid))
                        keys.append("IFC_unique_id")
                        values.append(str(shape.unique_id))
                        keys.append("IFC_name")
                        values.append(shape.name)
                        keys.append("IFC_type")
                        values.append(shape.type)
                        d = Dictionary.ByKeysValues(keys, values)
                        topology = Topology.SetDictionary(topology, d)
                    topologies.append(topology)
                except:
                    pass
            if not iterator.next():
                break
    return topologies
def ByIFCPath(path, transferDictionaries=False, includeTypes=[], excludeTypes=[])

Create a topology by importing it from an IFC file path.

Parameters

path : str
The path to the IFC file.
transferDictionaries : bool , optional
If set to True, the dictionaries from the IFC file will be transfered to the topology. Otherwise, they won't. The default is False.
includeTypes : list , optional
The list of IFC object types to include. It is case insensitive. If set to an empty list, all types are included. The default is [].
excludeTypes : list , optional
The list of IFC object types to exclude. It is case insensitive. If set to an empty list, no types are excluded. The default is [].

Returns

list
The created list of topologies.
Expand source code
@staticmethod
def ByIFCPath(path, transferDictionaries=False, includeTypes=[], excludeTypes=[]):
    """
    Create a topology by importing it from an IFC file path.

    Parameters
    ----------
    path : str
        The path to the IFC file.
    transferDictionaries : bool , optional
        If set to True, the dictionaries from the IFC file will be transfered to the topology. Otherwise, they won't. The default is False.
    includeTypes : list , optional
        The list of IFC object types to include. It is case insensitive. If set to an empty list, all types are included. The default is [].
    excludeTypes : list , optional
        The list of IFC object types to exclude. It is case insensitive. If set to an empty list, no types are excluded. The default is [].
    Returns
    -------
    list
        The created list of topologies.
    
    """
    import ifcopenshell

    if not path:
        print("Topology.ByIFCPath - Error: the input path parameter is not a valid path. Returning None.")
        return None
    try:
        file = ifcopenshell.open(path)
    except:
        file = None
    if not file:
        print("Topology.ByIFCPath - Error: the input file parameter is not a valid file. Returning None.")
        return None
    return Topology.ByIFCFile(file, transferDictionaries=transferDictionaries, includeTypes=includeTypes, excludeTypes=excludeTypes)
def ByJSONFile(file, tolerance=0.0001)

Imports the topology from a JSON file.

Parameters

file : file object
The input JSON file.
tolerance : float , optional
The desired tolerance. The default is 0.0001.

Returns

list
The list of imported topologies.
Expand source code
@staticmethod
def ByJSONFile(file, tolerance=0.0001):
    """
    Imports the topology from a JSON file.

    Parameters
    ----------
    file : file object
        The input JSON file.
    tolerance : float , optional
        The desired tolerance. The default is 0.0001.

    Returns
    -------
    list
        The list of imported topologies.

    """
    if not file:
        print("Topology.ByJSONFile - Error: the input file parameter is not a valid file. Returning None.")
        return None
    jsonData = json.load(file)
    jsonString = json.dumps(jsonData)
    return Topology.ByJSONString(jsonString, tolerance=tolerance)
def ByJSONPath(path, tolerance=0.0001)

Imports the topology from a JSON file.

Parameters

path : str
The file path to the json file.
tolerance : float , optional
The desired tolerance. The default is 0.0001.

Returns

list
The list of imported topologies.
Expand source code
@staticmethod
def ByJSONPath(path, tolerance=0.0001):
    """
    Imports the topology from a JSON file.

    Parameters
    ----------
    path : str
        The file path to the json file.
    tolerance : float , optional
        The desired tolerance. The default is 0.0001.

    Returns
    -------
    list
        The list of imported topologies.

    """
    if not path:
        print("Topology.ByJSONPath - Error: the input path parameter is not a valid path. Returning None.")
        return None
    data = None
    with open(path) as file:
        data = Topology.ByJSONFile(file=file, tolerance=tolerance)
    return data
def ByJSONString(string, progressBar=False, tolerance=0.0001)

Imports the topology from a JSON string.

Parameters

string : str
The input JSON string.
progressBar : bool , optional
If set to True a tqdm progress bar is shown. If not, it will not be shown. The default is False.
tolerance : float , optional
The desired tolerance. The default is 0.0001.

Returns

list or topologicpy.Topology
The list of imported topologies. If the list only contains one element, it returns that element.
Expand source code
@staticmethod
def ByJSONString(string, progressBar=False, tolerance=0.0001):
    """
    Imports the topology from a JSON string.

    Parameters
    ----------
    string : str
        The input JSON string.
    progressBar : bool , optional
        If set to True a tqdm progress bar is shown. If not, it will not be shown. The default is False.
    tolerance : float , optional
        The desired tolerance. The default is 0.0001.

    Returns
    -------
    list or topologicpy.Topology
        The list of imported topologies. If the list only contains one element, it returns that element.

    """

    from topologicpy.Dictionary import Dictionary
    from topologicpy.Context import Context
    from topologicpy.Vertex import Vertex
    from topologicpy.Edge import Edge
    from topologicpy.Wire import Wire
    from topologicpy.Face import Face
    from topologicpy.Shell import Shell
    from topologicpy.Cell import Cell
    from topologicpy.CellComplex import CellComplex
    from topologicpy.Cluster import Cluster
    from topologicpy.Aperture import Aperture
    from topologicpy.Helper import Helper
    from topologicpy.Topology import Topology
    from tqdm.auto import tqdm
    import time

    def getUUID(topology, uuidKey="uuid"):
        d = Topology.Dictionary(topology)
        if d == None:
            uuidOne = str(uuid.uuid1())
            d = Dictionary.ByKeyValue(uuidKey, uuidOne)
        elif uuidKey not in Dictionary.Keys(d):
            uuidOne = str(uuid.uuid1())
            d = Dictionary.SetValueAtKey(d, uuidKey, uuidOne)
            topology = Topology.SetDictionary(topology, d)
        else:
            uuidOne = Dictionary.ValueAtKey(d, uuidKey)
        return uuidOne
        
    def find_json_item(json_list, key, value):
        for item in json_list:
            if key in item and item[key] == value:
                return item
        return None

    def buildAperture(j_aperture):
        j_vertices = []
        j_edges = []
        j_wires = []
        j_faces = []
        j_shells = []
        j_cells = []
        j_cellComplexes = []
        for jsonItem in j_aperture:
            topology_type = jsonItem['type']
            if topology_type.lower() == "vertex":
                j_vertices.append(jsonItem)
            elif topology_type.lower() == "edge":
                j_edges.append(jsonItem)
            elif topology_type.lower() == "wire":
                j_wires.append(jsonItem)
            elif topology_type.lower() == "face":
                j_faces.append(jsonItem)
            elif topology_type.lower() == "shell":
                j_shells.append(jsonItem)
            elif topology_type.lower() == "cell":
                j_cells.append(jsonItem)
            elif topology_type.lower() == "cellcomplex":
                j_cellComplexes.append(jsonItem)

        vertices = [buildVertex(j_v) for j_v in j_vertices]
        edges = [buildEdge(j_e, j_vertices, uuidKey="uuid") for j_e in j_edges]
        wires = [buildWire(j_w, j_edges, j_vertices, uuidKey="uuid") for j_w in j_wires]
        faces = [buildFace(j_f, j_wires, j_edges, j_vertices, uuidKey="uuid") for j_f in j_faces]
        faces = Helper.Flatten(faces)
        shells = [buildShell(j_s, j_faces, j_wires, j_edges, j_vertices, uuidKey="uuid") for j_s in j_shells]
        cells = [buildCell(j_c, j_shells, j_faces, j_wires, j_edges, j_vertices, uuidKey="uuid") for j_c in j_cells]
        cellComplexes = [buildCellComplex(j_cc, j_cells, j_shells, j_faces, j_wires, j_edges, j_vertices, uuidKey="uuid") for j_cc in j_cellComplexes]
        if len(cellComplexes) > 0:
            everything = cellComplexes
        elif len(cells) > 0:
            everything = cells
        elif len(shells) > 0:
            everything = shells
        elif len(faces) > 0:
            everything = faces
        elif len(wires) > 0:
            everything = wires
        elif len(edges) > 0:
            everything = edges
        elif len(vertices) > 0:
            everything = vertices
        else:
            return None
        if len(everything) == 1:
            aperture = everything[0]
        else:
            aperture = Topology.SelfMerge(Cluster.ByTopologies(everything), tolerance=tolerance)
        return aperture

    def buildVertex(json_item):
        x, y, z = json_item['coordinates']
        d = json_item['dictionary']
        v = Vertex.ByCoordinates(x, y, z)
        if v == None:
            print("Topology.ByJSONString - Error: Could not build a vertex. Returning None.")
            return None
        v = Topology.SetDictionary(v, Dictionary.ByPythonDictionary(d))
        apertures = [buildAperture(j_ap) for j_ap in json_item['apertures']]
        context = Context.ByTopologyParameters(v, u=0.5, v=0.5, w=0.5)
        for ap in apertures:
            _ = Aperture.ByTopologyContext(ap, context)
        return v

    def buildEdge(json_item, j_vertices, uuidKey="uuid", tolerance=0.0001):
        edge_vertices = json_item['vertices']
        vertices = []
        for j_v in edge_vertices:
            vertices.append(buildVertex(find_json_item(j_vertices, uuidKey, j_v)))
        e = Edge.ByVertices(vertices, tolerance=tolerance)
        if e == None:
            print("Topology.ByJSONString - Error: Could not build an edge. Returning None.")
            return None
        d = json_item['dictionary']
        e = Topology.SetDictionary(e, Dictionary.ByPythonDictionary(d))
        apertures = [buildAperture(j_ap) for j_ap in json_item['apertures']]
        context = Context.ByTopologyParameters(e, u=0.5, v=0.5, w=0.5)
        for ap in apertures:
            _ = Aperture.ByTopologyContext(ap, context)
        return e

    def buildWire(json_item, j_edges, j_vertices, uuidKey="uuid", tolerance=0.0001):
        wire_edges = json_item['edges']
        edges = []
        for j_e in wire_edges:
            edges.append(buildEdge(find_json_item(j_edges, uuidKey, j_e), j_vertices, uuidKey=uuidKey, tolerance=tolerance))
        w = Wire.ByEdges(edges, tolerance=tolerance)
        if w == None:
            print("Topology.ByJSONString - Error: Could not build a wire. Returning None.")
            return None
        d = json_item['dictionary']
        w = Topology.SetDictionary(w, Dictionary.ByPythonDictionary(d))
        apertures = [buildAperture(j_ap) for j_ap in json_item['apertures']]
        context = Context.ByTopologyParameters(w, u=0.5, v=0.5, w=0.5)
        for ap in apertures:
            _ = Aperture.ByTopologyContext(ap, context)
        return w

    def buildFace(json_item, j_wires, j_edges, j_vertices, uuidKey="uuid", tolerance=0.0001):
        face_wires = json_item['wires']
        external_boundary = buildWire(find_json_item(j_wires, uuidKey, face_wires[0]), j_edges, j_vertices, uuidKey=uuidKey, tolerance=tolerance)
        if not Topology.IsInstance(external_boundary, "Wire"):
            print("Topology.ByJSONString - ERROR: Something went wrong with original external boundary. Returning None.")
            return None
        if not Topology.IsPlanar(external_boundary, tolerance=tolerance):
            temp_boundary = Wire.Planarize(external_boundary, tolerance=tolerance)
            if temp_boundary == None or not Topology.IsInstance(temp_boundary, "Wire"):
                print("Topology.ByJSONString - Error: Something went wrong with external boundary. Returning None.")
                return None
            else:
                external_boundary = temp_boundary
        if not Wire.IsClosed(external_boundary):
            external_boundary = Wire.Close(external_boundary)
        internal_boundaries = []
        for j_w in face_wires[1:]:
            ib = buildWire(find_json_item(j_wires, uuidKey, j_w),j_edges, j_vertices, uuidKey=uuidKey)
            if not Topology.IsPlanar(external_boundary):
                ib = Wire.Planarize(ib)
                if not Topology.IsInstance(ib, "Wire"):
                    print("Topology.ByJSONString - ERROR: Something went wrong with original internal boundary. Returning None.")
                    return None
            if not Wire.IsClosed(ib):
                ib = Wire.Close(ib)
            internal_boundaries.append(ib)

        f = Face.ByWires(external_boundary, internal_boundaries, tolerance=tolerance)
        if not Topology.IsInstance(f, "Face"):
            print("Topology.ByJSONString - Error: Could not build a face. Returning None.", f, "Ex Bound:", external_boundary)
            return None
        area = Face.Area(f)
        if area == None:
            print("Topology.ByJSONString - Error: Could not compute the area of the built face. Returning None.")
            return None
        if Face.Area(f) < 0:
            external_boundary = Wire.Invert(external_boundary)
        f = Face.ByWires(external_boundary, internal_boundaries, tolerance=tolerance)
        if f == None:
            print("Topology.ByJSONString - Error: Could not build a face. Returning None.")
            return None
        d = json_item['dictionary']
        f = Topology.SetDictionary(f, Dictionary.ByPythonDictionary(d))
        apertures = [buildAperture(j_ap) for j_ap in json_item['apertures']]
        if len(apertures) > 0:
            context = Context.ByTopologyParameters(f, u=0.5, v=0.5, w=0.5)
            for ap in apertures:
                _ = Aperture.ByTopologyContext(ap, context)
        return f

    def buildShell(json_item, j_faces, j_wires, j_edges, j_vertices, uuidKey="uuid", tolerance=0.0001):
        shell_faces = json_item['faces']
        faces = []
        for j_f in shell_faces:
            faces.append(buildFace(find_json_item(j_faces, uuidKey, j_f), j_wires, j_edges, j_vertices, uuidKey=uuidKey))
        faces = Helper.Flatten(faces)
        s = Shell.ByFaces(faces, tolerance=tolerance) # This can return a list
        if not Topology.IsInstance(s, "Shell"):
            print("Topology.ByJSONString - Error: Could not build a shell. Returning None.")
            return None
        d = json_item['dictionary']
        s = Topology.SetDictionary(s, Dictionary.ByPythonDictionary(d))
        apertures = [buildAperture(j_ap) for j_ap in json_item['apertures']]
        if len(apertures) > 0:
            context = Context.ByTopologyParameters(s, u=0.5, v=0.5, w=0.5)
            for ap in apertures:
                _ = Aperture.ByTopologyContext(ap, context)
        return s

    def buildCell(json_item, j_shells, j_faces, j_wires, j_edges, j_vertices, uuidKey="uuid", tolerance=0.0001):
        cell_shells = json_item['shells']
        shells = []
        external_boundary = buildShell(find_json_item(j_shells, uuidKey, cell_shells[0]), j_faces, j_wires, j_edges, j_vertices, uuidKey=uuidKey, tolerance=tolerance)
        internal_boundaries = []
        for j_s in cell_shells[1:]:
            internal_boundaries.append(buildShell(find_json_item(j_shells, uuidKey, j_s), j_faces, j_wires, j_edges, j_vertices, uuidKey=uuidKey, tolerance=tolerance))
        c = Cell.ByShell(external_boundary)
        if c == None:
            print("Topology.ByJSONString - Error: Could not build a cell. Returning None.")
            return None
        for ib in internal_boundaries:
            ib_c = Cell.ByShell(ib)
            c = Topology.Difference(c, ib_c, tolerance=tolerance)
        d = json_item['dictionary']
        c = Topology.SetDictionary(c, Dictionary.ByPythonDictionary(d))
        apertures = [buildAperture(j_ap) for j_ap in json_item['apertures']]
        context = Context.ByTopologyParameters(c, u=0.5, v=0.5, w=0.5)
        for ap in apertures:
            _ = Aperture.ByTopologyContext(ap, context)
        return c

    def buildCellComplex(json_item, j_cells, j_shells, j_faces, j_wires, j_edges, j_vertices, uuidKey="uuid", tolerance=0.0001):
        cc_cells = json_item['cells']
        cells = []
        for j_c in cc_cells:
            cells.append(buildCell(find_json_item(j_cells, uuidKey, j_c), j_shells, j_faces, j_wires, j_edges, j_vertices, uuidKey=uuidKey, tolerance=tolerance))
        cc = CellComplex.ByCells(cells, tolerance=tolerance)
        if cc == None:
            print("Topology.ByJSONString - Error: Could not build a cellcomplex. Returning None.")
            return None
        d = json_item['dictionary']
        cc = Topology.SetDictionary(cc, Dictionary.ByPythonDictionary(d))
        apertures = [buildAperture(j_ap) for j_ap in json_item['apertures']]
        context = Context.ByTopologyParameters(cc, u=0.5, v=0.5, w=0.5)
        for ap in apertures:
            _ = Aperture.ByTopologyContext(ap, context)
        return cc

    def addAperturesUUID(topology, uuidKey="uuid"):
        topology_apertures = [Aperture.Topology(ap) for ap in Topology.Apertures(topology)]
        apertures_uuid = []
        for top_a in topology_apertures:
            uuid = getUUID(top_a, uuidKey=uuidKey)
            apertures_uuid.append(uuid)
        d = Topology.Dictionary(topology)
        d = Dictionary.SetValueAtKey(d, 'apertures', apertures_uuid)
        topology = Topology.SetDictionary(topology, d)
        s = Topology.InternalVertex(topology, tolerance=tolerance)
        s = Topology.SetDictionary(s, d)
        return topology, s, topology_apertures

    def findAperture(uuid, apertures, uuidKey="uuid"):
        for ap in apertures:
            d = Topology.Dictionary(ap)
            ap_uuid = Dictionary.ValueAtKey(d, uuidKey)
            if uuid == ap_uuid:
                return ap
        return None

    def setApertures(topology, allApertures, uuidKey="uuid"):
        apertures = []
        d = Topology.Dictionary(topology)
        apertures_uuid = Dictionary.ValueAtKey(d, 'apertures')
        if not isinstance(apertures_uuid, list):
            apertures_uuid = [apertures_uuid]
        for ap_uuid in apertures_uuid:
            ap = findAperture(ap_uuid, allApertures, uuidKey=uuidKey)
            if ap != None:
                apertures.append(ap)
        context = Context.ByTopologyParameters(topology, u=0.5, v=0.5, w=0.5)
        for ap in apertures:
            _ = Aperture.ByTopologyContext(ap, context)
        return topology
                
            
    jsondata = json.loads(string)
    if not isinstance(jsondata, list):
        jsondata = [jsondata]

    j_vertices = []
    j_edges = []
    j_wires = []
    j_faces = []
    j_shells = []
    j_cells = []
    j_cellComplexes = []

    vertices = []
    edges = []
    wires = []
    faces = []
    shells = []
    cells = []
    cellComplexes = []

    if progressBar:
        for jsonItem in tqdm(jsondata):
            try:
                topology_type = jsonItem['type']
                if topology_type.lower() == "vertex":
                    j_vertices.append(jsonItem)
                elif topology_type.lower() == "edge":
                    j_edges.append(jsonItem)
                elif topology_type.lower() == "wire":
                    j_wires.append(jsonItem)
                elif topology_type.lower() == "face":
                    j_faces.append(jsonItem)
                elif topology_type.lower() == "shell":
                    j_shells.append(jsonItem)
                elif topology_type.lower() == "cell":
                    j_cells.append(jsonItem)
                elif topology_type.lower() == "cellcomplex":
                    j_cellComplexes.append(jsonItem)
            except:
                continue
    else:
        for jsonItem in jsondata:
            try:
                topology_type = jsonItem['type']
                if topology_type.lower() == "vertex":
                    j_vertices.append(jsonItem)
                elif topology_type.lower() == "edge":
                    j_edges.append(jsonItem)
                elif topology_type.lower() == "wire":
                    j_wires.append(jsonItem)
                elif topology_type.lower() == "face":
                    j_faces.append(jsonItem)
                elif topology_type.lower() == "shell":
                    j_shells.append(jsonItem)
                elif topology_type.lower() == "cell":
                    j_cells.append(jsonItem)
                elif topology_type.lower() == "cellcomplex":
                    j_cellComplexes.append(jsonItem)
            except:
                continue

    vertices = [buildVertex(j_v) for j_v in j_vertices]
    vertex_selectors = []
    all_vertex_apertures = []
    for v in vertices:
        v, s, vertex_apertures = addAperturesUUID(v, uuidKey="uuid")
        all_vertex_apertures += vertex_apertures
        vertex_selectors.append(s)

    edges = [buildEdge(j_e, j_vertices, uuidKey="uuid") for j_e in j_edges]
    edge_selectors = []
    all_edge_apertures = []
    for e in edges:
        e, s, edge_apertures = addAperturesUUID(e, uuidKey="uuid")
        all_edge_apertures += edge_apertures
        edge_selectors.append(s)

    wires = [buildWire(j_w, j_edges, j_vertices, uuidKey="uuid") for j_w in j_wires]
    wire_selectors = []
    all_wire_apertures = []
    for w in wires:
        w, s, wire_apertures = addAperturesUUID(w, uuidKey="uuid")
        all_wire_apertures += wire_apertures
        wire_selectors.append(s)

    faces = []
    for j_f in j_faces:
        f = buildFace(j_f, j_wires, j_edges, j_vertices, uuidKey="uuid")
        faces.append(f)
   
    faces = Helper.Flatten(faces)
    face_selectors = []
    all_face_apertures = []
    for f in faces:
        f, s, face_apertures = addAperturesUUID(f, uuidKey="uuid")
        all_face_apertures += face_apertures
        face_selectors.append(s)

    shells = [buildShell(j_s, j_faces, j_wires, j_edges, j_vertices, uuidKey="uuid") for j_s in j_shells]
    shell_selectors = []
    all_shell_apertures = []
    for sh in shells:
        sh, s, shell_apertures = addAperturesUUID(sh, uuidKey="uuid")
        all_shell_apertures += shell_apertures
        shell_selectors.append(s)

    cells = [buildCell(j_c, j_shells, j_faces, j_wires, j_edges, j_vertices, uuidKey="uuid") for j_c in j_cells]
    cell_selectors = []
    all_cell_apertures = []
    for c in cells:
        c, s, cell_apertures = addAperturesUUID(c, uuidKey="uuid")
        all_cell_apertures += cell_apertures
        cell_selectors.append(s)

    cellComplexes = [buildCellComplex(j_cc, j_cells, j_shells, j_faces, j_wires, j_edges, j_vertices, uuidKey="uuid") for j_cc in j_cellComplexes]
    cellComplex_selectors = []
    all_cellComplex_apertures = []
    for cc in cellComplexes:
        cc, s, cellComplex_apertures = addAperturesUUID(cc, uuidKey="uuid")
        all_cellComplex_apertures += cellComplex_apertures
        cellComplex_selectors.append(s)

    everything = vertices+edges+wires+faces+shells+cells+cellComplexes
    toplevelTopologies = []
    for ev in everything:
        d = Topology.Dictionary(ev)
        if Dictionary.ValueAtKey(d,'toplevel') == True:
            toplevelTopologies.append(ev)

    for tp in toplevelTopologies:
        # This is a hack because sometimes the imported topologies get weird. I think it is an opencascade bug.
        tp = Topology.ByBREPString(Topology.BREPString(tp))
        if len(vertex_selectors) > 0:
            _ = Topology.TransferDictionariesBySelectors(tp, vertex_selectors, tranVertices=True, tolerance=tolerance)
        if len(edge_selectors) > 0:
            _ = Topology.TransferDictionariesBySelectors(tp, edge_selectors, tranEdges=True, tolerance=tolerance)
        if len(face_selectors) > 0:
            _ = Topology.TransferDictionariesBySelectors(tp, face_selectors, tranFaces=True, tolerance=tolerance)
        if len(cell_selectors) > 0:
            _ = Topology.TransferDictionariesBySelectors(tp, cell_selectors, tranCells=True, tolerance=tolerance)
        if len(all_vertex_apertures) > 0:
            tp_vertices = Topology.Vertices(tp)
            for tp_vertex in tp_vertices:
                tp_vertex = setApertures(tp_vertex, all_vertex_apertures, uuidKey="uuid")
        if len(all_edge_apertures) > 0:
            tp_edges = Topology.Edges(tp)
            for tp_edge in tp_edges:
                tp_edge = setApertures(tp_edge, all_edge_apertures, uuidKey="uuid")
        if len(all_face_apertures) > 0:
            tp_faces = Topology.Faces(tp)
            for tp_face in tp_faces:
                tp_face = setApertures(tp_face, all_face_apertures, uuidKey="uuid")
        if len(all_cell_apertures) > 0:
            tp_cells = Topology.Cells(tp)
            for tp_cell in tp_cells:
                tp_cell = setApertures(tp_cell, all_cell_apertures, uuidKey="uuid")

    if len(toplevelTopologies) == 1:
        return toplevelTopologies[0]
    else:
        return toplevelTopologies
def ByOBJFile(file, transposeAxes=True, progressBar=False, tolerance=0.0001)

Imports the topology from a Weverfront OBJ file. This is a very experimental method and only works with simple planar solids. Materials and Colors are ignored.

Parameters

file : file object
The input OBJ file.
transposeAxes : bool , optional
If set to True the Z and Y coordinates are transposed so that Y points "up"
progressBar : bool , optional
If set to True a tqdm progress bar is shown. If not, it will not be shown. The default is False.
tolerance : float , optional
The desired tolerance. The default is 0.0001.

Returns

topology
The imported topology.
Expand source code
@staticmethod
def ByOBJFile(file, transposeAxes=True, progressBar=False, tolerance=0.0001):
    """
    Imports the topology from a Weverfront OBJ file. This is a very experimental method and only works with simple planar solids. Materials and Colors are ignored.

    Parameters
    ----------
    file : file object
        The input OBJ file.
    transposeAxes : bool , optional
        If set to True the Z and Y coordinates are transposed so that Y points "up"
    progressBar : bool , optional
        If set to True a tqdm progress bar is shown. If not, it will not be shown. The default is False.
    tolerance : float , optional
        The desired tolerance. The default is 0.0001.

    Returns
    -------
    topology
        The imported topology.

    """
    if not file:
        print("Topology.ByOBJFile - Error: the input file parameter is not a valid file. Returning None.")
        return None
    obj_string = file.read()
    topology = Topology.ByOBJString(obj_string, transposeAxes=transposeAxes, progressBar=progressBar, tolerance=tolerance)
    file.close()
    return topology
def ByOBJPath(path, transposeAxes=True, progressBar=False, tolerance=0.0001)

Imports the topology from a Weverfront OBJ file path. This is a very experimental method and only works with simple planar solids. Materials and Colors are ignored.

Parameters

path : str
The file path to the OBJ file.
transposeAxes : bool , optional
If set to True the Z and Y coordinates are transposed so that Y points "up".
progressBar : bool , optional
If set to True a tqdm progress bar is shown. If not, it will not be shown. The default is False.
tolerance : float , optional
The desired tolerance. The default is 0.0001.

Returns

topology
The imported topology.
Expand source code
@staticmethod
def ByOBJPath(path, transposeAxes=True, progressBar=False, tolerance=0.0001):
    """
    Imports the topology from a Weverfront OBJ file path. This is a very experimental method and only works with simple planar solids. Materials and Colors are ignored.

    Parameters
    ----------
    path : str
        The file path to the OBJ file.
    transposeAxes : bool , optional
        If set to True the Z and Y coordinates are transposed so that Y points "up".
    progressBar : bool , optional
        If set to True a tqdm progress bar is shown. If not, it will not be shown. The default is False.
    tolerance : float , optional
        The desired tolerance. The default is 0.0001.

    Returns
    -------
    topology
        The imported topology.

    """
    if not path:
        print("Topology.ByOBJPath - Error: the input path parameter is not a valid path. Returning None.")
        return None
    try:
        file = open(path)
    except:
        print("Topology.ByOBJPath - Error: the OBJ file is not a valid file. Returning None.")
        return None
    return Topology.ByOBJFile(file, transposeAxes=transposeAxes, progressBar=progressBar, tolerance=tolerance)
def ByOBJString(string, transposeAxes=True, progressBar=False, tolerance=0.0001)

Creates a topology from the input Waverfront OBJ string. This is a very experimental method and only works with simple planar solids. Materials and Colors are ignored.

Parameters

string : str
The input OBJ string.
transposeAxes : bool , optional
If set to True the Z and Y coordinates are transposed so that Y points "up"
progressBar : bool , optional
If set to True a tqdm progress bar is shown. If not, it will not be shown. The default is False.
tolerance : float , optional
The desired tolerance. The default is 0.0001.

Returns

topology
The created topology.
Expand source code
@staticmethod
def ByOBJString(string, transposeAxes = True, progressBar=False, tolerance=0.0001):
    """
    Creates a topology from the input Waverfront OBJ string. This is a very experimental method and only works with simple planar solids. Materials and Colors are ignored.

    Parameters
    ----------
    string : str
        The input OBJ string.
    transposeAxes : bool , optional
        If set to True the Z and Y coordinates are transposed so that Y points "up" 
    progressBar : bool , optional
        If set to True a tqdm progress bar is shown. If not, it will not be shown. The default is False.
    tolerance : float , optional
        The desired tolerance. The default is 0.0001.

    Returns
    -------
    topology
        The created topology.

    """
    from topologicpy.Vertex import Vertex
    from tqdm.auto import tqdm

    def parse(lines):
        vertices = []
        faces = []
        for i in range(len(lines)):
            l = lines[i].replace(",", " ")
            s = l.split()
            if isinstance(s, list):
                if len(s) > 3:
                    if s[0].lower() == "v":
                        vertices.append([float(s[1]), float(s[2]), float(s[3])])
                    elif s[0].lower() == "f":
                        temp_faces = []
                        for j in range(1,len(s)):
                            f = s[j].split("/")[0]
                            temp_faces.append(int(f)-1)
                        faces.append(temp_faces)
        return [vertices, faces]
    
    def parsetqdm(lines):
        vertices = []
        faces = []
        for i in tqdm(range(len(lines))):
            s = lines[i].split()
            if isinstance(s, list):
                if len(s) > 3:
                    if s[0].lower() == "v":
                            vertices.append([float(s[1]), float(s[2]), float(s[3])])
                    elif s[0].lower() == "f":
                        temp_faces = []
                        for j in range(1,len(s)):
                            f = s[j].split("/")[0]
                            temp_faces.append(int(f)-1)
                        faces.append(temp_faces)
        return [vertices, faces]
    
    
    lines = string.split("\n")
    if lines:
        if progressBar:
            vertices, faces = parsetqdm(lines)
        else:
            vertices, faces = parse(lines)
    if vertices or faces:
        topology = Topology.ByGeometry(vertices = vertices, faces = faces, outputMode="default", tolerance=tolerance)
        if transposeAxes == True:
            topology = Topology.Rotate(topology, origin=Vertex.Origin(), axis=[1, 0, 0], angle=90)
        return Topology.SelfMerge(topology)
    print("Topology.ByOBJString - Error: Could not find vertices or faces. Returning None.")
    return None
def ByOCCTShape(occtShape)

Creates a topology from the input OCCT shape. See https://dev.opencascade.org/doc/overview/html/occt_user_guides__modeling_data.html.

Parameters

occtShape : topologic_core.TopoDS_Shape
The inoput OCCT Shape.

Returns

topologic_core.Topology
The created topology.
Expand source code
@staticmethod
def ByOCCTShape(occtShape):
    """
    Creates a topology from the input OCCT shape. See https://dev.opencascade.org/doc/overview/html/occt_user_guides__modeling_data.html.

    Parameters
    ----------
    occtShape : topologic_core.TopoDS_Shape
        The inoput OCCT Shape.

    Returns
    -------
    topologic_core.Topology
        The created topology.

    """
    return topologic.Topology.ByOcctShape(occtShape, "")
def ByXYZFile(file, frameIdKey='id', vertexIdKey='id')

Imports the topology from an XYZ file path. This is a very experimental method. While variants of the format exist, topologicpy reads XYZ files that conform to the following: An XYZ file can be made out of one or more frames. Each frame will be stored in a sepatate topologic cluster. First line: total number of vertices in the frame. This must be an integer. No other words or characters are allowed on this line. Second line: frame label. This is free text and will be stored in the dictionary of each frame (topologic_core.Cluster) All other lines: vertex_label, x, y, and z coordinates, separated by spaces, tabs, or commas. The vertex label must be one word with no spaces. It is stored in the dictionary of each vertex.

Example: 3 Frame 1 A 5.67 -3.45 2.61 B 3.91 -1.91 4 A 3.2 1.2 -12.3 4 Frame 2 B 5.47 -3.45 2.61 B 3.91 -1.93 3.1 A 3.2 1.2 -22.4 A 3.2 1.2 -12.3 3 Frame 3 A 5.67 -3.45 2.61 B 3.91 -1.91 4 C 3.2 1.2 -12.3

Parameters

file : file object
The input XYZ file.
frameIdKey : str , optional
The desired id key to use to store the ID of each frame in its dictionary. The default is "id".
vertexIdKey : str , optional
The desired id key to use to store the ID of each point in its dictionary. The default is "id".

Returns

list
The list of frames (topologic_core.Cluster).
Expand source code
@staticmethod
def ByXYZFile(file, frameIdKey="id", vertexIdKey="id"):
    """
    Imports the topology from an XYZ file path. This is a very experimental method. While variants of the format exist, topologicpy reads XYZ files that conform to the following:
    An XYZ file can be made out of one or more frames. Each frame will be stored in a sepatate topologic cluster.
    First line: total number of vertices in the frame. This must be an integer. No other words or characters are allowed on this line.
    Second line: frame label. This is free text and will be stored in the dictionary of each frame (topologic_core.Cluster)
    All other lines: vertex_label, x, y, and z coordinates, separated by spaces, tabs, or commas. The vertex label must be one word with no spaces. It is stored in the dictionary of each vertex.

    Example:
    3
    Frame 1
    A 5.67 -3.45 2.61
    B 3.91 -1.91 4
    A 3.2 1.2 -12.3
    4
    Frame 2
    B 5.47 -3.45 2.61
    B 3.91 -1.93 3.1
    A 3.2 1.2 -22.4
    A 3.2 1.2 -12.3
    3
    Frame 3
    A 5.67 -3.45 2.61
    B 3.91 -1.91 4
    C 3.2 1.2 -12.3

    Parameters
    ----------
    file : file object
        The input XYZ file.
    frameIdKey : str , optional
        The desired id key to use to store the ID of each frame in its dictionary. The default is "id".
    vertexIdKey : str , optional
        The desired id key to use to store the ID of each point in its dictionary. The default is "id".

    Returns
    -------
    list
        The list of frames (topologic_core.Cluster).

    """
    from topologicpy.Vertex import Vertex
    from topologicpy.Cluster import Cluster
    from topologicpy.Dictionary import Dictionary

    def parse(lines):
        frames = []
        line_index = 0
        while line_index < len(lines):
            try:
                n_vertices = int(lines[line_index])
            except:
                return frames
            frame_label = lines[line_index+1][:-1]
            vertices = []
            for i in range(n_vertices):
                one_line = lines[line_index+2+i]
                s = one_line.split()
                vertex_label = s[0]
                v = Vertex.ByCoordinates(float(s[1]), float(s[2]), float(s[3]))
                vertex_dict = Dictionary.ByKeysValues([vertexIdKey], [vertex_label])
                v = Topology.SetDictionary(v, vertex_dict)
                vertices.append(v)
            frame = Cluster.ByTopologies(vertices)
            frame_dict = Dictionary.ByKeysValues([frameIdKey], [frame_label])
            frame = Topology.SetDictionary(frame, frame_dict)
            frames.append(frame)
            line_index = line_index + 2 + n_vertices
        return frames
    
    if not file:
        print("Topology.ByXYZFile - Error: the input file parameter is not a valid file. Returning None.")
        return None
    lines = []
    for lineo, line in enumerate(file):
            lines.append(line)
    if len(lines) > 0:
        frames = parse(lines)
    file.close()
    return frames
def ByXYZPath(path, frameIdKey='id', vertexIdKey='id')

Imports the topology from an XYZ file path. This is a very experimental method. While variants of the format exist, topologicpy reads XYZ files that conform to the following: An XYZ file can be made out of one or more frames. Each frame will be stored in a sepatate topologic cluster. First line: total number of vertices in the frame. This must be an integer. No other words or characters are allowed on this line. Second line: frame label. This is free text and will be stored in the dictionary of each frame (topologic_core.Cluster) All other lines: vertex_label, x, y, and z coordinates, separated by spaces, tabs, or commas. The vertex label must be one word with no spaces. It is stored in the dictionary of each vertex.

Example: 3 Frame 1 A 5.67 -3.45 2.61 B 3.91 -1.91 4 A 3.2 1.2 -12.3 4 Frame 2 B 5.47 -3.45 2.61 B 3.91 -1.93 3.1 A 3.2 1.2 -22.4 A 3.2 1.2 -12.3 3 Frame 3 A 5.67 -3.45 2.61 B 3.91 -1.91 4 C 3.2 1.2 -12.3

Parameters

path : str
The input XYZ file path.
frameIdKey : str , optional
The desired id key to use to store the ID of each frame in its dictionary. The default is "id".
vertexIdKey : str , optional
The desired id key to use to store the ID of each point in its dictionary. The default is "id".

Returns

list
The list of frames (topologic_core.Cluster).
Expand source code
@staticmethod
def ByXYZPath(path, frameIdKey="id", vertexIdKey="id"):
    """
    Imports the topology from an XYZ file path. This is a very experimental method. While variants of the format exist, topologicpy reads XYZ files that conform to the following:
    An XYZ file can be made out of one or more frames. Each frame will be stored in a sepatate topologic cluster.
    First line: total number of vertices in the frame. This must be an integer. No other words or characters are allowed on this line.
    Second line: frame label. This is free text and will be stored in the dictionary of each frame (topologic_core.Cluster)
    All other lines: vertex_label, x, y, and z coordinates, separated by spaces, tabs, or commas. The vertex label must be one word with no spaces. It is stored in the dictionary of each vertex.

    Example:
    3
    Frame 1
    A 5.67 -3.45 2.61
    B 3.91 -1.91 4
    A 3.2 1.2 -12.3
    4
    Frame 2
    B 5.47 -3.45 2.61
    B 3.91 -1.93 3.1
    A 3.2 1.2 -22.4
    A 3.2 1.2 -12.3
    3
    Frame 3
    A 5.67 -3.45 2.61
    B 3.91 -1.91 4
    C 3.2 1.2 -12.3

    Parameters
    ----------
    path : str
        The input XYZ file path.
    frameIdKey : str , optional
        The desired id key to use to store the ID of each frame in its dictionary. The default is "id".
    vertexIdKey : str , optional
        The desired id key to use to store the ID of each point in its dictionary. The default is "id".

    Returns
    -------
    list
        The list of frames (topologic_core.Cluster).

    """
    if not path:
        print("Topology.ByXYZPath - Error: the input path parameter is not a valid path. Returning None.")
        return None
    try:
        file = open(path)
    except:
        print("Topology.ByXYZPath - Error: the XYZ file is not a valid file. Returning None.")
        return None
    return Topology.ByXYZFile(file, frameIdKey=frameIdKey, vertexIdKey=frameIdKey)
def CellComplexes(topology)

Returns the cellcomplexes of the input topology.

Parameters

topology : topologic_core.Topology
The input topology.

Returns

list
The list of cellcomplexes.
Expand source code
@staticmethod
def CellComplexes(topology):
    """
    Returns the cellcomplexes of the input topology.

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.

    Returns
    -------
    list
        The list of cellcomplexes.

    """
    if Topology.IsInstance(topology, "CellComplex") or Topology.IsInstance(topology, "Cell") or Topology.IsInstance(topology, "Shell") or Topology.IsInstance(topology, "Face") or Topology.IsInstance(topology, "Wire") or Topology.IsInstance(topology, "Edge") or Topology.IsInstance(topology, "Vertex"):
        return []
    return Topology.SubTopologies(topology=topology, subTopologyType="cellcomplex")
def Cells(topology)

Returns the cells of the input topology.

Parameters

topology : topologic_core.Topology
The input topology.

Returns

list
The list of cells.
Expand source code
@staticmethod
def Cells(topology):
    """
    Returns the cells of the input topology.

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.

    Returns
    -------
    list
        The list of cells.

    """
    if Topology.IsInstance(topology, "Cell") or Topology.IsInstance(topology, "Shell") or Topology.IsInstance(topology, "Face") or Topology.IsInstance(topology, "Wire") or Topology.IsInstance(topology, "Edge") or Topology.IsInstance(topology, "Vertex"):
        return []
    return Topology.SubTopologies(topology=topology, subTopologyType="cell")
def CenterOfMass(topology)

Returns the center of mass of the input topology. See https://en.wikipedia.org/wiki/Center_of_mass.

Parameters

topology : topologic_core.Topology
The input topology.

Returns

topologic_core.Vertex
The center of mass of the input topology.
Expand source code
@staticmethod
def CenterOfMass(topology):
    """
    Returns the center of mass of the input topology. See https://en.wikipedia.org/wiki/Center_of_mass.

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.

    Returns
    -------
    topologic_core.Vertex
        The center of mass of the input topology.

    """
    if not Topology.IsInstance(topology, "Topology"):
        print("Topology.CenterofMass - Error: the input topology parameter is not a valid topology. Returning None.")
        return None
    return topology.CenterOfMass()
def Centroid(topology)

Returns the centroid of the vertices of the input topology. See https://en.wikipedia.org/wiki/Centroid.

Parameters

topology : topologic_core.Topology
The input topology.

Returns

topologic_core.Vertex
The centroid of the input topology.
Expand source code
@staticmethod
def Centroid(topology):
    """
    Returns the centroid of the vertices of the input topology. See https://en.wikipedia.org/wiki/Centroid.

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.

    Returns
    -------
    topologic_core.Vertex
        The centroid of the input topology.

    """
    from topologicpy.Aperture import Aperture

    if not Topology.IsInstance(topology, "Topology"):
        print("Topology.Centroid - Error: the input topology parameter is not a valid topology. Returning None.")
        return None
    if Topology.IsInstance(topology, "Aperture"):
        return Aperture.Topology(topology).Centroid()
    return topology.Centroid()
def Cleanup(topology=None)

Cleans up all resources in which are managed by topologic library. Use this to manage your application's memory consumption. USE WITH CARE. This methods deletes dictionaries, contents, and contexts

Parameters

topology : topologic_core.Topology , optional
If specified the resources used by the input topology will be deleted. If not, ALL resources will be deleted.

Returns

topologic_core.Topology
The input topology, but with its resources deleted or None.
Expand source code
@staticmethod
def Cleanup(topology=None):
    """
    Cleans up all resources in which are managed by topologic library. Use this to manage your application's memory consumption.
    USE WITH CARE. This methods deletes dictionaries, contents, and contexts

    Parameters
    ----------
    topology : topologic_core.Topology , optional
        If specified the resources used by the input topology will be deleted. If not, ALL resources will be deleted.
    
    Returns
    -------
    topologic_core.Topology
        The input topology, but with its resources deleted or None.
    """
    if not topology == None:
        if not Topology.IsInstance(topology, "Topology"):
            print("Topology.Cleanup - Error: The input topology parameter is not a valid topology. Returning None.")
            return None
    topologic.Topology.Cleanup(topology)
    return topology
def ClusterFaces(topology, angTolerance=2, tolerance=0.0001)

Clusters the faces of the input topology by their direction.

Parameters

topology : topologic_core.Topology
The input topology.
angTolerance : float , optional
The desired angular tolerance. The default is 0.1.
tolerance : float, optional
The desired tolerance. The default is 0.0001.

Returns

list
The list of clusters of faces where faces in the same cluster have the same direction.
Expand source code
@staticmethod
def ClusterFaces(topology, angTolerance=2, tolerance=0.0001):
    """
    Clusters the faces of the input topology by their direction.

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.
    angTolerance : float , optional
        The desired angular tolerance. The default is 0.1.
    tolerance : float, optional
        The desired tolerance. The default is 0.0001.

    Returns
    -------
    list
        The list of clusters of faces where faces in the same cluster have the same direction.

    """
    from topologicpy.Vector import Vector
    from topologicpy.Face import Face
    from topologicpy.Cluster import Cluster

    faces = Topology.SubTopologies(topology, subTopologyType="face")
    face_normals = []
    for face in faces:
        face_normals.append(Face.Normal(face))
    bins = []
    for face_normal in face_normals:
        minAngle = angTolerance * 100
        for bin in bins:
            ang = Vector.Angle(bin, face_normal)
            if ang < minAngle:
                minAngle = ang
        if minAngle > angTolerance:
            bins.append(face_normal)
    num_bins = len(bins)
    # Convert face_normals to a numpy array for efficient computation
    face_normals_array = np.array(face_normals)

    # Compute the bounds for each bin along each dimension
    bin_bounds = [np.linspace(-1, 1, num_bins + 1) for _ in range(3)]

    # Assign each face to a bin based on its normal
    bin_indices = [np.digitize(face_normal, bounds) - 1 for face_normal, bounds in zip(face_normals_array.T, bin_bounds)]
    # Combine the indices along the three dimensions to get a single bin index for each face
    cluster_labels = bin_indices[0] * (num_bins**2) + bin_indices[1] * num_bins + bin_indices[2]
    cluster_labels = list(cluster_labels)
    bins = list(set(cluster_labels))
    clusters = []
    for bin in bins:
        clusters.append([])
    for i, face in enumerate(faces):
        ind = bins.index(cluster_labels[i])
        clusters[ind].append(face)
    final_clusters = []
    for cluster in clusters:
        final_clusters.append(Topology.SelfMerge(Cluster.ByTopologies(cluster), tolerance=tolerance))
    return final_clusters
def ClusterFaces_orig(topology, angTolerance=0.1, tolerance=0.0001)

Clusters the faces of the input topology by their direction.

Parameters

topology : topologic_core.Topology
The input topology.
angTolerance : float , optional
The desired angular tolerance. The default is 0.1.
tolerance : float, optional
The desired tolerance. The default is 0.0001.

Returns

list
The list of clusters of faces where faces in the same cluster have the same direction.
Expand source code
@staticmethod
def ClusterFaces_orig(topology, angTolerance=0.1, tolerance=0.0001):
    """
    Clusters the faces of the input topology by their direction.

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.
    angTolerance : float , optional
        The desired angular tolerance. The default is 0.1.
    tolerance : float, optional
        The desired tolerance. The default is 0.0001.

    Returns
    -------
    list
        The list of clusters of faces where faces in the same cluster have the same direction.

    """
    from topologicpy.Face import Face
    from topologicpy.Cluster import Cluster

    def angle_between(v1, v2):
        u1 = v1 / norm(v1)
        u2 = v2 / norm(v2)
        y = u1 - u2
        x = u1 + u2
        if norm(x) == 0:
            return 0
        a0 = 2 * arctan(norm(y) / norm(x))
        if (not signbit(a0)) or signbit(pi - a0):
            return a0
        elif signbit(a0):
            return 0
        else:
            return pi

    def collinear(v1, v2, tol):
        ang = angle_between(v1, v2)
        if math.isnan(ang) or math.isinf(ang):
            raise Exception("Face.IsCollinear - Error: Could not determine the angle between the input faces")
        elif abs(ang) < tol or abs(pi - ang) < tol:
            return True
        return False
    
    def sumRow(matrix, i):
        return np.sum(matrix[i,:])
    
    def buildSimilarityMatrix(samples, tol):
        numOfSamples = len(samples)
        matrix = np.zeros(shape=(numOfSamples, numOfSamples))
        for i in range(len(matrix)):
            for j in range(len(matrix)):
                if collinear(samples[i], samples[j], tol):
                    matrix[i, j] = 1
        return matrix

    def determineRow(matrix):
        maxNumOfOnes = -1
        row = -1
        for i in range(len(matrix)):
            if maxNumOfOnes < sumRow(matrix, i):
                maxNumOfOnes = sumRow(matrix, i)
                row = i
        return row

    def categorizeIntoClusters(matrix):
        groups = []
        while np.sum(matrix) > 0:
            group = []
            row = determineRow(matrix)
            indexes = addIntoGroup(matrix, row)
            groups.append(indexes)
            matrix = deleteChosenRowsAndCols(matrix, indexes)
        return groups

    def addIntoGroup(matrix, ind):
        change = True
        indexes = []
        for col in range(len(matrix)):
            if matrix[ind, col] == 1:
                indexes.append(col)
        while change == True:
            change = False
            numIndexes = len(indexes)
            for i in indexes:
                for col in range(len(matrix)):
                    if matrix[i, col] == 1:
                        if col not in indexes:
                            indexes.append(col)
            numIndexes2 = len(indexes)
            if numIndexes != numIndexes2:
                change = True
        return indexes

    def deleteChosenRowsAndCols(matrix, indexes):
        for i in indexes:
            matrix[i, :] = 0
            matrix[:, i] = 0
        return matrix
    if not Topology.IsInstance(topology, "Topology"):
        print("Topology.ClusterFaces - Error: the input topology parameter is not a valid topology. Returning None.")
        return None
    faces = []
    _ = topology.Faces(None, faces)
    normals = []
    for aFace in faces:
        normals.append(Face.NormalAtParameters(aFace, 0.5, 0.5, "XYZ", 3))
    # build a matrix of similarity
    mat = buildSimilarityMatrix(normals, angTolerance)
    categories = categorizeIntoClusters(mat)
    returnList = []
    for aCategory in categories:
        tempList = []
        if len(aCategory) > 0:
            for index in aCategory:
                tempList.append(faces[index])
            returnList.append(Topology.SelfMerge(Cluster.ByTopologies(tempList), tolerance=tolerance))
    return returnList
def Clusters(topology)

Returns the clusters of the input topology.

Parameters

topology : topologic_core.Topology
The input topology.

Returns

list
The list of clusters.
Expand source code
@staticmethod
def Clusters(topology):
    """
    Returns the clusters of the input topology.

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.

    Returns
    -------
    list
        The list of clusters.

    """
    if not Topology.IsInstance(topology, "Cluster"):
        return []
    return Topology.SubTopologies(topology=topology, subTopologyType="cluster")
def Contents(topology)

Returns the contents of the input topology

Parameters

topology : topologic_core.Topology
The input topology.

Returns

list
The list of contents of the input topology.
Expand source code
@staticmethod
def Contents(topology):
    """
    Returns the contents of the input topology

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.

    Returns
    -------
    list
        The list of contents of the input topology.

    """
    if not Topology.IsInstance(topology, "Topology"):
        print("Topology.Contents - Error: the input topology parameter is not a valid topology. Returning None.")
        return None
    contents = []
    _ = topology.Contents(contents)
    return contents
def Contexts(topology)

Returns the list of contexts of the input topology

Parameters

topology : topologic_core.Topology
The input topology.

Returns

list
The list of contexts of the input topology.
Expand source code
@staticmethod
def Contexts(topology):
    """
    Returns the list of contexts of the input topology

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.

    Returns
    -------
    list
        The list of contexts of the input topology.

    """
    if not Topology.IsInstance(topology, "Topology"):
        print("Topology.Contexts - Error: the input topology parameter is not a valid topology. Returning None.")
        return None
    contexts = []
    _ = topology.Contexts(contexts)
    return contexts
def ConvexHull(topology, tolerance=0.0001)

Creates a convex hull

Parameters

topology : topologic_core.Topology
The input Topology.
tolerance : float , optional
The desired tolerance. The default is 0.0001.

Returns

topologic_core.Topology
The created convex hull of the input topology.
Expand source code
@staticmethod
def ConvexHull(topology, tolerance=0.0001):
    """
    Creates a convex hull

    Parameters
    ----------
    topology : topologic_core.Topology
        The input Topology.
    tolerance : float , optional
        The desired tolerance. The default is 0.0001.

    Returns
    -------
    topologic_core.Topology
        The created convex hull of the input topology.

    """
    from topologicpy.Vertex import Vertex
    from topologicpy.Edge import Edge
    from topologicpy.Wire import Wire
    from topologicpy.Face import Face
    from topologicpy.Shell import Shell
    from topologicpy.Cell import Cell
    from topologicpy.Cluster import Cluster        
    
    def convexHull3D(item, tolerance, option):
        if item:
            vertices = []
            _ = item.Vertices(None, vertices)
            pointList = []
            for v in vertices:
                pointList.append([v.X(), v.Y(), v.Z()])
            points = np.array(pointList)
            if option:
                hull = ConvexHull(points, qhull_options=option)
            else:
                hull = ConvexHull(points)
            faces = []
            for simplex in hull.simplices:
                edges = []
                for i in range(len(simplex)-1):
                    sp = hull.points[simplex[i]]
                    ep = hull.points[simplex[i+1]]
                    sv = Vertex.ByCoordinates(sp[0], sp[1], sp[2])
                    ev = Vertex.ByCoordinates(ep[0], ep[1], ep[2])
                    edges.append(Edge.ByVertices([sv, ev], tolerance=tolerance))
                sp = hull.points[simplex[-1]]
                ep = hull.points[simplex[0]]
                sv = Vertex.ByCoordinates(sp[0], sp[1], sp[2])
                ev = Vertex.ByCoordinates(ep[0], ep[1], ep[2])
                edges.append(Edge.ByVertices([sv, ev], tolerance=tolerance))
                faces.append(Face.ByWire(Wire.ByEdges(edges, tolerance=tolerance), tolerance=tolerance))
        try:
            c = Cell.ByFaces(faces, tolerance=tolerance)
            return c
        except:
            returnTopology = Topology.SelfMerge(Cluster.ByTopologies(faces), tolerance=tolerance)
            if Topology.Type(returnTopology) == Topology.TypeID("Shell"):
                return Shell.ExternalBoundary(returnTopology, tolerance=tolerance)
    if not Topology.IsInstance(topology, "Topology"):
        print("Topology.ConvexHull - Error: the input topology parameter is not a valid topology. Returning None.")
        return None
    returnObject = None
    try:
        returnObject = convexHull3D(topology, tolerance, None)
    except:
        returnObject = convexHull3D(topology, tolerance, 'QJ')
    return returnObject
def Copy(topology, deep=False)

Returns a copy of the input topology

Parameters

topology : topologic_core.Topology
The input topology.
deep : bool , optional
If set to True, a deep copy will be performed (this is slow). Othwerwise, it will not. The default is False

Returns

topologic_core.Topology
A copy of the input topology.
Expand source code
@staticmethod
def Copy(topology, deep=False):
    """
    Returns a copy of the input topology

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.
    deep : bool , optional
        If set to True, a deep copy will be performed (this is slow). Othwerwise, it will not. The default is False

    Returns
    -------
    topologic_core.Topology
        A copy of the input topology.

    """
    from topologicpy.Dictionary import Dictionary
    if not Topology.IsInstance(topology, "Topology"):
        print("Topology.Copy - Error: the input topology parameter is not a valid topology. Returning None.")
        return None
    if deep:
        return Topology.ByJSONString(Topology.JSONString([topology]), progressBar=False)
    d = Topology.Dictionary(topology)
    return_topology = Topology.ByBREPString(Topology.BREPString(topology))
    keys = Dictionary.Keys(d)
    if len(keys) > 0:
        return_topology = Topology.SetDictionary(return_topology, d)
    return return_topology
def Degree(topology, hostTopology)

Returns the number of immediate super topologies that use the input topology

Parameters

topology : topologic_core.Topology
The input topology.
hostTopology : topologic_core.Topology
The input host topology to which the input topology belongs

Returns

int
The degree of the topology (the number of immediate super topologies that use the input topology).
Expand source code
@staticmethod
def Degree(topology, hostTopology):
    """
    Returns the number of immediate super topologies that use the input topology

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.
    hostTopology : topologic_core.Topology
        The input host topology to which the input topology belongs
    
    Returns
    -------
    int
        The degree of the topology (the number of immediate super topologies that use the input topology).
    
    """
    if not Topology.IsInstance(topology, "Topology"):
        print("Topology.Degree - Error: the input topology parameter is not a valid topology. Returning None.")
        return None
    if not Topology.IsInstance(hostTopology, "Topology"):
        print("Topology.Degree - Error: the input hostTopology parameter is not a valid topology. Returning None.")
        return None
    
    hostTopologyType = Topology.TypeAsString(hostTopology).lower()
    type = Topology.TypeAsString(topology).lower()
    superType = ""
    if type == "vertex" and (hostTopologyType == "cellcomplex" or hostTopologyType == "cell" or hostTopologyType == "shell"):
        superType = "face"
    elif type == "vertex" and (hostTopologyType == "wire" or hostTopologyType == "edge"):
        superType = "edge"
    elif type == "edge" and (hostTopologyType == "cellcomplex" or hostTopologyType == "cell" or hostTopologyType == "shell"):
        superType = "face"
    elif type == "face" and (hostTopologyType == "cellcomplex"):
        superType = "cell"
    superTopologies = Topology.SuperTopologies(topology, hostTopology=hostTopology, topologyType=superType)
    if not superTopologies:
        return 0
    return len(superTopologies)
def Dictionary(topology)

Returns the dictionary of the input topology

Parameters

topology : topologic_core.Topology
The input topology.

Returns

topologic_core.Dictionary
The dictionary of the input topology.
Expand source code
@staticmethod
def Dictionary(topology):
    """
    Returns the dictionary of the input topology

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.

    Returns
    -------
    topologic_core.Dictionary
        The dictionary of the input topology.

    """
    if not Topology.IsInstance(topology, "Topology"):
        print("Topology.Dictionary - Error: the input topology parameter is not a valid topology. Returning None.")
        return None
    return topology.GetDictionary()
def Difference(topologyA, topologyB, tranDict=False, tolerance=0.0001)

See Topology.Boolean().

Expand source code
@staticmethod
def Difference(topologyA, topologyB, tranDict=False, tolerance=0.0001):
    """
    See Topology.Boolean().

    """
    return Topology.Boolean(topologyA=topologyA, topologyB=topologyB, operation="difference", tranDict=tranDict, tolerance=tolerance)
def Dimensionality(topology)

Returns the dimensionality of the input topology

Parameters

topology : topologic_core.Topology
The input topology.

Returns

int
The dimensionality of the input topology.
Expand source code
@staticmethod
def Dimensionality(topology):
    """
    Returns the dimensionality of the input topology

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.

    Returns
    -------
    int
        The dimensionality of the input topology.

    """
    if not Topology.IsInstance(topology, "Topology"):
        print("Topology.Dimensionality - Error: the input topology parameter is not a valid topology. Returning None.")
        return None
    return topology.Dimensionality()
def Divide(topologyA, topologyB, transferDictionary=False, addNestingDepth=False)

Divides the input topology by the input tool and places the results in the contents of the input topology.

Parameters

topologyA : topologic_core.Topology
The input topology to be divided.
topologyB : topologic_core.Topology
the tool used to divide the input topology.
transferDictionary : bool , optional
If set to True the dictionary of the input topology is transferred to the divided topologies.
addNestingDepth : bool , optional
If set to True the nesting depth of the division is added to the dictionaries of the divided topologies.

Returns

topologic_core.Topology
The input topology with the divided topologies added to it as contents.
Expand source code
@staticmethod
def Divide(topologyA, topologyB, transferDictionary=False, addNestingDepth=False):
    """
    Divides the input topology by the input tool and places the results in the contents of the input topology.

    Parameters
    ----------
    topologyA : topologic_core.Topology
        The input topology to be divided.
    topologyB : topologic_core.Topology
        the tool used to divide the input topology.
    transferDictionary : bool , optional
        If set to True the dictionary of the input topology is transferred to the divided topologies.
    addNestingDepth : bool , optional
        If set to True the nesting depth of the division is added to the dictionaries of the divided topologies.

    Returns
    -------
    topologic_core.Topology
        The input topology with the divided topologies added to it as contents.

    """
    
    from topologicpy.Dictionary import Dictionary
    if not Topology.IsInstance(topologyA, "Topology"):
        print("Topology.Divide - Error: the input topologyA parameter is not a valid topology. Returning None.")
        return None
    if not Topology.IsInstance(topologyB, "Topology"):
        print("Topology.Divide - Error: the input topologyB parameter is not a valid topology. Returning None.")
        return None
    try:
        _ = topologyA.Divide(topologyB, False) # Don't transfer dictionaries just yet
    except:
        raise Exception("TopologyDivide - Error: Divide operation failed.")
    nestingDepth = "1"
    keys = ["nesting_depth"]
    values = [nestingDepth]

    if not addNestingDepth and not transferDictionary:
        return topologyA

    contents = []
    _ = topologyA.Contents(contents)
    for i in range(len(contents)):
        if not addNestingDepth and transferDictionary:
            parentDictionary = Topology.Dictionary(topologyA)
            if parentDictionary != None:
                _ = contents[i].SetDictionary(parentDictionary)
        if addNestingDepth and transferDictionary:
            parentDictionary = Topology.Dictionary(topologyA)
            if parentDictionary != None:
                keys = Dictionary.Keys(parentDictionary)
                values = Dictionary.Values(parentDictionary)
                if ("nesting_depth" in keys):
                    nestingDepth = parentDictionary.ValueAtKey("nesting_depth").StringValue()
                else:
                    keys.append("nesting_depth")
                    values.append(nestingDepth)
                parentDictionary = Dictionary.ByKeysValues(keys, values)
            else:
                keys = ["nesting_depth"]
                values = [nestingDepth]
            parentDictionary = Dictionary.ByKeysValues(keys, values)
            _ = Topology.SetDictionary(topologyA, parentDictionary)
            values[keys.index("nesting_depth")] = nestingDepth+"_"+str(i+1)
            d = Dictionary.ByKeysValues(keys, values)
            _ = contents[i].SetDictionary(d)
        if addNestingDepth and  not transferDictionary:
            parentDictionary = Topology.Dictionary(topologyA)
            if parentDictionary != None:
                keys, values = Dictionary.ByKeysValues(parentDictionary)
                if ("nesting_depth" in keys):
                    nestingDepth = parentDictionary.ValueAtKey("nesting_depth").StringValue()
                else:
                    keys.append("nesting_depth")
                    values.append(nestingDepth)
                parentDictionary = Dictionary.ByKeysValues(keys, values)
            else:
                keys = ["nesting_depth"]
                values = [nestingDepth]
            parentDictionary = Dictionary.ByKeysValues(keys, values)
            _ = Topology.SetDictionary(topologyA, parentDictionary)
            keys = ["nesting_depth"]
            v = nestingDepth+"_"+str(i+1)
            values = [v]
            d = Dictionary.ByKeysValues(keys, values)
            _ = Topology.SetDictionary(contents[i], d)
    return topologyA
def Edges(topology)

Returns the edges of the input topology.

Parameters

topology : topologic_core.Topology
The input topology.

Returns

list
The list of edges.
Expand source code
@staticmethod
def Edges(topology):
    """
    Returns the edges of the input topology.

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.

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

    """
    if Topology.IsInstance(topology, "Edge") or Topology.IsInstance(topology, "Vertex"):
        return []
    return Topology.SubTopologies(topology=topology, subTopologyType="edge")
def Explode(topology, origin=None, scale=1.25, typeFilter=None, axes='xyz', tolerance=0.0001)

Explodes the input topology. See https://en.wikipedia.org/wiki/Exploded-view_drawing.

Parameters

topology : topologic_core.Topology
The input topology.
origin : topologic_core.Vertex , optional
The origin of the explosion. If set to None, the centroid of the input topology will be used. The default is None.
scale : float , optional
The scale factor of the explosion. The default is 1.25.
typeFilter : str , optional
The type of the subtopologies to explode. This can be any of "vertex", "edge", "face", or "cell". If set to None, a subtopology one level below the type of the input topology will be used. The default is None.
axes : str , optional
Sets what axes are to be used for exploding the topology. This can be any permutation or substring of "xyz". It is not case sensitive. The default is "xyz".
tolerance : float , optional
The desired tolerance. The default is 0.0001.

Returns

topologic_core.Cluster
The exploded topology.
Expand source code
@staticmethod
def Explode(topology, origin=None, scale=1.25, typeFilter=None, axes="xyz", tolerance=0.0001):
    """
    Explodes the input topology. See https://en.wikipedia.org/wiki/Exploded-view_drawing.

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.
    origin : topologic_core.Vertex , optional
        The origin of the explosion. If set to None, the centroid of the input topology will be used. The default is None.
    scale : float , optional
        The scale factor of the explosion. The default is 1.25.
    typeFilter : str , optional
        The type of the subtopologies to explode. This can be any of "vertex", "edge", "face", or "cell". If set to None, a subtopology one level below the type of the input topology will be used. The default is None.
    axes : str , optional
        Sets what axes are to be used for exploding the topology. This can be any permutation or substring of "xyz". It is not case sensitive. The default is "xyz".
    tolerance : float , optional
        The desired tolerance. The default is 0.0001.
    
    Returns
    -------
    topologic_core.Cluster
        The exploded topology.

    """
    from topologicpy.Vertex import Vertex
    from topologicpy.Cluster import Cluster
    from topologicpy.Graph import Graph

    def processClusterTypeFilter(cluster):
        if len(Cluster.CellComplexes(cluster)) > 0:
            return "cell"
        elif len(Cluster.Cells(cluster)) > 0:
            return "face"
        elif len(Cluster.Shells(cluster)) > 0:
            return "face"
        elif len(Cluster.Faces(cluster)) > 0:
            return "edge"
        elif len(Cluster.Wires(cluster)) > 0:
            return "edge"
        elif len(Cluster.Edges(cluster)) > 0:
            return "vertex"
        else:
            return "self"

    def getTypeFilter(topology):
        typeFilter = "self"
        if Topology.IsInstance(topology, "Vertex"):
            typeFilter = "self"
        elif Topology.IsInstance(topology, "Edge"):
            typeFilter = "vertex"
        elif Topology.IsInstance(topology, "Wire"):
            typeFilter = "edge"
        elif Topology.IsInstance(topology, "Face"):
            typeFilter = "edge"
        elif Topology.IsInstance(topology, "Shell"):
            typeFilter = "face"
        elif Topology.IsInstance(topology, "Cell"):
            typeFilter = "face"
        elif Topology.IsInstance(topology, "CellComplex"):
            typeFilter = "cell"
        elif Topology.IsInstance(topology, "Cluster"):
            typeFilter = processClusterTypeFilter(topology)
        elif Topology.IsInstance(topology, "Graph"):
            typeFilter = "edge"
        return typeFilter
    
    if not Topology.IsInstance(topology, "Topology"):
        print("Topology.Explode - Error: the input topology parameter is not a valid topology. Returning None.")
        return None
    if not Topology.IsInstance(origin, "Vertex"):
        origin = Topology.CenterOfMass(topology)
    if not typeFilter:
        typeFilter = getTypeFilter(topology)
    if not isinstance(typeFilter, str):
        print("Topology.Explode - Error: the input typeFilter parameter is not a valid string. Returning None.")
        return None
    if not isinstance(axes, str):
        print("Topology.Explode - Error: the input axes parameter is not a valid string. Returning None.")
        return None
    if Topology.IsInstance(topology, "Topology"):
        # Hack to fix a weird bug that seems to be a problem with OCCT memory handling.
        topology = Topology.ByBREPString(Topology.BREPString(topology))
    axes = axes.lower()
    x_flag = "x" in axes
    y_flag = "y" in axes
    z_flag = "z" in axes
    if not x_flag and not y_flag and not z_flag:
        print("Topology.Explode - Error: the input axes parameter is not a valid string. Returning None.")
        return None

    topologies = []
    newTopologies = []
    if Topology.IsInstance(topology, "Graph"):
        topology = Graph.Topology(topology)

    if typeFilter.lower() == "self":
        topologies = [topology]
    else:
        topologies = Topology.SubTopologies(topology, subTopologyType=typeFilter.lower())
    for aTopology in topologies:
        c = Topology.InternalVertex(aTopology, tolerance=tolerance)
        oldX = c.X()
        oldY = c.Y()
        oldZ = c.Z()
        if x_flag:
            newX = (oldX - origin.X())*scale + origin.X()
        else:
            newX = oldX
        if y_flag:
            newY = (oldY - origin.Y())*scale + origin.Y()
        else:
            newY = oldY
        if z_flag:
            newZ = (oldZ - origin.Z())*scale + origin.Z()
        else:
            newZ = oldZ
        xT = newX - oldX
        yT = newY - oldY
        zT = newZ - oldZ
        newTopology = Topology.Translate(aTopology, xT, yT, zT)
        newTopologies.append(newTopology)
    return Cluster.ByTopologies(newTopologies)
def ExportToBREP(topology, path, overwrite=False, version=3)

Exports the input topology to a BREP file. See https://dev.opencascade.org/doc/occt-6.7.0/overview/html/occt_brep_format.html.

Parameters

topology : topologic_core.Topology
The input topology.
path : str
The input file path.
overwrite : bool , optional
If set to True the ouptut file will overwrite any pre-existing file. Otherwise, it won't. The default is False.
version : int , optional
The desired version number for the BREP file. The default is 3.

Returns

bool
True if the export operation is successful. False otherwise.
Expand source code
@staticmethod
def ExportToBREP(topology, path, overwrite=False, version=3):
    """
    Exports the input topology to a BREP file. See https://dev.opencascade.org/doc/occt-6.7.0/overview/html/occt_brep_format.html.

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.
    path : str
        The input file path.
    overwrite : bool , optional
        If set to True the ouptut file will overwrite any pre-existing file. Otherwise, it won't. The default is False.
    version : int , optional
        The desired version number for the BREP file. The default is 3.

    Returns
    -------
    bool
        True if the export operation is successful. False otherwise.

    """
    from os.path import exists
    if not Topology.IsInstance(topology, "Topology"):
        print("Topology.ExportToBREP - Error: the input topology parameter is not a valid topology. Returning None.")
        return None
    if not isinstance(path, str):
        print("Topology.ExportToBREP - Error: the input path parameter is not a valid string. Returning None.")
        return None
    # Make sure the file extension is .brep
    ext = path[len(path)-5:len(path)]
    if ext.lower() != ".brep":
        path = path+".brep"
    if not overwrite and exists(path):
        print("Topology.ExportToBREP - Error: a file already exists at the specified path and overwrite is set to False. Returning None.")
        return None
    f = None
    try:
        if overwrite == True:
            f = open(path, "w")
        else:
            f = open(path, "x") # Try to create a new File
    except:
        raise Exception("Error: Could not create a new file at the following location: "+path)
    if (f):
        s = Topology.BREPString(topology, version)
        f.write(s)
        f.close()    
        return True
    return False
def ExportToJSON(topologies, path, overwrite=False)

Exports the input list of topologies to a JSON file.

Parameters

topologies : list
The input list of topologies.
path : str
The path to the JSON file.
overwrite : bool , optional
If set to True the ouptut file will overwrite any pre-existing file. Otherwise, it won't. The default is False.

Returns

bool
The status of exporting the JSON file. If True, the operation was successful. Otherwise, it was unsuccesful.
Expand source code
@staticmethod
def ExportToJSON(topologies, path, overwrite=False):
    """
    Exports the input list of topologies to a JSON file.

    Parameters
    ----------
    topologies : list
        The input list of topologies.
    path : str
        The path to the JSON file.
    overwrite : bool , optional
        If set to True the ouptut file will overwrite any pre-existing file. Otherwise, it won't. The default is False.

    Returns
    -------
    bool
        The status of exporting the JSON file. If True, the operation was successful. Otherwise, it was unsuccesful.

    """
    from os.path import exists
    # Make sure the file extension is .json
    ext = path[len(path)-5:len(path)]
    if ext.lower() != ".json":
        path = path+".json"
    if not overwrite and exists(path):
        print("Topology.ExportToJSON - Error: a file already exists at the specified path and overwrite is set to False. Returning None.")
        return None
    f = None
    try:
        if overwrite == True:
            f = open(path, "w")
        else:
            f = open(path, "x") # Try to create a new File
    except:
        raise Exception("Error: Could not create a new file at the following location: "+path)
    if (f):
        jsondata = json.loads(Topology.JSONString(topologies))
        if jsondata != None:
            json.dump(jsondata, f, indent=4, sort_keys=True)
            f.close()
            return True
        else:
            f.close()
            return False
    return False
def ExportToOBJ(topology, path, transposeAxes: bool = True, mode: int = 0, meshSize: float = None, overwrite: bool = False, mantissa: int = 6, tolerance: float = 0.0001)

Exports the input topology to a Wavefront OBJ file. This is very experimental and outputs a simple solid topology.

Parameters

topology : topologic_core.Topology
The input topology.
path : str
The input file path.
transposeAxes : bool , optional
If set to True the Z and Y coordinates are transposed so that Y points "up"
mode : int , optional
The desired mode of meshing algorithm. Several options are available: 0: Classic 1: MeshAdapt 3: Initial Mesh Only 5: Delaunay 6: Frontal-Delaunay 7: BAMG 8: Fontal-Delaunay for Quads 9: Packing of Parallelograms All options other than 0 (Classic) use the gmsh library. See https://gmsh.info/doc/texinfo/gmsh.html#Mesh-options WARNING: The options that use gmsh can be very time consuming and can create very heavy geometry.
meshSize : float , optional
The desired size of the mesh when using the "mesh" option. If set to None, it will be calculated automatically and set to 10% of the overall size of the face.
mantissa : int , optional
The desired length of the mantissa. The default is 6.
tolerance : float , optional
The desired tolerance. The default is 0.0001.
overwrite : bool , optional
If set to True the ouptut file will overwrite any pre-existing file. Otherwise, it won't. The default is False.

Returns

bool
True if the export operation is successful. False otherwise.
Expand source code
@staticmethod
def ExportToOBJ(topology, path, transposeAxes: bool = True, mode: int = 0, meshSize: float = None, overwrite: bool = False, mantissa: int = 6, tolerance: float = 0.0001):
    """
    Exports the input topology to a Wavefront OBJ file. This is very experimental and outputs a simple solid topology.

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.
    path : str
        The input file path.
    transposeAxes : bool , optional
        If set to True the Z and Y coordinates are transposed so that Y points "up"
    mode : int , optional
        The desired mode of meshing algorithm. Several options are available:
        0: Classic
        1: MeshAdapt
        3: Initial Mesh Only
        5: Delaunay
        6: Frontal-Delaunay
        7: BAMG
        8: Fontal-Delaunay for Quads
        9: Packing of Parallelograms
        All options other than 0 (Classic) use the gmsh library. See https://gmsh.info/doc/texinfo/gmsh.html#Mesh-options
        WARNING: The options that use gmsh can be very time consuming and can create very heavy geometry.
    meshSize : float , optional
        The desired size of the mesh when using the "mesh" option. If set to None, it will be
        calculated automatically and set to 10% of the overall size of the face.
    mantissa : int , optional
        The desired length of the mantissa. The default is 6.
    tolerance : float , optional
        The desired tolerance. The default is 0.0001.
    overwrite : bool , optional
        If set to True the ouptut file will overwrite any pre-existing file. Otherwise, it won't. The default is False.

    Returns
    -------
    bool
        True if the export operation is successful. False otherwise.

    """
    from os.path import exists

    if not Topology.IsInstance(topology, "Topology"):
        print("Topology.ExportToOBJ - Error: the input topology parameter is not a valid topology. Returning None.")
        return None
    if not overwrite and exists(path):
        print("Topology.ExportToOBJ - Error: a file already exists at the specified path and overwrite is set to False. Returning None.")
        return None
    
    # Make sure the file extension is .obj
    ext = path[len(path)-4:len(path)]
    if ext.lower() != ".obj":
        path = path+".obj"
    status = False
    objString = Topology.OBJString(topology, transposeAxes=transposeAxes, mode=mode, meshSize=meshSize, mantissa=mantissa, tolerance=tolerance)
    with open(path, "w") as f:
        f.writelines(objString)
        f.close()
        status = True
    return status
def Faces(topology)

Returns the faces of the input topology.

Parameters

topology : topologic_core.Topology
The input topology.

Returns

list
The list of faces.
Expand source code
@staticmethod
def Faces(topology):
    """
    Returns the faces of the input topology.

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.

    Returns
    -------
    list
        The list of faces.

    """
    if Topology.IsInstance(topology, "Face") or Topology.IsInstance(topology, "Wire") or Topology.IsInstance(topology, "Edge") or Topology.IsInstance(topology, "Vertex"):
        return []
    return Topology.SubTopologies(topology=topology, subTopologyType="face")
def Filter(topologies, topologyType='any', searchType='any', key=None, value=None)

Filters the input list of topologies based on the input parameters.

Parameters

topologies : list
The input list of topologies.
topologyType : str , optional
The type of topology to filter by. This can be one of "any", "vertex", "edge", "wire", "face", "shell", "cell", "cellcomplex", or "cluster". It is case insensitive. The default is "any".
searchType : str , optional
The type of search query to conduct in the topology's dictionary. This can be one of "any", "equal to", "contains", "starts with", "ends with", "not equal to", "does not contain". The default is "any".
key : str , optional
The dictionary key to search within. The default is None which means it will filter by topology type only.
value : str , optional
The value to search for at the specified key. The default is None which means it will filter by topology type only.

Returns

dict
A dictionary of filtered and other elements. The dictionary has two keys: - "filtered" The filtered topologies. - "other" The other topologies that did not meet the filter criteria.
Expand source code
@staticmethod
def Filter(topologies, topologyType="any", searchType="any", key=None, value=None):
    """
    Filters the input list of topologies based on the input parameters.

    Parameters
    ----------
    topologies : list
        The input list of topologies.
    topologyType : str , optional
        The type of topology to filter by. This can be one of "any", "vertex", "edge", "wire", "face", "shell", "cell", "cellcomplex", or "cluster". It is case insensitive. The default is "any".
    searchType : str , optional
        The type of search query to conduct in the topology's dictionary. This can be one of "any", "equal to", "contains", "starts with", "ends with", "not equal to", "does not contain". The default is "any".
    key : str , optional
        The dictionary key to search within. The default is None which means it will filter by topology type only.
    value : str , optional
        The value to search for at the specified key. The default is None which means it will filter by topology type only.

    Returns
    -------
    dict
        A dictionary of filtered and other elements. The dictionary has two keys:
        - "filtered" The filtered topologies.
        - "other" The other topologies that did not meet the filter criteria.

    """
    from topologicpy.Dictionary import Dictionary

    def listToString(item):
        returnString = ""
        if isinstance(item, list):
            if len(item) < 2:
                return str(item[0])
            else:
                returnString = item[0]
                for i in range(1, len(item)):
                    returnString = returnString+str(item[i])
        return returnString
    
    filteredTopologies = []
    otherTopologies = []
    for aTopology in topologies:
        if not aTopology:
            continue
        if (topologyType.lower() == "any") or (Topology.TypeAsString(aTopology).lower() == topologyType.lower()):
            if value == "" or key == "":
                filteredTopologies.append(aTopology)
            else:
                if isinstance(value, list):
                    value.sort()
                    value = str(value)
                value.replace("*",".+")
                value = value.lower()
                d = Topology.Dictionary(aTopology)
                v = Dictionary.ValueAtKey(d, key)
                if v != None:
                    v = v.lower()
                    if searchType.lower() == "equal to":
                        searchResult = (value == v)
                    elif searchType.lower() == "contains":
                        searchResult = (value in v)
                    elif searchType.lower() == "starts with":
                        searchResult = (value == v[0: len(value)])
                    elif searchType.lower() == "ends with":
                        searchResult = (value == v[len(v)-len(value):len(v)])
                    elif searchType.lower() == "not equal to":
                        searchResult = not (value == v)
                    elif searchType.lower() == "does not contain":
                        searchResult = not (value in v)
                    else:
                        searchResult = False
                    if searchResult:
                        filteredTopologies.append(aTopology)
                    else:
                        otherTopologies.append(aTopology)
                else:
                    otherTopologies.append(aTopology)
        else:
            otherTopologies.append(aTopology)
    return {"filtered": filteredTopologies, "other": otherTopologies}
def Fix(topology, topologyType: str = 'CellComplex', tolerance: float = 0.0001)

Attempts to fix the input topology to matched the desired output type.

Parameters

topology : topologic_core.Topology
The input topology
topologyType : str , optional
The desired output topology type. This must be one of "vertex", "edge", "wire", "face", "shell", "cell", "cellcomplex", "cluster". It is case insensitive. The default is "CellComplex"
tolerance : float , optional
The desired tolerance. The default is 0.0001.

Returns

topologic_core.Topology
The output topology in the desired type.
Expand source code
@staticmethod
def Fix(topology, topologyType: str = "CellComplex", tolerance: float = 0.0001):
    """
    Attempts to fix the input topology to matched the desired output type.

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology
    topologyType : str , optional
        The desired output topology type. This must be one of "vertex", "edge", "wire", "face", "shell", "cell", "cellcomplex", "cluster". It is case insensitive. The default is "CellComplex"
    tolerance : float , optional
        The desired tolerance. The default is 0.0001.
    
    Returns
    -------
    topologic_core.Topology
        The output topology in the desired type.

    """
    from topologicpy.Vertex import Vertex
    from topologicpy.Edge import Edge
    from topologicpy.Wire import Wire
    from topologicpy.Face import Face
    from topologicpy.Shell import Shell
    from topologicpy.Cell import Cell
    from topologicpy.CellComplex import CellComplex
    from topologicpy.Cluster import Cluster

    topology = Cluster.ByTopologies([topology])
    a_type = Topology.TypeAsString(topology).lower()
    b_type = topologyType.lower()
    if b_type not in ["vertex", "edge", "wire", "face", "shell", "cell", "cellcomplex", "cluster"]:
        print("Topology.Fix - Error: The input topologyType parameter is not recognized. Returning original topology.")
        return topology
    if a_type == b_type:
        return topology
    if b_type == "cluster":
        topology = Topology.SelfMerge(topology, tolerance=tolerance)
        return Cluster.ByTopologies([topology])
    if b_type == "cellcomplex":
        topology = Topology.SelfMerge(topology, tolerance=tolerance)
        if Topology.TypeAsString(topology).lower() == "cellcomplex":
            return topology
        cells = Topology.Cells(topology)
        if len(cells) < 2:
            print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
            return topology
        return_topology = CellComplex.ByCells(cells)
        if return_topology == None:
            print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
            return topology
        if Topology.TypeAsString(topology).lower() == "cellcomplex":
            return return_topology
        faces = Topology.Faces(topology)
        if len(faces) < 3:
            print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
            return topology
        return_topology = CellComplex.ByFaces(faces, tolerance=tolerance)
        if return_topology == None:
            print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
            return topology
        if Topology.TypeAsString(return_topology).lower() == "cellcomplex":
            return return_topology
        print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
        return topology
    if b_type == "cell":
        topology = Topology.SelfMerge(topology, tolerance=tolerance)
        if Topology.TypeAsString(topology).lower() == "cell":
            return topology
        if Topology.TypeAsString(topology).lower() == "cellComplex":
            return CellComplex.ExternalBoundary(topology)
        faces = Topology.Faces(topology)
        if len(faces) < 3:
            print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
            return topology
        return_topology = Cell.ByFaces(faces, tolerance=tolerance)
        if return_topology == None:
            return_topology = CellComplex.ByFaces(faces, tolerance=tolerance)
            if return_topology == None:
                print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
                return topology
            elif len(Topology.Cells(return_topology)) < 1:
                print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
                return topology
            return_topology = CellComplex.ExternalBoundary(return_topology)
        if return_topology == None:
            print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
            return topology
        if Topology.TypeAsString(return_topology).lower() == "cell":
            return return_topology
        print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
        return topology
    if b_type == "shell":
        topology = Topology.SelfMerge(topology, tolerance=tolerance)
        if Topology.TypeAsString(topology).lower() == "shell":
            return topology
        faces = Topology.Faces(topology)
        if len(faces) < 2:
            print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
            return topology
        return_topology = Shell.ByFaces(faces)
        if Topology.TypeAsString(return_topology).lower() == "shell":
            return return_topology
        print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
        return topology
    if b_type == "face":
        topology = Topology.SelfMerge(topology, tolerance=tolerance)
        if Topology.TypeAsString(topology).lower() == "face":
            return topology
        wires = Topology.Wires(topology)
        if len(wires) < 1:
            print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
            return topology
        return_topology = Face.ByWire(wires[0], tolerance=tolerance)
        if Topology.TypeAsString(return_topology).lower() == "face":
            return return_topology
        print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
        return topology
    if b_type == "wire":
        topology = Topology.SelfMerge(topology, tolerance=tolerance)
        if Topology.TypeAsString(topology).lower() == "wire":
            return topology
        edges = Topology.Edges(topology)
        if len(edges) < 2:
            print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
            return topology
        return_topology = Wire.ByEdges(edges, tolerance=tolerance)
        if Topology.TypeAsString(return_topology).lower() == "wire":
            return return_topology
        print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
        return topology
    if b_type == "edge":
        topology = Topology.SelfMerge(topology, tolerance=tolerance)
        if Topology.TypeAsString(topology).lower() == "edge":
            return topology
        vertices = Topology.Vertices(topology)
        if len(vertices) < 2:
            print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
            return topology
        return_topology = Edge.ByVertices(vertices, tolerance=tolerance)
        if Topology.TypeAsString(return_topology).lower() == "edge":
            return return_topology
        print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
        return topology
    if b_type == "vertex":
        topology = Topology.SelfMerge(topology, tolerance=tolerance)
        if Topology.TypeAsString(topology).lower() == "vertex":
            return topology
        vertices = Topology.Vertices(topology)
        if len(vertices) < 1:
            print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
            return topology
        return_topology = vertices[0]
        if Topology.TypeAsString(return_topology).lower() == "vertex":
            return return_topology
        print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
        return topology
    return topology
def Flatten(topology, origin=None, direction=[0, 0, 1])

Flattens the input topology such that the input origin is located at the world origin and the input topology is rotated such that the input vector is pointed in the Up direction (see Vector.Up()).

Parameters

topology : topologic_core.Topology
The input topology.
origin : topologic_core.Vertex , optional
The input origin. If set to None, The object's centroid will be used to place the world origin. The default is None.
vector : list , optional
The input direction vector. The input topology will be rotated such that this vector is pointed in the positive Z axis.

Returns

topologic_core.Topology
The flattened topology.
Expand source code
@staticmethod
def Flatten(topology, origin=None, direction=[0, 0, 1]):
    """
    Flattens the input topology such that the input origin is located at the world origin and the input topology is rotated such that the input vector is pointed in the Up direction (see Vector.Up()).

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.
    origin : topologic_core.Vertex , optional
        The input origin. If set to None, The object's centroid will be used to place the world origin. The default is None.
    vector : list , optional
        The input direction vector. The input topology will be rotated such that this vector is pointed in the positive Z axis.

    Returns
    -------
    topologic_core.Topology
        The flattened topology.

    """
    from topologicpy.Vertex import Vertex
    from topologicpy.Vector import Vector

    if not Topology.IsInstance(topology, "Topology"):
        print("Topology.Flatten - Error: the input topology parameter is not a valid topology. Returning None.")
        return None
    if origin == None:
        origin = Topology.Centroid(topology)
    up = Vector.Up()
    flat_topology = Topology.Translate(topology, -Vertex.X(origin), -Vertex.Y(origin), -Vertex.Z(origin))
    tran_mat = Vector.TransformationMatrix(direction, up)
    flat_topology = Topology.Transform(flat_topology, tran_mat)
    return flat_topology
def Geometry(topology, mantissa=6)

Returns the geometry (mesh data format) of the input topology as a dictionary of vertices, edges, and faces.

Parameters

topology : topologic_core.Topology
The input topology.
mantissa : int , optional
The desired length of the mantissa. The default is 6.

Returns

dict
A dictionary containing the vertices, edges, and faces data. The keys found in the dictionary are "vertices", "edges", and "faces".
Expand source code
@staticmethod
def Geometry(topology, mantissa=6):
    """
    Returns the geometry (mesh data format) of the input topology as a dictionary of vertices, edges, and faces.

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.
    mantissa : int , optional
        The desired length of the mantissa. The default is 6.

    Returns
    -------
    dict
        A dictionary containing the vertices, edges, and faces data. The keys found in the dictionary are "vertices", "edges", and "faces".

    """
    from topologicpy.Vertex import Vertex
    from topologicpy.Face import Face
    from topologicpy.Vector import Vector
    
    def getSubTopologies(topology, subTopologyClass):
        topologies = []
        if subTopologyClass == topologic.Vertex:
            _ = topology.Vertices(None, topologies)
        elif subTopologyClass == topologic.Edge:
            _ = topology.Edges(None, topologies)
        elif subTopologyClass == topologic.Wire:
            _ = topology.Wires(None, topologies)
        elif subTopologyClass == topologic.Face:
            _ = topology.Faces(None, topologies)
        elif subTopologyClass == topologic.Shell:
            _ = topology.Shells(None, topologies)
        elif subTopologyClass == topologic.Cell:
            _ = topology.Cells(None, topologies)
        elif subTopologyClass == topologic.CellComplex:
            _ = topology.CellComplexes(None, topologies)
        return topologies

    def triangulateFace(face):
        faceTriangles = []
        for i in range(0, 5, 1):
            try:
                _ = topologic.FaceUtility.Triangulate(face, float(i)*0.1, faceTriangles)
                return faceTriangles
            except:
                continue
        faceTriangles.append(face)
        return faceTriangles

    vertices = []
    edges = []
    faces = []
    if topology == None:
        return [None, None, None]
    topVerts = []
    if Topology.Type(topology) == Topology.TypeID("Vertex"): #input is a vertex, just add it and process it
        topVerts.append(topology)
    else:
        _ = topology.Vertices(None, topVerts)
    for aVertex in topVerts:
        try:
            vertices.index(Vertex.Coordinates(aVertex, mantissa=mantissa)) # Vertex already in list
        except:
            vertices.append(Vertex.Coordinates(aVertex, mantissa=mantissa)) # Vertex not in list, add it.
    topEdges = []
    if (Topology.Type(topology) == Topology.TypeID("Edge")): #Input is an Edge, just add it and process it
        topEdges.append(topology)
    elif (Topology.Type(topology) > Topology.TypeID("Vertex")):
        _ = topology.Edges(None, topEdges)
    for anEdge in topEdges:
        e = []
        sv = anEdge.StartVertex()
        ev = anEdge.EndVertex()
        try:
            svIndex = vertices.index(Vertex.Coordinates(sv, mantissa=mantissa))
        except:
            vertices.append(Vertex.Coordinates(sv, mantissa=mantissa))
            svIndex = len(vertices)-1
        try:
            evIndex = vertices.index(Vertex.Coordinates(ev, mantissa=mantissa))
        except:
            vertices.append(Vertex.Coordinates(ev, mantissa=mantissa))
            evIndex = len(vertices)-1
        e.append(svIndex)
        e.append(evIndex)
        if ([e[0], e[1]] not in edges) and ([e[1], e[0]] not in edges):
            edges.append(e)
    topFaces = []
    if (Topology.Type(topology) == Topology.TypeID("Face")): # Input is a Face, just add it and process it
        topFaces.append(topology)
    elif (Topology.Type(topology) > Topology.TypeID("Face")):
        _ = topology.Faces(None, topFaces)
    for aFace in topFaces:
        f_dir = Face.Normal(aFace)
        ib = []
        _ = aFace.InternalBoundaries(ib)
        if(len(ib) > 0):
            triFaces = triangulateFace(aFace)
            for aTriFace in triFaces:
                wire = aTriFace.ExternalBoundary()
                faceVertices = getSubTopologies(wire, topologic.Vertex)
                temp_face = Face.ByWire(wire)
                temp_dir = Face.Normal(temp_face)
                if Vector.IsAntiParallel(f_dir, temp_dir):
                    faceVertices.reverse()
                f = []
                for aVertex in faceVertices:
                    try:
                        fVertexIndex = vertices.index(Vertex.Coordinates(aVertex, mantissa=mantissa))
                    except:
                        vertices.append(Vertex.Coordinates(aVertex, mantissa=mantissa))
                        fVertexIndex = len(vertices)-1
                    f.append(fVertexIndex)
                faces.append(f)
        else:
            wire =  aFace.ExternalBoundary()
            #wire = topologic.WireUtility.RemoveCollinearEdges(wire, 0.1) #This is an angle Tolerance
            faceVertices = getSubTopologies(wire, topologic.Vertex)
            temp_face = Face.ByWire(wire)
            temp_dir = Face.Normal(temp_face)
            if Vector.IsAntiParallel(f_dir, temp_dir):
                faceVertices.reverse()
            f = []
            for aVertex in faceVertices:
                try:
                    fVertexIndex = vertices.index(Vertex.Coordinates(aVertex, mantissa=mantissa))
                except:
                    vertices.append(Vertex.Coordinates(aVertex, mantissa=mantissa))
                    fVertexIndex = len(vertices)-1
                f.append(fVertexIndex)
            faces.append(f)
    return {"vertices":vertices, "edges":edges, "faces":faces}
def HighestType(topology)

Returns the highest topology type found in the input topology.

Parameters

topology : topologic_core.Topology
The input topology.

Returns

int
The highest type found in the input topology.
Expand source code
@staticmethod
def HighestType(topology):
    """
    Returns the highest topology type found in the input topology.

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.

    Returns
    -------
    int
        The highest type found in the input topology.

    """
    from topologicpy.Cluster import Cluster

    if (Topology.Type(topology) == Topology.TypeID("Cluster")):
        return Cluster.HighestType(topology)
    else:
        return Topology.Type(topology)
def Impose(topologyA, topologyB, tranDict=False, tolerance=0.0001)

See Topology.Boolean().

Expand source code
@staticmethod
def Impose(topologyA, topologyB, tranDict=False, tolerance=0.0001):
    """
    See Topology.Boolean().

    """
    return Topology.Boolean(topologyA=topologyA, topologyB=topologyB, operation="impose", tranDict=tranDict, tolerance=tolerance)
def Imprint(topologyA, topologyB, tranDict=False, tolerance=0.0001)

See Topology.Boolean().

Expand source code
@staticmethod
def Imprint(topologyA, topologyB, tranDict=False, tolerance=0.0001):
    """
    See Topology.Boolean().

    """
    return Topology.Boolean(topologyA=topologyA, topologyB=topologyB, operation="imprint", tranDict=tranDict, tolerance=tolerance)
def InternalVertex(topology, tolerance: float = 0.0001)

Returns a vertex guaranteed to be inside the input topology.

Parameters

topology : topologic_core.Topology
The input topology.
tolerance : float , ptional
The desired tolerance. The default is 0.0001.

Returns

topologic_core.Vertex
A vertex guaranteed to be inside the input topology.
Expand source code
@staticmethod
def InternalVertex(topology, tolerance: float = 0.0001):
    """
    Returns a vertex guaranteed to be inside the input topology.

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.
    tolerance : float , ptional
        The desired tolerance. The default is 0.0001.

    Returns
    -------
    topologic_core.Vertex
        A vertex guaranteed to be inside the input topology.

    """
    from topologicpy.Vertex import Vertex
    from topologicpy.Edge import Edge
    from topologicpy.Face import Face
    from topologicpy.Cell import Cell
    from topologicpy.CellComplex import CellComplex
    from topologicpy.Aperture import Aperture

    if not Topology.IsInstance(topology, "Topology"):
        print("Topology.InternalVertex - Error: the input topology parameter is not a valid topology. Returning None.")
        return None
    vst = None
    top = Topology.Copy(topology)
    if Topology.IsInstance(top, "CellComplex"): #CellComplex
        tempCell = Topology.Cells(top)[0]
        vst = Cell.InternalVertex(tempCell, tolerance=tolerance)
    elif Topology.IsInstance(top, "Cell"): #Cell
        vst = Cell.InternalVertex(top, tolerance=tolerance)
    elif Topology.IsInstance(top, "Shell"): #Shell
        tempFace = Topology.Faces(top)[0]
        vst = Face.InternalVertex(tempFace, tolerance=tolerance)
    elif Topology.IsInstance(top, "Face"): #Face
        vst = Face.InternalVertex(top, tolerance=tolerance)
    elif Topology.IsInstance(top, "Wire"): #Wire
        if top.IsClosed():
            internalBoundaries = []
            try:
                tempFace = topologic.Face.ByExternalInternalBoundaries(top, internalBoundaries)
                vst = Face.InternalVertex(tempFace, tolerance=tolerance)
            except:
                vst = Topology.Centroid(top)
        else:
            tempEdge = Topology.Edges(top)[0]
            vst = Edge.VertexByParameter(tempEdge, 0.5)
    elif Topology.IsInstance(top, "Edge"): #Edge
        vst = Edge.VertexByParameter(top, 0.5)
    elif Topology.IsInstance(top, "Vertex"): #Vertex
        vst = top
    elif Topology.IsInstance(topology, "Aperture"): #Aperture
        vst = Face.InternalVertex(Aperture.Topology(top), tolerance)
    else:
        vst = Topology.Centroid(top)
    return vst
def Intersect(topologyA, topologyB, tranDict=False, tolerance=0.0001)

See Topology.Boolean().

Expand source code
@staticmethod
def Intersect(topologyA, topologyB, tranDict=False, tolerance=0.0001):
    """
    See Topology.Boolean().

    """

    if topologyA == None:
        return None
    if topologyB == None:
        return None
    
    from topologicpy.Vertex import Vertex
    # Sort the two topologies by their type from lower to higher so comparison can be eased.
    if Topology.Type(topologyB) < Topology.Type(topologyA):
        temp = topologyA
        topologyA = topologyB
        topologyB = temp
    
    if Topology.IsInstance(topologyB, "CellComplex") or Topology.IsInstance(topologyB, "Cluster"):
        merge = Topology.Merge(topologyA, topologyB)
        symdif = Topology.SymDif(topologyA, topologyB)
        return Topology.Difference(merge, symdif)
    else:
    # Vertex:
        if Topology.IsInstance(topologyA, "Vertex"):
            # Vertex:
            if Topology.IsInstance(topologyB, "Vertex"):
                if Vertex.Distance(topologyA, topologyB) < tolerance:
                    return topologyA
                else:
                    return None
            # Edge/Wire/Face/Shell/Cell:
            else:
                if Vertex.IsInternal(topologyA, topologyB):
                    return topologyA
                else:
                    return None
        else:
            return topologyA.Intersect(topologyB)
def IsInstance(topology, type: str)

Returns True if the input topology is an instance of the class specified by the input type string.

Parameters

topology : topologic_core.Topology
The input topology.
type : string
The topology type. This can be one of: "Vertex" "Edge" "Wire" "Face" "Shell" "Cell" "CellComplex" "Cluster" "Topology" "Graph" "Aperture" "Dictionary" "Context"

Returns

bool
True if the input topology is an instance of the class defined by the input type string. False otherwise.
Expand source code
@staticmethod
def IsInstance(topology, type: str):
    """
    Returns True if the input topology is an instance of the class specified by the input type string.
    
    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.
    type : string
        The topology type. This can be one of:
        "Vertex"
        "Edge"
        "Wire"
        "Face"
        "Shell"
        "Cell"
        "CellComplex"
        "Cluster"
        "Topology"
        "Graph"
        "Aperture"
        "Dictionary"
        "Context"

    Returns
    -------
    bool
        True if the input topology is an instance of the class defined by the input type string. False otherwise.
    """

    if "vertex" in type.lower():
        return isinstance(topology, topologic.Vertex)
    elif "edge" in type.lower():
        return isinstance(topology, topologic.Edge)
    elif "wire" in type.lower():
        return isinstance(topology, topologic.Wire)
    elif "face" in type.lower():
        return isinstance(topology, topologic.Face)
    elif "shell" in type.lower():
        return isinstance(topology, topologic.Shell)
    elif "cellcomplex" in type.lower(): #Hack to test for cellcomplex before cell as they share the same prefix.
        return isinstance(topology, topologic.CellComplex)
    elif "cell" in type.lower():
        return isinstance(topology, topologic.Cell)
    elif "cluster" in type.lower():
        return isinstance(topology, topologic.Cluster)
    elif "topology" in type.lower():
        return isinstance(topology, topologic.Topology)
    elif "graph" in type.lower():
        return isinstance(topology, topologic.Graph)
    elif "aperture" in type.lower():
        return isinstance(topology, topologic.Aperture)
    elif "dictionary" in type.lower():
        return isinstance(topology, topologic.Dictionary)
    elif "context" in type.lower():
        return isinstance(topology, topologic.Context)
    else:
        print("Topology.IsInstance - Error: The type input string is not a known topology type. Returning None.")
        return None
def IsPlanar(topology, tolerance=0.0001)

Returns True if all the vertices of the input topology are co-planar. Returns False otherwise.

Parameters

topology : topologic_core.Topology
The input topology.
tolerance : float , optional
The desired tolerance. The default is 0.0001.

Returns

bool
True if all the vertices of the input topology are co-planar. False otherwise.
Expand source code
@staticmethod
def IsPlanar(topology, tolerance=0.0001):
    """
    Returns True if all the vertices of the input topology are co-planar. Returns False otherwise.

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.
    tolerance : float , optional
        The desired tolerance. The default is 0.0001.

    Returns
    -------
    bool
        True if all the vertices of the input topology are co-planar. False otherwise.

    """
    
    def isOnPlane(v, plane, tolerance):
        x, y, z = v
        a, b, c, d = plane
        if math.fabs(a*x + b*y + c*z + d) <= tolerance:
            return True
        return False

    def plane(v1, v2, v3):
        a1 = v2.X() - v1.X() 
        b1 = v2.Y() - v1.Y() 
        c1 = v2.Z() - v1.Z() 
        a2 = v3.X() - v1.X() 
        b2 = v3.Y() - v1.Y() 
        c2 = v3.Z() - v1.Z() 
        a = b1 * c2 - b2 * c1 
        b = a2 * c1 - a1 * c2 
        c = a1 * b2 - b1 * a2 
        d = (- a * v1.X() - b * v1.Y() - c * v1.Z())
        return [a, b, c, d]

    if not Topology.IsInstance(topology, "Topology"):
        print("Topology.IsPlanar - Error: the input topology parameter is not a valid topology. Returning None.")
        return None
    vertices = Topology.Vertices(topology)

    result = True
    if len(vertices) <= 3:
        result = True
    else:
        p = plane(vertices[0], vertices[1], vertices[2])
        for i in range(len(vertices)):
            if isOnPlane([vertices[i].X(), vertices[i].Y(), vertices[i].Z()], p, tolerance) == False:
                result = False
                break
    return result
def IsSame(topologyA, topologyB)

Returns True if the input topologies are the same topology. Returns False otherwise.

Parameters

topologyA : topologic_core.Topology
The first input topology.
topologyB : topologic_core.Topology
The second input topology.

Returns

bool
True of the input topologies are the same topology. False otherwise.
Expand source code
@staticmethod
def IsSame(topologyA, topologyB):
    """
    Returns True if the input topologies are the same topology. Returns False otherwise.

    Parameters
    ----------
    topologyA : topologic_core.Topology
        The first input topology.
    topologyB : topologic_core.Topology
        The second input topology.

    Returns
    -------
    bool
        True of the input topologies are the same topology. False otherwise.

    """
    if not Topology.IsInstance(topologyA, "Topology"):
        print("Topology.IsSame - Error: the input topologyA parameter is not a valid topology. Returning None.")
        return None
    if not Topology.IsInstance(topologyB, "Topology"):
        print("Topology.IsSame - Error: the input topologyB parameter is not a valid topology. Returning None.")
        return None
    return topologic.Topology.IsSame(topologyA, topologyB)
def JSONString(topologies, mantissa: int = 6)

Exports the input list of topologies to a JSON string

Parameters

topologies : list or topologic_core.Topology
The input list of topologies or a single topology.
mantissa : int , optional
The desired length of the mantissa. The default is 6.

Returns

bool
The status of exporting the JSON file. If True, the operation was successful. Otherwise, it was unsuccesful.
Expand source code
@staticmethod
def JSONString(topologies, mantissa: int = 6):
    """
    Exports the input list of topologies to a JSON string

    Parameters
    ----------
    topologies : list or topologic_core.Topology
        The input list of topologies or a single topology.
    mantissa : int , optional
        The desired length of the mantissa. The default is 6.
    
    Returns
    -------
    bool
        The status of exporting the JSON file. If True, the operation was successful. Otherwise, it was unsuccesful.

    """

    from topologicpy.Vertex import Vertex
    from topologicpy.Edge import Edge
    from topologicpy.Face import Face
    from topologicpy.Cell import Cell
    from topologicpy.Dictionary import Dictionary
    from topologicpy.Aperture import Aperture

    def getUUID(topology, uuidKey="uuid"):
        d = Topology.Dictionary(topology)
        if uuidKey not in Dictionary.Keys(d):
            uuidOne = str(uuid.uuid1())
            d = Dictionary.SetValueAtKey(d, uuidKey, uuidOne)
            topology = Topology.SetDictionary(topology, d)
        else:
            uuidOne = Dictionary.ValueAtKey(d, uuidKey)
        return uuidOne

    def getVertex(topology, uuidKey="uuid"):
        returnDict = {}
        uuidOne = getUUID(topology, uuidKey=uuidKey)
        returnDict['type'] = "Vertex"
        returnDict['uuid'] = uuidOne
        returnDict['coordinates'] = Vertex.Coordinates(topology, mantissa=mantissa)
        returnDict['dictionary'] = Dictionary.PythonDictionary(Topology.Dictionary(topology))
        return returnDict

    def getEdge(topology, uuidKey="uuid"):
        returnDict = {}
        uuidOne = getUUID(topology, uuidKey=uuidKey)
        returnDict['type'] = "Edge"
        returnDict['uuid'] = uuidOne
        edge_vertices = Edge.Vertices(topology)
        returnDict['vertices'] = [getUUID(v, uuidKey=uuidKey) for v in Edge.Vertices(topology)]
        returnDict['dictionary'] = Dictionary.PythonDictionary(Topology.Dictionary(topology))
        return returnDict

    def getWire(topology, uuidKey="uuid"):
        returnDict = {}
        uuidOne = getUUID(topology, uuidKey="uuid")
        returnDict['type'] = "Wire"
        returnDict['uuid'] = uuidOne
        returnDict['edges'] = [getUUID(e, uuidKey=uuidKey) for e in Topology.Edges(topology)]
        returnDict['dictionary'] = Dictionary.PythonDictionary(Topology.Dictionary(topology))
        return returnDict

    def getFace(topology, uuidKey="uuid"):
        apertures = [Aperture.Topology(ap) for ap in Topology.Apertures(topology)]
        returnDict = {}
        uuidOne = getUUID(topology, uuidKey=uuidKey)
        returnDict['type'] = "Face"
        returnDict['uuid'] = uuidOne
        wires = []
        external_boundary = Face.ExternalBoundary(topology)
        wires.append(getUUID(Face.ExternalBoundary(topology), uuidKey=uuidKey))
        internal_boundaries = [getUUID(ib, uuidKey=uuidKey) for ib in Face.InternalBoundaries(topology)]
        wires += internal_boundaries
        returnDict['wires'] = wires
        dictionary = Dictionary.PythonDictionary(Topology.Dictionary(topology))
        returnDict['dictionary'] = dictionary
        return returnDict

    def getShell(topology, uuidKey="uuid"):
        returnDict = {}
        uuidOne = getUUID(topology, uuidKey=uuidKey)
        returnDict['type'] = "Shell"
        returnDict['uuid'] = uuidOne
        returnDict['faces'] = [getUUID(f, uuidKey=uuidKey) for f in Topology.Faces(topology)]
        returnDict['dictionary'] = Dictionary.PythonDictionary(Topology.Dictionary(topology))
        return returnDict

    def getCell(topology, uuidKey="uuid"):
        returnDict = {}
        uuidOne = getUUID(topology, uuidKey=uuidKey)
        returnDict['type'] = "Cell"
        returnDict['uuid'] = uuidOne
        shells = []
        external_boundary = Cell.ExternalBoundary(topology)
        shells.append(getUUID(external_boundary, uuidKey=uuidKey))
        internal_boundaries = [getUUID(ib, uuidKey=uuidKey) for ib in Cell.InternalBoundaries(topology)]
        shells += internal_boundaries
        returnDict['shells'] = shells
        dictionary = Dictionary.PythonDictionary(Topology.Dictionary(topology))
        returnDict['dictionary'] = dictionary
        return returnDict

    def getCellComplex(topology, uuidKey="uuid"):
        returnDict = {}
        uuidOne = getUUID(topology, uuidKey=uuidKey)
        returnDict['type'] = "CellComplex"
        returnDict['uuid'] = uuidOne
        returnDict['cells'] = [getUUID(c, uuidKey=uuidKey) for c in Topology.Cells(topology)]
        returnDict['dictionary'] = Dictionary.PythonDictionary(Topology.Dictionary(topology))
        return returnDict


    def getApertureData(topology, topLevel="False", uuidKey="uuid"):
        json_data = []
        if Topology.IsInstance(topology, "Vertex"):
            d = getVertex(topology, uuidKey=uuidKey)
        elif Topology.IsInstance(topology, "Edge"):
            d = getEdge(topology, uuidKey=uuidKey)
        elif Topology.IsInstance(topology, "Wire"):
            d = getWire(topology, uuidKey=uuidKey)
        elif Topology.IsInstance(topology, "Face"):
            d = getFace(topology, uuidKey=uuidKey)
        elif Topology.IsInstance(topology, "Shell"):
            d = getShell(topology, uuidKey=uuidKey)
        elif Topology.IsInstance(topology, "Cell"):
            d = getCell(topology, uuidKey=uuidKey)
        elif Topology.IsInstance(topology, "CellComplex"):
            d = getCellComplex(topology, uuidKey=uuidKey)
        d['dictionary']['toplevel'] = topLevel
        json_data += getSubTopologyData(topology, uuidKey=uuidKey)
        apertures = [Aperture.Topology(ap) for ap in Topology.Apertures(topology)]
        aperture_data = []
        for ap in apertures:
            aperture_data.append(getApertureData(ap, topLevel=False, uuidKey=uuidKey))
        d['apertures'] = aperture_data
        json_data.append(d)
        return json_data

    def getSubTopologyData(topology, uuidKey="uuid"):
        json_data = []
        vertices = Topology.Vertices(topology)
        for v in vertices:
            d = getVertex(v, uuidKey=uuidKey)
            d['dictionary']['toplevel'] = False
            apertures = [Aperture.Topology(ap) for ap in Topology.Apertures(v)]
            aperture_data = []
            for ap in apertures:
                aperture_data.append(getApertureData(ap, topLevel=False, uuidKey=uuidKey))
            d['apertures'] = aperture_data
            json_data.append(d)
        edges = Topology.Edges(topology)
        for e in edges:
            d = getEdge(e, uuidKey=uuidKey)
            d['dictionary']['toplevel'] = False
            apertures = [Aperture.Topology(ap) for ap in Topology.Apertures(e)]
            aperture_data = []
            for ap in apertures:
                aperture_data.append(getApertureData(ap, topLevel=False, uuidKey=uuidKey))
            d['apertures'] = aperture_data
            json_data.append(d)
        wires = Topology.Wires(topology)
        for w in wires:
            d = getWire(w, uuidKey=uuidKey)
            d['dictionary']['toplevel'] = False
            apertures = [Aperture.Topology(ap) for ap in Topology.Apertures(w)]
            aperture_data = []
            for ap in apertures:
                aperture_data.append(getApertureData(ap, topLevel=False, uuidKey=uuidKey))
            d['apertures'] = aperture_data
            json_data.append(d)
        faces = Topology.Faces(topology)
        for f in faces:
            d = getFace(f, uuidKey=uuidKey)
            d['dictionary']['toplevel'] = False
            apertures = [Aperture.Topology(ap) for ap in Topology.Apertures(f)]
            aperture_data = []
            for ap in apertures:
                aperture_data.append(getApertureData(ap, topLevel=False, uuidKey=uuidKey))
            d['apertures'] = aperture_data
            json_data.append(d)
        shells = Topology.Shells(topology)
        for s in shells:
            d = getShell(s, uuidKey=uuidKey)
            d['dictionary']['toplevel'] = False
            apertures = [Aperture.Topology(ap) for ap in Topology.Apertures(s)]
            aperture_data = []
            for ap in apertures:
                aperture_data.append(getApertureData(ap, topLevel=False, uuidKey=uuidKey))
            d['apertures'] = aperture_data
            json_data.append(d)
        cells = Topology.Cells(topology)
        for c in cells:
            d = getCell(c, uuidKey=uuidKey)
            d['dictionary']['toplevel'] = False
            apertures = [Aperture.Topology(ap) for ap in Topology.Apertures(c)]
            aperture_data = []
            for ap in apertures:
                aperture_data.append(getApertureData(ap, topLevel=False, uuidKey=uuidKey))
            d['apertures'] = aperture_data
            json_data.append(d)
        cellComplexes = Topology.CellComplexes(topology)
        for cc in cellComplexes:
            d = getCellComplex(cc, uuidKey=uuidKey)
            d['dictionary']['toplevel'] = False
            apertures = [Aperture.Topology(ap) for ap in Topology.Apertures(cc)]
            aperture_data = []
            for ap in apertures:
                aperture_data.append(getApertureData(ap, topLevel=False, uuidKey=uuidKey))
            d['apertures'] = aperture_data
            json_data.append(d)
        return json_data

    def getJSONData(topology, topLevel=False, uuidKey="uuid"):
        json_data = []
        if Topology.IsInstance(topology, "Vertex"):
            d = getVertex(topology, uuidKey=uuidKey)
        elif Topology.IsInstance(topology, "Edge"):
            d = getEdge(topology, uuidKey=uuidKey)
        elif Topology.IsInstance(topology, "Wire"):
            d = getWire(topology, uuidKey=uuidKey)
        elif Topology.IsInstance(topology, "Face"):
            d = getFace(topology, uuidKey=uuidKey)
        elif Topology.IsInstance(topology, "Shell"):
            d = getShell(topology, uuidKey=uuidKey)
        elif Topology.IsInstance(topology, "Cell"):
            d = getCell(topology, uuidKey=uuidKey)
        elif Topology.IsInstance(topology, "CellComplex"):
            d = getCellComplex(topology, uuidKey=uuidKey)
        else:
            print("Topology.JSONString - Error: Unknown topology type:", topology, ". Returning None.")
            return None
        d['dictionary']['toplevel'] = topLevel
        json_data += getSubTopologyData(topology, uuidKey=uuidKey)
        apertures = [Aperture.Topology(ap) for ap in Topology.Apertures(topology)]
        aperture_data = []
        for ap in apertures:
            aperture_data.append(getApertureData(ap, topLevel=False, uuidKey=uuidKey))
        d['apertures'] = aperture_data
        json_data.append(d)
        return json_data
    json_data = []
    if not isinstance(topologies, list):
        topologies = [topologies]
    topologies = [x for x in topologies if Topology.IsInstance(x, "Topology")]
    for topology in topologies:
        json_data += getJSONData(topology, topLevel=True, uuidKey="uuid")
    json_string = json.dumps(json_data, indent=4, sort_keys=False)
    return json_string
def Merge(topologyA, topologyB, tranDict=False, tolerance=0.0001)

See Topology.Boolean().

Expand source code
@staticmethod
def Merge(topologyA, topologyB, tranDict=False, tolerance=0.0001):
    """
    See Topology.Boolean().

    """
    return Topology.Boolean(topologyA=topologyA, topologyB=topologyB, operation="merge", tranDict=tranDict, tolerance=tolerance)
def MergeAll(topologies, tolerance=0.0001)

Merge all the input topologies.

Parameters

topologies : list
The list of input topologies.
tolerance : float , optional
The desired tolerance. The default is 0.0001.

Returns

topologic_core.Topology
The resulting merged Topology
Expand source code
@staticmethod
def MergeAll(topologies, tolerance=0.0001):
    """
    Merge all the input topologies.

    Parameters
    ----------
    topologies : list
        The list of input topologies.
    tolerance : float , optional
        The desired tolerance. The default is 0.0001.

    Returns
    -------
    topologic_core.Topology
        The resulting merged Topology

    """

    from topologicpy.Cluster import Cluster

    if not isinstance(topologies, list):
        print("Topology.MergeAll - Error: the input topologies parameter is not a valid list. Returning None.")
        return None
    
    topologyList = [t for t in topologies if Topology.IsInstance(t, "Topology")]
    if len(topologyList) < 1:
        print("Topology.MergeAll - Error: the input topologyList does not contain any valid topologies. Returning None.")
        return None
    return Topology.SelfMerge(Cluster.ByTopologies(topologyList), tolerance=tolerance)
def NonPlanarFaces(topology, tolerance=0.0001)

Returns any nonplanar faces in the input topology

Parameters

topology : topologic_core.Topology
The input topology.
tolerance : float , optional
The desired tolerance. The default is 0.0001.

Returns

list
The list of nonplanar faces.
Expand source code
@staticmethod
def NonPlanarFaces(topology, tolerance=0.0001):
    """
    Returns any nonplanar faces in the input topology
    
    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.
    tolerance : float , optional
        The desired tolerance. The default is 0.0001.
    Returns
    -------
    list
        The list of nonplanar faces.
    
    """
    if not Topology.IsInstance(topology, "Topology"):
        print("Topology.NonPlanarFaces - Error: the input topology parameter is not a valid topology. Returning None.")
        return None
    faces = Topology.SubTopologies(topology, subTopologyType="face")
    return [f for f in faces if not Topology.IsPlanar(f, tolerance=tolerance)]
def OBJString(topology, transposeAxes: bool = True, mode: int = 0, meshSize: float = None, mantissa: int = 6, tolerance: float = 0.0001)

Returns the Wavefront string of the input topology. This is very experimental and outputs a simple solid topology.

Parameters

topology : topologic_core.Topology
The input topology.
transposeAxes : bool , optional
If set to True the Z and Y coordinates are transposed so that Y points "up"
mode : int , optional
The desired mode of meshing algorithm. Several options are available: 0: Classic 1: MeshAdapt 3: Initial Mesh Only 5: Delaunay 6: Frontal-Delaunay 7: BAMG 8: Fontal-Delaunay for Quads 9: Packing of Parallelograms All options other than 0 (Classic) use the gmsh library. See https://gmsh.info/doc/texinfo/gmsh.html#Mesh-options WARNING: The options that use gmsh can be very time consuming and can create very heavy geometry.
meshSize : float , optional
The desired size of the mesh when using the "mesh" option. If set to None, it will be calculated automatically and set to 10% of the overall size of the face.
mantissa : int , optional
The desired length of the mantissa. The default is 6.
tolerance : float , optional
The desired tolerance. The default is 0.0001.

Returns

str
The Wavefront OBJ string of the input topology
Expand source code
@staticmethod
def OBJString(topology, transposeAxes: bool = True, mode: int = 0, meshSize: float = None, mantissa: int = 6, tolerance: float = 0.0001):
    """
    Returns the Wavefront string of the input topology. This is very experimental and outputs a simple solid topology.

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.
    transposeAxes : bool , optional
        If set to True the Z and Y coordinates are transposed so that Y points "up"
    mode : int , optional
        The desired mode of meshing algorithm. Several options are available:
        0: Classic
        1: MeshAdapt
        3: Initial Mesh Only
        5: Delaunay
        6: Frontal-Delaunay
        7: BAMG
        8: Fontal-Delaunay for Quads
        9: Packing of Parallelograms
        All options other than 0 (Classic) use the gmsh library. See https://gmsh.info/doc/texinfo/gmsh.html#Mesh-options
        WARNING: The options that use gmsh can be very time consuming and can create very heavy geometry.
    meshSize : float , optional
        The desired size of the mesh when using the "mesh" option. If set to None, it will be
        calculated automatically and set to 10% of the overall size of the face.
    mantissa : int , optional
        The desired length of the mantissa. The default is 6.
    tolerance : float , optional
        The desired tolerance. The default is 0.0001.

    Returns
    -------
    str
        The Wavefront OBJ string of the input topology

    """
    from topologicpy.Helper import Helper
    from topologicpy.Vertex import Vertex
    from topologicpy.Face import Face

    if not Topology.IsInstance(topology, "Topology"):
        print("Topology.ExportToOBJ - Error: the input topology parameter is not a valid topology. Returning None.")
        return None
    
    lines = []
    version = Helper.Version()
    lines.append("# topologicpy "+version)
    topology = Topology.Triangulate(topology, mode=mode, meshSize=meshSize, tolerance=tolerance)
    d = Topology.Geometry(topology, mantissa=mantissa)
    vertices = d['vertices']
    faces = d['faces']
    tVertices = []
    if transposeAxes:
        for v in vertices:
            tVertices.append([v[0], v[2], v[1]])
        vertices = tVertices
    for v in vertices:
        lines.append("v "+str(v[0])+" "+str(v[1])+" "+str(v[2]))
    for f in faces:
        line = "f"
        for j in f:
            line = line+" "+str(j+1)
        lines.append(line)
    finalLines = lines[0]
    for i in range(1,len(lines)):
        finalLines = finalLines+"\n"+lines[i]
    return finalLines
def OCCTShape(topology)

Returns the occt shape of the input topology.

Parameters

topology : topologic_core.Topology
The input topology.

Returns

topologic_core.TopoDS_Shape
The OCCT Shape.
Expand source code
@staticmethod
def OCCTShape(topology):
    """
    Returns the occt shape of the input topology.

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.

    Returns
    -------
    topologic_core.TopoDS_Shape
        The OCCT Shape.

    """
    if not Topology.IsInstance(topology, "Topology"):
        print("Topology.OCCTShape - Error: the input topology parameter is not a valid topology. Returning None.")
        return None
    return topology.GetOcctShape()
def OpenEdges(topology)

Returns the edges that border only one face.

Parameters

topology : topologic_core.Topology
The input topology.

Returns

list
The list of open edges.
Expand source code
@staticmethod
def OpenEdges(topology):
    """
    Returns the edges that border only one face.

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.
    
    Returns
    -------
    list
        The list of open edges.
    
    """

    if not Topology.IsInstance(topology, "Topology"):
        print("Topology.OpenEdges - Error: the input topology parameter is not a valid topology. Returning None.")
        return None
    
    return [e for e in Topology.SubTopologies(topology, subTopologyType="edge") if Topology.Degree(e, hostTopology=topology) < 2]
def OpenFaces(topology)

Returns the faces that border no cells.

Parameters

topology : topologic_core.Topology
The input topology.

Returns

list
The list of open edges.
Expand source code
@staticmethod
def OpenFaces(topology):
    """
    Returns the faces that border no cells.

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.
    
    Returns
    -------
    list
        The list of open edges.
    
    """

    if not Topology.IsInstance(topology, "Topology"):
        print("Topology.OpenFaces - Error: the input topology parameter is not a valid topology. Returning None.")
        return None
    
    return [f for f in Topology.SubTopologies(topology, subTopologyType="face") if Topology.Degree(f, hostTopology=topology) < 1]
def OpenVertices(topology)

Returns the vertices that border only one edge.

Parameters

topology : topologic_core.Topology
The input topology.

Returns

list
The list of open edges.
Expand source code
@staticmethod
def OpenVertices(topology):
    """
    Returns the vertices that border only one edge.

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.
    
    Returns
    -------
    list
        The list of open edges.
    
    """

    if not Topology.IsInstance(topology, "Topology"):
        print("Topology.OpenVertices - Error: the input topology parameter is not a valid topology. Returning None.")
        return None
    
    return [v for v in Topology.SubTopologies(topology, subTopologyType="vertex") if Topology.Degree(v, hostTopology=topology) < 2]
def Orient(topology, origin=None, dirA=[0, 0, 1], dirB=[0, 0, 1], tolerance=0.0001)

Orients the input topology such that the input such that the input dirA vector is parallel to the input dirB vector.

Parameters

topology : topologic_core.Topology
The input topology.
origin : topologic_core.Vertex , optional
The input origin. If set to None, The object's centroid will be used to locate the input topology. The default is None.
dirA : list , optional
The first input direction vector. The input topology will be rotated such that this vector is parallel to the input dirB vector. The default is [0, 0, 1].
dirB : list , optional
The target direction vector. The input topology will be rotated such that the input dirA vector is parallel to this vector. The default is [0, 0, 1].
tolerance : float , optional
The desired tolerance. The default is 0.0001

Returns

topologic_core.Topology
The flattened topology.
Expand source code
@staticmethod
def Orient(topology, origin=None, dirA=[0, 0, 1], dirB=[0, 0, 1], tolerance=0.0001):
    """
    Orients the input topology such that the input such that the input dirA vector is parallel to the input dirB vector.

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.
    origin : topologic_core.Vertex , optional
        The input origin. If set to None, The object's centroid will be used to locate the input topology. The default is None.
    dirA : list , optional
        The first input direction vector. The input topology will be rotated such that this vector is parallel to the input dirB vector. The default is [0, 0, 1].
    dirB : list , optional
        The target direction vector. The input topology will be rotated such that the input dirA vector is parallel to this vector. The default is [0, 0, 1].
    tolerance : float , optional
        The desired tolerance. The default is 0.0001

    Returns
    -------
    topologic_core.Topology
        The flattened topology.

    """
    from topologicpy.Vertex import Vertex
    from topologicpy.Vector import Vector
    if not Topology.IsInstance(topology, "Topology"):
        print("Topology.Orient - Error: the input topology parameter is not a valid topology. Returning None.")
        return None
    if not Topology.IsInstance(origin, "Vertex"):
        origin = Topology.Centroid(topology)
    return_topology = Topology.Place(topology, originA=origin, originB=Vertex.Origin())
    tran_mat = Vector.TransformationMatrix(dirA, dirB)
    return_topology = Topology.Transform(return_topology, tran_mat)
    return_topology = Topology.Place(return_topology, originA=Vertex.Origin(), originB=origin)
    return return_topology
def Place(topology, originA=None, originB=None)

Places the input topology at the specified location.

Parameters

topology : topologic_core.Topology
The input topology.
originA : topologic_core.Vertex , optional
The old location to use as the origin of the movement. If set to None, the centroid of the input topology is used. The default is None.
originB : topologic_core.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.

Returns

topologic_core.Topology
The placed topology.
Expand source code
@staticmethod
def Place(topology, originA=None, originB=None):
    """
    Places the input topology at the specified location.

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.
    originA : topologic_core.Vertex , optional
        The old location to use as the origin of the movement. If set to None, the centroid of the input topology is used. The default is None.
    originB : topologic_core.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.

    Returns
    -------
    topologic_core.Topology
        The placed topology.

    """
    from topologicpy.Vertex import Vertex
    if not Topology.IsInstance(topology, "Topology"):
        return None
    if not Topology.IsInstance(originA, "Vertex"):
        originA = Topology.Centroid(topology)
    if not Topology.IsInstance(originA, "Vertex"):
        originA = Vertex.ByCoordinates(0, 0, 0)

    x = originB.X() - originA.X()
    y = originB.Y() - originA.Y()
    z = originB.Z() - originA.Z()
    newTopology = None
    try:
        newTopology = Topology.Translate(topology, x, y, z)
    except:
        print("Topology.Place - Error: (Topologic>TopologyUtility.Place) operation failed. Returning None.")
        newTopology = None
    return newTopology
def RemoveCollinearEdges(topology, angTolerance=0.1, tolerance=0.0001)

Removes the collinear edges of the input topology

Parameters

topology : topologic_core.Topology
The input topology.
angTolerance : float , optional
The desired angular tolerance. The default is 0.1.
tolerance : float , optional
The desired tolerance. The default is 0.0001.

Returns

topologic_core.Topology
The input topology with the collinear edges removed.
Expand source code
@staticmethod
def RemoveCollinearEdges(topology, angTolerance=0.1, tolerance=0.0001):
    """
    Removes the collinear edges of the input topology

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.
    angTolerance : float , optional
        The desired angular tolerance. The default is 0.1.
    tolerance : float , optional
        The desired tolerance. The default is 0.0001.

    Returns
    -------
    topologic_core.Topology
        The input topology with the collinear edges removed.

    """
    from topologicpy.Wire import Wire
    from topologicpy.Face import Face
    from topologicpy.Shell import Shell
    from topologicpy.Cell import Cell
    from topologicpy.CellComplex import CellComplex
    from topologicpy.Cluster import Cluster
    
    if not Topology.IsInstance(topology, "Topology"):
        return None
    return_topology = topology
    if Topology.IsInstance(topology, "Vertex") or Topology.IsInstance(topology, "Edge"): #Vertex or Edge or Cluster, return the original topology
        return return_topology
    elif Topology.IsInstance(topology, "Wire"):
        return_topology = Wire.RemoveCollinearEdges(topology, angTolerance=angTolerance, tolerance=tolerance)
        return return_topology
    elif Topology.IsInstance(topology, "Face"):
        return_topology = Face.RemoveCollinearEdges(topology, angTolerance=angTolerance, tolerance=tolerance)
        return return_topology
    elif Topology.IsInstance(topology, "Shell"):
        return_topology = Shell.RemoveCollinearEdges(topology, angTolerance=angTolerance, tolerance=tolerance)
        return return_topology
    elif Topology.IsInstance(topology, "Cell"):
        return_topology = Cell.RemoveCollinearEdges(topology, angTolerance=angTolerance, tolerance=tolerance)
        return return_topology
    elif Topology.IsInstance(topology, "CellComplex"):
        return_topology = CellComplex.RemoveCollinearEdges(topology, angTolerance=angTolerance, tolerance=tolerance)
        return return_topology
    elif Topology.IsInstance(topology, "Cluster"):
        topologies = []
        topologies += Cluster.FreeVertices(topology)
        topologies += Cluster.FreeEdges(topology)
        faces = Topology.Faces(topology)
        for face in faces:
            topologies.append(Face.RemoveCollinearEdges(topology, angTolerance=angTolerance, tolerance=tolerance))
        return_topology = Topology.SelfMerge(Cluster.ByTopologies(topologies), tolerance=tolerance)
    return return_topology
def RemoveContent(topology, contents)

Removes the input content list from the input topology

Parameters

topology : topologic_core.Topology
The input topology.
contentList : list
The input list of contents.

Returns

topologic_core.Topology
The input topology with the input list of contents removed.
Expand source code
@staticmethod
def RemoveContent(topology, contents):
    """
    Removes the input content list from the input topology

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.
    contentList : list
        The input list of contents.

    Returns
    -------
    topologic_core.Topology
        The input topology with the input list of contents removed.

    """
    if isinstance(contents, list) == False:
        contents = [contents]
    return topology.RemoveContents(contents)
def RemoveCoplanarFaces(topology, epsilon=0.01, tolerance=0.0001)

Removes coplanar faces in the input topology

Parameters

topology : topologic_core.Topology
The input topology.
angTolerance : float , optional
The desired angular tolerance for removing coplanar faces. The default is 0.1.
epsilon : float , optional
The desired epsilon (another form of tolerance) for finding if two faces are coplanar. The default is 0.01.
tolerance : float , optional
The desired tolerance. The default is 0.0001.

Returns

topologic_core.Topology
The input topology with coplanar faces merged into one face.
Expand source code
@staticmethod
def RemoveCoplanarFaces(topology, epsilon=0.01, tolerance=0.0001):
    """
    Removes coplanar faces in the input topology

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.
    angTolerance : float , optional
        The desired angular tolerance for removing coplanar faces. The default is 0.1.
    epsilon : float , optional
        The desired epsilon (another form of tolerance) for finding if two faces are coplanar. The default is 0.01.
    tolerance : float , optional
        The desired tolerance. The default is 0.0001.

    Returns
    -------
    topologic_core.Topology
        The input topology with coplanar faces merged into one face.

    """
    from topologicpy.Vertex import Vertex
    from topologicpy.Face import Face
    from topologicpy.Shell import Shell
    from topologicpy.Cell import Cell
    from topologicpy.CellComplex import CellComplex
    from topologicpy.Cluster import Cluster

    if not Topology.IsInstance(topology, "Topology"):
        print("Topology.RemoveCoplanarFaces - Error: The input topology parameter is not a valid topologic topology. Returning None.")
        return None
    t = Topology.Type(topology)
    if (t == Topology.TypeID("Vertex")) or (t == Topology.TypeID("Edge")) or (t == Topology.TypeID("Wire")) or (t == Topology.TypeID("Face")):
        return topology

    def faces_on_same_plane(face1, face2, epsilon=1e-6):
        vertices = Face.Vertices(face1)
        distances = []
        for v in vertices:
            distances.append(Vertex.PerpendicularDistance(v, face=face2, mantissa=6))
        d = sum(distances)/len(distances)
        return d <= epsilon

    def cluster_faces_on_planes(faces, epsilon=1e-6):

        # Create a dictionary to store bins based on plane equations
        bins = {}

        # Iterate through each face
        for i, face in enumerate(faces):
            # Check if a bin already exists for the plane equation
            found_bin = False
            for bin_face in bins.values():
                if faces_on_same_plane(face, bin_face[0], epsilon=epsilon):
                    bin_face.append(face)
                    found_bin = True
                    break

            # If no bin is found, create a new bin
            if not found_bin:
                bins[i] = [face]

        # Convert bins to a list of lists
        return list(bins.values())

    faces = Topology.Faces(topology)
    face_clusters = cluster_faces_on_planes(faces, epsilon=epsilon)
    final_faces = []
    for face_cluster in face_clusters:
        t = Topology.SelfMerge(Cluster.ByTopologies(face_cluster), tolerance=tolerance)
        if Topology.IsInstance(t, "Face"):
            #final_faces.append(Face.RemoveCollinearEdges(t))
            final_faces.append(t)
        elif Topology.IsInstance(t, "Shell"):
                f = Face.ByShell(t)
                if Topology.IsInstance(f, "Face"):
                    final_faces.append(f)
                else:
                    print("Topology.RemoveCoplanarFaces - Warning: Could not remove some coplanar faces. Re-adding original faces.")
                    final_faces += Shell.Faces(shell)
        else: # It is a cluster
            shells = Topology.Shells(t)
            for shell in shells:
                f = Face.ByShell(shell)
                if Topology.IsInstance(f, "Face"):
                    final_faces.append(f)
                else:
                    print("Topology.RemoveCoplanarFaces - Warning: Could not remove some coplanar faces. Re-adding original faces.")
                    final_faces += Shell.Faces(shell)
            if len(shells) == 0:
                faces = Topology.Faces(t)
                final_faces += faces
            faces = Cluster.FreeFaces(t)
            final_faces += faces
    return_topology = None
    if Topology.IsInstance(topology, "CellComplex"):
        return_topology = CellComplex.ByFaces(final_faces, tolerance=tolerance)
    elif Topology.IsInstance(topology, "Cell"):
        return_topology = Cell.ByFaces(final_faces, tolerance=tolerance)
    elif Topology.IsInstance(topology, "Shell"):
        if len(final_faces) == 1:
            return_topology = final_faces[0]
        else:
            return_topology = Shell.ByFaces(final_faces, tolerance=tolerance)
    if not Topology.IsInstance(return_topology, "Topology"):
        return_topology = Cluster.ByTopologies(final_faces)
    return return_topology
def RemoveEdges(topology, edges=[], tolerance=0.0001)

Removes the input list of faces from the input topology

Parameters

topology : topologic_core.Topology
The input topology.
edges : list
The input list of edges.
tolerance : float , optional
The desired tolerance. The default is 0.0001.

Returns

topologic_core.Topology
The input topology with the input list of edges removed.
Expand source code
@staticmethod
def RemoveEdges(topology, edges=[], tolerance=0.0001):
    """
    Removes the input list of faces from the input topology

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.
    edges : list
        The input list of edges.
    tolerance : float , optional
        The desired tolerance. The default is 0.0001.

    Returns
    -------
    topologic_core.Topology
        The input topology with the input list of edges removed.

    """

    from topologicpy.Cluster import Cluster
    if not Topology.IsInstance(topology, "Topology"):
        return None
    edges = [e for e in edges if Topology.IsInstance(e, "Edge")]
    if len(edges) < 1:
        return topology
    t_edges = Topology.Edges(topology)
    t_faces = Topology.Faces(topology)
    if len(t_edges) < 1:
        return topology
    if len(t_faces) > 0:
        remove_faces = []
        for t_e in t_edges:
            remove = False
            for i, e in enumerate(edges):
                if Topology.IsSame(t_e, e):
                    remove = True
                    remove_faces += Topology.SuperTopologies(e, hostTopology=topology, topologyType="face")
                    edges = edges[:i]+ edges[i:]
                    break
        if len(remove_faces) > 0:
            return Topology.RemoveFaces(topology, remove_faces)
    else:
        remaining_edges = []
        for t_e in t_edges:
            remove = False
            for i, e in enumerate(edges):
                if Topology.IsSame(t_e, e):
                    remove = True
                    edges = edges[:i]+ edges[i:]
                    break
            if not remove:
                remaining_edges.append(t_e)
        if len(remaining_edges) < 1:
            return None
        elif len(remaining_edges) == 1:
            return remaining_edges[0]
        return Topology.SelfMerge(Cluster.ByTopologies(remaining_edges), tolerance=tolerance)
def RemoveFaces(topology, faces=[], tolerance=0.0001)

Removes the input list of faces from the input topology

Parameters

topology : topologic_core.Topology
The input topology.
faces : list
The input list of faces.
tolerance : float , optional
The desired tolerance. The default is 0.0001.

Returns

topologic_core.Topology
The input topology with the input list of faces removed.
Expand source code
@staticmethod
def RemoveFaces(topology, faces=[], tolerance=0.0001):
    """
    Removes the input list of faces from the input topology

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.
    faces : list
        The input list of faces.
    tolerance : float , optional
        The desired tolerance. The default is 0.0001.

    Returns
    -------
    topologic_core.Topology
        The input topology with the input list of faces removed.

    """

    from topologicpy.Cluster import Cluster
    if not Topology.IsInstance(topology, "Topology"):
        return None
    faces = [f for f in faces if Topology.IsInstance(f, "Face")]
    if len(faces) < 1:
        return topology
    t_faces = Topology.Faces(topology)
    if len(t_faces) < 1:
        return topology
    remaining_faces = []
    for t_f in t_faces:
        remove = False
        for i, f in enumerate(faces):
            if Topology.IsSame(t_f, f):
                remove = True
                faces = faces[:i]+ faces[i:]
                break
        if not remove:
            remaining_faces.append(t_f)
    if len(remaining_faces) < 1:
        return None
    elif len(remaining_faces) == 1:
        return remaining_faces[0]
    return Topology.SelfMerge(Cluster.ByTopologies(remaining_faces), tolerance=tolerance)
def RemoveFacesBySelectors(topology, selectors=[], tolerance=0.0001)

Removes faces that contain the input list of selectors (vertices) from the input topology

Parameters

topology : topologic_core.Topology
The input topology.
selectors : list
The input list of selectors (vertices).
tolerance : float , optional
The desired tolerance. The default is 0.0001.

Returns

topologic_core.Topology
The input topology with the identified faces removed.
Expand source code
@staticmethod
def RemoveFacesBySelectors(topology, selectors=[], tolerance = 0.0001):
    """
    Removes faces that contain the input list of selectors (vertices) from the input topology

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.
    selectors : list
        The input list of selectors (vertices).
    tolerance : float , optional
        The desired tolerance. The default is 0.0001.

    Returns
    -------
    topologic_core.Topology
        The input topology with the identified faces removed.

    """
    from topologicpy.Vertex import Vertex

    if not Topology.IsInstance(topology, "Topology"):
        return None
    selectors = [v for v in selectors if Topology.IsInstance(v, "Vertex")]
    if len(selectors) < 1:
        return topology
    t_faces = Topology.Faces(topology)
    to_remove = []
    for t_f in t_faces:
        remove = False
        for i, v in enumerate(selectors):
            if Vertex.IsInternal(v, t_f, tolerance=tolerance):
                remove = True
                selectors = selectors[:i]+ selectors[i:]
                break
        if remove:
            to_remove.append(t_f)
    if len(to_remove) < 1:
        return topology
    return Topology.RemoveFaces(topology, faces = to_remove)
def RemoveVertices(topology, vertices=[], tolerance=0.0001)

Removes the input list of vertices from the input topology

Parameters

topology : topologic_core.Topology
The input topology.
vertices : list
The input list of vertices.
tolerance : float , optional
The desired tolerance. The default is 0.0001.

Returns

topologic_core.Topology
The input topology with the input list of vertices removed.
Expand source code
@staticmethod
def RemoveVertices(topology, vertices=[], tolerance=0.0001):
    """
    Removes the input list of vertices from the input topology

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.
    vertices : list
        The input list of vertices.
    tolerance : float , optional
        The desired tolerance. The default is 0.0001.

    Returns
    -------
    topologic_core.Topology
        The input topology with the input list of vertices removed.

    """

    from topologicpy.Cluster import Cluster
    if not Topology.IsInstance(topology, "Topology"):
        return None
    vertices = [v for v in vertices if Topology.IsInstance(v, "Vertex")]
    if len(vertices) < 1:
        return topology
    t_vertices = Topology.Vertices(topology)
    t_edges = Topology.Edges(topology)
    if len(t_vertices) < 1:
        return topology
    if len(t_edges) > 0:
        remove_edges = []
        for t_v in t_vertices:
            remove = False
            for i, v in enumerate(vertices):
                if Topology.IsSame(t_v, v):
                    remove = True
                    remove_edges += Topology.SuperTopologies(v, hostTopology=topology, topologyType="edge")
                    vertices = vertices[:i]+ vertices[i:]
                    break
        if len(remove_edges) > 0:
            return Topology.RemoveEdges(topology, remove_edges)
    else:
        remaining_vertices = []
        for t_v in t_vertices:
            remove = False
            for i, e in enumerate(vertices):
                if Topology.IsSame(t_v, v):
                    remove = True
                    vertices = vertices[:i]+ vertices[i:]
                    break
            if not remove:
                remaining_vertices.append(t_v)
        if len(remaining_vertices) < 1:
            return None
        elif len(remaining_vertices) == 1:
            return remaining_vertices[0]
        return Topology.SelfMerge(Cluster.ByTopologies(remaining_vertices), tolerance=tolerance)
def ReplaceVertices(topology, verticesA=[], verticesB=[], mantissa=6, tolerance=0.0001)

Replaces the vertices in the first input list with the vertices in the second input list and rebuilds the input topology. The two lists must be of the same length.

Parameters

topology : topologic_core.Topology
The input topology.
verticesA : list
The first input list of vertices.
verticesB : list
The second input list of vertices.
mantissa : int , optional
The desired length of the mantissa. The default is 6.
tolerance : float , optional
The desired tolerance. The default is 0.0001.

Returns

topologic_core.Topology
The new topology.
Expand source code
@staticmethod
def ReplaceVertices(topology, verticesA=[], verticesB=[], mantissa=6, tolerance=0.0001):
    """
    Replaces the vertices in the first input list with the vertices in the second input list and rebuilds the input topology. The two lists must be of the same length.

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.
    verticesA : list
        The first input list of vertices.
    verticesB : list
        The second input list of vertices.
    mantissa : int , optional
        The desired length of the mantissa. The default is 6.
    tolerance : float , optional
        The desired tolerance. The default is 0.0001.

    Returns
    -------
    topologic_core.Topology
        The new topology.

    """
    if not len(verticesA) == len(verticesB):
        print("Topology.ReplaceVertices - Error: The input parameters verticesA and verticesB must be the same length")
        return None
    from topologicpy.Vertex import Vertex
    geom = Topology.Geometry(topology, mantissa=mantissa)
    g_verts = geom['vertices']
    g_edges = geom['edges']
    g_faces = geom['faces']
    verts = [Topology.Vertices(Topology.ByGeometry(vertices=[g_v]))[0] for g_v in g_verts]
    new_verts = [v for v in verts]
    for i, v in enumerate(verticesA):
        n = Vertex.Index(v, verts, tolerance=tolerance)
        if not n == None:
            new_verts[n] = verticesB[i]
    new_g_verts = [[Vertex.X(v),Vertex.Y(v),Vertex.Z(v)] for v in new_verts]
    new_topology = Topology.ByGeometry(vertices=new_g_verts, edges=g_edges, faces=g_faces)
    return new_topology
def Rotate(topology, origin=None, axis: list = [0, 0, 1], angle: float = 0, angTolerance: float = 0.001, tolerance: float = 0.0001)

Rotates the input topology

Parameters

topology : topologic_core.Topology
The input topology.
origin : topologic_core.Vertex , optional
The origin (center) of the rotation. If set to None, the world origin (0, 0, 0) is used. The default is None.
axis : list , optional
The vector representing the axis of rotation. The default is [0, 0, 1] which equates to the Z axis.
angle : float , optional
The angle of rotation in degrees. The default is 0.
angTolerance : float , optional
The angle tolerance in degrees under which no rotation is carried out. The default is 0.001 degrees.
tolerance : float , optional
The desired tolerance. The default is 0.0001.

Returns

topologic_core.Topology
The rotated topology.
Expand source code
@staticmethod
def Rotate(topology, origin=None, axis: list = [0, 0, 1], angle: float = 0, angTolerance: float = 0.001, tolerance: float = 0.0001):
    """
    Rotates the input topology

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.
    origin : topologic_core.Vertex , optional
        The origin (center) of the rotation. If set to None, the world origin (0, 0, 0) is used. The default is None.
    axis : list , optional
        The vector representing the axis of rotation. The default is [0, 0, 1] which equates to the Z axis.
    angle : float , optional
        The angle of rotation in degrees. The default is 0.
    angTolerance : float , optional
        The angle tolerance in degrees under which no rotation is carried out. The default is 0.001 degrees.
    tolerance : float , optional
        The desired tolerance. The default is 0.0001.

    Returns
    -------
    topologic_core.Topology
        The rotated topology.

    """
    from topologicpy.Vertex import Vertex
    from topologicpy.Dictionary import Dictionary

    def rotate_vertex_3d(vertex, axis, angle_degrees, origin):
        vertex = np.array(vertex)  # Vertex to be rotated
        axis = np.array(axis)    # Rotation axis (z-axis in this case)
        origin = np.array(origin)
        # Convert the angle from degrees to radians
        angle_radians = np.radians(angle_degrees)
        
        # Calculate the rotation matrix using the Rodrigues' formula
        axis = np.array(axis) / np.linalg.norm(axis)
        a = np.cos(angle_radians / 2)
        b, c, d = -axis * np.sin(angle_radians / 2)
        rotation_matrix = np.array([
            [a * a + b * b - c * c - d * d, 2 * (b * c - a * d), 2 * (b * d + a * c)],
            [2 * (b * c + a * d), a * a - b * b + c * c - d * d, 2 * (c * d - a * b)],
            [2 * (b * d - a * c), 2 * (c * d + a * b), a * a - b * b - c * c + d * d]
        ])
        
        # Translate the vertex to the origin, apply the rotation, and then translate it back
        translated_vertex = vertex - origin
        rotated_vertex = np.dot(rotation_matrix, translated_vertex) + origin
        
        rotated_vertex = [v for v in rotated_vertex] 
        return rotated_vertex

    if not Topology.IsInstance(topology, "Topology"):
        print("Topology.Rotate - Error: The input topology parameter is not a valid topologic topology. Returning None.")
        return None
    if not origin:
        origin = Vertex.ByCoordinates(0, 0, 0)
    if not Topology.IsInstance(origin, "Vertex"):
        print("Topology.Rotate - Error: The input origin parameter is not a valid topologic vertex. Returning None.")
        return None
    returnTopology = topology
    if abs(angle) >= angTolerance:
        try:
            x, y, z = axis
            returnTopology = topologic.TopologyUtility.Rotate(topology, origin, x, y, z, angle)
        except:
            print("Topology.Rotate - Warning: (topologic.TopologyUtility.Rotate) operation failed. Trying a workaround.")
            vertices = [Vertex.Coordinates(v) for v in Topology.Vertices(topology)]
            origin = Vertex.Coordinates(origin)
            rot_vertices = []
            for v in vertices:
                rot_vertices.append(rotate_vertex_3d(v, axis, angle, origin))
            rot_vertices = [Vertex.ByCoordinates(rot_v) for rot_v in rot_vertices]
            new_topology = Topology.ReplaceVertices(topology, verticesA=Topology.Vertices(topology), verticesB=rot_vertices)
            new_topology = Topology.SelfMerge(new_topology, tolerance=tolerance)
            return new_topology
    return returnTopology
def Scale(topology, origin=None, x=1, y=1, z=1)

Scales the input topology

Parameters

topology : topologic_core.Topology
The input topology.
origin : topologic_core.Vertex , optional
The origin (center) of the scaling. If set to None, the world origin (0, 0, 0) is used. The default is None.
x : float , optional
The 'x' component of the scaling factor. The default is 1.
y : float , optional
The 'y' component of the scaling factor. The default is 1.
z : float , optional
The 'z' component of the scaling factor. The default is 1..

Returns

topologic_core.Topology
The scaled topology.
Expand source code
@staticmethod
def Scale(topology, origin=None, x=1, y=1, z=1):
    """
    Scales the input topology

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.
    origin : topologic_core.Vertex , optional
        The origin (center) of the scaling. If set to None, the world origin (0, 0, 0) is used. The default is None.
    x : float , optional
        The 'x' component of the scaling factor. The default is 1.
    y : float , optional
        The 'y' component of the scaling factor. The default is 1.
    z : float , optional
        The 'z' component of the scaling factor. The default is 1..

    Returns
    -------
    topologic_core.Topology
        The scaled topology.

    """
    from topologicpy.Vertex import Vertex
    if not Topology.IsInstance(topology, "Topology"):
        return None
    if not origin:
        origin = Vertex.ByCoordinates(0, 0, 0)
    if not Topology.IsInstance(origin, "Vertex"):
        return None
    newTopology = None
    try:
        newTopology = topologic.TopologyUtility.Scale(topology, origin, x, y, z)
    except:
        print("Topology.Scale - ERROR: (Topologic>TopologyUtility.Scale) operation failed. Returning None.")
        newTopology = None
    return newTopology
def SelectSubTopology(topology, selector, subTopologyType='vertex')

Returns the subtopology within the input topology based on the input selector and the subTopologyType.

Parameters

topology : topologic_core.Topology
The input topology.
selector : topologic_core.Vertex
A vertex located on the desired subtopology.

subTopologyType : str , optional. The desired subtopology type. This can be of "vertex", "edge", "wire", "face", "shell", "cell", or "cellcomplex". It is case insensitive. The default is "vertex".

Returns

topologic_core.Topology
The selected subtopology.
Expand source code
@staticmethod
def SelectSubTopology(topology, selector, subTopologyType="vertex"):
    """
    Returns the subtopology within the input topology based on the input selector and the subTopologyType.

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.
    selector : topologic_core.Vertex
        A vertex located on the desired subtopology.
    subTopologyType : str , optional.
        The desired subtopology type. This can be of "vertex", "edge", "wire", "face", "shell", "cell", or "cellcomplex". It is case insensitive. The default is "vertex".

    Returns
    -------
    topologic_core.Topology
        The selected subtopology.

    """
    if not Topology.IsInstance(topology, "Topology"):
        return None
    if not Topology.IsInstance(selector, "Vertex"):
        return None
    t = 1
    if subTopologyType.lower() == "vertex":
        t = 1
    elif subTopologyType.lower() == "edge":
        t = 2
    elif subTopologyType.lower() == "wire":
        t = 4
    elif subTopologyType.lower() == "face":
        t = 8
    elif subTopologyType.lower() == "shell":
        t = 16
    elif subTopologyType.lower() == "cell":
        t = 32
    elif subTopologyType.lower() == "cellcomplex":
        t = 64
    return topology.SelectSubtopology(selector, t)
def SelfMerge(topology, transferDictionaries: bool = False, tolerance: float = 0.0001)

Self merges the input topology to return the most logical topology type given the input data.

Parameters

topology : topologic_core.Topology
The input topology.
tolerance : float , optional
The desired tolerance. The default is 0.0001

Returns

topologic_core.Topology
The self-merged topology.
Expand source code
@staticmethod
def SelfMerge(topology, transferDictionaries: bool = False, tolerance: float = 0.0001):
    """
    Self merges the input topology to return the most logical topology type given the input data.

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.
    tolerance : float , optional
        The desired tolerance. The default is 0.0001

    Returns
    -------
    topologic_core.Topology
        The self-merged topology.

    """
    from topologicpy.Cluster import Cluster

    if not Topology.IsInstance(topology, "Topology"):
        return None #return Silently
    if not Topology.Type(topology) == Topology.TypeID("Cluster"):
        topology = Cluster.ByTopologies([topology])
    resultingTopologies = []
    topCC = Topology.CellComplexes(topology)
    topCells = Topology.Cells(topology)
    topShells = Topology.Shells(topology)
    topFaces = Topology.Faces(topology)
    topWires = Topology.Wires(topology)
    topEdges = Topology.Edges(topology)
    topVertices = Topology.Vertices(topology)
    if len(topCC) == 1:
        cc = topCC[0]
        ccVertices = Topology.Vertices(cc)
        if len(topVertices) == len(ccVertices):
            resultingTopologies.append(cc)
    if len(topCC) == 0 and len(topCells) == 1:
        cell = topCells[0]
        ccVertices = Topology.Vertices(cell)
        if len(topVertices) == len(ccVertices):
            resultingTopologies.append(cell)
    if len(topCC) == 0 and len(topCells) == 0 and len(topShells) == 1:
        shell = topShells[0]
        ccVertices = Topology.Vertices(shell)
        if len(topVertices) == len(ccVertices):
            resultingTopologies.append(shell)
    if len(topCC) == 0 and len(topCells) == 0 and len(topShells) == 0 and len(topFaces) == 1:
        face = topFaces[0]
        ccVertices = Topology.Vertices(face)
        if len(topVertices) == len(ccVertices):
            resultingTopologies.append(face)
    if len(topCC) == 0 and len(topCells) == 0 and len(topShells) == 0 and len(topFaces) == 0 and len(topWires) == 1:
        wire = topWires[0]
        ccVertices = Topology.Vertices(wire)
        if len(topVertices) == len(ccVertices):
            resultingTopologies.append(wire)
    if len(topCC) == 0 and len(topCells) == 0 and len(topShells) == 0 and len(topFaces) == 0 and len(topWires) == 0 and len(topEdges) == 1:
        edge = topEdges[0]
        ccVertices = Topology.Vertices(edge)
        if len(topVertices) == len(ccVertices):
            resultingTopologies.append(edge)
    if len(topCC) == 0 and len(topCells) == 0 and len(topShells) == 0 and len(topFaces) == 0 and len(topWires) == 0 and len(topEdges) == 0 and len(topVertices) == 1:
        vertex = topVertices[0]
        resultingTopologies.append(vertex)
    if len(resultingTopologies) == 1:
        return resultingTopologies[0]
    try:
        return_topology = topology.SelfMerge()
    except:
        return_topology = None
    if Topology.IsInstance(return_topology, "CellComplex"):
        cells = Topology.Cells(return_topology)
        if isinstance(cells, list):
            if len(cells) > 1:
                topA = cells[0]
                topB = Cluster.ByTopologies(cells[1:])
                return_topology = Topology.Merge(topA, topB, tolerance=tolerance)
            else:
                return_topology = cells[0]
    return return_topology
def SetDictionary(topology, dictionary)

Sets the input topology's dictionary to the input dictionary

Parameters

topology : topologic_core.Topology
The input topology.
dictionary : topologic_core.Dictionary
The input dictionary.

Returns

topologic_core.Topology
The input topology with the input dictionary set in it.
Expand source code
@staticmethod
def SetDictionary(topology, dictionary):
    """
    Sets the input topology's dictionary to the input dictionary

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.
    dictionary : topologic_core.Dictionary
        The input dictionary.

    Returns
    -------
    topologic_core.Topology
        The input topology with the input dictionary set in it.

    """
    from topologicpy.Dictionary import Dictionary

    if not Topology.IsInstance(topology, "Topology") and not Topology.IsInstance(topology, "Graph"):
        print("Topology.SetDictionary - Error: the input topology parameter is not a valid topology or graph. Returning None.")
        return None
    if isinstance(dictionary, dict):
        dictionary = Dictionary.ByPythonDictionary(dictionary)
    if not Topology.IsInstance(dictionary, "Dictionary"):
        print("Topology.SetDictionary - Warning: the input dictionary parameter is not a valid dictionary. Returning original input.")
        return topology
    if len(dictionary.Keys()) < 1:
        print("Topology.SetDictionary - Warning: the input dictionary parameter is empty. Returning original input.")
        return topology
    _ = topology.SetDictionary(dictionary)
    return topology
def SetSnapshot(topology, snapshot=None, timestamp=None, key='timestamp', silent=False)
Expand source code
@staticmethod
def SetSnapshot(topology, snapshot=None, timestamp=None, key="timestamp", silent=False):
    from topologicpy.Dictionary import Dictionary
    from datetime import datetime
    def is_valid_timestamp(timestamp):
        if isinstance(timestamp, datetime):
            return True
        elif isinstance(timestamp, str):
            try:
                # Split the timestamp string into date and time parts
                date_part, time_part = timestamp.split(' ')
                # Parse the date part
                date_obj = datetime.strptime(date_part, '%Y-%m-%d')
                # Split the time part into hours, minutes, and seconds
                hours, minutes, seconds = map(float, time_part.split(':'))
                # Check if seconds are within valid range
                if seconds < 0 or seconds >= 60:
                    return False
                # Create a datetime object with the parsed date and time parts
                datetime_obj = datetime(date_obj.year, date_obj.month, date_obj.day, int(hours), int(minutes), int(seconds))
                return True
            except ValueError:
                return False
        else:
            return False

    if not Topology.IsInstance(topology, "Topology"):
        if not silent:
            print("Topology.SetSnapshot - Error: The input topology parameter is not a valid topology. Returning None.")
            return None
    if not Topology.IsInstance(snapshot, "Topology"):
        snapshot = Topology.Copy(topology)
    if not Topology.IsInstance(snapshot, "Topology"):
        if not silent:
            print("Topology.SetSnapshot - Error: The input snapshot parameter is not a valid topology. Returning None.")
            return None
    if timestamp == None:
        timestamp = datetime.now()
    if not is_valid_timestamp(timestamp):
        if not silent:
            print("Topology.SetSnapshot - Error: The input timestamp parameter is not a valid timestamp. Returning None.")
            return None
    
    d = Topology.Dictionary(snapshot)
    d = Dictionary.SetValueAtKey(d, key, str(timestamp))
    snapshot = Topology.SetDictionary(snapshot, d)
    topology = Topology.AddContent(topology, snapshot)
    return topology
def SharedEdges(topologyA, topologyB)

Returns the shared edges between the two input topologies

Parameters

topologyA : topologic_core.Topology
The first input topology.
topologyB : topologic_core.Topology
The second input topology.

Returns

list
The list of shared edges.
Expand source code
@staticmethod
def SharedEdges(topologyA, topologyB):
    """
    Returns the shared edges between the two input topologies

    Parameters
    ----------
    topologyA : topologic_core.Topology
        The first input topology.
    topologyB : topologic_core.Topology
        The second input topology.

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

    """
    d = Topology.SharedTopologies(topologyA, topologyB)
    l = None
    if isinstance(d, dict):
        try:
            l = d['edges']
        except:
            l = None
    return l
def SharedFaces(topologyA, topologyB)

Returns the shared faces between the two input topologies

Parameters

topologyA : topologic_core.Topology
The first input topology.
topologyB : topologic_core.Topology
The second input topology.

Returns

list
The list of shared faces.
Expand source code
@staticmethod
def SharedFaces(topologyA, topologyB):
    """
    Returns the shared faces between the two input topologies

    Parameters
    ----------
    topologyA : topologic_core.Topology
        The first input topology.
    topologyB : topologic_core.Topology
        The second input topology.

    Returns
    -------
    list
        The list of shared faces.

    """
    d = Topology.SharedTopologies(topologyA, topologyB)
    l = None
    if isinstance(d, dict):
        try:
            l = d['faces']
        except:
            l = None
    return l
def SharedTopologies(topologyA, topologyB)

Returns the shared topologies between the two input topologies

Parameters

topologyA : topologic_core.Topology
The first input topology.
topologyB : topologic_core.Topology
The second input topology.

Returns

dict
A dictionary with the list of vertices, edges, wires, and faces. The keys are "vertices", "edges", "wires", and "faces".
Expand source code
@staticmethod
def SharedTopologies(topologyA, topologyB):
    """
    Returns the shared topologies between the two input topologies

    Parameters
    ----------
    topologyA : topologic_core.Topology
        The first input topology.
    topologyB : topologic_core.Topology
        The second input topology.

    Returns
    -------
    dict
        A dictionary with the list of vertices, edges, wires, and faces. The keys are "vertices", "edges", "wires", and "faces".

    """

    if not Topology.IsInstance(topologyA, "Topology"):
        print("Topology.SharedTopologies - Error: the input topologyA parameter is not a valid topology. Returning None.")
        return None
    if not Topology.IsInstance(topologyB, "Topology"):
        print("Topology.SharedTopologies - Error: the input topologyB parameter is not a valid topology. Returning None.")
        return None
    vOutput = []
    eOutput = []
    wOutput = []
    fOutput = []
    _ = topologyA.SharedTopologies(topologyB, 1, vOutput)
    _ = topologyA.SharedTopologies(topologyB, 2, eOutput)
    _ = topologyA.SharedTopologies(topologyB, 4, wOutput)
    _ = topologyA.SharedTopologies(topologyB, 8, fOutput)
    return {"vertices":vOutput, "edges":eOutput, "wires":wOutput, "faces":fOutput}
def SharedVertices(topologyA, topologyB)

Returns the shared vertices between the two input topologies

Parameters

topologyA : topologic_core.Topology
The first input topology.
topologyB : topologic_core.Topology
The second input topology.

Returns

list
The list of shared vertices.
Expand source code
@staticmethod
def SharedVertices(topologyA, topologyB):
    """
    Returns the shared vertices between the two input topologies

    Parameters
    ----------
    topologyA : topologic_core.Topology
        The first input topology.
    topologyB : topologic_core.Topology
        The second input topology.

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

    """
    d = Topology.SharedTopologies(topologyA, topologyB)
    l = None
    if isinstance(d, dict):
        try:
            l = d['vertices']
        except:
            l = None
    return l
def SharedWires(topologyA, topologyB)

Returns the shared wires between the two input topologies

Parameters

topologyA : topologic_core.Topology
The first input topology.
topologyB : topologic_core.Topology
The second input topology.

Returns

list
The list of shared wires.
Expand source code
@staticmethod
def SharedWires(topologyA, topologyB):
    """
    Returns the shared wires between the two input topologies

    Parameters
    ----------
    topologyA : topologic_core.Topology
        The first input topology.
    topologyB : topologic_core.Topology
        The second input topology.

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

    """
    d = Topology.SharedTopologies(topologyA, topologyB)
    l = None
    if isinstance(d, dict):
        try:
            l = d['wires']
        except:
            l = None
    return l
def Shells(topology)

Returns the shells of the input topology.

Parameters

topology : topologic_core.Topology
The input topology.

Returns

list
The list of shells.
Expand source code
@staticmethod
def Shells(topology):
    """
    Returns the shells of the input topology.

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.

    Returns
    -------
    list
        The list of shells.

    """
    if Topology.IsInstance(topology, "Shell") or Topology.IsInstance(topology, "Face") or Topology.IsInstance(topology, "Wire") or Topology.IsInstance(topology, "Edge") or Topology.IsInstance(topology, "Vertex"):
        return []
    return Topology.SubTopologies(topology=topology, subTopologyType="shell")
def Show(*topologies, showVertices=True, vertexSize=1.1, vertexColor='black', vertexLabelKey=None, vertexGroupKey=None, vertexGroups=[], vertexMinGroup=None, vertexMaxGroup=None, showVertexLegend=False, vertexLegendLabel='Topology Vertices', vertexLegendRank=1, vertexLegendGroup=1, showEdges=True, edgeWidth=1, edgeColor='black', edgeLabelKey=None, edgeGroupKey=None, edgeGroups=[], edgeMinGroup=None, edgeMaxGroup=None, showEdgeLegend=False, edgeLegendLabel='Topology Edges', edgeLegendRank=2, edgeLegendGroup=2, showFaces=True, faceOpacity=0.5, faceColor='#FAFAFA', faceLabelKey=None, faceGroupKey=None, faceGroups=[], faceMinGroup=None, faceMaxGroup=None, showFaceLegend=False, faceLegendLabel='Topology Faces', faceLegendRank=3, faceLegendGroup=3, intensityKey=None, intensities=[], width=950, height=500, xAxis=False, yAxis=False, zAxis=False, axisSize=1, backgroundColor='rgba(0,0,0,0)', marginLeft=0, marginRight=0, marginTop=20, marginBottom=0, camera=[-1.25, -1.25, 1.25], center=[0, 0, 0], up=[0, 0, 1], projection='perspective', renderer='notebook', showScale=False, cbValues=[], cbTicks=5, cbX=-0.15, cbWidth=15, cbOutlineWidth=0, cbTitle='', cbSubTitle='', cbUnits='', colorScale='Viridis', mantissa=6, tolerance=0.0001)

Shows the input topology on screen.

Parameters

topologies : topologic_core.Topology or list
The input topology. This must contain faces and or edges. If the input is a list, a cluster is first created
showVertices : bool , optional
If set to True the vertices will be drawn. Otherwise, they will not be drawn. The default is True.
vertexSize : float , optional
The desired size of the vertices. The default is 1.1.
vertexColor : str , optional
The desired color of the output vertices. This can be any plotly color string and may be specified as: - A hex string (e.g. '#ff0000') - An rgb/rgba string (e.g. 'rgb(255,0,0)') - An hsl/hsla string (e.g. 'hsl(0,100%,50%)') - An hsv/hsva string (e.g. 'hsv(0,100%,100%)') - A named CSS color. The default is "black".
vertexLabelKey : str , optional
The dictionary key to use to display the vertex label. The default is None.
vertexGroupKey : str , optional
The dictionary key to use to display the vertex group. The default is None.
vertexGroups : list , optional
The list of vertex groups against which to index the color of the vertex. The default is [].
vertexMinGroup : int or float , optional
For numeric vertexGroups, vertexMinGroup is the desired minimum value for the scaling of colors. This should match the type of value associated with the vertexGroupKey. If set to None, it is set to the minimum value in vertexGroups. The default is None.
edgeMaxGroup : int or float , optional
For numeric vertexGroups, vertexMaxGroup is the desired maximum value for the scaling of colors. This should match the type of value associated with the vertexGroupKey. If set to None, it is set to the maximum value in vertexGroups. The default is None.
showVertexLegend : bool, optional
If set to True, the legend for the vertices of this topology is shown. Otherwise, it isn't. The default is False.
vertexLegendLabel : str , optional
The legend label string used to identify vertices. The default is "Topology Vertices".
vertexLegendRank : int , optional
The legend rank order of the vertices of this topology. The default is 1.
vertexLegendGroup : int , optional
The number of the vertex legend group to which the vertices of this topology belong. The default is 1.
showEdges : bool , optional
If set to True the edges will be drawn. Otherwise, they will not be drawn. The default is True.
edgeWidth : float , optional
The desired thickness of the output edges. The default is 1.
edgeColor : str , optional
The desired color of the output edges. This can be any plotly color string and may be specified as: - A hex string (e.g. '#ff0000') - An rgb/rgba string (e.g. 'rgb(255,0,0)') - An hsl/hsla string (e.g. 'hsl(0,100%,50%)') - An hsv/hsva string (e.g. 'hsv(0,100%,100%)') - A named CSS color. The default is "black".
edgeLabelKey : str , optional
The dictionary key to use to display the edge label. The default is None.
edgeGroupKey : str , optional
The dictionary key to use to display the edge group. The default is None.
edgeGroups : list , optional
The list of edge groups against which to index the color of the edge. The default is [].
edgeMinGroup : int or float , optional
For numeric edgeGroups, edgeMinGroup is the desired minimum value for the scaling of colors. This should match the type of value associated with the edgeGroupKey. If set to None, it is set to the minimum value in edgeGroups. The default is None.
edgeMaxGroup : int or float , optional
For numeric edgeGroups, edgeMaxGroup is the desired maximum value for the scaling of colors. This should match the type of value associated with the edgeGroupKey. If set to None, it is set to the maximum value in edgeGroups. The default is None.
showEdgeLegend : bool, optional
If set to True, the legend for the edges of this topology is shown. Otherwise, it isn't. The default is False.
edgeLegendLabel : str , optional
The legend label string used to identify edges. The default is "Topology Edges".
edgeLegendRank : int , optional
The legend rank order of the edges of this topology. The default is 2.
edgeLegendGroup : int , optional
The number of the edge legend group to which the edges of this topology belong. The default is 2.
showFaces : bool , optional
If set to True the faces will be drawn. Otherwise, they will not be drawn. The default is True.
faceOpacity : float , optional
The desired opacity of the output faces (0=transparent, 1=opaque). The default is 0.5.
faceColor : str , optional
The desired color of the output faces. This can be any plotly color string and may be specified as: - A hex string (e.g. '#ff0000') - An rgb/rgba string (e.g. 'rgb(255,0,0)') - An hsl/hsla string (e.g. 'hsl(0,100%,50%)') - An hsv/hsva string (e.g. 'hsv(0,100%,100%)') - A named CSS color. The default is "#FAFAFA".
faceLabelKey : str , optional
The dictionary key to use to display the face label. The default is None.
faceGroupKey : str , optional
The dictionary key to use to display the face group. The default is None.
faceGroups : list , optional
The list of face groups against which to index the color of the face. This can bhave numeric or string values. This should match the type of value associated with the faceGroupKey. The default is [].
faceMinGroup : int or float , optional
For numeric faceGroups, minGroup is the desired minimum value for the scaling of colors. This should match the type of value associated with the faceGroupKey. If set to None, it is set to the minimum value in faceGroups. The default is None.
faceMaxGroup : int or float , optional
For numeric faceGroups, maxGroup is the desired maximum value for the scaling of colors. This should match the type of value associated with the faceGroupKey. If set to None, it is set to the maximum value in faceGroups. The default is None.
showFaceLegend : bool, optional
If set to True, the legend for the faces of this topology is shown. Otherwise, it isn't. The default is False.
faceLegendLabel : str , optional
The legend label string used to idenitfy edges. The default is "Topology Faces".
faceLegendRank : int , optional
The legend rank order of the faces of this topology. The default is 3.
faceLegendGroup : int , optional
The number of the face legend group to which the faces of this topology belong. The default is 3.
width : int , optional
The width in pixels of the figure. The default value is 950.
height : int , optional
The height in pixels of the figure. The default value is 950.
xAxis : bool , optional
If set to True the x axis is drawn. Otherwise it is not drawn. The default is False.
yAxis : bool , optional
If set to True the y axis is drawn. Otherwise it is not drawn. The default is False.
zAxis : bool , optional
If set to True the z axis is drawn. Otherwise it is not drawn. The default is False.
backgroundColor : str , optional
The desired color of the background. This can be any plotly color string and may be specified as: - A hex string (e.g. '#ff0000') - An rgb/rgba string (e.g. 'rgb(255,0,0)') - An hsl/hsla string (e.g. 'hsl(0,100%,50%)') - An hsv/hsva string (e.g. 'hsv(0,100%,100%)') - A named CSS color. The default is "rgba(0,0,0,0)".
marginLeft : int , optional
The size in pixels of the left margin. The default value is 0.
marginRight : int , optional
The size in pixels of the right margin. The default value is 0.
marginTop : int , optional
The size in pixels of the top margin. The default value is 20.
marginBottom : int , optional
The size in pixels of the bottom margin. The default value is 0.
camera : list , optional
The desired location of the camera). The default is [-1.25, -1.25, 1.25].
center : list , optional
The desired center (camera target). The default is [0, 0, 0].
up : list , optional
The desired up vector. The default is [0, 0, 1].
projection : str , optional
The desired type of projection. The options are "orthographic" or "perspective". It is case insensitive. The default is "perspective"
renderer : str , optional
The desired renderer. See Plotly.Renderers(). If set to None, the code will attempt to discover the most suitable renderer. The default is None.
intensityKey : str , optional
If not None, the dictionary of each vertex is searched for the value associated with the intensity key. This value is then used to color-code the vertex based on the colorScale. The default is None.
intensities : list , optional
The list of intensities against which to index the intensity of the vertex. The default is [].
showScale : bool , optional
If set to True, the colorbar is shown. The default is False.
cbValues : list , optional
The input list of values to use for the colorbar. The default is [].
cbTicks : int , optional
The number of ticks to use on the colorbar. The default is 5.
cbX : float , optional
The x location of the colorbar. The default is -0.15.
cbWidth : int , optional
The width in pixels of the colorbar. The default is 15
cbOutlineWidth : int , optional
The width in pixels of the outline of the colorbar. The default is 0.
cbTitle : str , optional
The title of the colorbar. The default is "".
cbSubTitle : str , optional
The subtitle of the colorbar. The default is "".
cbUnits : str , optional
The units used in the colorbar. The default is ""
colorScale : str , optional
The desired type of plotly color scales to use (e.g. "viridis", "plasma"). The default is "viridis". For a full list of names, see https://plotly.com/python/builtin-colorscales/.
mantissa : int , optional
The desired length of the mantissa for the values listed on the colorbar. The default is 6.
tolerance : float , optional
The desired tolerance. The default is 0.0001.

Returns

None
 
Expand source code
@staticmethod
def Show(*topologies,
         showVertices=True, vertexSize=1.1, vertexColor="black", 
         vertexLabelKey=None, vertexGroupKey=None, vertexGroups=[], 
         vertexMinGroup=None, vertexMaxGroup=None, 
         showVertexLegend=False, vertexLegendLabel="Topology Vertices", vertexLegendRank=1, 
         vertexLegendGroup=1, 

         showEdges=True, edgeWidth=1, edgeColor="black", 
         edgeLabelKey=None, edgeGroupKey=None, edgeGroups=[], 
         edgeMinGroup=None, edgeMaxGroup=None, 
         showEdgeLegend=False, edgeLegendLabel="Topology Edges", edgeLegendRank=2, 
         edgeLegendGroup=2, 

         showFaces=True, faceOpacity=0.5, faceColor="#FAFAFA",
         faceLabelKey=None, faceGroupKey=None, faceGroups=[], 
         faceMinGroup=None, faceMaxGroup=None, 
         showFaceLegend=False, faceLegendLabel="Topology Faces", faceLegendRank=3,
         faceLegendGroup=3, 
         intensityKey=None,
         intensities=[],
         
         width=950, height=500,
         xAxis=False, yAxis=False, zAxis=False, axisSize=1, backgroundColor='rgba(0,0,0,0)',
         marginLeft=0, marginRight=0, marginTop=20, marginBottom=0, camera=[-1.25, -1.25, 1.25],
         center=[0, 0, 0], up=[0, 0, 1], projection="perspective", renderer="notebook", showScale=False,
         
         cbValues=[], cbTicks=5, cbX=-0.15, cbWidth=15, cbOutlineWidth=0, cbTitle="",
         cbSubTitle="", cbUnits="", colorScale="Viridis", mantissa=6, tolerance=0.0001):
    """
        Shows the input topology on screen.

    Parameters
    ----------
    topologies : topologic_core.Topology or list
        The input topology. This must contain faces and or edges. If the input is a list, a cluster is first created

    showVertices : bool , optional
        If set to True the vertices will be drawn. Otherwise, they will not be drawn. The default is True.
    vertexSize : float , optional
        The desired size of the vertices. The default is 1.1.
    vertexColor : str , optional
        The desired color of the output vertices. This can be any plotly color string and may be specified as:
        - A hex string (e.g. '#ff0000')
        - An rgb/rgba string (e.g. 'rgb(255,0,0)')
        - An hsl/hsla string (e.g. 'hsl(0,100%,50%)')
        - An hsv/hsva string (e.g. 'hsv(0,100%,100%)')
        - A named CSS color.
        The default is "black".
    vertexLabelKey : str , optional
        The dictionary key to use to display the vertex label. The default is None.
    vertexGroupKey : str , optional
        The dictionary key to use to display the vertex group. The default is None.
    vertexGroups : list , optional
        The list of vertex groups against which to index the color of the vertex. The default is [].
    vertexMinGroup : int or float , optional
        For numeric vertexGroups, vertexMinGroup is the desired minimum value for the scaling of colors. This should match the type of value associated with the vertexGroupKey. If set to None, it is set to the minimum value in vertexGroups. The default is None.
    edgeMaxGroup : int or float , optional
        For numeric vertexGroups, vertexMaxGroup is the desired maximum value for the scaling of colors. This should match the type of value associated with the vertexGroupKey. If set to None, it is set to the maximum value in vertexGroups. The default is None.
    showVertexLegend : bool, optional
        If set to True, the legend for the vertices of this topology is shown. Otherwise, it isn't. The default is False.
    vertexLegendLabel : str , optional
        The legend label string used to identify vertices. The default is "Topology Vertices".
    vertexLegendRank : int , optional
        The legend rank order of the vertices of this topology. The default is 1.
    vertexLegendGroup : int , optional
        The number of the vertex legend group to which the vertices of this topology belong. The default is 1.
    
    showEdges : bool , optional
        If set to True the edges will be drawn. Otherwise, they will not be drawn. The default is True.
    edgeWidth : float , optional
        The desired thickness of the output edges. The default is 1.
    edgeColor : str , optional
        The desired color of the output edges. This can be any plotly color string and may be specified as:
        - A hex string (e.g. '#ff0000')
        - An rgb/rgba string (e.g. 'rgb(255,0,0)')
        - An hsl/hsla string (e.g. 'hsl(0,100%,50%)')
        - An hsv/hsva string (e.g. 'hsv(0,100%,100%)')
        - A named CSS color.
        The default is "black".
    edgeLabelKey : str , optional
        The dictionary key to use to display the edge label. The default is None.
    edgeGroupKey : str , optional
        The dictionary key to use to display the edge group. The default is None.
    edgeGroups : list , optional
        The list of edge groups against which to index the color of the edge. The default is [].
    edgeMinGroup : int or float , optional
        For numeric edgeGroups, edgeMinGroup is the desired minimum value for the scaling of colors. This should match the type of value associated with the edgeGroupKey. If set to None, it is set to the minimum value in edgeGroups. The default is None.
    edgeMaxGroup : int or float , optional
        For numeric edgeGroups, edgeMaxGroup is the desired maximum value for the scaling of colors. This should match the type of value associated with the edgeGroupKey. If set to None, it is set to the maximum value in edgeGroups. The default is None.
    showEdgeLegend : bool, optional
        If set to True, the legend for the edges of this topology is shown. Otherwise, it isn't. The default is False.
    edgeLegendLabel : str , optional
        The legend label string used to identify edges. The default is "Topology Edges".
    edgeLegendRank : int , optional
        The legend rank order of the edges of this topology. The default is 2.
    edgeLegendGroup : int , optional
        The number of the edge legend group to which the edges of this topology belong. The default is 2.
    
    showFaces : bool , optional
        If set to True the faces will be drawn. Otherwise, they will not be drawn. The default is True.
    faceOpacity : float , optional
        The desired opacity of the output faces (0=transparent, 1=opaque). The default is 0.5.
    faceColor : str , optional
        The desired color of the output faces. This can be any plotly color string and may be specified as:
        - A hex string (e.g. '#ff0000')
        - An rgb/rgba string (e.g. 'rgb(255,0,0)')
        - An hsl/hsla string (e.g. 'hsl(0,100%,50%)')
        - An hsv/hsva string (e.g. 'hsv(0,100%,100%)')
        - A named CSS color.
        The default is "#FAFAFA".
    faceLabelKey : str , optional
        The dictionary key to use to display the face label. The default is None.
    faceGroupKey : str , optional
        The dictionary key to use to display the face group. The default is None.
    faceGroups : list , optional
        The list of face groups against which to index the color of the face. This can bhave numeric or string values. This should match the type of value associated with the faceGroupKey. The default is [].
    faceMinGroup : int or float , optional
        For numeric faceGroups, minGroup is the desired minimum value for the scaling of colors. This should match the type of value associated with the faceGroupKey. If set to None, it is set to the minimum value in faceGroups. The default is None.
    faceMaxGroup : int or float , optional
        For numeric faceGroups, maxGroup is the desired maximum value for the scaling of colors. This should match the type of value associated with the faceGroupKey. If set to None, it is set to the maximum value in faceGroups. The default is None.
    showFaceLegend : bool, optional
        If set to True, the legend for the faces of this topology is shown. Otherwise, it isn't. The default is False.
    faceLegendLabel : str , optional
        The legend label string used to idenitfy edges. The default is "Topology Faces".
    faceLegendRank : int , optional
        The legend rank order of the faces of this topology. The default is 3.
    faceLegendGroup : int , optional
        The number of the face legend group to which the faces of this topology belong. The default is 3.
    width : int , optional
        The width in pixels of the figure. The default value is 950.
    height : int , optional
        The height in pixels of the figure. The default value is 950.
    xAxis : bool , optional
        If set to True the x axis is drawn. Otherwise it is not drawn. The default is False.
    yAxis : bool , optional
        If set to True the y axis is drawn. Otherwise it is not drawn. The default is False.
    zAxis : bool , optional
        If set to True the z axis is drawn. Otherwise it is not drawn. The default is False.
    backgroundColor : str , optional
        The desired color of the background. This can be any plotly color string and may be specified as:
        - A hex string (e.g. '#ff0000')
        - An rgb/rgba string (e.g. 'rgb(255,0,0)')
        - An hsl/hsla string (e.g. 'hsl(0,100%,50%)')
        - An hsv/hsva string (e.g. 'hsv(0,100%,100%)')
        - A named CSS color.
        The default is "rgba(0,0,0,0)".
    marginLeft : int , optional
        The size in pixels of the left margin. The default value is 0.
    marginRight : int , optional
        The size in pixels of the right margin. The default value is 0.
    marginTop : int , optional
        The size in pixels of the top margin. The default value is 20.
    marginBottom : int , optional
        The size in pixels of the bottom margin. The default value is 0.
    camera : list , optional
        The desired location of the camera). The default is [-1.25, -1.25, 1.25].
    center : list , optional
        The desired center (camera target). The default is [0, 0, 0].
    up : list , optional
        The desired up vector. The default is [0, 0, 1].
    projection : str , optional
        The desired type of projection. The options are "orthographic" or "perspective". It is case insensitive. The default is "perspective"
    renderer : str , optional
        The desired renderer. See Plotly.Renderers(). If set to None, the code will attempt to discover the most suitable renderer. The default is None.
    intensityKey : str , optional
        If not None, the dictionary of each vertex is searched for the value associated with the intensity key. This value is then used to color-code the vertex based on the colorScale. The default is None.
    intensities : list , optional
        The list of intensities against which to index the intensity of the vertex. The default is [].
    showScale : bool , optional
        If set to True, the colorbar is shown. The default is False.
    cbValues : list , optional
        The input list of values to use for the colorbar. The default is [].
    cbTicks : int , optional
        The number of ticks to use on the colorbar. The default is 5.
    cbX : float , optional
        The x location of the colorbar. The default is -0.15.
    cbWidth : int , optional
        The width in pixels of the colorbar. The default is 15
    cbOutlineWidth : int , optional
        The width in pixels of the outline of the colorbar. The default is 0.
    cbTitle : str , optional
        The title of the colorbar. The default is "".
    cbSubTitle : str , optional
        The subtitle of the colorbar. The default is "".
    cbUnits: str , optional
        The units used in the colorbar. The default is ""
    colorScale : str , optional
        The desired type of plotly color scales to use (e.g. "viridis", "plasma"). The default is "viridis". For a full list of names, see https://plotly.com/python/builtin-colorscales/.
    mantissa : int , optional
        The desired length of the mantissa for the values listed on the colorbar. The default is 6.
    tolerance : float , optional
        The desired tolerance. The default is 0.0001.

    Returns
    -------
    None

    """

    from topologicpy.Cluster import Cluster
    from topologicpy.Plotly import Plotly
    from topologicpy.Helper import Helper
    from topologicpy.Graph import Graph
    
    if isinstance(topologies, tuple):
        topologies = Helper.Flatten(list(topologies))
    if isinstance(topologies, list):
        new_topologies = [t for t in topologies if Topology.IsInstance(t, "Topology")]
        graphs = [Graph.Topology(g) for g in topologies if Topology.IsInstance(g, "Graph")]
        new_topologies += graphs
    if len(new_topologies) == 0:
        print("Topology.Show - Error: the input topologies parameter does not contain any valid topology. Returning None.")
        return None
    if len(new_topologies) == 1:
        topology = new_topologies[0]
    else:
        topology = Cluster.ByTopologies(new_topologies)
    if not Topology.IsInstance(topology, "Topology"):
        print("Topology.Show - Error: the input topology parameter is not a valid topology. Returning None.")
        return None
    data = Plotly.DataByTopology(topology=topology,
                   showVertices=showVertices, vertexSize=vertexSize, vertexColor=vertexColor, 
                   vertexLabelKey=vertexLabelKey, vertexGroupKey=vertexGroupKey, vertexGroups=vertexGroups, 
                   vertexMinGroup=vertexMinGroup, vertexMaxGroup=vertexMaxGroup, 
                   showVertexLegend=showVertexLegend, vertexLegendLabel=vertexLegendLabel, vertexLegendRank=vertexLegendRank,
                   vertexLegendGroup=vertexLegendGroup,
                   showEdges=showEdges, edgeWidth=edgeWidth, edgeColor=edgeColor, 
                   edgeLabelKey=edgeLabelKey, edgeGroupKey=edgeGroupKey, edgeGroups=edgeGroups, 
                   edgeMinGroup=edgeMinGroup, edgeMaxGroup=edgeMaxGroup, 
                   showEdgeLegend=showEdgeLegend, edgeLegendLabel=edgeLegendLabel, edgeLegendRank=edgeLegendRank, 
                   edgeLegendGroup=edgeLegendGroup,
                   showFaces=showFaces, faceOpacity=faceOpacity, faceColor=faceColor,
                   faceLabelKey=faceLabelKey, faceGroupKey=faceGroupKey, faceGroups=faceGroups, 
                   faceMinGroup=faceMinGroup, faceMaxGroup=faceMaxGroup, 
                   showFaceLegend=showFaceLegend, faceLegendLabel=faceLegendLabel, faceLegendRank=faceLegendRank,
                   faceLegendGroup=faceLegendGroup, 
                   intensityKey=intensityKey, intensities=intensities, colorScale=colorScale, mantissa=mantissa, tolerance=tolerance)
    figure = Plotly.FigureByData(data=data, width=width, height=height,
                                 xAxis=xAxis, yAxis=yAxis, zAxis=zAxis, axisSize=axisSize,
                                 backgroundColor=backgroundColor,
                                 marginLeft=marginLeft, marginRight=marginRight,
                                 marginTop=marginTop, marginBottom=marginBottom,
                                 tolerance=tolerance)
    if showScale:
        figure = Plotly.AddColorBar(figure, values=cbValues, nTicks=cbTicks, xPosition=cbX, width=cbWidth, outlineWidth=cbOutlineWidth, title=cbTitle, subTitle=cbSubTitle, units=cbUnits, colorScale=colorScale, mantissa=mantissa)
    Plotly.Show(figure=figure, renderer=renderer, camera=camera, center=center, up=up, projection=projection)
def Slice(topologyA, topologyB, tranDict=False, tolerance=0.0001)

See Topology.Boolean().

Expand source code
@staticmethod
def Slice(topologyA, topologyB, tranDict=False, tolerance=0.0001):
    """
    See Topology.Boolean().

    """
    return Topology.Boolean(topologyA=topologyA, topologyB=topologyB, operation="slice", tranDict=tranDict, tolerance=tolerance)
def Snapshots(topology, key='timestamp', start=None, end=None, silent=False)
Expand source code
@staticmethod
def Snapshots(topology, key="timestamp", start=None, end=None, silent=False):
    from topologicpy.Dictionary import Dictionary
    from datetime import datetime
    def is_valid_timestamp(timestamp):
        if isinstance(timestamp, datetime):
            return True
        elif isinstance(timestamp, str):
            try:
                # Split the timestamp string into date and time parts
                date_part, time_part = timestamp.split(' ')
                # Parse the date part
                date_obj = datetime.strptime(date_part, '%Y-%m-%d')
                # Split the time part into hours, minutes, and seconds
                hours, minutes, seconds = map(float, time_part.split(':'))
                # Check if seconds are within valid range
                if seconds < 0 or seconds >= 60:
                    return False
                # Create a datetime object with the parsed date and time parts
                return datetime(date_obj.year, date_obj.month, date_obj.day, int(hours), int(minutes), int(seconds))
            except ValueError:
                return False
        else:
            return False
    
    if not Topology.IsInstance(topology, "Topology"):
        if not silent:
            print("Topology.Snapshots - Error: The input topology parameter is not a valid topology. Returning None.")
            return None
    if start == None:
        start = datetime.datetime(year=1900, month=1, day=1) # Set the start date to a date in the distant past
    if end == None:
        end = datetime.now() # Set the end date to the present.
    if not is_valid_timestamp(start):
        if not silent:
            print("Topology.Snapshots - Error: The input start parameter is not a valid timestamp. Returning None.")
            return None
    if not is_valid_timestamp(end):
        if not silent:
            print("Topology.Snapshots - Error: The input end parameter is not a valid timestamp. Returning None.")
            return None
    contents = Topology.Contents(topology)    
    snapshots = []
    for content in contents:
        d = Topology.Dictionary(content)
        timestamp = Dictionary.ValueAtKey(d, key)
        timestamp = is_valid_timestamp(timestamp)
        if not timestamp == False:
            if start <= timestamp <= end:
                snapshots.append(content)
    return snapshots
def SortBySelectors(topologies, selectors, exclusive=False, tolerance=0.0001)

Sorts the input list of topologies according to the input list of selectors.

Parameters

topologies : list
The input list of topologies.
selectors : list
The input list of selectors (vertices).
exclusive : bool , optional
If set to True only one selector can be used to select on topology. The default is False.
tolerance : float , optional
The desired tolerance. The default is 0.0001.

Returns

dict
A dictionary containing the list of sorted and unsorted topologies. The keys are "sorted" and "unsorted".
Expand source code
@staticmethod
def SortBySelectors(topologies, selectors, exclusive=False, tolerance=0.0001):
    """
    Sorts the input list of topologies according to the input list of selectors.

    Parameters
    ----------
    topologies : list
        The input list of topologies.
    selectors : list
        The input list of selectors (vertices).
    exclusive : bool , optional
        If set to True only one selector can be used to select on topology. The default is False.
    tolerance : float , optional
        The desired tolerance. The default is 0.0001.

    Returns
    -------
    dict
        A dictionary containing the list of sorted and unsorted topologies. The keys are "sorted" and "unsorted".

    """

    from topologicpy.Vertex import Vertex
    usedTopologies = []
    sortedTopologies = []
    unsortedTopologies = []
    for i in range(len(topologies)):
        usedTopologies.append(0)
    
    for i in range(len(selectors)):
        found = False
        for j in range(len(topologies)):
            if usedTopologies[j] == 0:
                if Vertex.IsInternal( selectors[i], topologies[j], tolerance):
                    sortedTopologies.append(topologies[j])
                    if exclusive == True:
                        usedTopologies[j] = 1
                    found = True
                    break
        if found == False:
            sortedTopologies.append(None)
    for i in range(len(usedTopologies)):
        if usedTopologies[i] == 0:
            unsortedTopologies.append(topologies[i])
    return {"sorted":sortedTopologies, "unsorted":unsortedTopologies}
def Spin(topology, origin=None, triangulate: bool = True, direction: list = [0, 0, 1], angle: float = 360, sides: int = 16, tolerance: float = 0.0001, silent: bool = False)

Spins the input topology around an axis to create a new topology.See https://en.wikipedia.org/wiki/Solid_of_revolution.

Parameters

topology : topologic_core.Topology
The input topology.
origin : topologic_core.Vertex
The origin (center) of the spin.
triangulate : bool , optional
If set to True, the result will be triangulated. The default is True.
direction : list , optional
The vector representing the direction of the spin axis. The default is [0, 0, 1].
angle : float , optional
The angle in degrees for the spin. The default is 360.
sides : int , optional
The desired number of sides. The default is 16.
tolerance : float , optional
The desired tolerance. The default is 0.0001.

Returns

topologic_core.Topology
The spun topology.
Expand source code
@staticmethod
def Spin(topology, origin=None, triangulate: bool = True, direction: list = [0, 0, 1], angle: float = 360, sides: int = 16,
                 tolerance: float = 0.0001, silent: bool = False):
    """
    Spins the input topology around an axis to create a new topology.See https://en.wikipedia.org/wiki/Solid_of_revolution.

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.
    origin : topologic_core.Vertex
        The origin (center) of the spin.
    triangulate : bool , optional
        If set to True, the result will be triangulated. The default is True.
    direction : list , optional
        The vector representing the direction of the spin axis. The default is [0, 0, 1].
    angle : float , optional
        The angle in degrees for the spin. The default is 360.
    sides : int , optional
        The desired number of sides. The default is 16.
    tolerance : float , optional
        The desired tolerance. The default is 0.0001.

    Returns
    -------
    topologic_core.Topology
        The spun topology.

    """
    from topologicpy.Vertex import Vertex
    from topologicpy.Wire import Wire
    from topologicpy.Shell import Shell
    from topologicpy.Cell import Cell
    from topologicpy.CellComplex import CellComplex
    from topologicpy.Cluster import Cluster

    if not origin:
        origin = Vertex.ByCoordinates(0, 0, 0)
    if not Topology.IsInstance(topology, "Topology"):
        if not silent:
            print("Topology.Spin - Error: the input topology parameter is not a valid topology. Returning None.")
            return None
    if not Topology.IsInstance(origin, "Vertex"):
        if not silent:
            print("Topology.Spin - Error: the input origin parameter is not a valid vertex. Returning None.")
            return None
    topologies = []
    unit_degree = angle / float(sides)
    for i in range(sides+1):
        tempTopology = Topology.Rotate(topology, origin=origin, axis=direction, angle=unit_degree*i)
        if tempTopology:
            topologies.append(tempTopology)
    returnTopology = None
    if Topology.Type(topology) == Topology.TypeID("Vertex"):
        returnTopology = Wire.ByVertices(topologies, False)
    elif Topology.Type(topology) == Topology.TypeID("Edge"):
        try:
            returnTopology = Shell.ByWires(topologies,triangulate=triangulate, tolerance=tolerance, silent=True)
        except:
            try:
                returnTopology = Cluster.ByTopologies(topologies)
            except:
                returnTopology = None
    elif Topology.Type(topology) == Topology.TypeID("Wire"):
        if topology.IsClosed():
            #returnTopology = Cell.ByWires(topologies, triangulate=triangulate, tolerance=tolerance, silent=True)
            try:
                returnTopology = Cell.ByWires(topologies, triangulate=triangulate, tolerance=tolerance, silent=True)
                returnTopology = Cell.ExternalBoundary(returnTopology)
                returnTopology = Cell.ByShell(returnTopology)
            except:
                try:
                    returnTopology = CellComplex.ByWires(topologies, tolerance)
                    try:
                        returnTopology = returnTopology.ExternalBoundary()
                    except:
                        pass
                except:
                    try:
                        returnTopology = Shell.ByWires(topologies, triangulate=triangulate, tolerance=tolerance, silent=True)
                    except:
                        try:
                            returnTopology = Cluster.ByTopologies(topologies)
                        except:
                            returnTopology = None
        else:
            Shell.ByWires(topologies, triangulate=triangulate, tolerance=tolerance, silent=True)
            try:
                returnTopology = Shell.ByWires(topologies, triangulate=triangulate, tolerance=tolerance, silent=True)
            except:
                try:
                    returnTopology = Cluster.ByTopologies(topologies)
                except:
                    returnTopology = None
    elif Topology.IsInstance(topology, "Face"):
        external_wires = []
        for t in topologies:
            external_wires.append(topologic.Face.ExternalBoundary(t))
        try:
            returnTopology = CellComplex.ByWires(external_wires, tolerance)
        except:
            try:
                returnTopology = Shell.ByWires(external_wires, triangulate=triangulate, tolerance=tolerance, silent=True)
            except:
                try:
                    returnTopology = Cluster.ByTopologies(topologies)
                except:
                    returnTopology = None
    else:
        returnTopology = Topology.SelfMerge(Cluster.ByTopologies(topologies), tolerance=tolerance)
    if not returnTopology:
        return Cluster.ByTopologies(topologies)
    if Topology.Type(returnTopology) == Topology.TypeID("Shell"):
        try:
            new_t = Cell.ByShell(returnTopology)
            if new_t:
                returnTopology = new_t
        except:
            pass
    return returnTopology
def SubTopologies(topology, subTopologyType='vertex')

Returns the subtopologies of the input topology as specified by the subTopologyType input string.

Parameters

topology : topologic_core.Topology
The input topology.
subTopologyType : str , optional
The requested subtopology type. This can be one of "vertex", "edge", "wire", "face", "shell", "cell", "cellcomplex", "cluster". It is case insensitive. The default is "vertex".

Returns

list
The list of subtopologies.
Expand source code
@staticmethod
def SubTopologies(topology, subTopologyType="vertex"):
    """
    Returns the subtopologies of the input topology as specified by the subTopologyType input string.

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.
    subTopologyType : str , optional
        The requested subtopology type. This can be one of "vertex", "edge", "wire", "face", "shell", "cell", "cellcomplex", "cluster". It is case insensitive. The default is "vertex".

    Returns
    -------
    list
        The list of subtopologies.

    """
    if not Topology.IsInstance(topology, "Topology"):
        print("Topology.SubTopologies - Error: the input topology parameter is not a valid topology. Returning None.")
        return None
    if Topology.TypeAsString(topology).lower() == subTopologyType.lower():
        return [topology]
    subTopologies = []
    if subTopologyType.lower() == "vertex":
        _ = topology.Vertices(None, subTopologies)
    elif subTopologyType.lower() == "edge":
        _ = topology.Edges(None, subTopologies)
    elif subTopologyType.lower() == "wire":
        _ = topology.Wires(None, subTopologies)
    elif subTopologyType.lower() == "face":
        _ = topology.Faces(None, subTopologies)
    elif subTopologyType.lower() == "shell":
        _ = topology.Shells(None, subTopologies)
    elif subTopologyType.lower() == "cell":
        _ = topology.Cells(None, subTopologies)
    elif subTopologyType.lower() == "cellcomplex":
        _ = topology.CellComplexes(None, subTopologies)
    elif subTopologyType.lower() == "cluster":
        _ = topology.Clusters(None, subTopologies)
    elif subTopologyType.lower() == "aperture":
        _ = topology.Apertures(subTopologies)
    if not subTopologies:
        return [] # Make sure to return an empty list instead of None
    return subTopologies
def SuperTopologies(topology, hostTopology, topologyType: str = None) ‑> list

Returns the supertopologies connected to the input topology.

Parameters

topology : topologic_core.Topology
The input topology.
hostTopology : topologic_core.Topology
The host to topology in which to search for ther supertopologies.
topologyType : str , optional
The topology type to search for. This can be any of "edge", "wire", "face", "shell", "cell", "cellcomplex", "cluster". It is case insensitive. If set to None, the immediate supertopology type is searched for. The default is None.

Returns

list
The list of supertopologies connected to the input topology.
Expand source code
@staticmethod
def SuperTopologies(topology, hostTopology, topologyType: str = None) -> list:
    """
    Returns the supertopologies connected to the input topology.

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.
    hostTopology : topologic_core.Topology
        The host to topology in which to search for ther supertopologies.
    topologyType : str , optional
        The topology type to search for. This can be any of "edge", "wire", "face", "shell", "cell", "cellcomplex", "cluster". It is case insensitive. If set to None, the immediate supertopology type is searched for. The default is None.

    Returns
    -------
    list
        The list of supertopologies connected to the input topology.

    """
    
    if not Topology.IsInstance(topology, "Topology"):
        print("Topology.SuperTopologies - Error: the input topology parameter is not a valid topology. Returning None.")
        return None
    if not Topology.IsInstance(hostTopology, "Topology"):
        print("Topology.SuperTopologies - Error: the input hostTopology parameter is not a valid topology. Returning None.")
        return None

    superTopologies = []

    if topologyType == None:
        typeID = 2*Topology.TypeID(topology)
    else:
        typeID = Topology.TypeID(topologyType)
    if Topology.Type(topology) >= typeID:
        print("Topology.SuperTopologies - Error: The input topologyType parameter is not a valid type for a super topology of the input topology. Returning None.")
        return None #The user has asked for a topology type lower than the input topology
    elif typeID == Topology.TypeID("Edge"):
        topology.Edges(hostTopology, superTopologies)
    elif typeID == Topology.TypeID("Wire"):
        topology.Wires(hostTopology, superTopologies)
    elif typeID == Topology.TypeID("Face"):
        topology.Faces(hostTopology, superTopologies)
    elif typeID == Topology.TypeID("Shell"):
        topology.Shells(hostTopology, superTopologies)
    elif typeID == Topology.TypeID("Cell"):
        topology.Cells(hostTopology, superTopologies)
    elif typeID == Topology.TypeID("CellComplex"):
        topology.CellComplexes(hostTopology, superTopologies)
    elif typeID == Topology.TypeID("Cluster"):
        topology.Cluster(hostTopology, superTopologies)
    else:
        print("Topology.SuperTopologies - Error: The input topologyType parameter is not a valid type for a super topology of the input topology. Returning None.")
        return None
    if not superTopologies:
        return [] # Make sure you return an empty list instead of None
    return superTopologies
def SymDif(topologyA, topologyB, tranDict=False, tolerance=0.0001)

See Topology.Boolean().

Expand source code
@staticmethod
def SymDif(topologyA, topologyB, tranDict=False, tolerance=0.0001):
    """
    See Topology.Boolean().

    """
    return Topology.Boolean(topologyA=topologyA, topologyB=topologyB, operation="symdif", tranDict=tranDict, tolerance=tolerance)
def SymmetricDifference(topologyA, topologyB, tranDict=False, tolerance=0.0001)

See Topology.Boolean().

Expand source code
@staticmethod
def SymmetricDifference(topologyA, topologyB, tranDict=False, tolerance=0.0001):
    """
    See Topology.Boolean().

    """
    return Topology.Boolean(topologyA=topologyA, topologyB=topologyB, operation="symdif", tranDict=tranDict, tolerance=tolerance)
def Taper(topology, origin=None, ratioRange=[0, 1], triangulate=False, tolerance=0.0001)

Tapers the input topology. This method tapers the input geometry along its Z-axis based on the ratio range input.

Parameters

topology : topologic_core.Topology
The input topology.
origin : topologic_core.Vertex , optional
The desired origin for tapering. If not specified, the centroid of the input topology is used. The tapering will use the X, Y coordinates of the specified origin, but will use the Z of the point being tapered. The default is None.
ratioRange : list , optional
The desired ratio range. This will specify a linear range from bottom to top for tapering the vertices. 0 means no tapering, and 1 means maximum (inward) tapering. Negative numbers mean that tapering will be outwards.
triangulate : bool , optional
If set to true, the input topology is triangulated before tapering. Otherwise, it will not be traingulated. The default is False.
tolerance : float , optional
The desired tolerance. Vertices will not be moved if the calculated distance is at or less than this tolerance.

Returns

topologic_core.Topology
The tapered topology.
Expand source code
@staticmethod
def Taper(topology, origin=None, ratioRange=[0, 1], triangulate=False, tolerance=0.0001):
    """
    Tapers the input topology. This method tapers the input geometry along its Z-axis based on the ratio range input.

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.
    origin : topologic_core.Vertex , optional
        The desired origin for tapering. If not specified, the centroid of the input topology is used. The tapering will use the X, Y coordinates of the specified origin, but will use the Z of the point being tapered. The default is None.
    ratioRange : list , optional
        The desired ratio range. This will specify a linear range from bottom to top for tapering the vertices. 0 means no tapering, and 1 means maximum (inward) tapering. Negative numbers mean that tapering will be outwards.
    triangulate : bool , optional
        If set to true, the input topology is triangulated before tapering. Otherwise, it will not be traingulated. The default is False.
    tolerance : float , optional
        The desired tolerance. Vertices will not be moved if the calculated distance is at or less than this tolerance.

    Returns
    -------
    topologic_core.Topology
        The tapered topology.

    """
    from topologicpy.Vertex import Vertex
    ratioRange = [min(1,ratioRange[0]), min(1,ratioRange[1])]
    if ratioRange == [0, 0]:
        return topology
    if ratioRange == [1, 1]:
        print("Topology.Taper - Error: Degenerate result. Returning original topology.")
        return topology
    if triangulate == True:
        topology = Topology.Triangulate(topology)
    if origin == None:
        origin = Topology.Centroid(topology)
    vertices = Topology.Vertices(topology)
    zList = [Vertex.Z(v) for v in vertices]
    minZ = min(zList)
    maxZ = max(zList)
    new_vertices = []
    for v in vertices:
        ht = (Vertex.Z(v)-minZ)/(maxZ - minZ)
        rt = ratioRange[0] + ht*(ratioRange[1] - ratioRange[0])
        new_origin = Vertex.ByCoordinates(Vertex.X(origin), Vertex.Y(origin), Vertex.Z(v))
        new_dist = Vertex.Distance(new_origin, v)*rt
        c_a = Vertex.Coordinates(new_origin)
        c_b = Vertex.Coordinates(v)
        new_dir = [(c_a[0]-c_b[0]), (c_a[1]-c_b[1]), 0]
        if abs(new_dist) > tolerance:
            new_v = Topology.TranslateByDirectionDistance(v, direction=new_dir, distance=new_dist)
        else:
            new_v = v
        new_vertices.append(new_v)
    return_topology = Topology.ReplaceVertices(topology, vertices, new_vertices)
    return return_topology
def TransferDictionaries(sources, sinks, tolerance=0.0001, numWorkers=None)

Transfers the dictionaries from the list of sources to the list of sinks.

Parameters

sources : list
The list of topologies from which to transfer the dictionaries.
sinks : list
The list of topologies to which to transfer the dictionaries.
tolerance : float , optional
The desired tolerance. The default is 0.0001.
numWorkers : int, optional
Number of workers run in parallel to process.

Returns

dict
Returns a dictionary with the lists of sources and sinks. The keys are "sinks" and "sources".
Expand source code
@staticmethod
def TransferDictionaries(sources, sinks, tolerance=0.0001, numWorkers=None):
    """
    Transfers the dictionaries from the list of sources to the list of sinks.

    Parameters
    ----------
    sources : list
        The list of topologies from which to transfer the dictionaries.
    sinks : list
        The list of topologies to which to transfer the dictionaries.
    tolerance : float , optional
        The desired tolerance. The default is 0.0001.
    numWorkers : int, optional
        Number of workers run in parallel to process.

    Returns
    -------
    dict
        Returns a dictionary with the lists of sources and sinks. The keys are "sinks" and "sources".

    """
    from topologicpy.Dictionary import Dictionary
    if not isinstance(sources, list):
        print("Topology.TransferDictionaries - Error: The input sources parameter is not a valid list. Returning None.")
        return None
    if not isinstance(sinks, list):
        print("Topology.TransferDictionaries - Error: The input sinks parameter is not a valid list. Returning None.")
        return None
    if numWorkers == None:
        import multiprocessing
        numWorkers = multiprocessing.cpu_count()*2
    sources = [x for x in sources if Topology.IsInstance(x, "Topology")]
    sinks = [x for x in sinks if Topology.IsInstance(x, "Topology")]
    so_dicts = [Dictionary.PythonDictionary(Topology.Dictionary(s)) for s in sources]
    if len(sources) < 1:
        print("Topology.TransferDictionaries - Error: The input sources does not contain any valid topologies. Returning None.")
        return None
    if len(sinks) < 1:
        print("Topology.TransferDictionaries - Error: The input sinks does not contain any valid topologies. Returning None.")
        return None

    queue = Queue()
    sources_str = [Topology.BREPString(s) for s in sources]
    sink_items = [SinkItem(id(s), Topology.BREPString(s)) for s in sinks]
    mergingProcess = MergingProcess(queue, sources_str, sink_items, so_dicts)
    mergingProcess.start()

    workerProcessPool = WorkerProcessPool(numWorkers, queue, sources_str, sink_items, so_dicts, tolerance)
    workerProcessPool.startProcesses()
    workerProcessPool.join()

    queue.put_nowait(None)
    sinkMap = queue.get()
    mergingProcess.join()

    for i, sink in enumerate(sink_items):
        mapItem = sinkMap[sink.ID]
        newDict = Dictionary.ByKeysValues(mapItem.sinkKeys, mapItem.sinkValues)
        _ = sinks[i].SetDictionary(newDict)
    return {"sources": sources, "sinks": sinks}
def TransferDictionariesBySelectors(topology, selectors, tranVertices=False, tranEdges=False, tranFaces=False, tranCells=False, tolerance=0.0001, numWorkers=None)

Transfers the dictionaries of the list of selectors to the subtopologies of the input topology based on the input parameters.

Parameters

topology : topologic_core.Topology
The input topology.
selectors : list
The list of input selectors from which to transfer the dictionaries.
tranVertices : bool , optional
If True transfer dictionaries to the vertices of the input topology.
tranEdges : bool , optional
If True transfer dictionaries to the edges of the input topology.
tranFaces : bool , optional
If True transfer dictionaries to the faces of the input topology.
tranCells : bool , optional
If True transfer dictionaries to the cells of the input topology.
tolerance : float , optional
The desired tolerance. The default is 0.0001.
numWorkers : int , optional
Number of workers run in parallel to process. The default is None which causes the algorithm to use twice the number of cpu cores in the host computer.

Returns

Topology
The input topology with the dictionaries transferred to its subtopologies.
Expand source code
@staticmethod
def TransferDictionariesBySelectors(topology, selectors, tranVertices=False, tranEdges=False, tranFaces=False, tranCells=False, tolerance=0.0001, numWorkers=None):
    """
    Transfers the dictionaries of the list of selectors to the subtopologies of the input topology based on the input parameters.

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.
    selectors : list
        The list of input selectors from which to transfer the dictionaries.
    tranVertices : bool , optional
        If True transfer dictionaries to the vertices of the input topology.
    tranEdges : bool , optional
        If True transfer dictionaries to the edges of the input topology.
    tranFaces : bool , optional
        If True transfer dictionaries to the faces of the input topology.
    tranCells : bool , optional
        If True transfer dictionaries to the cells of the input topology.
    tolerance : float , optional
        The desired tolerance. The default is 0.0001.
    numWorkers : int , optional
        Number of workers run in parallel to process. The default is None which causes the algorithm to use twice the number of cpu cores in the host computer.

    Returns
    -------
    Topology
        The input topology with the dictionaries transferred to its subtopologies.

    """

    if not Topology.IsInstance(topology, "Topology"):
        print("Topology.TransferDictionariesBySelectors - Error: The input topology parameter is not a valid topology. Returning None.")
        return None
    if not isinstance(selectors, list):
        print("Topology.TransferDictionariesBySelectors - Error: The input selectors parameter is not a valid list. Returning None.")
        return None
    if numWorkers == None:
        import multiprocessing
        numWorkers = multiprocessing.cpu_count()*2
    selectors_tmp = [x for x in selectors if Topology.IsInstance(x, "Vertex")]
    if len(selectors_tmp) < 1:
        print("Topology.TransferDictionariesBySelectors - Error: The input selectors do not contain any valid topologies. Returning None.")
        return None
    sinkEdges = []
    sinkFaces = []
    sinkCells = []
    hidimSink = Topology.HighestType(topology)
    if tranVertices == True:
        sinkVertices = []
        if Topology.Type(topology) == Topology.TypeID("Vertex"):
            sinkVertices.append(topology)
        elif hidimSink >= Topology.TypeID("Vertex"):
            topology.Vertices(None, sinkVertices)
        _ = Topology.TransferDictionaries(selectors, sinkVertices, tolerance=tolerance, numWorkers=numWorkers)
    if tranEdges == True:
        sinkEdges = []
        if Topology.Type(topology) == Topology.TypeID("Edge"):
            sinkEdges.append(topology)
        elif hidimSink >= Topology.TypeID("Edge"):
            topology.Edges(None, sinkEdges)
            _ = Topology.TransferDictionaries(selectors, sinkEdges, tolerance=tolerance, numWorkers=numWorkers)
    if tranFaces == True:
        sinkFaces = []
        if Topology.Type(topology) == Topology.TypeID("Face"):
            sinkFaces.append(topology)
        elif hidimSink >= Topology.TypeID("Face"):
            topology.Faces(None, sinkFaces)
        _ = Topology.TransferDictionaries(selectors, sinkFaces, tolerance=tolerance, numWorkers=numWorkers)
    if tranCells == True:
        sinkCells = []
        if Topology.Type(topology) == Topology.TypeID("Cell"):
            sinkCells.append(topology)
        elif hidimSink >= Topology.TypeID("Cell"):
            topology.Cells(None, sinkCells)
        _ = Topology.TransferDictionaries(selectors, sinkCells, tolerance=tolerance, numWorkers=numWorkers)
    return topology
def Transform(topology, matrix)

Transforms the input topology by the input 4X4 transformation matrix.

Parameters

topology : topologic_core.Topology
The input topology.
matrix : list
The input 4x4 transformation matrix.

Returns

topologic_core.Topology
The transformed topology.
Expand source code
@staticmethod
def Transform(topology, matrix):
    """
    Transforms the input topology by the input 4X4 transformation matrix.

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.
    matrix : list
        The input 4x4 transformation matrix.

    Returns
    -------
    topologic_core.Topology
        The transformed topology.

    """
    kTranslationX = 0.0
    kTranslationY = 0.0
    kTranslationZ = 0.0
    kRotation11 = 1.0
    kRotation12 = 0.0
    kRotation13 = 0.0
    kRotation21 = 0.0
    kRotation22 = 1.0
    kRotation23 = 0.0
    kRotation31 = 0.0
    kRotation32 = 0.0
    kRotation33 = 1.0

    
    kRotation11 = matrix[0][0]
    kRotation12 = matrix[0][1]
    kRotation13 = matrix[0][2]
    kRotation21 = matrix[1][0]
    kRotation22 = matrix[1][1]
    kRotation23 = matrix[1][2]
    kRotation31 = matrix[2][0]
    kRotation32 = matrix[2][1]
    kRotation33 = matrix[2][2]
    kTranslationX = matrix[3][0]
    kTranslationY = matrix[3][1]
    kTranslationZ = matrix[3][2]

    return topologic.TopologyUtility.Transform(topology, kTranslationX, kTranslationY, kTranslationZ, kRotation11, kRotation12, kRotation13, kRotation21, kRotation22, kRotation23, kRotation31, kRotation32, kRotation33)
def Translate(topology, x=0, y=0, z=0)

Translates (moves) the input topology.

Parameters

topology : topologic_core.topology
The input topology.
x : float , optional
The x translation value. The default is 0.
y : float , optional
The y translation value. The default is 0.
z : float , optional
The z translation value. The default is 0.

Returns

topologic_core.Topology
The translated topology.
Expand source code
@staticmethod
def Translate(topology, x=0, y=0, z=0):
    """
    Translates (moves) the input topology.

    Parameters
    ----------
    topology : topologic_core.topology
        The input topology.
    x : float , optional
        The x translation value. The default is 0.
    y : float , optional
        The y translation value. The default is 0.
    z : float , optional
        The z translation value. The default is 0.

    Returns
    -------
    topologic_core.Topology
        The translated topology.

    """
    if not Topology.IsInstance(topology, "Topology"):
        print("Topology.Translate - Error: The input topology parameter is not a valid topology. Returning None.")
        return None
    try:
        return topologic.TopologyUtility.Translate(topology, x, y, z)
    except:
        return topology
def TranslateByDirectionDistance(topology, direction: list = [0, 0, 0], distance: float = 0)

Translates (moves) the input topology along the input direction by the specified distance.

Parameters

topology : topologic_core.topology
The input topology.
direction : list , optional
The direction vector in which the topology should be moved. The default is [0, 0, 0]
distance : float , optional
The distance by which the toplogy should be moved. The default is 0.

Returns

topologic_core.Topology
The translated topology.
Expand source code
@staticmethod
def TranslateByDirectionDistance(topology, direction: list = [0, 0, 0], distance: float = 0):
    """
    Translates (moves) the input topology along the input direction by the specified distance.

    Parameters
    ----------
    topology : topologic_core.topology
        The input topology.
    direction : list , optional
        The direction vector in which the topology should be moved. The default is [0, 0, 0]
    distance : float , optional
        The distance by which the toplogy should be moved. The default is 0.

    Returns
    -------
    topologic_core.Topology
        The translated topology.

    """
    from topologicpy.Vector import Vector
    if not Topology.IsInstance(topology, "Topology"):
        print("Topology.TranslateByDirectionDistance - Error: The input topology parameter is not a valid topology. Returning None.")
        return None
    v = Vector.SetMagnitude(direction, distance)
    return Topology.Translate(topology, v[0], v[1], v[2])
def Triangulate(topology, transferDictionaries: bool = False, mode: int = 0, meshSize: float = None, tolerance: float = 0.0001)

Triangulates the input topology.

Parameters

topology : topologic_core.Topology
The input topologgy.
transferDictionaries : bool , optional
If set to True, the dictionaries of the faces in the input topology will be transferred to the created triangular faces. The default is False.
mode : int , optional
The desired mode of meshing algorithm. Several options are available: 0: Classic 1: MeshAdapt 3: Initial Mesh Only 5: Delaunay 6: Frontal-Delaunay 7: BAMG 8: Fontal-Delaunay for Quads 9: Packing of Parallelograms All options other than 0 (Classic) use the gmsh library. See https://gmsh.info/doc/texinfo/gmsh.html#Mesh-options WARNING: The options that use gmsh can be very time consuming and can create very heavy geometry.
meshSize : float , optional
The desired size of the mesh when using the "mesh" option. If set to None, it will be calculated automatically and set to 10% of the overall size of the face.
tolerance : float , optional
The desired tolerance. The default is 0.0001.

Returns

topologic_core.Topology
The triangulated topology.
Expand source code
@staticmethod
def Triangulate(topology, transferDictionaries: bool = False, mode: int = 0, meshSize: float = None, tolerance: float = 0.0001):
    """
    Triangulates the input topology.

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topologgy.
    transferDictionaries : bool , optional
        If set to True, the dictionaries of the faces in the input topology will be transferred to the created triangular faces. The default is False.
    mode : int , optional
        The desired mode of meshing algorithm. Several options are available:
        0: Classic
        1: MeshAdapt
        3: Initial Mesh Only
        5: Delaunay
        6: Frontal-Delaunay
        7: BAMG
        8: Fontal-Delaunay for Quads
        9: Packing of Parallelograms
        All options other than 0 (Classic) use the gmsh library. See https://gmsh.info/doc/texinfo/gmsh.html#Mesh-options
        WARNING: The options that use gmsh can be very time consuming and can create very heavy geometry.
    meshSize : float , optional
        The desired size of the mesh when using the "mesh" option. If set to None, it will be
        calculated automatically and set to 10% of the overall size of the face.
    tolerance : float , optional
        The desired tolerance. The default is 0.0001.

    Returns
    -------
    topologic_core.Topology
        The triangulated topology.

    """
    from topologicpy.Face import Face
    from topologicpy.Shell import Shell
    from topologicpy.Cell import Cell
    from topologicpy.CellComplex import CellComplex
    from topologicpy.Cluster import Cluster

    if not Topology.IsInstance(topology, "Topology"):
        print("Topology.Triangulate - Error: The input parameter is not a valid topology. Returning None.")
        return None
    t = Topology.Type(topology)
    if (t == Topology.TypeID("Vertex")) or (t == Topology.TypeID("Edge")) or (t == Topology.TypeID("Wire")):
        return topology
    elif t == Topology.TypeID("Cluster"):
        temp_topologies = []
        cellComplexes = Topology.SubTopologies(topology, subTopologyType="cellcomplex") or []
        for cc in cellComplexes:
            temp_topologies.append(Topology.Triangulate(cc, transferDictionaries=transferDictionaries, mode=mode, meshSize=meshSize, tolerance=tolerance))
        cells = Cluster.FreeCells(topology, tolerance=tolerance) or []
        for c in cells:
            temp_topologies.append(Topology.Triangulate(c, transferDictionaries=transferDictionaries, mode=mode, meshSize=meshSize, tolerance=tolerance))
        shells = Cluster.FreeShells(topology, tolerance=tolerance) or []
        for s in shells:
            temp_topologies.append(Topology.Triangulate(s, transferDictionaries=transferDictionaries, mode=mode, meshSize=meshSize, tolerance=tolerance))
        faces = Cluster.FreeFaces(topology, tolerance=tolerance) or []
        for f in faces:
            temp_topologies.append(Topology.Triangulate(f, transferDictionaries=transferDictionaries, mode=mode, meshSize=meshSize, tolerance=tolerance))
        if len(temp_topologies) > 0:
            return Cluster.ByTopologies(temp_topologies)
        else:
            return topology
    topologyFaces = []
    _ = topology.Faces(None, topologyFaces)
    faceTriangles = []
    selectors = []
    for aFace in topologyFaces:
        if len(Topology.Vertices(aFace)) > 3:
            triFaces = Face.Triangulate(aFace, mode=mode, meshSize=meshSize, tolerance=tolerance)
        else:
            triFaces = [aFace]
        for triFace in triFaces:
            if transferDictionaries:
                selectors.append(Topology.SetDictionary(Face.Centroid(triFace), Topology.Dictionary(aFace)))
            faceTriangles.append(triFace)
    if t == Topology.TypeID("Face") or t == Topology.TypeID("Shell"): # Face or Shell
        return_topology = Shell.ByFaces(faceTriangles, tolerance=tolerance)
        if transferDictionaries and not return_topology == None:
            return_topology = Topology.TransferDictionariesBySelectors(return_topology, selectors, tranFaces=True, tolerance=tolerance)
    elif t == Topology.TypeID("Cell"): # Cell
        return_topology = Cell.ByFaces(faceTriangles, tolerance=tolerance)
        if transferDictionaries and not return_topology == None:
            return_topology = Topology.TransferDictionariesBySelectors(return_topology, selectors, tranFaces=True, tolerance=tolerance)
    elif t == Topology.TypeID("CellComplex"): #CellComplex
        return_topology = CellComplex.ByFaces(faceTriangles, tolerance=tolerance)
        if transferDictionaries and not return_topology == None:
            return_topology = Topology.TransferDictionariesBySelectors(return_topology, selectors, tranFaces=True, tolerance=tolerance)
    
    if return_topology == None:
        return_topology = Topology.SelfMerge(Cluster.ByTopologies(faceTriangles), tolerance=tolerance)
        if transferDictionaries == True:
            return_topology = Topology.TransferDictionariesBySelectors(return_topology, selectors, tranFaces=True, tolerance=tolerance)
    
    return return_topology
def Twist(topology, origin=None, angleRange=[45, 90], triangulate=False)

Twists the input topology. This method twists the input geometry along its Z-axis based on the degree range input.

Parameters

topology : topologic_core.Topology
The input topology.
origin : topologic_core.Vertex , optional
The desired origin for tapering. If not specified, the centroid of the input topology is used. The tapering will use the X, Y coordinates of the specified origin, but will use the Z of the point being tapered. The default is None.
angleRange : list , optional
The desired angle range in degrees. This will specify a linear range from bottom to top for twisting the vertices. positive numbers mean a clockwise rotation.
triangulate : bool , optional
If set to true, the input topology is triangulated before tapering. Otherwise, it will not be traingulated. The default is False.

Returns

topologic_core.Topology
The twisted topology.
Expand source code
@staticmethod
def Twist(topology, origin=None, angleRange=[45, 90], triangulate=False):
    """
    Twists the input topology. This method twists the input geometry along its Z-axis based on the degree range input.

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.
    origin : topologic_core.Vertex , optional
        The desired origin for tapering. If not specified, the centroid of the input topology is used. The tapering will use the X, Y coordinates of the specified origin, but will use the Z of the point being tapered. The default is None.
    angleRange : list , optional
        The desired angle range in degrees. This will specify a linear range from bottom to top for twisting the vertices. positive numbers mean a clockwise rotation.
    triangulate : bool , optional
        If set to true, the input topology is triangulated before tapering. Otherwise, it will not be traingulated. The default is False.
    
    Returns
    -------
    topologic_core.Topology
        The twisted topology.

    """
    from topologicpy.Vertex import Vertex

    if angleRange == [0, 0]:
        return topology
    if triangulate == True:
        topology = Topology.Triangulate(topology)
    if origin == None:
        origin = Topology.Centroid(topology)
        
    vertices = Topology.Vertices(topology)
    zList = [Vertex.Z(v) for v in vertices]
    minZ = min(zList)
    maxZ = max(zList)
    h = maxZ - minZ
    new_vertices = []
    for v in vertices:
        ht = (Vertex.Z(v)-minZ)/(maxZ - minZ)
        new_rot = angleRange[0] + ht*(angleRange[1] - angleRange[0])
        orig = Vertex.ByCoordinates(Vertex.X(origin), Vertex.Y(origin), Vertex.Z(v))
        new_vertices.append(Topology.Rotate(v, origin=orig, axis=[0, 0, 1], angle=new_rot))
    return_topology = Topology.ReplaceVertices(topology, vertices, new_vertices)
    return_topology = Topology.Fix(return_topology, topologyType=Topology.TypeAsString(topology))
    return return_topology
def Type(topology)

Returns the type of the input topology.

Parameters

topology : topologic_core.Topology
The input topology.

Returns

int
The type of the input topology.
Expand source code
@staticmethod
def Type(topology):
    """
    Returns the type of the input topology.

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.

    Returns
    -------
    int
        The type of the input topology.

    """
    #if not Topology.IsInstance(topology, "Topology"):
        #print("Topology.Type - Error: The input topology parameter is not a valid topology. Returning None.")
        #return None
    return topology.Type()
def TypeAsString(topology)

Returns the type of the input topology as a string.

Parameters

topology : topologic_core.Topology
The input topology.

Returns

str
The type of the topology as a string.
Expand source code
@staticmethod
def TypeAsString(topology):
    """
    Returns the type of the input topology as a string.

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.

    Returns
    -------
    str
        The type of the topology as a string.

    """
    if not Topology.IsInstance(topology, "Topology"):
        print("Topology.TypeAsString - Error: The input topology parameter is not a valid topology. Returning None.")
        return None
    return topology.GetTypeAsString()
def TypeID(name: str = None) ‑> int

Returns the type id of the input name string.

Parameters

name : str , optional

The input class name string. This could be one of: "vertex", "edge", "wire", "face", "shell", "cell", "cellcomplex", "cluster", "aperture", "context", "dictionary", "graph", "topology"

It is case insensitive. The default is None.

Returns

int
The type id of the input topologyType string.
Expand source code
@staticmethod
def TypeID(name : str = None) -> int:
    """
    Returns the type id of the input name string.

    Parameters
    ----------
    name : str , optional
        The input class name string. This could be one of:
            "vertex",
            "edge",
            "wire",
            "face",
            "shell",
            "cell",
            "cellcomplex",
            "cluster",
            "aperture",
            "context",
            "dictionary",
            "graph",
            "topology"
        
        It is case insensitive. The default is None.

    Returns
    -------
    int
        The type id of the input topologyType string.

    """

    if not isinstance(name, str):
        print("Topology.TypeID - Error: The input topologyType parameter is not a valid string. Returning None.")
        return None
    name = name.lower()
    if not name in ["vertex", "edge", "wire",
                            "face", "shell", "cell",
                            "cellcomplex", "cluster", "aperture",
                            "context", "dictionary", "graph", "topology"]:
        print("Topology.TypeID - Error: The input name parameter is not a recognized string. Returning None.")
        return None
    typeID = None
    if name == "vertex":
        typeID = 1
    elif name == "edge":
        typeID = 2
    elif name == "wire":
        typeID = 4
    elif name == "face":
        typeID = 8
    elif name == "shell":
        typeID = 16
    elif name == "cell":
        typeID = 32
    elif name == "cellComplex":
        typeID = 64
    elif name == "cluster":
        typeID = 128
    elif name == "aperture":
        typeID = 256
    elif name == "context":
        typeID = 512
    elif name == "dictionary":
        typeID = 1024
    elif name == "graph":
        typeID = 2048
    elif name == "topology":
        typeID = 4096
    return typeID
def Unflatten(topology, origin=None, direction=[0, 0, 1])

Unflattens the input topology such that the world origin is translated to the input origin and the input topology is rotated such that the Up direction (see Vector.Up()) is aligned with the input vector.

Parameters

topology : topologic_core.Topology
The input topology.
origin : topologic_core.Vertex , optional
The input origin. If set to None, The object's centroid will be used to translate the world origin. The default is None.
vector : list , optional
The input direction vector. The input topology will be rotated such that this vector is pointed in the positive Z axis.

Returns

topologic_core.Topology
The flattened topology.
Expand source code
@staticmethod
def Unflatten(topology, origin=None, direction=[0, 0, 1]):
    """
    Unflattens the input topology such that the world origin is translated to the input origin and the input topology is rotated such that the Up direction (see Vector.Up()) is aligned with the input vector.

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.
    origin : topologic_core.Vertex , optional
        The input origin. If set to None, The object's centroid will be used to translate the world origin. The default is None.
    vector : list , optional
        The input direction vector. The input topology will be rotated such that this vector is pointed in the positive Z axis.

    Returns
    -------
    topologic_core.Topology
        The flattened topology.

    """
    from topologicpy.Vertex import Vertex
    from topologicpy.Vector import Vector

    if not Topology.IsInstance(topology, "Topology"):
        print("Topology.Unflatten - Error: the input topology parameter is not a valid topology. Returning None.")
        return None
    if origin == None:
        origin = Vertex.Origin()
    up = Vector.Up()
    tran_mat = Vector.TransformationMatrix(up, direction)
    unflat_topology = Topology.Transform(topology, tran_mat)
    unflat_topology = Topology.Translate(unflat_topology, Vertex.X(origin), Vertex.Y(origin), Vertex.Z(origin))
    return unflat_topology
def Union(topologyA, topologyB, tranDict=False, tolerance=0.0001)

See Topology.Boolean()

Expand source code
@staticmethod
def Union(topologyA, topologyB, tranDict=False, tolerance=0.0001):
    """
    See Topology.Boolean()

    """
    return Topology.Boolean(topologyA, topologyB, operation="union", tranDict=tranDict, tolerance=tolerance)
def Vertices(topology)

Returns the vertices of the input topology.

Parameters

topology : topologic_core.Topology
The input topology.

Returns

list
The list of vertices.
Expand source code
@staticmethod
def Vertices(topology):
    """
    Returns the vertices of the input topology.

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.

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

    """
    return Topology.SubTopologies(topology=topology, subTopologyType="vertex")
def Wires(topology)

Returns the wires of the input topology.

Parameters

topology : topologic_core.Topology
The input topology.

Returns

list
The list of wires.
Expand source code
@staticmethod
def Wires(topology):
    """
    Returns the wires of the input topology.

    Parameters
    ----------
    topology : topologic_core.Topology
        The input topology.

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

    """
    if Topology.IsInstance(topology, "Wire") or Topology.IsInstance(topology, "Edge") or Topology.IsInstance(topology, "Vertex"):
        return []
    return Topology.SubTopologies(topology=topology, subTopologyType="wire")
def XOR(topologyA, topologyB, tranDict=False, tolerance=0.0001)

See Topology.Boolean().

Expand source code
@staticmethod
def XOR(topologyA, topologyB, tranDict=False, tolerance=0.0001):
    """
    See Topology.Boolean().

    """
    return Topology.Boolean(topologyA=topologyA, topologyB=topologyB, operation="symdif", tranDict=tranDict, tolerance=tolerance)

Methods

def ExportToDXF(topologies, path, overwrite=False)

Exports the input topology to a DXF file. See https://en.wikipedia.org/wiki/AutoCAD_DXF. THe DXF version is 'R2010' This is experimental and only geometry is exported.

Parameters

topologies : list or topologic_core.Topology
The input list of topologies. This can also be a single topologic_core.Topology.
path : str
The input file path.
overwrite : bool , optional
If set to True the ouptut file will overwrite any pre-existing file. Otherwise, it won't. The default is False.

Returns

bool
True if the export operation is successful. False otherwise.
Expand source code
def ExportToDXF(topologies, path,  overwrite=False):
    """
    Exports the input topology to a DXF file. See https://en.wikipedia.org/wiki/AutoCAD_DXF.
    THe DXF version is 'R2010'
    This is experimental and only geometry is exported.

    Parameters
    ----------
    topologies : list or topologic_core.Topology
        The input list of topologies. This can also be a single topologic_core.Topology.
    path : str
        The input file path.
    overwrite : bool , optional
        If set to True the ouptut file will overwrite any pre-existing file. Otherwise, it won't. The default is False.

    Returns
    -------
    bool
        True if the export operation is successful. False otherwise.

    """
    import os
    import warnings

    try:
        import ezdxf
    except:
        print("Topology.ExportToDXF - Information: Installing required ezdxf library.")
        try:
            os.system("pip install ezdxf")
        except:
            os.system("pip install ezdxf --user")
        try:
            import ezdxf
            print("Topology.ExportToDXF - Information: ezdxf library installed successfully.")
        except:
            warnings.warn("Topology.ExportToDXF - Error: Could not import ezdxf library. Please install it manually. Returning None.")
            return None
    
    from topologicpy.Vertex import Vertex
    from topologicpy.Edge import Edge
    from topologicpy.Cluster import Cluster
    from topologicpy.Topology import Topology

    from os.path import exists
    if not isinstance(topologies, list):
        topologies = [topologies]
    topologies = [topology for topology in topologies if Topology.IsInstance(topology, "Topology")]
    if len(topologies) < 1:
        print("Topology.ExportToDXF - Error: The inupt list parameter topologies does not contain any valid topologies. Returning None.")
    # Make sure the file extension is .brep
    ext = path[len(path)-4:len(path)]
    if ext.lower() != ".dxf":
        path = path+".dxf"
    if not overwrite and exists(path):
        print("Topology.ExportToDXF - Error: a file already exists at the specified path and overwrite is set to False. Returning None.")
        return None
    
    def add_vertices(vertices, msp):
        for v in vertices:
            if Topology.IsInstance(v, "Vertex"):
                msp.add_point((Vertex.X(v), Vertex.Y(v), Vertex.Z(v)))
    def add_edges(edges, msp):
        for e in edges:
            if Topology.IsInstance(e, "Edge"):
                sv = Edge.StartVertex(e)
                ev = Edge.EndVertex(e)
                start = (Vertex.X(sv), Vertex.Y(sv), Vertex.Z(sv))
                end = (Vertex.X(ev), Vertex.Y(ev), Vertex.Z(ev))
                msp.add_line(start, end)
    def add_wires(wires, msp):
        for i, w in enumerate(wires):
            if Topology.IsInstance(w, "Wire"):
                block_name = "Wire_"+str(i+1).zfill(3)
                block = doc.blocks.new(name=block_name)
                # Add edges to the block
                edges = Topology.Edges(w)
                for edge in edges:
                    sv = Edge.StartVertex(edge)
                    ev = Edge.EndVertex(edge)
                    start = (Vertex.X(sv), Vertex.Y(sv), Vertex.Z(sv))
                    end = (Vertex.X(ev), Vertex.Y(ev), Vertex.Z(ev))
                    block.add_line(start, end)
                # Insert the block into the model space
                msp.add_blockref(block_name, insert=(0, 0, 0))
    def add_meshes(meshes, msp):
        for m in meshes:
            data = Topology.Geometry(m)
            vertices = data['vertices']
            faces = data['faces']
            mesh = msp.add_mesh()
            mesh.dxf.subdivision_levels = 0
            with mesh.edit_data() as mesh_data:
                mesh_data.vertices = vertices
                mesh_data.faces = faces
    # Create a new DXF document
    doc = ezdxf.new(dxfversion='R2010')
    msp = doc.modelspace()
    i = 1
    for topology in topologies:
        if Topology.IsInstance(topology, "Vertex"):
            add_vertices([topology], msp)
        elif Topology.IsInstance(topology, "Edge"):
            add_edges([topology], msp)
        elif Topology.IsInstance(topology, "Wire"):
            add_wires([topology], msp)
        elif Topology.IsInstance(topology, "Face"):
            add_meshes([topology], msp)
        elif Topology.IsInstance(topology, "Shell"):
            add_meshes([topology], msp)
        elif Topology.IsInstance(topology, "Cell"):
            add_meshes([topology], msp)
        elif Topology.IsInstance(topology, "CellComplex"):
            add_meshes([topology], msp)
        elif Topology.IsInstance(topology, "Cluster"):
            cellComplexes = Topology.CellComplexes(topology)
            add_meshes(cellComplexes, msp)
            cells = Cluster.FreeCells(topology)
            add_meshes(cells, msp)
            shells = Cluster.FreeShells(topology)
            add_meshes(shells, msp)
            faces = Cluster.FreeFaces(topology)
            add_meshes(faces, msp)
            wires = Cluster.FreeWires(topology)
            add_wires(wires, msp)
            edges = Cluster.FreeEdges(topology)
            add_edges(edges, msp)
            vertices = Cluster.FreeVertices(topology)
            add_vertices(vertices, msp)
    
    # Save the DXF document
    status = False
    try:
        doc.saveas(path)
        status = True
    except:
        status = False
    return status
class WorkerProcess (message_queue, sources, sinks, so_dicts, tolerance=0.0001)

Transfers the dictionaries from a subset of sources to the list of sinks.

Expand source code
class WorkerProcess(Process):
    """
    Transfers the dictionaries from a subset of sources to the list of sinks.
    """
    def __init__(self, message_queue, sources, sinks, so_dicts, tolerance=0.0001):
        Process.__init__(self, target=self.run)
        self.message_queue = message_queue
        self.sources = sources
        self.sinks = sinks
        self.so_dicts = so_dicts
        self.tolerance = tolerance

    def run(self):
        from topologicpy.Vertex import Vertex
        from topologicpy.Dictionary import Dictionary
        for sink_item in self.sinks:
            sink = Topology.ByBREPString(sink_item.sink_str)
            sinkKeys = []
            sinkValues = []
            iv = Topology.InternalVertex(sink, tolerance=self.tolerance)
            for j, source_str in enumerate(self.sources):
                source = Topology.ByBREPString(source_str)
                flag = False
                if Topology.IsInstance(source, "Vertex"):
                    flag = Vertex.IsInternal(source, sink, self.tolerance)
                else:
                    flag = Vertex.IsInternal(iv, source, self.tolerance)
                if flag:
                    d = Dictionary.ByPythonDictionary(self.so_dicts[j])
                    if d == None:
                        continue
                    stlKeys = d.Keys()
                    if len(stlKeys) > 0:
                        sourceKeys = d.Keys()
                        for aSourceKey in sourceKeys:
                            if aSourceKey not in sinkKeys:
                                sinkKeys.append(aSourceKey)
                                sinkValues.append("")
                        for i in range(len(sourceKeys)):
                            index = sinkKeys.index(sourceKeys[i])
                            sourceValue = Dictionary.ValueAtKey(d, sourceKeys[i])
                            if sourceValue != None:
                                if sinkValues[index] != "":
                                    if isinstance(sinkValues[index], list):
                                        sinkValues[index].append(sourceValue)
                                    else:
                                        sinkValues[index] = [sinkValues[index], sourceValue]
                                else:
                                    sinkValues[index] = sourceValue
                    break
            if len(sinkKeys) > 0 and len(sinkValues) > 0:
                self.message_queue.put(QueueItem(sink_item.ID, sinkKeys, sinkValues))

Ancestors

  • multiprocessing.context.Process
  • multiprocessing.process.BaseProcess

Methods

def run(self)

Method to be run in sub-process; can be overridden in sub-class

Expand source code
def run(self):
    from topologicpy.Vertex import Vertex
    from topologicpy.Dictionary import Dictionary
    for sink_item in self.sinks:
        sink = Topology.ByBREPString(sink_item.sink_str)
        sinkKeys = []
        sinkValues = []
        iv = Topology.InternalVertex(sink, tolerance=self.tolerance)
        for j, source_str in enumerate(self.sources):
            source = Topology.ByBREPString(source_str)
            flag = False
            if Topology.IsInstance(source, "Vertex"):
                flag = Vertex.IsInternal(source, sink, self.tolerance)
            else:
                flag = Vertex.IsInternal(iv, source, self.tolerance)
            if flag:
                d = Dictionary.ByPythonDictionary(self.so_dicts[j])
                if d == None:
                    continue
                stlKeys = d.Keys()
                if len(stlKeys) > 0:
                    sourceKeys = d.Keys()
                    for aSourceKey in sourceKeys:
                        if aSourceKey not in sinkKeys:
                            sinkKeys.append(aSourceKey)
                            sinkValues.append("")
                    for i in range(len(sourceKeys)):
                        index = sinkKeys.index(sourceKeys[i])
                        sourceValue = Dictionary.ValueAtKey(d, sourceKeys[i])
                        if sourceValue != None:
                            if sinkValues[index] != "":
                                if isinstance(sinkValues[index], list):
                                    sinkValues[index].append(sourceValue)
                                else:
                                    sinkValues[index] = [sinkValues[index], sourceValue]
                            else:
                                sinkValues[index] = sourceValue
                break
        if len(sinkKeys) > 0 and len(sinkValues) > 0:
            self.message_queue.put(QueueItem(sink_item.ID, sinkKeys, sinkValues))
class WorkerProcessPool (num_workers, message_queue, sources, sinks, so_dicts, tolerance=0.0001)

Create and manage a list of Worker processes. Each worker process transfers the dictionaries from a subset of sources to the list of sinks.

Expand source code
class WorkerProcessPool(object):
    """
    Create and manage a list of Worker processes. Each worker process
    transfers the dictionaries from a subset of sources to the list of sinks.
    """
    def __init__(self, num_workers, message_queue, sources, sinks, so_dicts, tolerance=0.0001):
        self.num_workers = num_workers
        self.message_queue = message_queue
        self.sources = sources
        self.sinks = sinks
        self.so_dicts = so_dicts
        self.tolerance = tolerance
        self.process_list = []

    def startProcesses(self):
        num_item_per_worker = len(self.sources) // self.num_workers
        for i in range(self.num_workers):
            if i == self.num_workers - 1:
                begin = i * num_item_per_worker
                sub_sources = self.sources[begin:]
                sub_dict = self.so_dicts[begin:]
            else:
                begin = i * num_item_per_worker
                end = begin + num_item_per_worker
                sub_sources = self.sources[begin : end]
                sub_dict = self.so_dicts[begin : end]
            wp = WorkerProcess(self.message_queue, sub_sources, self.sinks, sub_dict, self.tolerance)
            wp.start()
            self.process_list.append(wp)

    def stopProcesses(self):
        for p in self.process_list:
            p.join()
        self.process_list = []

    def join(self):
        for p in self.process_list:
            p.join()

Methods

def join(self)
Expand source code
def join(self):
    for p in self.process_list:
        p.join()
def startProcesses(self)
Expand source code
def startProcesses(self):
    num_item_per_worker = len(self.sources) // self.num_workers
    for i in range(self.num_workers):
        if i == self.num_workers - 1:
            begin = i * num_item_per_worker
            sub_sources = self.sources[begin:]
            sub_dict = self.so_dicts[begin:]
        else:
            begin = i * num_item_per_worker
            end = begin + num_item_per_worker
            sub_sources = self.sources[begin : end]
            sub_dict = self.so_dicts[begin : end]
        wp = WorkerProcess(self.message_queue, sub_sources, self.sinks, sub_dict, self.tolerance)
        wp.start()
        self.process_list.append(wp)
def stopProcesses(self)
Expand source code
def stopProcesses(self):
    for p in self.process_list:
        p.join()
    self.process_list = []