Skip to content

Geometry Pancake3D

Implements geometry generation class and methods for the Pancake3D magnets.

Geometry

Bases: Base

Main geometry class for Pancake3D.

Parameters:

Name Type Description Default
fdm

FiQuS data model

required
geom_folder str

folder where the geometry files are saved

required
mesh_folder str

folder where the mesh files are saved

required
solution_folder str

folder where the solution files are saved

required
Source code in fiqus/geom_generators/GeometryPancake3D.py
class Geometry(Base):
    """
    Main geometry class for Pancake3D.

    :param fdm: FiQuS data model
    :param geom_folder: folder where the geometry files are saved
    :type geom_folder: str
    :param mesh_folder: folder where the mesh files are saved
    :type mesh_folder: str
    :param solution_folder: folder where the solution files are saved
    :type solution_folder: str
    """

    def __init__(
        self,
        fdm,
        geom_folder,
        mesh_folder,
        solution_folder,
    ) -> None:
        super().__init__(fdm, geom_folder, mesh_folder, solution_folder)

        # Clear if there is any existing dimTags storage:
        dimTagsStorage.clear()

        # Start GMSH:
        self.gu = GmshUtils(self.geom_folder)
        self.gu.initialize()

        # To speed up the GUI:
        gmsh.option.setNumber("Geometry.NumSubEdges", 10)

        # To see the surfaces in a better way in GUI:
        gmsh.option.setNumber("Geometry.SurfaceType", 1)

        # Makes the gmsh.mode.occ.cut function faster:
        gmsh.option.setNumber("Geometry.OCCParallel", 1)

        # To avoid any unwanted modifications to the geometry, the automatic fixing of
        # the geometry is disabled:
        gmsh.option.setNumber("Geometry.OCCAutoFix", 0)

        # Set the tolerance:
        if self.geo.dimTol < gmsh.option.getNumber("Geometry.Tolerance"):
            gmsh.option.setNumber("Geometry.Tolerance", self.geo.dimTol)

        gmsh.option.setNumber("Geometry.ToleranceBoolean", self.geo.dimTol)

        spiralCurve.sectionsPerTurn = self.geo.wi.spt
        spiralCurve.curvesPerTurn = self.geo.wi.NofVolPerTurn

    def generate_geometry(self):
        """
        Generates geometry and saves it as a .brep file.


        """
        logger.info(
            f"Generating Pancake3D geometry ({self.brep_file}) has been started."
        )
        start_time = timeit.default_timer()

        self.pancakeCoil = pancakeCoilsWithAir(self.geo, self.mesh)

        gmsh.model.occ.synchronize()
        gmsh.write(self.brep_file)

        logger.info(
            f"Generating Pancake3D geometry ({self.brep_file}) has been finished in"
            f" {timeit.default_timer() - start_time:.2f} s."
        )

    def load_geometry(self):
        """
        Loads geometry from .brep file.
        """
        logger.info("Loading Pancake3D geometry has been started.")
        start_time = timeit.default_timer()

        previousGeo = FilesAndFolders.read_data_from_yaml(
            self.geometry_data_file, Pancake3DGeometry
        )

        if previousGeo.dict() != self.geo.dict():
            raise ValueError(
                "Geometry data has been changed. Please regenerate the geometry or load"
                " the previous geometry data."
            )

        gmsh.clear()
        gmsh.model.occ.importShapes(self.brep_file, format="brep")
        gmsh.model.occ.synchronize()

        logger.info(
            "Loading Pancake3D geometry has been finished in"
            f" {timeit.default_timer() - start_time:.2f} s."
        )

    def generate_vi_file(self):
        """
        Generates volume information file. Volume information file stores dimTags of all
        the stored volumes in the geometry. Without this file, regions couldn't be
        created, meaning that finite element simulation cannot be done.

        The file extension is custom because users are not supposed to edit or open this
        file, and it makes it intuitively clear that it is a volume information file.
        """
        logger.info(
            f"Generating volume information file ({self.vi_file}) has been started."
        )
        start_time = timeit.default_timer()

        dimTagsDict = dimTagsStorage.getDimTagsDict()
        json.dump(
            dimTagsDict,
            open(self.vi_file, "w"),
        )

        logger.info(
            "Generating volume information file has been finished in"
            f" {timeit.default_timer() - start_time:.2f} s."
        )

        if self.geo_gui:
            self.generate_physical_groups()
            self.gu.launch_interactive_GUI()
        else:
            gmsh.clear()
            gmsh.finalize()

    @staticmethod
    def generate_physical_groups():
        """
        Generates physical groups. Physical groups are not saved in the BREP file but
        it can be useful for debugging purposes.


        """
        gmsh.model.occ.synchronize()

        dimTagsDict = dimTagsStorage.getDimTagsDict(forPhysicalGroups=True)

        for key, value in dimTagsDict.items():
            tags = [dimTag[1] for dimTag in value]
            gmsh.model.addPhysicalGroup(
                3,
                tags,
                name=key,
            )

    @staticmethod
    def remove_all_duplicates():
        """
        Removes all the duplicates and then prints the entities that are created or
        removed during the operation. It prints the line number where the function is
        called as well. This function is helpful for debugging. Finding duplicates means
        there is a problem in geometry creation logic, and the meshes will not be
        conformal. It shouldn't be used in the final version of the code since removing
        duplicates is computationally expensive, and there shouldn't be duplicates at
        all.

        WARNING:
        This function currently does not work properly. It is not recommended to use
        right now. It finds duplicates even if there are no duplicates (topology
        problems).
        """

        logger.info(f"Removing all the duplicates has been started.")
        start_time = timeit.default_timer()

        gmsh.model.occ.synchronize()
        oldEntities = []
        oldEntities.extend(gmsh.model.getEntities(3))
        oldEntities.extend(gmsh.model.getEntities(2))
        oldEntities.extend(gmsh.model.getEntities(1))
        oldEntities.extend(gmsh.model.getEntities(0))
        oldEntities = set(oldEntities)

        gmsh.model.occ.removeAllDuplicates()

        gmsh.model.occ.synchronize()
        newEntities = []
        newEntities.extend(gmsh.model.getEntities(3))
        newEntities.extend(gmsh.model.getEntities(2))
        newEntities.extend(gmsh.model.getEntities(1))
        newEntities.extend(gmsh.model.getEntities(0))
        newEntities = set(newEntities)
        NewlyCreated = newEntities - oldEntities
        Removed = oldEntities - newEntities

        frameinfo = getframeinfo(currentframe().f_back)

        if len(NewlyCreated) > 0 or len(Removed) > 0:
            logger.warning(f"Duplicates found! Line: {frameinfo.lineno}")
            logger.warning(f"{len(NewlyCreated)}NewlyCreated = {list(NewlyCreated)}")
            logger.warning(f"{len(Removed)}Removed = {list(Removed)}")
        else:
            logger.info(f"No duplicates found! Line: {frameinfo.lineno}")

        logger.info(
            "Removing all the duplicates has been finished in"
            f" {timeit.default_timer() - start_time:.2f} s."
        )

generate_geometry()

Generates geometry and saves it as a .brep file.

Source code in fiqus/geom_generators/GeometryPancake3D.py
def generate_geometry(self):
    """
    Generates geometry and saves it as a .brep file.


    """
    logger.info(
        f"Generating Pancake3D geometry ({self.brep_file}) has been started."
    )
    start_time = timeit.default_timer()

    self.pancakeCoil = pancakeCoilsWithAir(self.geo, self.mesh)

    gmsh.model.occ.synchronize()
    gmsh.write(self.brep_file)

    logger.info(
        f"Generating Pancake3D geometry ({self.brep_file}) has been finished in"
        f" {timeit.default_timer() - start_time:.2f} s."
    )

generate_physical_groups() staticmethod

Generates physical groups. Physical groups are not saved in the BREP file but it can be useful for debugging purposes.

Source code in fiqus/geom_generators/GeometryPancake3D.py
@staticmethod
def generate_physical_groups():
    """
    Generates physical groups. Physical groups are not saved in the BREP file but
    it can be useful for debugging purposes.


    """
    gmsh.model.occ.synchronize()

    dimTagsDict = dimTagsStorage.getDimTagsDict(forPhysicalGroups=True)

    for key, value in dimTagsDict.items():
        tags = [dimTag[1] for dimTag in value]
        gmsh.model.addPhysicalGroup(
            3,
            tags,
            name=key,
        )

generate_vi_file()

Generates volume information file. Volume information file stores dimTags of all the stored volumes in the geometry. Without this file, regions couldn't be created, meaning that finite element simulation cannot be done.

The file extension is custom because users are not supposed to edit or open this file, and it makes it intuitively clear that it is a volume information file.

Source code in fiqus/geom_generators/GeometryPancake3D.py
def generate_vi_file(self):
    """
    Generates volume information file. Volume information file stores dimTags of all
    the stored volumes in the geometry. Without this file, regions couldn't be
    created, meaning that finite element simulation cannot be done.

    The file extension is custom because users are not supposed to edit or open this
    file, and it makes it intuitively clear that it is a volume information file.
    """
    logger.info(
        f"Generating volume information file ({self.vi_file}) has been started."
    )
    start_time = timeit.default_timer()

    dimTagsDict = dimTagsStorage.getDimTagsDict()
    json.dump(
        dimTagsDict,
        open(self.vi_file, "w"),
    )

    logger.info(
        "Generating volume information file has been finished in"
        f" {timeit.default_timer() - start_time:.2f} s."
    )

    if self.geo_gui:
        self.generate_physical_groups()
        self.gu.launch_interactive_GUI()
    else:
        gmsh.clear()
        gmsh.finalize()

load_geometry()

Loads geometry from .brep file.

Source code in fiqus/geom_generators/GeometryPancake3D.py
def load_geometry(self):
    """
    Loads geometry from .brep file.
    """
    logger.info("Loading Pancake3D geometry has been started.")
    start_time = timeit.default_timer()

    previousGeo = FilesAndFolders.read_data_from_yaml(
        self.geometry_data_file, Pancake3DGeometry
    )

    if previousGeo.dict() != self.geo.dict():
        raise ValueError(
            "Geometry data has been changed. Please regenerate the geometry or load"
            " the previous geometry data."
        )

    gmsh.clear()
    gmsh.model.occ.importShapes(self.brep_file, format="brep")
    gmsh.model.occ.synchronize()

    logger.info(
        "Loading Pancake3D geometry has been finished in"
        f" {timeit.default_timer() - start_time:.2f} s."
    )

remove_all_duplicates() staticmethod

Removes all the duplicates and then prints the entities that are created or removed during the operation. It prints the line number where the function is called as well. This function is helpful for debugging. Finding duplicates means there is a problem in geometry creation logic, and the meshes will not be conformal. It shouldn't be used in the final version of the code since removing duplicates is computationally expensive, and there shouldn't be duplicates at all.

WARNING: This function currently does not work properly. It is not recommended to use right now. It finds duplicates even if there are no duplicates (topology problems).

Source code in fiqus/geom_generators/GeometryPancake3D.py
@staticmethod
def remove_all_duplicates():
    """
    Removes all the duplicates and then prints the entities that are created or
    removed during the operation. It prints the line number where the function is
    called as well. This function is helpful for debugging. Finding duplicates means
    there is a problem in geometry creation logic, and the meshes will not be
    conformal. It shouldn't be used in the final version of the code since removing
    duplicates is computationally expensive, and there shouldn't be duplicates at
    all.

    WARNING:
    This function currently does not work properly. It is not recommended to use
    right now. It finds duplicates even if there are no duplicates (topology
    problems).
    """

    logger.info(f"Removing all the duplicates has been started.")
    start_time = timeit.default_timer()

    gmsh.model.occ.synchronize()
    oldEntities = []
    oldEntities.extend(gmsh.model.getEntities(3))
    oldEntities.extend(gmsh.model.getEntities(2))
    oldEntities.extend(gmsh.model.getEntities(1))
    oldEntities.extend(gmsh.model.getEntities(0))
    oldEntities = set(oldEntities)

    gmsh.model.occ.removeAllDuplicates()

    gmsh.model.occ.synchronize()
    newEntities = []
    newEntities.extend(gmsh.model.getEntities(3))
    newEntities.extend(gmsh.model.getEntities(2))
    newEntities.extend(gmsh.model.getEntities(1))
    newEntities.extend(gmsh.model.getEntities(0))
    newEntities = set(newEntities)
    NewlyCreated = newEntities - oldEntities
    Removed = oldEntities - newEntities

    frameinfo = getframeinfo(currentframe().f_back)

    if len(NewlyCreated) > 0 or len(Removed) > 0:
        logger.warning(f"Duplicates found! Line: {frameinfo.lineno}")
        logger.warning(f"{len(NewlyCreated)}NewlyCreated = {list(NewlyCreated)}")
        logger.warning(f"{len(Removed)}Removed = {list(Removed)}")
    else:
        logger.info(f"No duplicates found! Line: {frameinfo.lineno}")

    logger.info(
        "Removing all the duplicates has been finished in"
        f" {timeit.default_timer() - start_time:.2f} s."
    )

coordinate

Bases: Enum

A class to specify coordinate types easily.

Source code in fiqus/geom_generators/GeometryPancake3D.py
class coordinate(Enum):
    """
    A class to specify coordinate types easily.
    """

    rectangular = 0
    cylindrical = 1
    spherical = 2

dimTags

This is a class for (dim, tag) tuple lists. DimTags are heavily used in GMSH, and dimTags class makes it easier to store and manipulate them.

Every dimTags instance with the save option True will be stored in the dimTagsStorage class. dimTags instances with the save option False will not be stored. dimTagsStorage class stores all the dimTags with the corresponding names so that later the dimTagsStorage class can be used to create the volume information file. The volume information file is required in the meshing stage to create the appropriate regions.

If parentName is specified, during the volume information file generation, another key that is equal to parentName is created, and all the dimTags are also added there. For example, air consists of many parts such as gap, outer tube, inner cylinder parts, and their dimTags object all have the parentName of self.geo.ai.name (user input of the air name) so that in the volume information file, they are all combined under the air name as well.

Parameters:

Name Type Description Default
name str

name of the dimTags object (default: None)

None
parentName str

name of the parent dimTags object (default: None)

None
save bool

True if the instance to be stored in dimTagsStorage class, False otherwise (default: False)

False
Source code in fiqus/geom_generators/GeometryPancake3D.py
class dimTags:
    """
    This is a class for (dim, tag) tuple lists. DimTags are heavily used in GMSH, and
    dimTags class makes it easier to store and manipulate them.

    Every dimTags instance with the save option True will be stored in the
    dimTagsStorage class. dimTags instances with the save option False will not be
    stored. dimTagsStorage class stores all the dimTags with the corresponding names so
    that later the dimTagsStorage class can be used to create the volume information
    file. The volume information file is required in the meshing stage to create the
    appropriate regions.

    If parentName is specified, during the volume information file generation, another
    key that is equal to parentName is created, and all the dimTags are also added
    there. For example, air consists of many parts such as gap, outer tube, inner
    cylinder parts, and their dimTags object all have the parentName of self.geo.ai.name
    (user input of the air name) so that in the volume information file, they are all
    combined under the air name as well.

    :param name: name of the dimTags object (default: None)
    :type name: str, optional
    :param parentName: name of the parent dimTags object (default: None)
    :type parentName: str, optional
    :param save: True if the instance to be stored in dimTagsStorage class, False
        otherwise (default: False)
    :type save: bool, optional
    """

    point = 0
    curve = 1
    surface = 2
    volume = 3

    def storageUpdateRequired(func):
        """
        A decorator for dimTags class. It will update the dimTagsStorage class if the
        save option is True and the decorated function is called. Use this decorator
        for every function that changes the dimTags instance so that the storage gets
        updated.

        :param func: function to be decorated
        :type func: function
        :return: decorated function
        :rtype: function
        """

        def wrapper(self, *args, **kwargs):
            func(self, *args, **kwargs)

            if self.save:
                # Update the dimTagsStorage:
                dimTagsStorage.updateDimTags(self)

        return wrapper

    def __init__(
        self,
        name: str = None,
        parentName: str = None,
        save: bool = False,
    ):
        self.name = name

        dimTagsObjects = dimTagsStorage.getDimTagsObject(name)
        if dimTagsObjects != []:
            dimTagsObject = dimTagsObjects[0]
            self.physicalTag = dimTagsObject.physicalTag
            # To store points, curves, surfaces, and volumes separately:
            self.dimTags = dimTagsObject.dimTags
            self.dimTagsForPG = dimTagsObject.dimTagsForPG
            self.allDimTags = dimTagsObject.allDimTags
            self.parentName = dimTagsObject.parentName
            self.save = dimTagsObject.save
        else:
            self.physicalTag = None
            # To store points, curves, surfaces, and volumes separately:
            self.dimTags = [[] for _ in range(4)]
            self.dimTagsForPG = [[] for _ in range(4)]  # dim tags for physical groups
            self.allDimTags = []
            self.parentName = parentName
            self.save = save
            if self.save:
                dimTagsStorage.updateDimTags(self)

    @storageUpdateRequired
    def add(
        self,
        dimTagsList: List[Tuple[int, int]],
        dimTagsListForPG: List[Tuple[int, int]] = None,
    ):
        """
        Adds a list of (dim, tag) tuples to the dimTags object.

        dimTagsListForPG is also accepted as an argument because sometimes, the stored
        dimTags and the current dimTags in the geometry generation can be different. For
        example, if volume 61 is deleted, the other volume tags (62, 63, ...) won't
        shift back. However, after saving the geometry as a BREP file and rereading it,
        the volume tags will be shifted back. In this case, the stored dimTags should be
        shifted as well. But to create the correct physical region in the
        geometry-creating process (which won't be saved in the BREP file, just used for
        debugging purposes), another optional dimTagsListForPG argument is accepted.

        :param dimTagsList: list of (dim, tag) tuples
        :type dimTagsList: list[tuple[int, int]]
        :param dimTagsListForPG: list of (dim, tag) tuples for physical groups
            (default: None). If dimTagsListForPG is None, dimTagsList will be used for
            physical groups as well.
        :type dimTagsListForPG: list[tuple[int, int]], optional

        """
        if not isinstance(dimTagsList, list):
            dimTagsList = [dimTagsList]

        if not all(isinstance(element, tuple) for element in dimTagsList):
            raise TypeError("Dim tags must be a list of tuples!")

        # Sometimes, negative entities can be added for topology.
        for i, v in enumerate(dimTagsList):
            if v[1] < 0:
                dimTagsList[i] = (v[0], -v[1])

        # Add dim tags if they are not already added:
        for v in dimTagsList:
            if v not in self.allDimTags:
                self.dimTags[v[0]].append(v)
                if dimTagsListForPG is None:
                    self.dimTagsForPG[v[0]].append(v)
                self.allDimTags.append(v)

        if dimTagsListForPG is not None:
            if not isinstance(dimTagsListForPG, list):
                dimTagsListForPG = [dimTagsListForPG]

            if not all(isinstance(element, tuple) for element in dimTagsListForPG):
                raise TypeError("Dim tags must be a list of tuples!")

            for i, v in enumerate(dimTagsListForPG):
                if v[1] < 0:
                    dimTagsListForPG[i] = (v[0], -v[1])
            for v in dimTagsListForPG:
                if v not in self.dimTagsForPG:
                    self.dimTagsForPG[v[0]].append(v)

    def addWithTags(self, dim: int, tags: List[int], tagsForPG: List[int] = None):
        """
        Adds a list of tags with a specific dimension to the dimTags object. The
        explanation of the tagsForPG argument is given in the add() method.

        :param dim: dimension of the tags
        :type dim: int
        :param tags: list of tags
        :type tags: list[tuple[int, int]]
        :param tagsForPG: list of tags for physical groups (default: None)
        :type tagsForPG: list[tuple[int, int]], optional

        """
        if not isinstance(tags, list):
            tags = [tags]

        if not isinstance(dim, int):
            raise TypeError("Dimension must be an integer!")

        if not all(isinstance(element, int) for element in tags):
            raise TypeError("Tags must be a list of integers!")

        dims = [dim] * len(tags)
        dimTagsList = list(zip(dims, tags))

        if tagsForPG is not None:
            if not isinstance(tagsForPG, list):
                tagsForPG = [tagsForPG]

            if not all(isinstance(element, int) for element in tagsForPG):
                raise TypeError("Tags must be a list of integers!")

            dimTagsListForPG = list(zip(dims, tagsForPG))
            self.add(dimTagsList, dimTagsListForPG)
        else:
            self.add(dimTagsList)

    def getDimTags(self, dim: int = None) -> List[Tuple[int, int]]:
        """
        Returns the stored list of (dim, tag) tuples with a specific dimension. If dim
        is not specified, returns all the (dim, tag) tuples.

        :param dim: dimension of the tags to be returned (default: None)
        :type dim: int, optional
        :return: list of (dim, tag) tuples
        :rtype: list[tuple[int, int]]
        """
        if dim is None:
            return self.dimTags[0] + self.dimTags[1] + self.dimTags[2] + self.dimTags[3]
        else:
            return self.dimTags[dim]

    def getDimTagsForPG(self, dim: int = None) -> List[Tuple[int, int]]:
        """
        Returns the stored list of (dim, tag) tuples for physical groups with a specific
        dimension. If dim is not specified, returns all the (dim, tag) tuples for
        physical groups.

        :param dim: dimension of the tags to be returned (default: None)
        :type dim: int, optional
        :return: list of (dim, tag) tuples for physical groups
        :rtype: list[tuple[int, int]]
        """
        if dim is None:
            return (
                self.dimTagsForPG[0]
                + self.dimTagsForPG[1]
                + self.dimTagsForPG[2]
                + self.dimTagsForPG[3]
            )
        else:
            return self.dimTagsForPG[dim]

    def getTags(self, dim: int, forPhysicalGroup=False) -> List[int]:
        """
        Returns the stored list of tags with a specific dimension.

        :param dim: dimension of the tags to be returned
        :type dim: int
        :return: list of tags
        :rtype: list[int]
        """
        if forPhysicalGroup:
            return [v[1] for v in self.dimTagsForPG[dim]]
        else:
            return [v[1] for v in self.dimTags[dim]]

    def getExtrusionTop(self, dim=3):
        """
        Returns the top surfaces, lines, or points of an extrusion operation if the
        dimTags object contains the tags of an extrusion operation.
        gmsh.model.occ.extrusion() function returns all the entities that are created
        by the extrusion as a dim tags list. The first element is always the top
        surface, the second is the volume. However, when more than one surface is
        extruded, extracting the top surfaces is not trivial. This function returns
        the top surfaces of an extrusion operation. It does that by finding the entities
        right before the volume entities. If dim is 2, the function will return the top
        curves of an extrusion operation. If dim is 1, the function will return the top
        points of an extrusion.

        :param dim: dimension of the entity that is being created (default: 3)
        :type dim: int, optional
        :return: list of (dim, tag) tuples of the top entities of an extrusion operation
        :rtype: list[tuple[int, int]]
        """

        topSurfaces = []
        for index, dimTag in enumerate(self.allDimTags):
            if dimTag[0] == dim:
                topSurfaces.append(self.allDimTags[index - 1])

        return topSurfaces

    def getExtrusionSide(self, dim=3):
        """
        Returns the side surfaces, lines, or points of an extrusion operation if the
        dimTags object contains the tags of an extrusion operation.
        gmsh.model.occ.extrusion() function returns all the entities that are created
        by the extrusion as a dim tags list. The first element is always the top
        surface, the second is the volume. The other elements are the side surfaces.
        However, when more than one surface is extruded, extracting the side surfaces
        is not trivial. This function returns the side surfaces of an extrusion
        operation. It does that by finding returning all the entities except the top
        surface and the volume. If dim is 2, the function will return the side curves of
        an extrusion operation.

        :param dim: dimension of the entity that is being created (default: 3)
        :type dim: int, optional
        :return: list of (dim, tag) tuples of the side entities of an extrusion operation
        :rtype: list[tuple[int, int]]
        """
        sideSurfaces = []
        sideSurfaceStartIndex = None
        for index, dimTag in enumerate(self.allDimTags):
            if dimTag[0] == dim:
                if sideSurfaceStartIndex is not None:
                    sideSurfaces.append(
                        self.allDimTags[sideSurfaceStartIndex : index - 1]
                    )
                    sideSurfaceStartIndex = index + 1
                else:
                    sideSurfaceStartIndex = index + 1

        sideSurfaces.append(self.allDimTags[sideSurfaceStartIndex:])

        return sideSurfaces

    def __add__(self, other):
        """
        Adds two dimTags objects and returns a new dimTags object with the same save and
        name attirbues of the first dimTags object.

        It might cause bugs because of the recursive behavior of the
        @storageUpdateRequired decorator. Use with caution. Currently only used by
        dimTagsStorage.updateDimTags method.

        :param other: dimTags object to be added
        :type other: dimTags
        :return: dimTags object with the sum of the two dimTags objects
        :rtype: dimTags
        """
        result = dimTags()
        result.name = self.name
        result.parentName = self.parentName
        result.physicalTag = self.physicalTag
        result.dimTags = self.dimTags
        result.dimTagsForPG = self.dimTagsForPG
        result.allDimTags = self.allDimTags

        result.add(other.allDimTags)
        result.save = self.save
        return result

    def __repr__(self):
        """
        Returns the string representation of the dimTags object. If the dimTags object
        is saved, it will return "SAVED: name". If the dimTags object is not saved, it
        will return "NOT SAVED: name".

        dimTags objects are used as dictionary keys throughout the code. This
        representation makes debugging easier.

        :return: string representation of the dimTags object
        :rtype: str
        """
        if self.save:
            return "SAVED: " + self.name
        else:
            return "NOT SAVED: " + self.name

__add__(other)

Adds two dimTags objects and returns a new dimTags object with the same save and name attirbues of the first dimTags object.

It might cause bugs because of the recursive behavior of the @storageUpdateRequired decorator. Use with caution. Currently only used by dimTagsStorage.updateDimTags method.

Parameters:

Name Type Description Default
other dimTags

dimTags object to be added

required

Returns:

Type Description
dimTags

dimTags object with the sum of the two dimTags objects

Source code in fiqus/geom_generators/GeometryPancake3D.py
def __add__(self, other):
    """
    Adds two dimTags objects and returns a new dimTags object with the same save and
    name attirbues of the first dimTags object.

    It might cause bugs because of the recursive behavior of the
    @storageUpdateRequired decorator. Use with caution. Currently only used by
    dimTagsStorage.updateDimTags method.

    :param other: dimTags object to be added
    :type other: dimTags
    :return: dimTags object with the sum of the two dimTags objects
    :rtype: dimTags
    """
    result = dimTags()
    result.name = self.name
    result.parentName = self.parentName
    result.physicalTag = self.physicalTag
    result.dimTags = self.dimTags
    result.dimTagsForPG = self.dimTagsForPG
    result.allDimTags = self.allDimTags

    result.add(other.allDimTags)
    result.save = self.save
    return result

__repr__()

Returns the string representation of the dimTags object. If the dimTags object is saved, it will return "SAVED: name". If the dimTags object is not saved, it will return "NOT SAVED: name".

dimTags objects are used as dictionary keys throughout the code. This representation makes debugging easier.

Returns:

Type Description
str

string representation of the dimTags object

Source code in fiqus/geom_generators/GeometryPancake3D.py
def __repr__(self):
    """
    Returns the string representation of the dimTags object. If the dimTags object
    is saved, it will return "SAVED: name". If the dimTags object is not saved, it
    will return "NOT SAVED: name".

    dimTags objects are used as dictionary keys throughout the code. This
    representation makes debugging easier.

    :return: string representation of the dimTags object
    :rtype: str
    """
    if self.save:
        return "SAVED: " + self.name
    else:
        return "NOT SAVED: " + self.name

add(dimTagsList, dimTagsListForPG=None)

Adds a list of (dim, tag) tuples to the dimTags object.

dimTagsListForPG is also accepted as an argument because sometimes, the stored dimTags and the current dimTags in the geometry generation can be different. For example, if volume 61 is deleted, the other volume tags (62, 63, ...) won't shift back. However, after saving the geometry as a BREP file and rereading it, the volume tags will be shifted back. In this case, the stored dimTags should be shifted as well. But to create the correct physical region in the geometry-creating process (which won't be saved in the BREP file, just used for debugging purposes), another optional dimTagsListForPG argument is accepted.

Parameters:

Name Type Description Default
dimTagsList List[Tuple[int, int]]

list of (dim, tag) tuples

required
dimTagsListForPG List[Tuple[int, int]]

list of (dim, tag) tuples for physical groups (default: None). If dimTagsListForPG is None, dimTagsList will be used for physical groups as well.

None
Source code in fiqus/geom_generators/GeometryPancake3D.py
@storageUpdateRequired
def add(
    self,
    dimTagsList: List[Tuple[int, int]],
    dimTagsListForPG: List[Tuple[int, int]] = None,
):
    """
    Adds a list of (dim, tag) tuples to the dimTags object.

    dimTagsListForPG is also accepted as an argument because sometimes, the stored
    dimTags and the current dimTags in the geometry generation can be different. For
    example, if volume 61 is deleted, the other volume tags (62, 63, ...) won't
    shift back. However, after saving the geometry as a BREP file and rereading it,
    the volume tags will be shifted back. In this case, the stored dimTags should be
    shifted as well. But to create the correct physical region in the
    geometry-creating process (which won't be saved in the BREP file, just used for
    debugging purposes), another optional dimTagsListForPG argument is accepted.

    :param dimTagsList: list of (dim, tag) tuples
    :type dimTagsList: list[tuple[int, int]]
    :param dimTagsListForPG: list of (dim, tag) tuples for physical groups
        (default: None). If dimTagsListForPG is None, dimTagsList will be used for
        physical groups as well.
    :type dimTagsListForPG: list[tuple[int, int]], optional

    """
    if not isinstance(dimTagsList, list):
        dimTagsList = [dimTagsList]

    if not all(isinstance(element, tuple) for element in dimTagsList):
        raise TypeError("Dim tags must be a list of tuples!")

    # Sometimes, negative entities can be added for topology.
    for i, v in enumerate(dimTagsList):
        if v[1] < 0:
            dimTagsList[i] = (v[0], -v[1])

    # Add dim tags if they are not already added:
    for v in dimTagsList:
        if v not in self.allDimTags:
            self.dimTags[v[0]].append(v)
            if dimTagsListForPG is None:
                self.dimTagsForPG[v[0]].append(v)
            self.allDimTags.append(v)

    if dimTagsListForPG is not None:
        if not isinstance(dimTagsListForPG, list):
            dimTagsListForPG = [dimTagsListForPG]

        if not all(isinstance(element, tuple) for element in dimTagsListForPG):
            raise TypeError("Dim tags must be a list of tuples!")

        for i, v in enumerate(dimTagsListForPG):
            if v[1] < 0:
                dimTagsListForPG[i] = (v[0], -v[1])
        for v in dimTagsListForPG:
            if v not in self.dimTagsForPG:
                self.dimTagsForPG[v[0]].append(v)

addWithTags(dim, tags, tagsForPG=None)

Adds a list of tags with a specific dimension to the dimTags object. The explanation of the tagsForPG argument is given in the add() method.

Parameters:

Name Type Description Default
dim int

dimension of the tags

required
tags List[int]

list of tags

required
tagsForPG List[int]

list of tags for physical groups (default: None)

None
Source code in fiqus/geom_generators/GeometryPancake3D.py
def addWithTags(self, dim: int, tags: List[int], tagsForPG: List[int] = None):
    """
    Adds a list of tags with a specific dimension to the dimTags object. The
    explanation of the tagsForPG argument is given in the add() method.

    :param dim: dimension of the tags
    :type dim: int
    :param tags: list of tags
    :type tags: list[tuple[int, int]]
    :param tagsForPG: list of tags for physical groups (default: None)
    :type tagsForPG: list[tuple[int, int]], optional

    """
    if not isinstance(tags, list):
        tags = [tags]

    if not isinstance(dim, int):
        raise TypeError("Dimension must be an integer!")

    if not all(isinstance(element, int) for element in tags):
        raise TypeError("Tags must be a list of integers!")

    dims = [dim] * len(tags)
    dimTagsList = list(zip(dims, tags))

    if tagsForPG is not None:
        if not isinstance(tagsForPG, list):
            tagsForPG = [tagsForPG]

        if not all(isinstance(element, int) for element in tagsForPG):
            raise TypeError("Tags must be a list of integers!")

        dimTagsListForPG = list(zip(dims, tagsForPG))
        self.add(dimTagsList, dimTagsListForPG)
    else:
        self.add(dimTagsList)

getDimTags(dim=None)

Returns the stored list of (dim, tag) tuples with a specific dimension. If dim is not specified, returns all the (dim, tag) tuples.

Parameters:

Name Type Description Default
dim int

dimension of the tags to be returned (default: None)

None

Returns:

Type Description
list[tuple[int, int]]

list of (dim, tag) tuples

Source code in fiqus/geom_generators/GeometryPancake3D.py
def getDimTags(self, dim: int = None) -> List[Tuple[int, int]]:
    """
    Returns the stored list of (dim, tag) tuples with a specific dimension. If dim
    is not specified, returns all the (dim, tag) tuples.

    :param dim: dimension of the tags to be returned (default: None)
    :type dim: int, optional
    :return: list of (dim, tag) tuples
    :rtype: list[tuple[int, int]]
    """
    if dim is None:
        return self.dimTags[0] + self.dimTags[1] + self.dimTags[2] + self.dimTags[3]
    else:
        return self.dimTags[dim]

getDimTagsForPG(dim=None)

Returns the stored list of (dim, tag) tuples for physical groups with a specific dimension. If dim is not specified, returns all the (dim, tag) tuples for physical groups.

Parameters:

Name Type Description Default
dim int

dimension of the tags to be returned (default: None)

None

Returns:

Type Description
list[tuple[int, int]]

list of (dim, tag) tuples for physical groups

Source code in fiqus/geom_generators/GeometryPancake3D.py
def getDimTagsForPG(self, dim: int = None) -> List[Tuple[int, int]]:
    """
    Returns the stored list of (dim, tag) tuples for physical groups with a specific
    dimension. If dim is not specified, returns all the (dim, tag) tuples for
    physical groups.

    :param dim: dimension of the tags to be returned (default: None)
    :type dim: int, optional
    :return: list of (dim, tag) tuples for physical groups
    :rtype: list[tuple[int, int]]
    """
    if dim is None:
        return (
            self.dimTagsForPG[0]
            + self.dimTagsForPG[1]
            + self.dimTagsForPG[2]
            + self.dimTagsForPG[3]
        )
    else:
        return self.dimTagsForPG[dim]

getExtrusionSide(dim=3)

Returns the side surfaces, lines, or points of an extrusion operation if the dimTags object contains the tags of an extrusion operation. gmsh.model.occ.extrusion() function returns all the entities that are created by the extrusion as a dim tags list. The first element is always the top surface, the second is the volume. The other elements are the side surfaces. However, when more than one surface is extruded, extracting the side surfaces is not trivial. This function returns the side surfaces of an extrusion operation. It does that by finding returning all the entities except the top surface and the volume. If dim is 2, the function will return the side curves of an extrusion operation.

Parameters:

Name Type Description Default
dim int, optional

dimension of the entity that is being created (default: 3)

3

Returns:

Type Description
list[tuple[int, int]]

list of (dim, tag) tuples of the side entities of an extrusion operation

Source code in fiqus/geom_generators/GeometryPancake3D.py
def getExtrusionSide(self, dim=3):
    """
    Returns the side surfaces, lines, or points of an extrusion operation if the
    dimTags object contains the tags of an extrusion operation.
    gmsh.model.occ.extrusion() function returns all the entities that are created
    by the extrusion as a dim tags list. The first element is always the top
    surface, the second is the volume. The other elements are the side surfaces.
    However, when more than one surface is extruded, extracting the side surfaces
    is not trivial. This function returns the side surfaces of an extrusion
    operation. It does that by finding returning all the entities except the top
    surface and the volume. If dim is 2, the function will return the side curves of
    an extrusion operation.

    :param dim: dimension of the entity that is being created (default: 3)
    :type dim: int, optional
    :return: list of (dim, tag) tuples of the side entities of an extrusion operation
    :rtype: list[tuple[int, int]]
    """
    sideSurfaces = []
    sideSurfaceStartIndex = None
    for index, dimTag in enumerate(self.allDimTags):
        if dimTag[0] == dim:
            if sideSurfaceStartIndex is not None:
                sideSurfaces.append(
                    self.allDimTags[sideSurfaceStartIndex : index - 1]
                )
                sideSurfaceStartIndex = index + 1
            else:
                sideSurfaceStartIndex = index + 1

    sideSurfaces.append(self.allDimTags[sideSurfaceStartIndex:])

    return sideSurfaces

getExtrusionTop(dim=3)

Returns the top surfaces, lines, or points of an extrusion operation if the dimTags object contains the tags of an extrusion operation. gmsh.model.occ.extrusion() function returns all the entities that are created by the extrusion as a dim tags list. The first element is always the top surface, the second is the volume. However, when more than one surface is extruded, extracting the top surfaces is not trivial. This function returns the top surfaces of an extrusion operation. It does that by finding the entities right before the volume entities. If dim is 2, the function will return the top curves of an extrusion operation. If dim is 1, the function will return the top points of an extrusion.

Parameters:

Name Type Description Default
dim int, optional

dimension of the entity that is being created (default: 3)

3

Returns:

Type Description
list[tuple[int, int]]

list of (dim, tag) tuples of the top entities of an extrusion operation

Source code in fiqus/geom_generators/GeometryPancake3D.py
def getExtrusionTop(self, dim=3):
    """
    Returns the top surfaces, lines, or points of an extrusion operation if the
    dimTags object contains the tags of an extrusion operation.
    gmsh.model.occ.extrusion() function returns all the entities that are created
    by the extrusion as a dim tags list. The first element is always the top
    surface, the second is the volume. However, when more than one surface is
    extruded, extracting the top surfaces is not trivial. This function returns
    the top surfaces of an extrusion operation. It does that by finding the entities
    right before the volume entities. If dim is 2, the function will return the top
    curves of an extrusion operation. If dim is 1, the function will return the top
    points of an extrusion.

    :param dim: dimension of the entity that is being created (default: 3)
    :type dim: int, optional
    :return: list of (dim, tag) tuples of the top entities of an extrusion operation
    :rtype: list[tuple[int, int]]
    """

    topSurfaces = []
    for index, dimTag in enumerate(self.allDimTags):
        if dimTag[0] == dim:
            topSurfaces.append(self.allDimTags[index - 1])

    return topSurfaces

getTags(dim, forPhysicalGroup=False)

Returns the stored list of tags with a specific dimension.

Parameters:

Name Type Description Default
dim int

dimension of the tags to be returned

required

Returns:

Type Description
list[int]

list of tags

Source code in fiqus/geom_generators/GeometryPancake3D.py
def getTags(self, dim: int, forPhysicalGroup=False) -> List[int]:
    """
    Returns the stored list of tags with a specific dimension.

    :param dim: dimension of the tags to be returned
    :type dim: int
    :return: list of tags
    :rtype: list[int]
    """
    if forPhysicalGroup:
        return [v[1] for v in self.dimTagsForPG[dim]]
    else:
        return [v[1] for v in self.dimTags[dim]]

storageUpdateRequired(func)

A decorator for dimTags class. It will update the dimTagsStorage class if the save option is True and the decorated function is called. Use this decorator for every function that changes the dimTags instance so that the storage gets updated.

Parameters:

Name Type Description Default
func function

function to be decorated

required

Returns:

Type Description
function

decorated function

Source code in fiqus/geom_generators/GeometryPancake3D.py
def storageUpdateRequired(func):
    """
    A decorator for dimTags class. It will update the dimTagsStorage class if the
    save option is True and the decorated function is called. Use this decorator
    for every function that changes the dimTags instance so that the storage gets
    updated.

    :param func: function to be decorated
    :type func: function
    :return: decorated function
    :rtype: function
    """

    def wrapper(self, *args, **kwargs):
        func(self, *args, **kwargs)

        if self.save:
            # Update the dimTagsStorage:
            dimTagsStorage.updateDimTags(self)

    return wrapper

dimTagsStorage

This is a global class to store the dimTags of important entities in the model. Every dimTags instance with self.save = True will be stored in this class. Later, the storage will be used to generate the volume information (.vi) file. .vi file will be used for generating the physical regions in the meshing part.

Users should not use this class directly. Instead, they should use the dimTags class. If they assign save = True to the dimTags instance, it will be stored in this class.

This class is a singleton class. It means that there will be only one instance of this class in the whole module. This is done to be able to use the same storage throughout this module. See the singleton design pattern for more information.

Source code in fiqus/geom_generators/GeometryPancake3D.py
class dimTagsStorage:
    """
    This is a global class to store the dimTags of important entities in the model.
    Every dimTags instance with self.save = True will be stored in this class. Later,
    the storage will be used to generate the volume information (*.vi) file. *.vi file
    will be used for generating the physical regions in the meshing part.

    Users should not use this class directly. Instead, they should use the dimTags
    class. If they assign save = True to the dimTags instance, it will be stored in this
    class.

    This class is a singleton class. It means that there will be only one instance of
    this class in the whole module. This is done to be able to use the same storage
    throughout this module. See the singleton design pattern for more information.
    """

    __instance = None
    __dimTagsDict = {}  # Dictionary with the names of the dimTags objects as keys and
    # dimTags objects as values

    def __new__(cls):
        if cls.__instance is None:
            cls.__instance = super().__new__(cls)

        return cls.__instance

    @classmethod
    def updateDimTags(cls, dimTagsObject: dimTags):
        """
        Either adds or updates the dimTags object in the storage.

        :param dimTags: dimTags object to be added or updated.
        :type dimTags: dimTags

        """
        if dimTagsObject.name in cls.__dimTagsDict:
            newDimTags = dimTagsObject + cls.__dimTagsDict[dimTagsObject.name]
            cls.__dimTagsDict[dimTagsObject.name] = newDimTags
        else:
            cls.__dimTagsDict[dimTagsObject.name] = dimTagsObject

    @classmethod
    def getDimTagsObject(cls, names: List[str]):
        """
        Returns the dimTags object with the given names.

        :param names: names of the dimTags objects.
        :type names: list[str]
        :return: dimTags objects with the given name.
        :rtype: list[dimTags]
        """
        if not isinstance(names, list):
            names = [names]

        dimTagsObjects = []
        for name in names:
            if name in cls.__dimTagsDict:
                dimTagsObjects.append(cls.__dimTagsDict[name])

        return dimTagsObjects

    @classmethod
    def getDimTags(cls, names: List[str], dim: int = None) -> List[Tuple[int, int]]:
        """
        Returns the stored list of (dim, tag) tuples with dimension a specific dimenions
        and names. If dim is not specified, all the stored (dim, tag) tuples under the
        given names will be returned.

        :param names: names of the dimTags object that will be returned
        :type names: list[str]
        :param dim: dimension of the (dim, tag) tuples to be returned (default: None).
            If dim is None, all the stored (dim, tag) tuples under the name names will
            be returned.
        :type dim: int, optional
        :return: list of (dim, tag) tuples
        """
        if not isinstance(names, list):
            names = [names]

        dimTagsResult = []
        for name in names:
            dimTagsResult.extend(cls.__dimTagsDict[name].getDimTags(dim))

        return dimTagsResult

    @classmethod
    def getTags(cls, names: List[str], dim: int) -> List[int]:
        """
        Returns the stored list of tags with dimension a specific dimension and names.

        :param names: names of the dimTags objects
        :type names: list[str]
        :param dim: dimension of the tags to be returned
        :type dim: int
        :return: list of tags
        :rtype: list[int]
        """
        dimTags = cls.getDimTags(names, dim)
        tags = [dimTag[1] for dimTag in dimTags]

        return tags

    @classmethod
    def getDimTagsDict(
        cls, forPhysicalGroups=False
    ) -> Dict[str, List[Tuple[int, int]]]:
        """
        Returns a dictionary with the names of the dimTags objects as keys and the
        stored list of (dim, tag) tuples as values. This method is used to generate the
        .vi file. If forPhysicalGroups is True, the dimTags for physical groups will be
        returned instead of the dimTags.

        :param forPhysicalGroups: True if the dimTags for physical groups should be
            returned, False otherwise (default: False)
        :type forPhysicalGroups: bool, optional
        :return: dictionary with the names of the dimTags objects as keys and the
            stored list of (dim, tag) tuples as values
        :rtype: dict[str, list[tuple[int, int]]]
        """
        dictionary = {}
        for name, dimTagsObject in cls.__dimTagsDict.items():
            if dimTagsObject.parentName is not None:
                if dimTagsObject.parentName in dictionary:
                    if forPhysicalGroups:
                        dictionary[dimTagsObject.parentName].extend(
                            dimTagsObject.getDimTagsForPG()
                        )
                    else:
                        dictionary[dimTagsObject.parentName].extend(
                            dimTagsObject.getDimTags()
                        )
                else:
                    if forPhysicalGroups:
                        dictionary[dimTagsObject.parentName] = (
                            dimTagsObject.getDimTagsForPG()
                        )
                    else:
                        dictionary[dimTagsObject.parentName] = (
                            dimTagsObject.getDimTags()
                        )
            if forPhysicalGroups:
                dictionary[name] = dimTagsObject.getDimTagsForPG()
            else:
                dictionary[name] = dimTagsObject.getDimTags()

        return dictionary

    @classmethod
    def getAllStoredDimTags(cls) -> List[Tuple[int, int]]:
        """
        Returns a list of all the stored (dim, tag) tuples, regardless of the name of
        the dimTags object (i.e. all the dimTags objects are merged into one list).

        :return: list of all the stored (dim, tag) tuples.
        :rtype: list[tuple[int, int]]
        """
        AllStoredDimTags = []
        for name, dimTagsObject in cls.__dimTagsDict.items():
            AllStoredDimTags.extend(dimTagsObject.getDimTags())

        return AllStoredDimTags

    @classmethod
    def clear(cls):
        """
        Clears the dimTagsStorage class.


        """
        cls.__instance = None
        cls.__dimTagsDict = (
            {}
        )  # Dictionary with the names of the dimTags objects as keys and

clear() classmethod

Clears the dimTagsStorage class.

Source code in fiqus/geom_generators/GeometryPancake3D.py
@classmethod
def clear(cls):
    """
    Clears the dimTagsStorage class.


    """
    cls.__instance = None
    cls.__dimTagsDict = (
        {}
    )  # Dictionary with the names of the dimTags objects as keys and

getAllStoredDimTags() classmethod

Returns a list of all the stored (dim, tag) tuples, regardless of the name of the dimTags object (i.e. all the dimTags objects are merged into one list).

Returns:

Type Description
list[tuple[int, int]]

list of all the stored (dim, tag) tuples.

Source code in fiqus/geom_generators/GeometryPancake3D.py
@classmethod
def getAllStoredDimTags(cls) -> List[Tuple[int, int]]:
    """
    Returns a list of all the stored (dim, tag) tuples, regardless of the name of
    the dimTags object (i.e. all the dimTags objects are merged into one list).

    :return: list of all the stored (dim, tag) tuples.
    :rtype: list[tuple[int, int]]
    """
    AllStoredDimTags = []
    for name, dimTagsObject in cls.__dimTagsDict.items():
        AllStoredDimTags.extend(dimTagsObject.getDimTags())

    return AllStoredDimTags

getDimTags(names, dim=None) classmethod

Returns the stored list of (dim, tag) tuples with dimension a specific dimenions and names. If dim is not specified, all the stored (dim, tag) tuples under the given names will be returned.

Parameters:

Name Type Description Default
names List[str]

names of the dimTags object that will be returned

required
dim int

dimension of the (dim, tag) tuples to be returned (default: None). If dim is None, all the stored (dim, tag) tuples under the name names will be returned.

None

Returns:

Type Description
List[Tuple[int, int]]

list of (dim, tag) tuples

Source code in fiqus/geom_generators/GeometryPancake3D.py
@classmethod
def getDimTags(cls, names: List[str], dim: int = None) -> List[Tuple[int, int]]:
    """
    Returns the stored list of (dim, tag) tuples with dimension a specific dimenions
    and names. If dim is not specified, all the stored (dim, tag) tuples under the
    given names will be returned.

    :param names: names of the dimTags object that will be returned
    :type names: list[str]
    :param dim: dimension of the (dim, tag) tuples to be returned (default: None).
        If dim is None, all the stored (dim, tag) tuples under the name names will
        be returned.
    :type dim: int, optional
    :return: list of (dim, tag) tuples
    """
    if not isinstance(names, list):
        names = [names]

    dimTagsResult = []
    for name in names:
        dimTagsResult.extend(cls.__dimTagsDict[name].getDimTags(dim))

    return dimTagsResult

getDimTagsDict(forPhysicalGroups=False) classmethod

Returns a dictionary with the names of the dimTags objects as keys and the stored list of (dim, tag) tuples as values. This method is used to generate the .vi file. If forPhysicalGroups is True, the dimTags for physical groups will be returned instead of the dimTags.

Parameters:

Name Type Description Default
forPhysicalGroups bool, optional

True if the dimTags for physical groups should be returned, False otherwise (default: False)

False

Returns:

Type Description
dict[str, list[tuple[int, int]]]

dictionary with the names of the dimTags objects as keys and the stored list of (dim, tag) tuples as values

Source code in fiqus/geom_generators/GeometryPancake3D.py
@classmethod
def getDimTagsDict(
    cls, forPhysicalGroups=False
) -> Dict[str, List[Tuple[int, int]]]:
    """
    Returns a dictionary with the names of the dimTags objects as keys and the
    stored list of (dim, tag) tuples as values. This method is used to generate the
    .vi file. If forPhysicalGroups is True, the dimTags for physical groups will be
    returned instead of the dimTags.

    :param forPhysicalGroups: True if the dimTags for physical groups should be
        returned, False otherwise (default: False)
    :type forPhysicalGroups: bool, optional
    :return: dictionary with the names of the dimTags objects as keys and the
        stored list of (dim, tag) tuples as values
    :rtype: dict[str, list[tuple[int, int]]]
    """
    dictionary = {}
    for name, dimTagsObject in cls.__dimTagsDict.items():
        if dimTagsObject.parentName is not None:
            if dimTagsObject.parentName in dictionary:
                if forPhysicalGroups:
                    dictionary[dimTagsObject.parentName].extend(
                        dimTagsObject.getDimTagsForPG()
                    )
                else:
                    dictionary[dimTagsObject.parentName].extend(
                        dimTagsObject.getDimTags()
                    )
            else:
                if forPhysicalGroups:
                    dictionary[dimTagsObject.parentName] = (
                        dimTagsObject.getDimTagsForPG()
                    )
                else:
                    dictionary[dimTagsObject.parentName] = (
                        dimTagsObject.getDimTags()
                    )
        if forPhysicalGroups:
            dictionary[name] = dimTagsObject.getDimTagsForPG()
        else:
            dictionary[name] = dimTagsObject.getDimTags()

    return dictionary

getDimTagsObject(names) classmethod

Returns the dimTags object with the given names.

Parameters:

Name Type Description Default
names List[str]

names of the dimTags objects.

required

Returns:

Type Description
list[dimTags]

dimTags objects with the given name.

Source code in fiqus/geom_generators/GeometryPancake3D.py
@classmethod
def getDimTagsObject(cls, names: List[str]):
    """
    Returns the dimTags object with the given names.

    :param names: names of the dimTags objects.
    :type names: list[str]
    :return: dimTags objects with the given name.
    :rtype: list[dimTags]
    """
    if not isinstance(names, list):
        names = [names]

    dimTagsObjects = []
    for name in names:
        if name in cls.__dimTagsDict:
            dimTagsObjects.append(cls.__dimTagsDict[name])

    return dimTagsObjects

getTags(names, dim) classmethod

Returns the stored list of tags with dimension a specific dimension and names.

Parameters:

Name Type Description Default
names List[str]

names of the dimTags objects

required
dim int

dimension of the tags to be returned

required

Returns:

Type Description
list[int]

list of tags

Source code in fiqus/geom_generators/GeometryPancake3D.py
@classmethod
def getTags(cls, names: List[str], dim: int) -> List[int]:
    """
    Returns the stored list of tags with dimension a specific dimension and names.

    :param names: names of the dimTags objects
    :type names: list[str]
    :param dim: dimension of the tags to be returned
    :type dim: int
    :return: list of tags
    :rtype: list[int]
    """
    dimTags = cls.getDimTags(names, dim)
    tags = [dimTag[1] for dimTag in dimTags]

    return tags

updateDimTags(dimTagsObject) classmethod

Either adds or updates the dimTags object in the storage.

Parameters:

Name Type Description Default
dimTags dimTags

dimTags object to be added or updated.

required
Source code in fiqus/geom_generators/GeometryPancake3D.py
@classmethod
def updateDimTags(cls, dimTagsObject: dimTags):
    """
    Either adds or updates the dimTags object in the storage.

    :param dimTags: dimTags object to be added or updated.
    :type dimTags: dimTags

    """
    if dimTagsObject.name in cls.__dimTagsDict:
        newDimTags = dimTagsObject + cls.__dimTagsDict[dimTagsObject.name]
        cls.__dimTagsDict[dimTagsObject.name] = newDimTags
    else:
        cls.__dimTagsDict[dimTagsObject.name] = dimTagsObject

direction

Bases: Enum

A class to specify direction easily.

Source code in fiqus/geom_generators/GeometryPancake3D.py
class direction(Enum):
    """
    A class to specify direction easily.
    """

    ccw = 0
    cw = 1

pancakeCoilsWithAir

A class to create Pancake3D coil. With this class, any number of pancake coils stack can be created in GMSH. Moreover, the class also creates some parts of the air volume as well. It creates the inner cylinder air volume and the outer tube air volume. However, the air between the pancake coils is not created. It is created in the gapAir class.

self.fundamentalSurfaces are the surfaces at the bottom of each pancake coil. They are created one by one with self.generateFundamentalSurfaces() method. The first created self.fundamentalSurfaces are the first pancake coil's bottom surfaces. Those surfaces include the outer air shell surface, outer air tube surface, outer terminal's outer tube part, outer terminal's touching part, winding surfaces, contact layer surfaces, inner terminal's touching part, inner terminal's inner tube part, and inner air disc. Terminals are divided into two because they can only be connected with the perfect tubes since each pancake coil is rotated in a different direction.

For the first pancake coil, self.fundamentalSurfaces are extruded downwards to connect the terminal to the end of the geometry at the bottom.

Then self.fundamentalSurfaces are extruded upwards to create the first pancake coil with self.extrudeWindingPart method. The method returns the extrusion's top surfaces, which are saved in the topSurfaces variable.

Then those topSurfaces variable is given to another method, self.extrudeGapPart, and they are further extruded upwards up to the bottom of the next pancake coil. However, only the air tube, air cylinder, and connection terminal (the perfect inner terminal tube or outer terminal tube) are extruded. Otherwise, conformality would be impossible. The gaps are filled with air in gapAir class with fragment operation later. Then the top surfaces are returned by the method and saved in self.contactSurfaces variable.

Then using the self.contactSurfaces, self.generateFundamentalSurfaces method creates the new fundamental surfaces. All the surfaces from the self.contactSurfaces are used in the new self.fundamentalSurfaces variable to avoid surface duplication.

The logic goes until the last topSurfaces are extruded upwards to connect the last terminal to the top of the geometry.

Every pancake coil's rotation direction is different each time. Otherwise, their magnetic fields would neutralize each other.

The first and second pancake coils are connected with the inner terminal. Then the second and the third pancake coils are connected with the outer terminal. And so on.

Parameters:

Name Type Description Default
geometryData

geometry information

required
Source code in fiqus/geom_generators/GeometryPancake3D.py
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880
2881
2882
2883
2884
2885
2886
2887
2888
2889
2890
2891
2892
2893
2894
2895
2896
2897
2898
2899
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
2934
2935
2936
2937
2938
2939
2940
2941
2942
2943
2944
2945
2946
2947
2948
2949
2950
2951
2952
2953
2954
2955
2956
2957
2958
2959
2960
2961
2962
2963
2964
2965
2966
2967
2968
2969
2970
2971
2972
2973
2974
2975
2976
2977
2978
2979
2980
2981
2982
2983
2984
2985
2986
2987
2988
2989
2990
2991
2992
2993
2994
2995
2996
2997
2998
2999
3000
3001
3002
3003
3004
3005
3006
3007
3008
3009
3010
3011
3012
3013
3014
3015
3016
3017
3018
3019
3020
3021
3022
3023
3024
3025
3026
3027
3028
3029
3030
3031
3032
3033
3034
3035
3036
3037
3038
3039
3040
3041
3042
3043
3044
3045
3046
3047
3048
3049
3050
3051
3052
3053
3054
3055
3056
3057
3058
3059
3060
3061
3062
3063
3064
3065
3066
3067
3068
3069
3070
3071
3072
3073
3074
3075
3076
3077
3078
3079
3080
3081
3082
3083
3084
3085
3086
3087
3088
3089
3090
3091
3092
3093
3094
3095
3096
3097
3098
3099
3100
3101
3102
3103
3104
3105
3106
3107
3108
3109
3110
3111
3112
3113
3114
3115
3116
3117
3118
3119
3120
3121
3122
3123
3124
3125
3126
3127
3128
3129
3130
3131
3132
3133
3134
3135
3136
3137
3138
3139
3140
3141
3142
3143
3144
3145
3146
3147
3148
3149
3150
3151
3152
3153
3154
3155
3156
3157
3158
3159
3160
3161
3162
3163
3164
3165
3166
3167
3168
3169
3170
3171
3172
3173
3174
3175
3176
3177
3178
3179
3180
3181
3182
3183
3184
3185
3186
3187
3188
3189
3190
3191
3192
3193
3194
3195
3196
3197
3198
3199
3200
3201
3202
3203
3204
3205
3206
3207
3208
3209
3210
3211
3212
3213
3214
3215
3216
3217
3218
3219
3220
3221
3222
3223
3224
3225
3226
3227
3228
3229
3230
3231
3232
3233
3234
3235
3236
3237
3238
3239
3240
3241
3242
3243
3244
3245
3246
3247
3248
3249
3250
3251
3252
3253
3254
3255
3256
3257
3258
3259
3260
3261
3262
3263
3264
3265
3266
3267
3268
3269
3270
3271
3272
3273
3274
3275
3276
3277
3278
3279
3280
3281
3282
3283
3284
3285
3286
3287
3288
3289
3290
3291
3292
3293
3294
3295
3296
3297
3298
3299
3300
3301
3302
3303
3304
3305
3306
3307
3308
3309
3310
3311
3312
3313
3314
3315
3316
3317
3318
3319
3320
3321
3322
3323
3324
3325
3326
3327
3328
3329
3330
3331
3332
3333
3334
3335
3336
3337
3338
3339
3340
3341
3342
3343
3344
3345
3346
3347
3348
3349
3350
3351
3352
3353
3354
3355
3356
3357
3358
3359
3360
3361
3362
3363
3364
3365
3366
3367
3368
3369
3370
3371
3372
3373
3374
3375
3376
3377
3378
3379
3380
3381
3382
3383
3384
3385
3386
3387
3388
3389
3390
3391
3392
3393
3394
3395
3396
3397
3398
3399
3400
3401
3402
3403
3404
3405
3406
3407
3408
3409
3410
3411
3412
3413
3414
3415
3416
3417
3418
3419
3420
3421
3422
3423
3424
3425
3426
3427
3428
3429
3430
3431
3432
3433
3434
3435
3436
3437
3438
3439
3440
3441
3442
3443
3444
3445
3446
3447
3448
3449
3450
3451
3452
3453
3454
3455
3456
3457
3458
3459
3460
3461
3462
3463
3464
3465
3466
3467
3468
3469
3470
3471
3472
3473
3474
3475
3476
3477
3478
3479
3480
3481
3482
3483
3484
3485
3486
3487
3488
3489
3490
3491
3492
3493
3494
3495
3496
3497
3498
3499
3500
3501
3502
3503
3504
3505
3506
3507
3508
3509
3510
3511
3512
3513
3514
3515
3516
3517
3518
3519
3520
3521
3522
3523
3524
3525
3526
3527
3528
3529
3530
3531
3532
3533
3534
3535
3536
3537
3538
3539
3540
3541
3542
3543
3544
3545
3546
3547
3548
3549
3550
3551
3552
3553
3554
3555
3556
3557
3558
3559
3560
3561
3562
3563
3564
3565
3566
3567
3568
3569
3570
3571
3572
3573
3574
3575
3576
3577
3578
3579
3580
3581
3582
3583
3584
3585
3586
3587
3588
3589
3590
3591
3592
3593
3594
3595
3596
3597
3598
3599
3600
3601
3602
3603
3604
3605
3606
3607
3608
3609
3610
3611
3612
3613
3614
3615
3616
3617
3618
3619
3620
3621
3622
3623
3624
3625
3626
3627
3628
3629
3630
3631
3632
3633
3634
3635
3636
3637
3638
3639
3640
3641
3642
3643
3644
3645
3646
3647
3648
3649
3650
3651
3652
3653
3654
3655
3656
3657
3658
3659
3660
3661
3662
3663
3664
3665
3666
3667
3668
3669
3670
3671
3672
3673
3674
3675
3676
3677
3678
3679
3680
3681
3682
3683
3684
3685
3686
3687
3688
3689
3690
3691
3692
3693
3694
3695
3696
3697
3698
3699
3700
3701
3702
3703
3704
3705
3706
3707
3708
3709
3710
3711
3712
3713
3714
3715
3716
3717
3718
3719
3720
3721
3722
3723
3724
3725
3726
3727
3728
3729
3730
3731
3732
3733
3734
3735
3736
3737
3738
3739
3740
3741
3742
3743
3744
3745
3746
3747
3748
3749
3750
3751
3752
3753
3754
3755
3756
3757
3758
3759
3760
3761
3762
3763
3764
3765
3766
3767
3768
3769
3770
3771
3772
3773
3774
3775
3776
3777
3778
3779
3780
3781
3782
3783
3784
3785
3786
3787
3788
3789
3790
3791
3792
3793
3794
3795
3796
3797
3798
3799
3800
3801
3802
3803
3804
3805
3806
3807
3808
3809
3810
3811
3812
3813
3814
3815
3816
3817
3818
3819
3820
3821
3822
3823
3824
3825
3826
3827
3828
3829
3830
3831
3832
3833
3834
3835
3836
3837
3838
3839
3840
3841
3842
3843
3844
3845
3846
3847
3848
3849
3850
3851
3852
3853
3854
3855
3856
3857
3858
3859
3860
3861
3862
3863
3864
3865
3866
3867
3868
3869
3870
3871
3872
3873
3874
3875
3876
class pancakeCoilsWithAir:
    """
    A class to create Pancake3D coil. With this class, any number of pancake coils stack
    can be created in GMSH. Moreover, the class also creates some parts of the air
    volume as well. It creates the inner cylinder air volume and the outer tube air
    volume. However, the air between the pancake coils is not created. It is created in
    the gapAir class.

    self.fundamentalSurfaces are the surfaces at the bottom of each pancake coil. They
    are created one by one with self.generateFundamentalSurfaces() method. The first
    created self.fundamentalSurfaces are the first pancake coil's bottom surfaces. Those
    surfaces include the outer air shell surface, outer air tube surface, outer
    terminal's outer tube part, outer terminal's touching part, winding surfaces,
    contact layer surfaces, inner terminal's touching part, inner terminal's inner tube
    part, and inner air disc. Terminals are divided into two because they can only be
    connected with the perfect tubes since each pancake coil is rotated in a different
    direction.

    For the first pancake coil, self.fundamentalSurfaces are extruded downwards to
    connect the terminal to the end of the geometry at the bottom.

    Then self.fundamentalSurfaces are extruded upwards to create the first pancake coil
    with self.extrudeWindingPart method. The method returns the extrusion's top
    surfaces, which are saved in the topSurfaces variable.

    Then those topSurfaces variable is given to another method, self.extrudeGapPart, and
    they are further extruded upwards up to the bottom of the next pancake coil.
    However, only the air tube, air cylinder, and connection terminal (the perfect inner
    terminal tube or outer terminal tube) are extruded. Otherwise, conformality would be
    impossible. The gaps are filled with air in gapAir class with fragment operation
    later. Then the top surfaces are returned by the method and saved in
    self.contactSurfaces variable.

    Then using the self.contactSurfaces, self.generateFundamentalSurfaces method creates
    the new fundamental surfaces. All the surfaces from the self.contactSurfaces are
    used in the new self.fundamentalSurfaces variable to avoid surface duplication.

    The logic goes until the last topSurfaces are extruded upwards to connect the last
    terminal to the top of the geometry.

    Every pancake coil's rotation direction is different each time. Otherwise, their
    magnetic fields would neutralize each other.

    The first and second pancake coils are connected with the inner terminal. Then the
    second and the third pancake coils are connected with the outer terminal. And so on.

    :param geometryData: geometry information
    """

    def __init__(self, geometryData, meshData) -> None:
        logger.info("Generating pancake coils has been started.")
        start_time = timeit.default_timer()

        # Data:
        self.geo = geometryData
        self.mesh = meshData

        # ==============================================================================
        # CREATING VOLUME STORAGES STARTS ==============================================
        # ==============================================================================
        # Air shell (they will be empty if shellTransformation == False):
        # For cylinder type:
        self.airShellVolume = dimTags(name=self.geo.ai.shellVolumeName, save=True)

        # For cuboid type:
        self.airShellVolumePart1 = dimTags(
            name=self.geo.ai.shellVolumeName + "-Part1", save=True
        )
        self.airShellVolumePart2 = dimTags(
            name=self.geo.ai.shellVolumeName + "-Part2", save=True
        )
        self.airShellVolumePart3 = dimTags(
            name=self.geo.ai.shellVolumeName + "-Part3", save=True
        )
        self.airShellVolumePart4 = dimTags(
            name=self.geo.ai.shellVolumeName + "-Part4", save=True
        )

        # Outer air tube volume (actually it is not a tube if the air type is cuboid):
        self.outerAirTubeVolume = dimTags(
            name=self.geo.ai.name + "-OuterTube", save=True, parentName=self.geo.ai.name
        )

        # Outer terminal's outer tube part:
        self.outerTerminalTubeVolume = dimTags(
            name=self.geo.ti.o.name + "-Tube", save=True, parentName=self.geo.ti.o.name
        )

        # Outer terminal's volume that touches the winding:
        self.outerTerminalTouchingVolume = dimTags(
            name=self.geo.ti.o.name + "-Touching",
            save=True,
            parentName=self.geo.ti.o.name,
        )

        # Inner terminal's volume that touches the winding:
        self.innerTerminalTouchingVolume = dimTags(
            name=self.geo.ti.i.name + "-Touching",
            save=True,
            parentName=self.geo.ti.i.name,
        )

        # Inner terminal's inner tube part:
        self.innerTerminalTubeVolume = dimTags(
            name=self.geo.ti.i.name + "-Tube", save=True, parentName=self.geo.ti.i.name
        )

        # Transition layers:
        self.innerTransitionNotchVolume = dimTags(
            name="innerTransitionNotch",
            save=True,
        )
        self.outerTransitionNotchVolume = dimTags(
            name="outerTransitionNotch",
            save=True,
        )

        # Inner air cylinder volume:
        self.centerAirCylinderVolume = dimTags(
            name=self.geo.ai.name + "-InnerCylinder",
            save=True,
            parentName=self.geo.ai.name,
        )

        # Top and bottom parts of the air volume:
        self.topAirPancakeWindingExtursionVolume = dimTags(
            name=self.geo.ai.name + "-TopPancakeWindingExtursion",
            save=True,
            parentName=self.geo.ai.name,
        )
        self.topAirPancakeContactLayerExtursionVolume = dimTags(
            name=self.geo.ai.name + "-TopPancakeContactLayerExtursion",
            save=True,
            parentName=self.geo.ai.name,
        )
        self.topAirTerminalsExtrusionVolume = dimTags(
            name=self.geo.ai.name + "-TopTerminalsExtrusion",
            save=True,
            parentName=self.geo.ai.name,
        )
        self.topAirTubeTerminalsExtrusionVolume = dimTags(
            name=self.geo.ai.name + "-TopTubeTerminalsExtrusion",
            save=True,
            parentName=self.geo.ai.name,
        )

        self.bottomAirPancakeWindingExtursionVolume = dimTags(
            name=self.geo.ai.name + "-BottomPancakeWindingExtursion",
            save=True,
            parentName=self.geo.ai.name,
        )
        self.bottomAirPancakeContactLayerExtursionVolume = dimTags(
            name=self.geo.ai.name + "-BottomPancakeContactLayerExtursion",
            save=True,
            parentName=self.geo.ai.name,
        )
        self.bottomAirTerminalsExtrusionVolume = dimTags(
            name=self.geo.ai.name + "-BottomTerminalsExtrusion",
            save=True,
            parentName=self.geo.ai.name,
        )
        self.bottomAirTubeTerminalsExtrusionVolume = dimTags(
            name=self.geo.ai.name + "-BottomTubeTerminalsExtrusion",
            save=True,
            parentName=self.geo.ai.name,
        )

        # Gap air:
        self.gapAirVolume = dimTags(
            name=self.geo.ai.name + "-Gap", save=True, parentName=self.geo.ai.name
        )

        # Create additional/optional volume storages (they might be used in the meshing
        # process):
        self.firstTerminalVolume = dimTags(name=self.geo.ti.firstName, save=True)
        self.lastTerminalVolume = dimTags(name=self.geo.ti.lastName, save=True)

        # ==============================================================================
        # CREATING VOLUME STORAGES ENDS ================================================
        # ==============================================================================

        # self.fundamentalSurfaces is a dictionary of surface dimTags tuples. The keys
        # are the dimTags objects of the corresponding volumes. The values are the
        # dimTags tuples of the surfaces that are used to extrude the volumes. It is
        # created in self.generateFundamentalSurfaces method.
        self.fundamentalSurfaces = {}

        # self.pancakeIndex stores the index of the current pancake coil.
        self.pancakeIndex = 0

        # self.contactSurfaces is a dictionary of surface dimTags tuples. The keys are
        # the dimTags objects of the corresponding volumes. The values are the dimTags
        # tuples of the surfaces that are obtained from the previous extrusion and used
        # for the next extrusion. The same surface is used for the next extrusion to
        # avoid surface duplication. It is created in self.extrudeGapPart and
        # self.extrudeWindingPart methods.
        self.contactSurfaces = {}

        # They will be lists of dimTags objects:
        self.individualWinding = []
        self.individualContactLayer = []

        self.gapAirSurfacesDimTags = []

        for i in range(self.geo.N):
            # Itterate over the number of pancake coils:
            self.individualWinding.append(
                dimTags(
                    name=self.geo.wi.name + str(self.pancakeIndex + 1),
                    save=True,
                    parentName=self.geo.wi.name,
                )
            )
            self.individualContactLayer.append(
                dimTags(
                    name=self.geo.ii.name + str(self.pancakeIndex + 1),
                    save=True,
                    parentName=self.geo.ii.name,
                )
            )

            # Generate the fundamental surfaces:
            self.fundamentalSurfaces = self.generateFundamentalSurfaces()

            # Create gap air or collect the gap air surfaces:
            if i != 0:
                bottomSurfacesDimTags = []
                for key, value in topSurfaces.items():
                    if (
                        key is self.individualWinding[self.pancakeIndex - 1]
                        or key is self.individualContactLayer[self.pancakeIndex - 1]
                        or key is self.outerTerminalTouchingVolume
                        or key is self.innerTerminalTouchingVolume
                        or key is self.innerTransitionNotchVolume
                        or key is self.outerTransitionNotchVolume
                    ):
                        bottomSurfacesDimTags.extend(value)

                topSurfacesDimTags = []
                for key, value in self.fundamentalSurfaces.items():
                    if (
                        key is self.individualWinding[self.pancakeIndex]
                        or key is self.individualContactLayer[self.pancakeIndex]
                        or key is self.outerTerminalTouchingVolume
                        or key is self.innerTerminalTouchingVolume
                        or key is self.innerTransitionNotchVolume
                        or key is self.outerTransitionNotchVolume
                    ):
                        topSurfacesDimTags.extend(value)

                sideSurfacesDimTags = []
                if i % 2 == 1:
                    # Touches it tube and air tube
                    bottomSurfacesDimTags.extend(
                        topSurfaces[self.outerTerminalTubeVolume]
                    )
                    topSurfacesDimTags.extend(
                        self.fundamentalSurfaces[self.outerTerminalTubeVolume]
                    )

                    if self.mesh.ti.structured:
                        lastItTubeVolDimTags = self.innerTerminalTubeVolume.getDimTags(
                            3
                        )[-4:]
                    else:
                        lastItTubeVolDimTags = self.innerTerminalTubeVolume.getDimTags(
                            3
                        )[-1:]

                    lastItTubeSurfsDimTags = gmsh.model.getBoundary(
                        lastItTubeVolDimTags, oriented=False
                    )
                    lastItTubeSideSurfsDimTags = findSurfacesWithNormalsOnXYPlane(
                        lastItTubeSurfsDimTags
                    )
                    sideSurfacesDimTags.extend(
                        findOuterOnes(lastItTubeSideSurfsDimTags)
                    )

                    if self.mesh.ai.structured:
                        lastAirTubeVolDimTags = self.outerAirTubeVolume.getDimTags(3)[
                            -4:
                        ]
                    else:
                        lastAirTubeVolDimTags = self.outerAirTubeVolume.getDimTags(3)[
                            -1:
                        ]
                    lastAirTubeSurfsDimTags = gmsh.model.getBoundary(
                        lastAirTubeVolDimTags, oriented=False
                    )
                    lastAirTubeSurfsDimTags = findSurfacesWithNormalsOnXYPlane(
                        lastAirTubeSurfsDimTags
                    )
                    sideSurfacesDimTags.extend(
                        findOuterOnes(lastAirTubeSurfsDimTags, findInnerOnes=True)
                    )

                else:
                    # Touches ot tube and air cylinder
                    bottomSurfacesDimTags.extend(
                        topSurfaces[self.innerTerminalTubeVolume]
                    )
                    topSurfacesDimTags.extend(
                        self.fundamentalSurfaces[self.innerTerminalTubeVolume]
                    )
                    if self.mesh.ti.structured:
                        lastOtTubeVolDimTags = self.outerTerminalTubeVolume.getDimTags(
                            3
                        )[-4:]
                    else:
                        lastOtTubeVolDimTags = self.outerTerminalTubeVolume.getDimTags(
                            3
                        )[-1:]

                    lastOtTubeSurfsDimTags = gmsh.model.getBoundary(
                        lastOtTubeVolDimTags, oriented=False
                    )
                    lastOtTubeSurfsDimTags = findSurfacesWithNormalsOnXYPlane(
                        lastOtTubeSurfsDimTags
                    )
                    sideSurfacesDimTags.extend(
                        findOuterOnes(lastOtTubeSurfsDimTags, findInnerOnes=True)
                    )

                    if self.mesh.ai.structured:
                        lastAirCylinderVolDimTags = (
                            self.centerAirCylinderVolume.getDimTags(3)[-4:]
                        )
                    else:
                        lastAirCylinderVolDimTags = (
                            self.centerAirCylinderVolume.getDimTags(3)[-1:]
                        )

                    lastAirCylinderSurfsDimTags = gmsh.model.getBoundary(
                        lastAirCylinderVolDimTags, oriented=False
                    )
                    lastAirCylinderSurfsDimTags = findSurfacesWithNormalsOnXYPlane(
                        lastAirCylinderSurfsDimTags
                    )
                    sideSurfacesDimTags.extend(
                        findOuterOnes(lastAirCylinderSurfsDimTags)
                    )

                allGapAirSurfacesDimTags = (
                    bottomSurfacesDimTags + topSurfacesDimTags + sideSurfacesDimTags
                )

                # Technically, since all the boundary surfaces of the gap air volumes
                # are found here, we should be able to create the gap air volumes with
                # addSurfaceLoop and addVolume functions. However, when those are used,
                # Geometry.remove_all_duplicates() will indicate some
                # duplicates/ill-shaped geometry entities. The indication is
                # gmsh.model.occ.remove_all_duplicates() will change the geometry
                # (delete some volumes and create new ones), and I have always thought
                # that means there are big errors in the geometry and that geometry
                # should not be used.

                # Alternatively, using these surface tags, the gap air can be created
                # with fragment operations as well. Geometry.remove_all_duplicates()
                # will tell everything is fine when the fragment operation is used.

                # However, I checked manually as well, the way I am using the
                # addSurfaceLoop and addVolume should definitely work (because the end
                # result is the same with fragments), and I think it is a gmsh/occ
                # related problem. In the end, I realized creating the gap air with
                # addSurfaceLoop and addVolume won't even affect the mesh, and
                # everything seems conformal and nice. Since the fragment operation
                # is also very slow, I decided to use addSurfaceLoop and addVolume them.
                # However, I keep it as an option so that if the user feels something
                # funny about the geometry, the gap air can be created with fragment
                # operations as well.

                if not self.geo.ai.fragment:
                    allGapAirSurfacesTags = [
                        dimTag[1] for dimTag in allGapAirSurfacesDimTags
                    ]
                    surfaceLoop = gmsh.model.occ.addSurfaceLoop(allGapAirSurfacesTags)
                    volume = gmsh.model.occ.addVolume([surfaceLoop])
                    self.gapAirVolume.add([(3, volume)])

                else:
                    # Save the surface tags for a fast fragment operation:
                    self.gapAirSurfacesDimTags.append(allGapAirSurfacesDimTags)

            # self.extrudeSurfaces uses self.fundamentalSurfaces for extrusion and adds
            # the new volumes to the dimTags objects and returns the dictionary of the
            # new top surfaces. The new top surfaces then will be used in extrudeGapPart
            # method.
            topSurfaces = self.extrudeWindingPart()

            if i == 0:
                # If it is the first pancake coil, fundemental surfaces are extruded
                # downwards to create the bottom air volume and terminal volume.
                _ = self.extrudeGapPart(
                    self.fundamentalSurfaces,
                    -self.geo.ai.margin,
                    terminalDimTagsObject=self.outerTerminalTubeVolume,
                    firstTerminal=True,
                )

            if not i == self.geo.N - 1:
                # If it is not the last pancake coil, extrude the terminal surface to
                # create the next contactTerminalSurface and store the new volume in the
                # corresponding dimTags object.
                self.contactSurfaces = self.extrudeGapPart(topSurfaces)

            else:
                # If it is the last pancake coil, extrude the terminal surface all the
                # way up to the top and store the new volume in the corresponding
                # dimTags object.
                _ = self.extrudeGapPart(
                    topSurfaces,
                    self.geo.ai.margin,
                    lastTerminal=True,
                )
            self.pancakeIndex = self.pancakeIndex + 1

        # Create the gap air volume:
        if self.geo.ai.fragment and self.geo.N > 1:
            self.generateGapAirWithFragment(self.gapAirSurfacesDimTags)

        logger.info(
            "Generating pancake coils has been finished in"
            f" {timeit.default_timer() - start_time:.2f} s."
        )

    def generateFundamentalSurfaces(self):
        """
        Generates the inner air, outer air, winding, contact layer, and terminal surfaces
        of the current pancake coil and returns them. It finds the z coordinate of the
        surfaces and the direction of the pancake coil, depending on the pancake index.

        :return: list of dimTags that contains fundamental surfaces
        :rtype: list[tuple[int, int]]
        """
        fundamentalSurfaces = {}

        # Select the direction of the spiral:
        if self.pancakeIndex % 2 == 0:
            spiralDirection = direction.ccw
        else:
            spiralDirection = direction.cw

        # Calculate the z coordinate of the surfaces:
        z = (
            -self.geo.ai.h / 2
            + self.geo.ai.margin
            + self.pancakeIndex * (self.geo.wi.h + self.geo.gap)
        )

        # Create the winding and contact layer surface:
        surface = spiralSurface(
            self.geo.wi.r_i,
            self.geo.wi.t,
            self.geo.ii.t,
            self.geo.wi.N,
            z,
            self.geo.wi.theta_i,
            self.geo.ti.transitionNotchAngle,
            spiralDirection,
            thinShellApproximation=self.geo.ii.tsa,
        )

        # Save the surface tags (if TSA, contactLayerSurfaceTags will be empty):
        fundamentalSurfaces[self.individualWinding[self.pancakeIndex]] = [
            (2, tag) for tag in surface.surfaceTags
        ]
        fundamentalSurfaces[self.individualContactLayer[self.pancakeIndex]] = [
            (2, tag) for tag in surface.contactLayerSurfaceTags
        ]

        if self.geo.ai.type == "cylinder":
            outerAirSurf = outerAirSurface(
                self.geo.ai.r,
                self.geo.ti.o.r,
                type="cylinder",
                divideIntoFourParts=self.mesh.ai.structured,
                divideTerminalPartIntoFourParts=self.mesh.ti.structured,
            )
        elif self.geo.ai.type == "cuboid":
            outerAirSurf = outerAirSurface(
                self.geo.ai.a / 2,
                self.geo.ti.o.r,
                type="cuboid",
                divideIntoFourParts=self.mesh.ai.structured,
                divideTerminalPartIntoFourParts=self.mesh.ti.structured,
            )

        outerTerminalSurf = outerTerminalSurface(
            self.geo.ti.o.r,
            self.geo.ti.o.t,
            divideIntoFourParts=self.mesh.ti.structured,
        )
        innerTerminalSurf = innerTerminalSurface(
            self.geo.ti.i.r,
            self.geo.ti.i.t,
            divideIntoFourParts=self.mesh.ti.structured,
        )
        innerAirSurf = innerAirSurface(
            self.geo.ti.i.r,
            divideIntoFourParts=self.mesh.ai.structured,
            divideTerminalPartIntoFourParts=self.mesh.ti.structured,
        )

        if self.contactSurfaces:
            # If self.contactSurfaces is not empty, it means that it is not the
            # first pancake coil. In that case, contactSurfaces should be used to
            # avoid surface duplication.

            # Create outer air:
            outerAirPSDimTags = self.contactSurfaces[self.outerAirTubeVolume]
            outerAirPSTags = [dimTag[1] for dimTag in outerAirPSDimTags]
            if self.geo.ai.shellTransformation:
                if self.geo.ai.type == "cuboid":
                    cuboidShellDimTags1 = self.contactSurfaces[self.airShellVolumePart1]
                    cuboidShellTags1 = [dimTag[1] for dimTag in cuboidShellDimTags1]
                    cuboidShellDimTags2 = self.contactSurfaces[self.airShellVolumePart2]
                    cuboidShellTags2 = [dimTag[1] for dimTag in cuboidShellDimTags2]
                    cuboidShellDimTags3 = self.contactSurfaces[self.airShellVolumePart3]
                    cuboidShellTags3 = [dimTag[1] for dimTag in cuboidShellDimTags3]
                    cuboidShellDimTags4 = self.contactSurfaces[self.airShellVolumePart4]
                    cuboidShellTags4 = [dimTag[1] for dimTag in cuboidShellDimTags4]
                    outerAirSurf.setPrecreatedSurfaceTags(
                        outerAirPSTags,
                        cuboidShellTags1=cuboidShellTags1,
                        cuboidShellTags2=cuboidShellTags2,
                        cuboidShellTags3=cuboidShellTags3,
                        cuboidShellTags4=cuboidShellTags4,
                    )
                elif self.geo.ai.type == "cylinder":
                    cylinderShellDimTags = self.contactSurfaces[self.airShellVolume]
                    cylinderShellTags = [dimTag[1] for dimTag in cylinderShellDimTags]
                    outerAirSurf.setPrecreatedSurfaceTags(
                        outerAirPSTags,
                        cylinderShellTags=cylinderShellTags,
                    )
            else:
                outerAirSurf.setPrecreatedSurfaceTags(outerAirPSTags)

            # Create inner air:
            innerAirPSDimTags = self.contactSurfaces[self.centerAirCylinderVolume]
            innerAirPSTags = [dimTag[1] for dimTag in innerAirPSDimTags]
            innerAirSurf.setPrecreatedSurfaceTags(innerAirPSTags)

            if self.pancakeIndex % 2 == 0:
                # In this case, we should create all the surfaces for the inner terminal
                # but not for outer terminal. Because it is a pancake coil with an even
                # index (self.pancakeIndex%2==0) which means that it is connected to the
                # previous pancake coil with outer terminal and the outer terminal
                # surface is ready (extruded before).

                # Create outer terminal:
                outerTerminalTubePSDimTags = self.contactSurfaces[
                    self.outerTerminalTubeVolume
                ]
                outerTerminalTubePSTags = [
                    dimTag[1] for dimTag in outerTerminalTubePSDimTags
                ]
                outerTerminalSurf.createWithWindingAndTubeTags(
                    surface, outerTerminalTubePSTags, self.pancakeIndex
                )

                # Create inner terminal:
                innerTerminalSurf.createWithInnerAirAndWinding(
                    innerAirSurf, surface, self.pancakeIndex
                )

            else:
                # In this case, we should create all the surfaces for the outer terminal
                # but not for inner terminal. Because it is a pancake coil with an odd
                # index (self.pancakeIndex%2==1) which means that it is connected to the
                # previous pancake coil with inner terminal and the inner terminal
                # surface is ready (extruded before).

                # Create outer terminal:
                outerTerminalSurf.createWithOuterAirAndWinding(
                    outerAirSurf, surface, self.pancakeIndex
                )

                # Create inner terminal:
                innerTerminalTubePSDimTags = self.contactSurfaces[
                    self.innerTerminalTubeVolume
                ]
                innerTerminalTubePSTags = [
                    dimTag[1] for dimTag in innerTerminalTubePSDimTags
                ]
                innerTerminalSurf.createWithWindingAndTubeTags(
                    surface, innerTerminalTubePSTags, self.pancakeIndex
                )

        else:
            # If self.contactSurfaces is empty, it means that it is the first pancake
            # coil. In that case, the surfaces should be created from scratch.

            if self.geo.ai.shellTransformation:
                if self.geo.ai.type == "cuboid":
                    outerAirSurf.createFromScratch(
                        z,
                        shellTransformation=True,
                        shellRadius=self.geo.ai.shellSideLength / 2,
                    )
                else:
                    outerAirSurf.createFromScratch(
                        z,
                        shellTransformation=True,
                        shellRadius=self.geo.ai.shellOuterRadius,
                    )
            else:
                outerAirSurf.createFromScratch(z)

            innerAirSurf.createFromScratch(z)
            outerTerminalSurf.createWithOuterAirAndWinding(
                outerAirSurf, surface, self.pancakeIndex
            )
            innerTerminalSurf.createWithInnerAirAndWinding(
                innerAirSurf, surface, self.pancakeIndex
            )

        # Save the surface tags:
        fundamentalSurfaces[self.outerAirTubeVolume] = [
            (2, tag) for tag in outerAirSurf.surfaceTags
        ]

        fundamentalSurfaces[self.centerAirCylinderVolume] = [
            (2, tag) for tag in innerAirSurf.surfaceTags
        ]

        fundamentalSurfaces[self.outerTerminalTubeVolume] = [
            (2, tag) for tag in outerTerminalSurf.tubeSurfaceTags
        ]
        fundamentalSurfaces[self.outerTerminalTouchingVolume] = [
            (2, tag) for tag in outerTerminalSurf.nontubeSurfaceTags
        ]

        fundamentalSurfaces[self.innerTerminalTubeVolume] = [
            (2, tag) for tag in innerTerminalSurf.tubeSurfaceTags
        ]
        fundamentalSurfaces[self.innerTerminalTouchingVolume] = [
            (2, tag) for tag in innerTerminalSurf.nontubeSurfaceTags
        ]
        fundamentalSurfaces[self.innerTransitionNotchVolume] = [
            (2, tag) for tag in surface.innerNotchSurfaceTags
        ]
        fundamentalSurfaces[self.outerTransitionNotchVolume] = [
            (2, tag) for tag in surface.outerNotchSurfaceTags
        ]

        if self.geo.ai.shellTransformation:
            if self.geo.ai.type == "cuboid":
                fundamentalSurfaces[self.airShellVolumePart1] = [
                    (2, tag) for tag in outerAirSurf.shellTagsPart1
                ]
                fundamentalSurfaces[self.airShellVolumePart2] = [
                    (2, tag) for tag in outerAirSurf.shellTagsPart2
                ]
                fundamentalSurfaces[self.airShellVolumePart3] = [
                    (2, tag) for tag in outerAirSurf.shellTagsPart3
                ]
                fundamentalSurfaces[self.airShellVolumePart4] = [
                    (2, tag) for tag in outerAirSurf.shellTagsPart4
                ]
            elif self.geo.ai.type == "cylinder":
                fundamentalSurfaces[self.airShellVolume] = [
                    (2, tag) for tag in outerAirSurf.shellTags
                ]

        return fundamentalSurfaces

    def extrudeGapPart(
        self,
        surfacesDict,
        tZ: float = None,
        terminalDimTagsObject: dimTags = None,
        firstTerminal=False,
        lastTerminal=False,
    ):
        """
        Extrudes the given surfaces dimTags dictionary to a given height (tZ) and adds
        the created volumes to the corresponding dictionary keys (dimTags objects). It
        returns the extrusion's top surfaces as a dictionary again, where the keys are
        the corresponding dimTagsObjects and the values are the dimTags of the surfaces.

        If tZ is not given, then it is set to the gap height (self.geo.gap). This is the
        default value used for connecting the pancake coils. Only for the creation of
        the first and the last pancake coils different tZ values are used.

        If terminalDimTagsObject is not given, then the created volume is added
        automatically to the innerTerminalVolume or outerTerminalVolume dimTags object,
        depending on the value of self.pancakeIndex. However, giving
        terminalDimTagsObject is necessary for creating the first and the last terminal.
        Otherwise, finding out the correct terminal dimTagsObject would be very
        challenging.

        :param surfaces: the surface dimTag dictionary to be extruded. The keys are the
                    dimTags objects and the values are the dimTags of the surfaces. The
                    keys are used to easily add the corresponding volumes to the correct
                    dimTags objects
        :type surfaces: dict[dimTags, list[tuple[int, int]]]
        :param tZ: the height of the extrusion
        :type tZ: float, optional
        :param terminalDimTagsObject: the dimTags object of the terminal to be extruded
        :type terminalDimTagsObject: dimTags, optional
        :return: top surfaces of the extrusion as a dictionary where the keys are the
                    dimTags objects and the values are the dimTags of the surfaces
        :rtype: dict[dimTags, list[tuple[int, int]]]
        """
        bottomPart = False
        topPart = False
        if tZ is None:
            tZ = self.geo.gap
        elif tZ < 0:
            bottomPart = True
        elif tZ > 0:
            topPart = True

        if terminalDimTagsObject is None:
            # terminalDimTagsObject needs to be given for the first terminal that is
            # extruded downwards.
            if self.pancakeIndex % 2 == 0:
                terminalDimTagsObject = self.innerTerminalTubeVolume
            else:
                terminalDimTagsObject = self.outerTerminalTubeVolume

        # if terminalDimTagsObject is self.innerTerminalVolume:
        #     otherTerminal = self.outerTerminalVolume
        # else:
        #     otherTerminal = self.innerTerminalVolume

        # Create the list of surfaces to be extruded:
        listOfDimTags = []
        listOfDimTagsObjects = []
        listOfDimTagsForTopSurfaces = []
        if topPart:
            # Then in this case, most of the surfaces should be added to the air volumes
            # instead of the terminal, winding, and contact layer volumes.
            for key, dimTagsList in surfacesDict.items():
                if key is self.individualWinding[self.pancakeIndex]:
                    dimTagsObjects = [self.topAirPancakeWindingExtursionVolume] * len(
                        dimTagsList
                    )
                elif key is self.individualContactLayer[self.pancakeIndex]:
                    dimTagsObjects = [
                        self.topAirPancakeContactLayerExtursionVolume
                    ] * len(dimTagsList)
                elif (
                    key is terminalDimTagsObject
                    or key is self.airShellVolume
                    or key is self.airShellVolumePart1
                    or key is self.airShellVolumePart2
                    or key is self.airShellVolumePart3
                    or key is self.airShellVolumePart4
                    or key is self.outerAirTubeVolume
                    or key is self.centerAirCylinderVolume
                ):
                    dimTagsObjects = [key] * len(dimTagsList)
                else:
                    # key is self.outerTerminalTouchingVolume
                    # or key is self.innerTerminalTouchingVolume
                    # or key is (other terminal's tube volume)
                    dimTagsObjects = [self.topAirTerminalsExtrusionVolume] * len(
                        dimTagsList
                    )
                    if (
                        key is self.innerTerminalTubeVolume
                        or key is self.outerTerminalTubeVolume
                    ):
                        dimTagsObjects = [
                            self.topAirTubeTerminalsExtrusionVolume
                        ] * len(dimTagsList)

                listOfDimTagsForTopSurfaces = listOfDimTagsForTopSurfaces + [key] * len(
                    dimTagsList
                )
                listOfDimTags = listOfDimTags + dimTagsList
                listOfDimTagsObjects = listOfDimTagsObjects + dimTagsObjects
        elif bottomPart:
            # Then in this case, most of the surfaces should be added to the air volumes
            # instead of the terminal, winding, and contact layer volumes.
            for key, dimTagsList in surfacesDict.items():
                if key is self.individualWinding[self.pancakeIndex]:
                    dimTagsObjects = [
                        self.bottomAirPancakeWindingExtursionVolume
                    ] * len(dimTagsList)
                elif key is self.individualContactLayer[self.pancakeIndex]:
                    dimTagsObjects = [
                        self.bottomAirPancakeContactLayerExtursionVolume
                    ] * len(dimTagsList)
                elif (
                    key is terminalDimTagsObject
                    or key is self.airShellVolume
                    or key is self.airShellVolumePart1
                    or key is self.airShellVolumePart2
                    or key is self.airShellVolumePart3
                    or key is self.airShellVolumePart4
                    or key is self.outerAirTubeVolume
                    or key is self.centerAirCylinderVolume
                ):
                    dimTagsObjects = [key] * len(dimTagsList)
                else:
                    # key is self.outerTerminalTouchingVolume
                    # or key is self.innerTerminalTouchingVolume
                    # or key is (other terminal's tube volume)
                    dimTagsObjects = [self.bottomAirTerminalsExtrusionVolume] * len(
                        dimTagsList
                    )
                    if (
                        key is self.innerTerminalTubeVolume
                        or key is self.outerTerminalTubeVolume
                    ):
                        dimTagsObjects = [
                            self.bottomAirTubeTerminalsExtrusionVolume
                        ] * len(dimTagsList)

                listOfDimTagsForTopSurfaces = listOfDimTagsForTopSurfaces + [key] * len(
                    dimTagsList
                )
                listOfDimTags = listOfDimTags + dimTagsList
                listOfDimTagsObjects = listOfDimTagsObjects + dimTagsObjects
        else:
            for key, dimTagsList in surfacesDict.items():
                if (
                    key is self.outerAirTubeVolume
                    or key is self.centerAirCylinderVolume
                    or key is self.airShellVolume
                    or key is self.airShellVolumePart1
                    or key is self.airShellVolumePart2
                    or key is self.airShellVolumePart3
                    or key is self.airShellVolumePart4
                    or key is terminalDimTagsObject
                ):
                    dimTagsObjects = [key] * len(dimTagsList)

                    listOfDimTags = listOfDimTags + dimTagsList
                    listOfDimTagsObjects = listOfDimTagsObjects + dimTagsObjects

            listOfDimTagsForTopSurfaces = listOfDimTagsObjects

        extrusionResult = dimTags()
        extrusionResult.add(gmsh.model.occ.extrude(listOfDimTags, 0, 0, tZ))

        # Add the created volumes to the corresponding dimTags objects:
        volumeDimTags = extrusionResult.getDimTags(3)
        for i, volumeDimTag in enumerate(volumeDimTags):
            listOfDimTagsObjects[i].add(volumeDimTag)

        if firstTerminal:
            self.firstTerminalVolume.add(terminalDimTagsObject.getDimTags(3))
        elif lastTerminal:
            self.lastTerminalVolume.add(terminalDimTagsObject.getDimTags(3))

        topSurfacesDimTags = extrusionResult.getExtrusionTop()
        topSurfacesDict = {}
        for i, topSurfaceDimTag in enumerate(topSurfacesDimTags):
            if listOfDimTagsObjects[i] in topSurfacesDict:
                topSurfacesDict[listOfDimTagsForTopSurfaces[i]].append(topSurfaceDimTag)
            else:
                topSurfacesDict[listOfDimTagsForTopSurfaces[i]] = [topSurfaceDimTag]

        return topSurfacesDict

    def extrudeWindingPart(self):
        """
        Extrudes all the fundamental surfaces of the pancake coil by self.geo.wi.h and
        returns the next connection terminal's top surface dimTag, and other air dimTags
        in a dictionary so that they can be further extruded.

        :return: dictionary of top surfaces where the keys are the dimTags objects and
                    the values are the dimTags of the surfaces
        :rtype: dict[dimTags, list[tuple[int, int]]]
        """
        # Create the list of surfaces to be extruded:
        listOfDimTags = []
        listOfDimTagsObjects = []
        for key, dimTagsList in self.fundamentalSurfaces.items():
            dimTagsObjects = [key] * len(dimTagsList)

            listOfDimTags = listOfDimTags + dimTagsList
            listOfDimTagsObjects = listOfDimTagsObjects + dimTagsObjects

        # Extrude the fundamental surfaces:
        extrusionResult = dimTags()
        extrusionResult.add(gmsh.model.occ.extrude(listOfDimTags, 0, 0, self.geo.wi.h))

        # Add the created volumes to the corresponding dimTags objects:
        volumes = extrusionResult.getDimTags(3)
        for i, volumeDimTag in enumerate(volumes):
            listOfDimTagsObjects[i].add(volumeDimTag)

        if self.pancakeIndex == 0:
            # Note the first pancake (sometimes useful for creating regions in the
            # meshing part):
            for i, volumeDimTag in enumerate(volumes):
                if listOfDimTagsObjects[i].parentName == self.geo.ti.o.name:
                    self.firstTerminalVolume.add(volumeDimTag)

        # Not elif! Because the first pancake coil is also the last pancake coil if
        # there is only one pancake coil.
        if self.pancakeIndex == self.geo.N - 1:
            # Note the last pancake (sometimes useful for creating regions in the
            # meshing part):
            for i, volumeDimTag in enumerate(volumes):
                if (
                    self.pancakeIndex % 2 == 1
                    and listOfDimTagsObjects[i].parentName == self.geo.ti.o.name
                ):
                    self.lastTerminalVolume.add(volumeDimTag)
                elif (
                    self.pancakeIndex % 2 == 0
                    and listOfDimTagsObjects[i].parentName == self.geo.ti.i.name
                ):
                    self.lastTerminalVolume.add(volumeDimTag)

        # Return the top surfaces:
        # Add the created top surfaces to a new dictionary:
        topSurfacesDimTags = extrusionResult.getExtrusionTop()
        topSurfaces = {}
        for i, topSurfaceDimTag in enumerate(topSurfacesDimTags):
            if listOfDimTagsObjects[i] in topSurfaces:
                topSurfaces[listOfDimTagsObjects[i]].append(topSurfaceDimTag)
            else:
                topSurfaces[listOfDimTagsObjects[i]] = [topSurfaceDimTag]

        return topSurfaces

    def generateGapAirWithFragment(
        self, gapAirSurfacesDimTags: List[List[Tuple[int, int]]]
    ):
        """
        A class to fill the gap between the multiple pancake coils with air. First, it
        creates a dummy cylinder with the same radius as the outer terminal's outer
        radius. Then using gapAirSurfacesDimTags, gmsh.model.occ.fragment() operation is
        applied to the dummy cylinder volume in a for loop to create the gap air
        volumes. After each fragment operation, one of the volumes created is removed
        because it is the solid volume which is the combination of windings,
        contact layers, and terminals. In the end, dummy cylinder is removed as well.


        WARNING:
        Currently, this method doesn't work.

        :param geometry: geometry information
        :param pancakeCoils: pancakeCoilsWithAir object
        :type pancakeCoils: pancakeCoilsWithAir
        """
        logger.info("Generating gap air has been started.")
        start_time = timeit.default_timer()

        # Create the dummy air volume:
        dummyAir = gmsh.model.occ.addCylinder(
            0,
            0,
            -self.geo.ai.h / 2,
            0,
            0,
            self.geo.ai.h,
            self.geo.ti.o.r,
        )

        toBeDeletedDimTags = []
        gapAirVolumesCurrentDimTags = []
        for i in range(len(gapAirSurfacesDimTags)):
            # Get the outer surfaces of the pancake coils for cutting the pancake coils
            # from the dummy air. The outer surfaces are used instead of pancake volumes
            # to reduce the amount of work for gmsh. It makes it significantly faster.
            # if len(gapAirSurfacesDimTags[i]) !=12:
            fragmentResults = gmsh.model.occ.fragment(
                [(3, dummyAir)],
                gapAirSurfacesDimTags[i],
                removeObject=False,
                removeTool=False,
            )
            fragmentVolumeResultsDimTags = fragmentResults[1][0]
            toBeDeletedDimTags.append(fragmentVolumeResultsDimTags[0])
            gapAirVolumesCurrentDimTags.append(fragmentVolumeResultsDimTags[1])

        toBeDeletedDimTags.append((3, dummyAir))
        # Fragmnet operation both creates the air volume and solid pancake coils volume
        # because the surfaces are used for cutting. Therefore, the solid pancake coils
        # volume should be removed from the fragment results:
        gmsh.model.occ.remove(toBeDeletedDimTags)

        # Add results to the air volume storage. After the geometry is saves as a .brep
        # file, and loaded back, the gaps between the tags are avoided by moving the
        # the other tags. Therefore, this is how the tags are stored:
        toBeDeletedTags = [dimTag[1] for dimTag in toBeDeletedDimTags]
        volumeTagsStart = min(toBeDeletedTags)
        numberOfGapAirVolumes = len(gapAirVolumesCurrentDimTags)
        gapAirVolumesToBeSaved = [
            (3, volumeTagsStart + i) for i in range(numberOfGapAirVolumes)
        ]

        # For debugging purposes, physical groups are being created in the geometry
        # generation process as well. Normally, it us done during meshing because BREP
        # files cannot store physical groups. Since the tags above (airVolumes) will be
        # valid after only saving the geometry as a BREP file and loading it back, the
        # current tags are given to the airVolume.add() method as well. This is done to
        # be able to create the correct physical group.
        self.gapAirVolume.add(
            dimTagsList=gapAirVolumesToBeSaved,
            dimTagsListForPG=gapAirVolumesCurrentDimTags,
        )

        logger.info(
            "Generating gap air has been finished in"
            f" {timeit.default_timer() - start_time:.2f} s."
        )

extrudeGapPart(surfacesDict, tZ=None, terminalDimTagsObject=None, firstTerminal=False, lastTerminal=False)

Extrudes the given surfaces dimTags dictionary to a given height (tZ) and adds the created volumes to the corresponding dictionary keys (dimTags objects). It returns the extrusion's top surfaces as a dictionary again, where the keys are the corresponding dimTagsObjects and the values are the dimTags of the surfaces.

If tZ is not given, then it is set to the gap height (self.geo.gap). This is the default value used for connecting the pancake coils. Only for the creation of the first and the last pancake coils different tZ values are used.

If terminalDimTagsObject is not given, then the created volume is added automatically to the innerTerminalVolume or outerTerminalVolume dimTags object, depending on the value of self.pancakeIndex. However, giving terminalDimTagsObject is necessary for creating the first and the last terminal. Otherwise, finding out the correct terminal dimTagsObject would be very challenging.

Parameters:

Name Type Description Default
surfaces dict[dimTags, list[tuple[int, int]]]

the surface dimTag dictionary to be extruded. The keys are the dimTags objects and the values are the dimTags of the surfaces. The keys are used to easily add the corresponding volumes to the correct dimTags objects

required
tZ float

the height of the extrusion

None
terminalDimTagsObject dimTags

the dimTags object of the terminal to be extruded

None

Returns:

Type Description
dict[dimTags, list[tuple[int, int]]]

top surfaces of the extrusion as a dictionary where the keys are the dimTags objects and the values are the dimTags of the surfaces

Source code in fiqus/geom_generators/GeometryPancake3D.py
def extrudeGapPart(
    self,
    surfacesDict,
    tZ: float = None,
    terminalDimTagsObject: dimTags = None,
    firstTerminal=False,
    lastTerminal=False,
):
    """
    Extrudes the given surfaces dimTags dictionary to a given height (tZ) and adds
    the created volumes to the corresponding dictionary keys (dimTags objects). It
    returns the extrusion's top surfaces as a dictionary again, where the keys are
    the corresponding dimTagsObjects and the values are the dimTags of the surfaces.

    If tZ is not given, then it is set to the gap height (self.geo.gap). This is the
    default value used for connecting the pancake coils. Only for the creation of
    the first and the last pancake coils different tZ values are used.

    If terminalDimTagsObject is not given, then the created volume is added
    automatically to the innerTerminalVolume or outerTerminalVolume dimTags object,
    depending on the value of self.pancakeIndex. However, giving
    terminalDimTagsObject is necessary for creating the first and the last terminal.
    Otherwise, finding out the correct terminal dimTagsObject would be very
    challenging.

    :param surfaces: the surface dimTag dictionary to be extruded. The keys are the
                dimTags objects and the values are the dimTags of the surfaces. The
                keys are used to easily add the corresponding volumes to the correct
                dimTags objects
    :type surfaces: dict[dimTags, list[tuple[int, int]]]
    :param tZ: the height of the extrusion
    :type tZ: float, optional
    :param terminalDimTagsObject: the dimTags object of the terminal to be extruded
    :type terminalDimTagsObject: dimTags, optional
    :return: top surfaces of the extrusion as a dictionary where the keys are the
                dimTags objects and the values are the dimTags of the surfaces
    :rtype: dict[dimTags, list[tuple[int, int]]]
    """
    bottomPart = False
    topPart = False
    if tZ is None:
        tZ = self.geo.gap
    elif tZ < 0:
        bottomPart = True
    elif tZ > 0:
        topPart = True

    if terminalDimTagsObject is None:
        # terminalDimTagsObject needs to be given for the first terminal that is
        # extruded downwards.
        if self.pancakeIndex % 2 == 0:
            terminalDimTagsObject = self.innerTerminalTubeVolume
        else:
            terminalDimTagsObject = self.outerTerminalTubeVolume

    # if terminalDimTagsObject is self.innerTerminalVolume:
    #     otherTerminal = self.outerTerminalVolume
    # else:
    #     otherTerminal = self.innerTerminalVolume

    # Create the list of surfaces to be extruded:
    listOfDimTags = []
    listOfDimTagsObjects = []
    listOfDimTagsForTopSurfaces = []
    if topPart:
        # Then in this case, most of the surfaces should be added to the air volumes
        # instead of the terminal, winding, and contact layer volumes.
        for key, dimTagsList in surfacesDict.items():
            if key is self.individualWinding[self.pancakeIndex]:
                dimTagsObjects = [self.topAirPancakeWindingExtursionVolume] * len(
                    dimTagsList
                )
            elif key is self.individualContactLayer[self.pancakeIndex]:
                dimTagsObjects = [
                    self.topAirPancakeContactLayerExtursionVolume
                ] * len(dimTagsList)
            elif (
                key is terminalDimTagsObject
                or key is self.airShellVolume
                or key is self.airShellVolumePart1
                or key is self.airShellVolumePart2
                or key is self.airShellVolumePart3
                or key is self.airShellVolumePart4
                or key is self.outerAirTubeVolume
                or key is self.centerAirCylinderVolume
            ):
                dimTagsObjects = [key] * len(dimTagsList)
            else:
                # key is self.outerTerminalTouchingVolume
                # or key is self.innerTerminalTouchingVolume
                # or key is (other terminal's tube volume)
                dimTagsObjects = [self.topAirTerminalsExtrusionVolume] * len(
                    dimTagsList
                )
                if (
                    key is self.innerTerminalTubeVolume
                    or key is self.outerTerminalTubeVolume
                ):
                    dimTagsObjects = [
                        self.topAirTubeTerminalsExtrusionVolume
                    ] * len(dimTagsList)

            listOfDimTagsForTopSurfaces = listOfDimTagsForTopSurfaces + [key] * len(
                dimTagsList
            )
            listOfDimTags = listOfDimTags + dimTagsList
            listOfDimTagsObjects = listOfDimTagsObjects + dimTagsObjects
    elif bottomPart:
        # Then in this case, most of the surfaces should be added to the air volumes
        # instead of the terminal, winding, and contact layer volumes.
        for key, dimTagsList in surfacesDict.items():
            if key is self.individualWinding[self.pancakeIndex]:
                dimTagsObjects = [
                    self.bottomAirPancakeWindingExtursionVolume
                ] * len(dimTagsList)
            elif key is self.individualContactLayer[self.pancakeIndex]:
                dimTagsObjects = [
                    self.bottomAirPancakeContactLayerExtursionVolume
                ] * len(dimTagsList)
            elif (
                key is terminalDimTagsObject
                or key is self.airShellVolume
                or key is self.airShellVolumePart1
                or key is self.airShellVolumePart2
                or key is self.airShellVolumePart3
                or key is self.airShellVolumePart4
                or key is self.outerAirTubeVolume
                or key is self.centerAirCylinderVolume
            ):
                dimTagsObjects = [key] * len(dimTagsList)
            else:
                # key is self.outerTerminalTouchingVolume
                # or key is self.innerTerminalTouchingVolume
                # or key is (other terminal's tube volume)
                dimTagsObjects = [self.bottomAirTerminalsExtrusionVolume] * len(
                    dimTagsList
                )
                if (
                    key is self.innerTerminalTubeVolume
                    or key is self.outerTerminalTubeVolume
                ):
                    dimTagsObjects = [
                        self.bottomAirTubeTerminalsExtrusionVolume
                    ] * len(dimTagsList)

            listOfDimTagsForTopSurfaces = listOfDimTagsForTopSurfaces + [key] * len(
                dimTagsList
            )
            listOfDimTags = listOfDimTags + dimTagsList
            listOfDimTagsObjects = listOfDimTagsObjects + dimTagsObjects
    else:
        for key, dimTagsList in surfacesDict.items():
            if (
                key is self.outerAirTubeVolume
                or key is self.centerAirCylinderVolume
                or key is self.airShellVolume
                or key is self.airShellVolumePart1
                or key is self.airShellVolumePart2
                or key is self.airShellVolumePart3
                or key is self.airShellVolumePart4
                or key is terminalDimTagsObject
            ):
                dimTagsObjects = [key] * len(dimTagsList)

                listOfDimTags = listOfDimTags + dimTagsList
                listOfDimTagsObjects = listOfDimTagsObjects + dimTagsObjects

        listOfDimTagsForTopSurfaces = listOfDimTagsObjects

    extrusionResult = dimTags()
    extrusionResult.add(gmsh.model.occ.extrude(listOfDimTags, 0, 0, tZ))

    # Add the created volumes to the corresponding dimTags objects:
    volumeDimTags = extrusionResult.getDimTags(3)
    for i, volumeDimTag in enumerate(volumeDimTags):
        listOfDimTagsObjects[i].add(volumeDimTag)

    if firstTerminal:
        self.firstTerminalVolume.add(terminalDimTagsObject.getDimTags(3))
    elif lastTerminal:
        self.lastTerminalVolume.add(terminalDimTagsObject.getDimTags(3))

    topSurfacesDimTags = extrusionResult.getExtrusionTop()
    topSurfacesDict = {}
    for i, topSurfaceDimTag in enumerate(topSurfacesDimTags):
        if listOfDimTagsObjects[i] in topSurfacesDict:
            topSurfacesDict[listOfDimTagsForTopSurfaces[i]].append(topSurfaceDimTag)
        else:
            topSurfacesDict[listOfDimTagsForTopSurfaces[i]] = [topSurfaceDimTag]

    return topSurfacesDict

extrudeWindingPart()

Extrudes all the fundamental surfaces of the pancake coil by self.geo.wi.h and returns the next connection terminal's top surface dimTag, and other air dimTags in a dictionary so that they can be further extruded.

Returns:

Type Description
dict[dimTags, list[tuple[int, int]]]

dictionary of top surfaces where the keys are the dimTags objects and the values are the dimTags of the surfaces

Source code in fiqus/geom_generators/GeometryPancake3D.py
def extrudeWindingPart(self):
    """
    Extrudes all the fundamental surfaces of the pancake coil by self.geo.wi.h and
    returns the next connection terminal's top surface dimTag, and other air dimTags
    in a dictionary so that they can be further extruded.

    :return: dictionary of top surfaces where the keys are the dimTags objects and
                the values are the dimTags of the surfaces
    :rtype: dict[dimTags, list[tuple[int, int]]]
    """
    # Create the list of surfaces to be extruded:
    listOfDimTags = []
    listOfDimTagsObjects = []
    for key, dimTagsList in self.fundamentalSurfaces.items():
        dimTagsObjects = [key] * len(dimTagsList)

        listOfDimTags = listOfDimTags + dimTagsList
        listOfDimTagsObjects = listOfDimTagsObjects + dimTagsObjects

    # Extrude the fundamental surfaces:
    extrusionResult = dimTags()
    extrusionResult.add(gmsh.model.occ.extrude(listOfDimTags, 0, 0, self.geo.wi.h))

    # Add the created volumes to the corresponding dimTags objects:
    volumes = extrusionResult.getDimTags(3)
    for i, volumeDimTag in enumerate(volumes):
        listOfDimTagsObjects[i].add(volumeDimTag)

    if self.pancakeIndex == 0:
        # Note the first pancake (sometimes useful for creating regions in the
        # meshing part):
        for i, volumeDimTag in enumerate(volumes):
            if listOfDimTagsObjects[i].parentName == self.geo.ti.o.name:
                self.firstTerminalVolume.add(volumeDimTag)

    # Not elif! Because the first pancake coil is also the last pancake coil if
    # there is only one pancake coil.
    if self.pancakeIndex == self.geo.N - 1:
        # Note the last pancake (sometimes useful for creating regions in the
        # meshing part):
        for i, volumeDimTag in enumerate(volumes):
            if (
                self.pancakeIndex % 2 == 1
                and listOfDimTagsObjects[i].parentName == self.geo.ti.o.name
            ):
                self.lastTerminalVolume.add(volumeDimTag)
            elif (
                self.pancakeIndex % 2 == 0
                and listOfDimTagsObjects[i].parentName == self.geo.ti.i.name
            ):
                self.lastTerminalVolume.add(volumeDimTag)

    # Return the top surfaces:
    # Add the created top surfaces to a new dictionary:
    topSurfacesDimTags = extrusionResult.getExtrusionTop()
    topSurfaces = {}
    for i, topSurfaceDimTag in enumerate(topSurfacesDimTags):
        if listOfDimTagsObjects[i] in topSurfaces:
            topSurfaces[listOfDimTagsObjects[i]].append(topSurfaceDimTag)
        else:
            topSurfaces[listOfDimTagsObjects[i]] = [topSurfaceDimTag]

    return topSurfaces

generateFundamentalSurfaces()

Generates the inner air, outer air, winding, contact layer, and terminal surfaces of the current pancake coil and returns them. It finds the z coordinate of the surfaces and the direction of the pancake coil, depending on the pancake index.

Returns:

Type Description
list[tuple[int, int]]

list of dimTags that contains fundamental surfaces

Source code in fiqus/geom_generators/GeometryPancake3D.py
def generateFundamentalSurfaces(self):
    """
    Generates the inner air, outer air, winding, contact layer, and terminal surfaces
    of the current pancake coil and returns them. It finds the z coordinate of the
    surfaces and the direction of the pancake coil, depending on the pancake index.

    :return: list of dimTags that contains fundamental surfaces
    :rtype: list[tuple[int, int]]
    """
    fundamentalSurfaces = {}

    # Select the direction of the spiral:
    if self.pancakeIndex % 2 == 0:
        spiralDirection = direction.ccw
    else:
        spiralDirection = direction.cw

    # Calculate the z coordinate of the surfaces:
    z = (
        -self.geo.ai.h / 2
        + self.geo.ai.margin
        + self.pancakeIndex * (self.geo.wi.h + self.geo.gap)
    )

    # Create the winding and contact layer surface:
    surface = spiralSurface(
        self.geo.wi.r_i,
        self.geo.wi.t,
        self.geo.ii.t,
        self.geo.wi.N,
        z,
        self.geo.wi.theta_i,
        self.geo.ti.transitionNotchAngle,
        spiralDirection,
        thinShellApproximation=self.geo.ii.tsa,
    )

    # Save the surface tags (if TSA, contactLayerSurfaceTags will be empty):
    fundamentalSurfaces[self.individualWinding[self.pancakeIndex]] = [
        (2, tag) for tag in surface.surfaceTags
    ]
    fundamentalSurfaces[self.individualContactLayer[self.pancakeIndex]] = [
        (2, tag) for tag in surface.contactLayerSurfaceTags
    ]

    if self.geo.ai.type == "cylinder":
        outerAirSurf = outerAirSurface(
            self.geo.ai.r,
            self.geo.ti.o.r,
            type="cylinder",
            divideIntoFourParts=self.mesh.ai.structured,
            divideTerminalPartIntoFourParts=self.mesh.ti.structured,
        )
    elif self.geo.ai.type == "cuboid":
        outerAirSurf = outerAirSurface(
            self.geo.ai.a / 2,
            self.geo.ti.o.r,
            type="cuboid",
            divideIntoFourParts=self.mesh.ai.structured,
            divideTerminalPartIntoFourParts=self.mesh.ti.structured,
        )

    outerTerminalSurf = outerTerminalSurface(
        self.geo.ti.o.r,
        self.geo.ti.o.t,
        divideIntoFourParts=self.mesh.ti.structured,
    )
    innerTerminalSurf = innerTerminalSurface(
        self.geo.ti.i.r,
        self.geo.ti.i.t,
        divideIntoFourParts=self.mesh.ti.structured,
    )
    innerAirSurf = innerAirSurface(
        self.geo.ti.i.r,
        divideIntoFourParts=self.mesh.ai.structured,
        divideTerminalPartIntoFourParts=self.mesh.ti.structured,
    )

    if self.contactSurfaces:
        # If self.contactSurfaces is not empty, it means that it is not the
        # first pancake coil. In that case, contactSurfaces should be used to
        # avoid surface duplication.

        # Create outer air:
        outerAirPSDimTags = self.contactSurfaces[self.outerAirTubeVolume]
        outerAirPSTags = [dimTag[1] for dimTag in outerAirPSDimTags]
        if self.geo.ai.shellTransformation:
            if self.geo.ai.type == "cuboid":
                cuboidShellDimTags1 = self.contactSurfaces[self.airShellVolumePart1]
                cuboidShellTags1 = [dimTag[1] for dimTag in cuboidShellDimTags1]
                cuboidShellDimTags2 = self.contactSurfaces[self.airShellVolumePart2]
                cuboidShellTags2 = [dimTag[1] for dimTag in cuboidShellDimTags2]
                cuboidShellDimTags3 = self.contactSurfaces[self.airShellVolumePart3]
                cuboidShellTags3 = [dimTag[1] for dimTag in cuboidShellDimTags3]
                cuboidShellDimTags4 = self.contactSurfaces[self.airShellVolumePart4]
                cuboidShellTags4 = [dimTag[1] for dimTag in cuboidShellDimTags4]
                outerAirSurf.setPrecreatedSurfaceTags(
                    outerAirPSTags,
                    cuboidShellTags1=cuboidShellTags1,
                    cuboidShellTags2=cuboidShellTags2,
                    cuboidShellTags3=cuboidShellTags3,
                    cuboidShellTags4=cuboidShellTags4,
                )
            elif self.geo.ai.type == "cylinder":
                cylinderShellDimTags = self.contactSurfaces[self.airShellVolume]
                cylinderShellTags = [dimTag[1] for dimTag in cylinderShellDimTags]
                outerAirSurf.setPrecreatedSurfaceTags(
                    outerAirPSTags,
                    cylinderShellTags=cylinderShellTags,
                )
        else:
            outerAirSurf.setPrecreatedSurfaceTags(outerAirPSTags)

        # Create inner air:
        innerAirPSDimTags = self.contactSurfaces[self.centerAirCylinderVolume]
        innerAirPSTags = [dimTag[1] for dimTag in innerAirPSDimTags]
        innerAirSurf.setPrecreatedSurfaceTags(innerAirPSTags)

        if self.pancakeIndex % 2 == 0:
            # In this case, we should create all the surfaces for the inner terminal
            # but not for outer terminal. Because it is a pancake coil with an even
            # index (self.pancakeIndex%2==0) which means that it is connected to the
            # previous pancake coil with outer terminal and the outer terminal
            # surface is ready (extruded before).

            # Create outer terminal:
            outerTerminalTubePSDimTags = self.contactSurfaces[
                self.outerTerminalTubeVolume
            ]
            outerTerminalTubePSTags = [
                dimTag[1] for dimTag in outerTerminalTubePSDimTags
            ]
            outerTerminalSurf.createWithWindingAndTubeTags(
                surface, outerTerminalTubePSTags, self.pancakeIndex
            )

            # Create inner terminal:
            innerTerminalSurf.createWithInnerAirAndWinding(
                innerAirSurf, surface, self.pancakeIndex
            )

        else:
            # In this case, we should create all the surfaces for the outer terminal
            # but not for inner terminal. Because it is a pancake coil with an odd
            # index (self.pancakeIndex%2==1) which means that it is connected to the
            # previous pancake coil with inner terminal and the inner terminal
            # surface is ready (extruded before).

            # Create outer terminal:
            outerTerminalSurf.createWithOuterAirAndWinding(
                outerAirSurf, surface, self.pancakeIndex
            )

            # Create inner terminal:
            innerTerminalTubePSDimTags = self.contactSurfaces[
                self.innerTerminalTubeVolume
            ]
            innerTerminalTubePSTags = [
                dimTag[1] for dimTag in innerTerminalTubePSDimTags
            ]
            innerTerminalSurf.createWithWindingAndTubeTags(
                surface, innerTerminalTubePSTags, self.pancakeIndex
            )

    else:
        # If self.contactSurfaces is empty, it means that it is the first pancake
        # coil. In that case, the surfaces should be created from scratch.

        if self.geo.ai.shellTransformation:
            if self.geo.ai.type == "cuboid":
                outerAirSurf.createFromScratch(
                    z,
                    shellTransformation=True,
                    shellRadius=self.geo.ai.shellSideLength / 2,
                )
            else:
                outerAirSurf.createFromScratch(
                    z,
                    shellTransformation=True,
                    shellRadius=self.geo.ai.shellOuterRadius,
                )
        else:
            outerAirSurf.createFromScratch(z)

        innerAirSurf.createFromScratch(z)
        outerTerminalSurf.createWithOuterAirAndWinding(
            outerAirSurf, surface, self.pancakeIndex
        )
        innerTerminalSurf.createWithInnerAirAndWinding(
            innerAirSurf, surface, self.pancakeIndex
        )

    # Save the surface tags:
    fundamentalSurfaces[self.outerAirTubeVolume] = [
        (2, tag) for tag in outerAirSurf.surfaceTags
    ]

    fundamentalSurfaces[self.centerAirCylinderVolume] = [
        (2, tag) for tag in innerAirSurf.surfaceTags
    ]

    fundamentalSurfaces[self.outerTerminalTubeVolume] = [
        (2, tag) for tag in outerTerminalSurf.tubeSurfaceTags
    ]
    fundamentalSurfaces[self.outerTerminalTouchingVolume] = [
        (2, tag) for tag in outerTerminalSurf.nontubeSurfaceTags
    ]

    fundamentalSurfaces[self.innerTerminalTubeVolume] = [
        (2, tag) for tag in innerTerminalSurf.tubeSurfaceTags
    ]
    fundamentalSurfaces[self.innerTerminalTouchingVolume] = [
        (2, tag) for tag in innerTerminalSurf.nontubeSurfaceTags
    ]
    fundamentalSurfaces[self.innerTransitionNotchVolume] = [
        (2, tag) for tag in surface.innerNotchSurfaceTags
    ]
    fundamentalSurfaces[self.outerTransitionNotchVolume] = [
        (2, tag) for tag in surface.outerNotchSurfaceTags
    ]

    if self.geo.ai.shellTransformation:
        if self.geo.ai.type == "cuboid":
            fundamentalSurfaces[self.airShellVolumePart1] = [
                (2, tag) for tag in outerAirSurf.shellTagsPart1
            ]
            fundamentalSurfaces[self.airShellVolumePart2] = [
                (2, tag) for tag in outerAirSurf.shellTagsPart2
            ]
            fundamentalSurfaces[self.airShellVolumePart3] = [
                (2, tag) for tag in outerAirSurf.shellTagsPart3
            ]
            fundamentalSurfaces[self.airShellVolumePart4] = [
                (2, tag) for tag in outerAirSurf.shellTagsPart4
            ]
        elif self.geo.ai.type == "cylinder":
            fundamentalSurfaces[self.airShellVolume] = [
                (2, tag) for tag in outerAirSurf.shellTags
            ]

    return fundamentalSurfaces

generateGapAirWithFragment(gapAirSurfacesDimTags)

A class to fill the gap between the multiple pancake coils with air. First, it creates a dummy cylinder with the same radius as the outer terminal's outer radius. Then using gapAirSurfacesDimTags, gmsh.model.occ.fragment() operation is applied to the dummy cylinder volume in a for loop to create the gap air volumes. After each fragment operation, one of the volumes created is removed because it is the solid volume which is the combination of windings, contact layers, and terminals. In the end, dummy cylinder is removed as well.

WARNING: Currently, this method doesn't work.

Parameters:

Name Type Description Default
geometry

geometry information

required
pancakeCoils pancakeCoilsWithAir

pancakeCoilsWithAir object

required
Source code in fiqus/geom_generators/GeometryPancake3D.py
def generateGapAirWithFragment(
    self, gapAirSurfacesDimTags: List[List[Tuple[int, int]]]
):
    """
    A class to fill the gap between the multiple pancake coils with air. First, it
    creates a dummy cylinder with the same radius as the outer terminal's outer
    radius. Then using gapAirSurfacesDimTags, gmsh.model.occ.fragment() operation is
    applied to the dummy cylinder volume in a for loop to create the gap air
    volumes. After each fragment operation, one of the volumes created is removed
    because it is the solid volume which is the combination of windings,
    contact layers, and terminals. In the end, dummy cylinder is removed as well.


    WARNING:
    Currently, this method doesn't work.

    :param geometry: geometry information
    :param pancakeCoils: pancakeCoilsWithAir object
    :type pancakeCoils: pancakeCoilsWithAir
    """
    logger.info("Generating gap air has been started.")
    start_time = timeit.default_timer()

    # Create the dummy air volume:
    dummyAir = gmsh.model.occ.addCylinder(
        0,
        0,
        -self.geo.ai.h / 2,
        0,
        0,
        self.geo.ai.h,
        self.geo.ti.o.r,
    )

    toBeDeletedDimTags = []
    gapAirVolumesCurrentDimTags = []
    for i in range(len(gapAirSurfacesDimTags)):
        # Get the outer surfaces of the pancake coils for cutting the pancake coils
        # from the dummy air. The outer surfaces are used instead of pancake volumes
        # to reduce the amount of work for gmsh. It makes it significantly faster.
        # if len(gapAirSurfacesDimTags[i]) !=12:
        fragmentResults = gmsh.model.occ.fragment(
            [(3, dummyAir)],
            gapAirSurfacesDimTags[i],
            removeObject=False,
            removeTool=False,
        )
        fragmentVolumeResultsDimTags = fragmentResults[1][0]
        toBeDeletedDimTags.append(fragmentVolumeResultsDimTags[0])
        gapAirVolumesCurrentDimTags.append(fragmentVolumeResultsDimTags[1])

    toBeDeletedDimTags.append((3, dummyAir))
    # Fragmnet operation both creates the air volume and solid pancake coils volume
    # because the surfaces are used for cutting. Therefore, the solid pancake coils
    # volume should be removed from the fragment results:
    gmsh.model.occ.remove(toBeDeletedDimTags)

    # Add results to the air volume storage. After the geometry is saves as a .brep
    # file, and loaded back, the gaps between the tags are avoided by moving the
    # the other tags. Therefore, this is how the tags are stored:
    toBeDeletedTags = [dimTag[1] for dimTag in toBeDeletedDimTags]
    volumeTagsStart = min(toBeDeletedTags)
    numberOfGapAirVolumes = len(gapAirVolumesCurrentDimTags)
    gapAirVolumesToBeSaved = [
        (3, volumeTagsStart + i) for i in range(numberOfGapAirVolumes)
    ]

    # For debugging purposes, physical groups are being created in the geometry
    # generation process as well. Normally, it us done during meshing because BREP
    # files cannot store physical groups. Since the tags above (airVolumes) will be
    # valid after only saving the geometry as a BREP file and loading it back, the
    # current tags are given to the airVolume.add() method as well. This is done to
    # be able to create the correct physical group.
    self.gapAirVolume.add(
        dimTagsList=gapAirVolumesToBeSaved,
        dimTagsListForPG=gapAirVolumesCurrentDimTags,
    )

    logger.info(
        "Generating gap air has been finished in"
        f" {timeit.default_timer() - start_time:.2f} s."
    )

point

This is a class for creating points in GMSH. It supports rectangular and cylindrical coordinates. Moreover, vector operations are supported.

Parameters:

Name Type Description Default
r0 float, optional

x, r, or r (default: 0.0)

0.0
r1 float, optional

y, theta, or theta (default: 0.0)

0.0
r2 float, optional

z, z, or phi (default: 0.0)

0.0
type coordinate, optional

coordinate type (default: coordinate.rectangular)

coordinate.rectangular
Source code in fiqus/geom_generators/GeometryPancake3D.py
class point:
    """
    This is a class for creating points in GMSH. It supports rectangular and cylindrical
    coordinates. Moreover, vector operations are supported.

    :param r0: x, r, or r (default: 0.0)
    :type r0: float, optional
    :param r1: y, theta, or theta (default: 0.0)
    :type r1: float, optional
    :param r2: z, z, or phi (default: 0.0)
    :type r2: float, optional
    :param type: coordinate type (default: coordinate.rectangular)
    :type type: coordinate, optional
    """

    def __init__(self, r0=0.0, r1=0.0, r2=0.0, type=coordinate.rectangular) -> None:
        if type is coordinate.rectangular:
            self.x = r0
            self.y = r1
            self.z = r2

            self.r = math.sqrt(self.x**2 + self.y**2)
            self.theta = math.atan2(self.y, self.x)
        elif type is coordinate.cylindrical:
            self.r = r0
            self.theta = r1
            self.x = self.r * math.cos(self.theta)
            self.y = self.r * math.sin(self.theta)
            self.z = r2
        elif type is coordinate.spherical:
            raise ValueError("Spherical coordinates are not supported yet!")
        else:
            raise ValueError("Improper coordinate type value!")

        self.tag = gmsh.model.occ.addPoint(self.x, self.y, self.z)

    def __repr__(self):
        """
        Returns the string representation of the point.

        :return: string representation of the point
        :rtype: str
        """
        return "point(%r, %r, %r, %r)" % (self.x, self.y, self.z, self.type)

    def __abs__(self):
        """
        Returns the magnitude of the point vector.

        :return: the magnitude of the point vector
        :rtype: float
        """
        return math.hypot(self.x, self.y, self.z)

    def __add__(self, other):
        """
        Returns the summation of two point vectors.

        :param other: point vector to be added
        :type other: point
        :return: the summation of two point vectors
        :rtype: point
        """
        x = self.x + other.x
        y = self.y + other.y
        z = self.z + other.z
        return point(x, y, z, coordinate.rectangular)

    def __mul__(self, scalar):
        """
        Returns the product of a point vector and a scalar.

        :param scalar: a scalar value
        :type scalar: float
        :return: point
        :rtype: point
        """
        return point(
            self.x * scalar,
            self.y * scalar,
            self.z * scalar,
            coordinate.rectangular,
        )

__abs__()

Returns the magnitude of the point vector.

Returns:

Type Description
float

the magnitude of the point vector

Source code in fiqus/geom_generators/GeometryPancake3D.py
def __abs__(self):
    """
    Returns the magnitude of the point vector.

    :return: the magnitude of the point vector
    :rtype: float
    """
    return math.hypot(self.x, self.y, self.z)

__add__(other)

Returns the summation of two point vectors.

Parameters:

Name Type Description Default
other point

point vector to be added

required

Returns:

Type Description
point

the summation of two point vectors

Source code in fiqus/geom_generators/GeometryPancake3D.py
def __add__(self, other):
    """
    Returns the summation of two point vectors.

    :param other: point vector to be added
    :type other: point
    :return: the summation of two point vectors
    :rtype: point
    """
    x = self.x + other.x
    y = self.y + other.y
    z = self.z + other.z
    return point(x, y, z, coordinate.rectangular)

__mul__(scalar)

Returns the product of a point vector and a scalar.

Parameters:

Name Type Description Default
scalar float

a scalar value

required

Returns:

Type Description
point

point

Source code in fiqus/geom_generators/GeometryPancake3D.py
def __mul__(self, scalar):
    """
    Returns the product of a point vector and a scalar.

    :param scalar: a scalar value
    :type scalar: float
    :return: point
    :rtype: point
    """
    return point(
        self.x * scalar,
        self.y * scalar,
        self.z * scalar,
        coordinate.rectangular,
    )

__repr__()

Returns the string representation of the point.

Returns:

Type Description
str

string representation of the point

Source code in fiqus/geom_generators/GeometryPancake3D.py
def __repr__(self):
    """
    Returns the string representation of the point.

    :return: string representation of the point
    :rtype: str
    """
    return "point(%r, %r, %r, %r)" % (self.x, self.y, self.z, self.type)

spiralCurve

A class to create a spiral curves parallel to XY plane in GMSH. The curve is defined by a spline and it is divided into sub-curves. Sub-curves are used because it makes the geometry creation process easier.

Parameters:

Name Type Description Default
innerRadius float

inner radius

required
gap float

gap after each turn

required
turns float

number of turns

required
z float

z coordinate

required
initialTheta float

initial theta angle in radians

required
direction direction, optional

direction of the spiral (default: direction.ccw)

direction.ccw
cutPlaneNormal tuple[float, float, float], optional

normal vector of the plane that will cut the spiral curve (default: None)

Tuple[float, float, float]
Source code in fiqus/geom_generators/GeometryPancake3D.py
class spiralCurve:
    """
    A class to create a spiral curves parallel to XY plane in GMSH. The curve is defined
    by a spline and it is divided into sub-curves. Sub-curves are used because it makes
    the geometry creation process easier.

    :param innerRadius: inner radius
    :type innerRadius: float
    :param gap: gap after each turn
    :type gap: float
    :param turns: number of turns
    :type turns: float
    :param z: z coordinate
    :type z: float
    :param initialTheta: initial theta angle in radians
    :type initialTheta: float
    :param direction: direction of the spiral (default: direction.ccw)
    :type direction: direction, optional
    :param cutPlaneNormal: normal vector of the plane that will cut the spiral curve
        (default: None)
    :type cutPlaneNormal: tuple[float, float, float], optional
    """

    # If the number of points used per turn is n, then the number of sections per turn
    # is n-1. They set the resolution of the spiral curve. It sets the limit of the
    # precision of the float number of turns that can be used to create the spiral
    # curve. The value below might be modified in Geometry.__init__ method.
    sectionsPerTurn = 16

    # There will be curvesPerTurn curve entities per turn. It will be effectively the
    # number of volumes per turn in the end. The value below might be modified in
    # Geometry.__init__ method.
    curvesPerTurn = 2

    def __init__(
        self,
        innerRadius,
        gap,
        turns,
        z,
        initialTheta,
        transitionNotchAngle,
        direction=direction.ccw,
        cutPlaneNormal=Tuple[float, float, float],
    ) -> None:
        spt = self.sectionsPerTurn  # just to make the code shorter
        self.turnRes = 1 / spt  # turn resolution
        cpt = self.curvesPerTurn  # just to make the code shorter
        self.turns = turns

        # =============================================================================
        # GENERATING POINTS STARTS ====================================================
        # =============================================================================

        # Calculate the coordinates of the points that define the spiral curve:
        if direction is direction.ccw:
            # If the spiral is counter-clockwise, the initial theta angle decreases,
            # and r increases as the theta angle decreases.
            multiplier = 1
        elif direction is direction.cw:
            # If the spiral is clockwise, the initial theta angle increases, and r
            # increases as the theta angle increases.
            multiplier = -1

        NofPointsPerTurn = int(spt + 1)
        thetaArrays = []
        for turn in range(1, int(self.turns) + 1):
            thetaArrays.append(
                np.linspace(
                    initialTheta + (turn - 1) * 2 * math.pi * multiplier,
                    initialTheta + (turn) * 2 * math.pi * multiplier,
                    NofPointsPerTurn,
                )
            )

        thetaArrays.append(
            np.linspace(
                initialTheta + (turn) * 2 * math.pi * multiplier,
                initialTheta + (self.turns) * 2 * math.pi * multiplier,
                round(spt * (self.turns - turn) + 1),
            )
        )

        if cutPlaneNormal is not None:
            # If the cutPlaneNormal is specified, the spiral curve will be cut by a
            # plane that is normal to the cutPlaneNormal vector and passes through the
            # origin.

            alpha = math.atan2(cutPlaneNormal[1], cutPlaneNormal[0]) - math.pi / 2
            alpha2 = alpha + math.pi

            listOfBreakPoints = []
            for turn in range(1, int(self.turns) + 2):
                breakPoint1 = alpha + (turn - 1) * 2 * math.pi * multiplier
                breakPoint2 = alpha2 + (turn - 1) * 2 * math.pi * multiplier
                if (
                    breakPoint1 > initialTheta
                    and breakPoint1 < initialTheta + 2 * math.pi * self.turns
                ):
                    listOfBreakPoints.append(breakPoint1)
                if (
                    breakPoint2 > initialTheta
                    and breakPoint2 < initialTheta + 2 * math.pi * self.turns
                ):
                    listOfBreakPoints.append(breakPoint2)

            thetaArrays.append(np.array(listOfBreakPoints))

        theta = np.concatenate(thetaArrays)
        theta = np.round(theta, 10)
        theta = np.unique(theta)
        theta = np.sort(theta)
        theta = theta[::multiplier]

        r = innerRadius + (theta - initialTheta) / (2 * math.pi) * (gap) * multiplier
        z = np.ones(theta.shape) * z

        # Create the points and store their tags:
        points = []  # point objects
        pointTags = []  # point tags
        breakPointObjectsDueToCutPlane = []  # only used if cutPlaneNormal is not None
        breakPointTagsDueToCutPlane = []  # only used if cutPlaneNormal is not None
        pointObjectsWithoutBreakPoints = []  # only used if cutPlaneNormal is not None
        pointTagsWithoutBreakPoints = []  # only used if cutPlaneNormal is not None
        breakPointObjectsDueToTransition = []
        breakPointTagsDueToTransition = []

        for j in range(len(theta)):
            pointObject = point(r[j], theta[j], z[j], coordinate.cylindrical)
            points.append(pointObject)
            pointTags.append(pointObject.tag)
            if cutPlaneNormal is not None:
                if theta[j] in listOfBreakPoints:
                    breakPointObjectsDueToCutPlane.append(pointObject)
                    breakPointTagsDueToCutPlane.append(pointObject.tag)
                else:
                    pointObjectsWithoutBreakPoints.append(pointObject)
                    pointTagsWithoutBreakPoints.append(pointObject.tag)

            # identify if the point is a break point due to the layer transition:
            angle1 = initialTheta + (2 * math.pi - transitionNotchAngle) * multiplier
            angle2 = (
                initialTheta
                + ((self.turns % 1) * 2 * math.pi + transitionNotchAngle) * multiplier
            )
            if math.isclose(
                math.fmod(theta[j], 2 * math.pi), angle1, abs_tol=1e-6
            ) or math.isclose(math.fmod(theta[j], 2 * math.pi), angle2, abs_tol=1e-6):
                breakPointObjectsDueToTransition.append(pointObject)
                breakPointTagsDueToTransition.append(pointObject.tag)

        # =============================================================================
        # GENERATING POINTS ENDS ======================================================
        # =============================================================================

        # =============================================================================
        # GENERATING SPLINES STARTS ===================================================
        # =============================================================================

        # Create the spline with the points:
        spline = gmsh.model.occ.addSpline(pointTags)

        # Split the spline into sub-curves:
        sectionsPerCurve = int(spt / cpt)

        # Create a list of point tags that will split the spline:
        # Basically, they are the points to divide the spirals into sectionsPerCurve
        # turns. However, some other points are also included to support the float
        # number of turns. It is best to visually look at the divisions with
        # gmsh.fltk.run() to understand why the split points are chosen the way they are
        # selected.
        if cutPlaneNormal is None:
            pointObjectsWithoutBreakPoints = points
            pointTagsWithoutBreakPoints = pointTags

        splitPointTags = list(
            set(pointTagsWithoutBreakPoints[:-1:sectionsPerCurve])
            | set(pointTagsWithoutBreakPoints[-spt - 1 :: -spt])
            | set(breakPointTagsDueToCutPlane)
            | set(breakPointTagsDueToTransition)
        )
        splitPointTags = sorted(splitPointTags)
        # Remove the first element of the list (starting point):
        _, *splitPointTags = splitPointTags

        # Also create a list of corresponding point objects:
        splitPoints = list(
            set(pointObjectsWithoutBreakPoints[:-1:sectionsPerCurve])
            | set(pointObjectsWithoutBreakPoints[-spt - 1 :: -spt])
            | set(breakPointObjectsDueToCutPlane)
            | set(breakPointObjectsDueToTransition)
        )
        splitPoints = sorted(splitPoints, key=lambda x: x.tag)
        # Remove the first element of the list (starting point):
        _, *splitPoints = splitPoints

        # Split the spline:
        dims = [0] * len(splitPointTags)
        _, splines = gmsh.model.occ.fragment(
            [(1, spline)],
            list(zip(dims, splitPointTags)),
            removeObject=True,
            removeTool=True,
        )
        splines = splines[0]
        self.splineTags = [j for _, j in splines]

        # Note the turn number of each spline. This will be used in getSplineTag and
        # getSplineTags methods.
        self.splineTurns = []
        for i in range(len(self.splineTags)):
            if i == 0:
                startPoint = points[0]
                endPoint = splitPoints[0]
            elif i == len(self.splineTags) - 1:
                startPoint = splitPoints[-1]
                endPoint = points[-1]
            else:
                startPoint = splitPoints[i - 1]
                endPoint = splitPoints[i]

            startTurn = (startPoint.theta - initialTheta) / (2 * math.pi)
            startTurn = round(startTurn / self.turnRes) * self.turnRes
            endTurn = (endPoint.theta - initialTheta) / (2 * math.pi)
            endTurn = round(endTurn / self.turnRes) * self.turnRes

            if direction is direction.ccw:
                self.splineTurns.append((startTurn, endTurn))
            else:
                self.splineTurns.append((-startTurn, -endTurn))

        # Check if splineTurn tuples starts with the small turn number:
        for i in range(len(self.splineTurns)):
            self.splineTurns[i] = sorted(self.splineTurns[i])

        # =============================================================================
        # GENERATING SPLINES ENDS =====================================================
        # =============================================================================

        # Find start and end points of the spiral curve:
        gmsh.model.occ.synchronize()  # synchronize the model to make getBoundary work
        self.startPointTag = gmsh.model.getBoundary([(1, self.getSplineTag(0))])[1][1]
        self.endPointTag = gmsh.model.getBoundary(
            [(1, self.getSplineTag(self.turns, endPoint=True))]
        )[1][1]

    def getSplineTag(self, turn, endPoint=False):
        """
        Returns the spline tag at a specific turn. It returns the spline tag of the
        section that is on the turn except its end point.

        :param turn: turn number (it can be a float)
        :type turn: float
        :param endPoint: if True, return the spline tag of the section that is on the
            turn including its end point but not its start point (default: False)
        :type endPoint: bool, optional
        :return: spline tag
        """
        if endPoint:
            for i, r in enumerate(self.splineTurns):
                if r[0] + (self.turnRes / 2) < turn <= r[1] + (self.turnRes / 2):
                    return self.splineTags[i]
        else:
            for i, r in enumerate(self.splineTurns):
                if r[0] - (self.turnRes / 2) <= turn < r[1] - (self.turnRes / 2):
                    return self.splineTags[i]

    def getPointTag(self, turn, endPoint=False):
        """
        Returns the point object at a specific turn.

        :param turn: turn number (it can be a float)
        :type turn: float
        :return: point object
        :rtype: point
        """
        if turn < 0 or turn > self.turns:
            raise ValueError("Turn number is out of range!")

        if turn == 0:
            return self.startPointTag
        elif turn == self.turns:
            return self.endPointTag
        else:
            curveTag = self.getSplineTag(turn, endPoint=endPoint)
            if endPoint:
                points = gmsh.model.getBoundary([(1, curveTag)])
                return points[1][1]
            else:
                points = gmsh.model.getBoundary([(1, curveTag)])
                return points[0][1]

    def getSplineTags(self, turnStart=None, turnEnd=None):
        """
        Get the spline tags from a specific turn to another specific turn. If turnStart
        and turnEnd are not specified, it returns all the spline tags.

        :param turnStart: start turn number (it can be a float) (default: None)
        :type turnStart: float, optional
        :param turnEnd: end turn number (it can be a float) (default: None)
        :type turnEnd: float, optional
        :return: spline tags
        :rtype: list[int]
        """
        if turnStart is None and turnEnd is None:
            return self.splineTags
        elif turnStart is None or turnEnd is None:
            raise ValueError(
                "turnStart and turnEnd must be both specified or both not specified."
                " You specified only one of them."
            )
        else:
            start = self.splineTags.index(self.getSplineTag(turnStart, False))
            end = self.splineTags.index(self.getSplineTag(turnEnd, True)) + 1
            return self.splineTags[start:end]

getPointTag(turn, endPoint=False)

Returns the point object at a specific turn.

Parameters:

Name Type Description Default
turn float

turn number (it can be a float)

required

Returns:

Type Description
point

point object

Source code in fiqus/geom_generators/GeometryPancake3D.py
def getPointTag(self, turn, endPoint=False):
    """
    Returns the point object at a specific turn.

    :param turn: turn number (it can be a float)
    :type turn: float
    :return: point object
    :rtype: point
    """
    if turn < 0 or turn > self.turns:
        raise ValueError("Turn number is out of range!")

    if turn == 0:
        return self.startPointTag
    elif turn == self.turns:
        return self.endPointTag
    else:
        curveTag = self.getSplineTag(turn, endPoint=endPoint)
        if endPoint:
            points = gmsh.model.getBoundary([(1, curveTag)])
            return points[1][1]
        else:
            points = gmsh.model.getBoundary([(1, curveTag)])
            return points[0][1]

getSplineTag(turn, endPoint=False)

Returns the spline tag at a specific turn. It returns the spline tag of the section that is on the turn except its end point.

Parameters:

Name Type Description Default
turn float

turn number (it can be a float)

required
endPoint bool, optional

if True, return the spline tag of the section that is on the turn including its end point but not its start point (default: False)

False

Returns:

Type Description

spline tag

Source code in fiqus/geom_generators/GeometryPancake3D.py
def getSplineTag(self, turn, endPoint=False):
    """
    Returns the spline tag at a specific turn. It returns the spline tag of the
    section that is on the turn except its end point.

    :param turn: turn number (it can be a float)
    :type turn: float
    :param endPoint: if True, return the spline tag of the section that is on the
        turn including its end point but not its start point (default: False)
    :type endPoint: bool, optional
    :return: spline tag
    """
    if endPoint:
        for i, r in enumerate(self.splineTurns):
            if r[0] + (self.turnRes / 2) < turn <= r[1] + (self.turnRes / 2):
                return self.splineTags[i]
    else:
        for i, r in enumerate(self.splineTurns):
            if r[0] - (self.turnRes / 2) <= turn < r[1] - (self.turnRes / 2):
                return self.splineTags[i]

getSplineTags(turnStart=None, turnEnd=None)

Get the spline tags from a specific turn to another specific turn. If turnStart and turnEnd are not specified, it returns all the spline tags.

Parameters:

Name Type Description Default
turnStart float, optional

start turn number (it can be a float) (default: None)

None
turnEnd float, optional

end turn number (it can be a float) (default: None)

None

Returns:

Type Description
list[int]

spline tags

Source code in fiqus/geom_generators/GeometryPancake3D.py
def getSplineTags(self, turnStart=None, turnEnd=None):
    """
    Get the spline tags from a specific turn to another specific turn. If turnStart
    and turnEnd are not specified, it returns all the spline tags.

    :param turnStart: start turn number (it can be a float) (default: None)
    :type turnStart: float, optional
    :param turnEnd: end turn number (it can be a float) (default: None)
    :type turnEnd: float, optional
    :return: spline tags
    :rtype: list[int]
    """
    if turnStart is None and turnEnd is None:
        return self.splineTags
    elif turnStart is None or turnEnd is None:
        raise ValueError(
            "turnStart and turnEnd must be both specified or both not specified."
            " You specified only one of them."
        )
    else:
        start = self.splineTags.index(self.getSplineTag(turnStart, False))
        end = self.splineTags.index(self.getSplineTag(turnEnd, True)) + 1
        return self.splineTags[start:end]

spiralSurface

This is a class to create a spiral surface parallel to the XY plane in GMSH. If thinShellApproximation is set to False, it creates two spiral surfaces parallel to the XY plane, and their inner and outer curve loops in GMSH. One of the surfaces is the main surface specified, which is the winding surface, and the other is the contact layer surface (the gap between the winding surface). If thinShellApproximation is set to True, it creates only one spiral surface that touches each other (conformal).

Note that surfaces are subdivided depending on the spiral curve divisions because this is required for the thin-shell approximation. Otherwise, when thinShellApproximation is set to True, it would be a disc rather than a spiral since it touches each other. However, this can be avoided by dividing the surfaces into small parts and making them conformal. Dividing the surfaces is not necessary when thinShellApproximation is set to false, but in order to use the same logic with TSA, it is divided anyway.

Parameters:

Name Type Description Default
innerRadius float

inner radius

required
thickness float

thickness

required
contactLayerThickness float

contact layer thickness

required
turns float

number of turns

required
z float

z coordinate

required
initialTheta float

initial theta angle in radians

required
spiralDirection direction, optional

direction of the spiral (default: direction.ccw)

direction.ccw
thinShellApproximation bool, optional

if True, the thin shell approximation is used (default: False)

False
cutPlaneNormal tuple[float, float, float], optional

normal vector of the plane that will cut the spiral surface (default: None)

None
Source code in fiqus/geom_generators/GeometryPancake3D.py
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
class spiralSurface:
    """
    This is a class to create a spiral surface parallel to the XY plane in GMSH. If
    thinShellApproximation is set to False, it creates two spiral surfaces parallel to
    the XY plane, and their inner and outer curve loops in GMSH. One of the surfaces is
    the main surface specified, which is the winding surface, and the other is the
    contact layer surface (the gap between the winding surface). If thinShellApproximation
    is set to True, it creates only one spiral surface that touches each other
    (conformal).

    Note that surfaces are subdivided depending on the spiral curve divisions because
    this is required for the thin-shell approximation. Otherwise, when
    thinShellApproximation is set to True, it would be a disc rather than a spiral since
    it touches each other. However, this can be avoided by dividing the surfaces into
    small parts and making them conformal. Dividing the surfaces is not necessary when
    thinShellApproximation is set to false, but in order to use the same logic with TSA,
    it is divided anyway.

    :param innerRadius: inner radius
    :type innerRadius: float
    :param thickness: thickness
    :type thickness: float
    :param contactLayerThickness: contact layer thickness
    :type contactLayerThickness: float
    :param turns: number of turns
    :type turns: float
    :param z: z coordinate
    :type z: float
    :param initialTheta: initial theta angle in radians
    :type initialTheta: float
    :param spiralDirection: direction of the spiral (default: direction.ccw)
    :type spiralDirection: direction, optional
    :param thinShellApproximation: if True, the thin shell approximation is used
        (default: False)
    :type thinShellApproximation: bool, optional
    :param cutPlaneNormal: normal vector of the plane that will cut the spiral surface
        (default: None)
    :type cutPlaneNormal: tuple[float, float, float], optional
    """

    def __init__(
        self,
        innerRadius,
        thickness,
        contactLayerThickness,
        turns,
        z,
        initialTheta,
        transitionNotchAngle,
        spiralDirection=direction.ccw,
        thinShellApproximation=False,
        cutPlaneNormal=None,
    ) -> None:
        r_i = innerRadius
        t = thickness
        theta_i = initialTheta
        self.theta_i = theta_i

        self.surfaceTags = []
        self.contactLayerSurfaceTags = []

        self.direction = spiralDirection
        self.tsa = thinShellApproximation
        self.transitionNotchAngle = transitionNotchAngle
        # =============================================================================
        # GENERATING SPIRAL CURVES STARTS =============================================
        # =============================================================================
        if thinShellApproximation:
            # Create only one spiral curve because the thin shell approximation is used:
            # Winding thickness is increased slightly to ensure that the outer radius
            # would be the same without the thin shell approximation.
            turns = (
                turns + 1
            )  # for TSA, spiral has (n+1) turns but spiral surface has n
            spiral = spiralCurve(
                r_i,
                t + contactLayerThickness * (turns - 1) / (turns),
                turns,
                z,
                theta_i,
                transitionNotchAngle,
                spiralDirection,
                cutPlaneNormal=cutPlaneNormal,
            )

            # These are created to be able to use the same code with TSA and without TSA:
            innerSpiral = spiral
            outerSpiral = spiral
        else:
            # Create two spiral curves because the thin shell approximation is not used:
            innerSpiral = spiralCurve(
                r_i - contactLayerThickness,
                t + contactLayerThickness,
                turns + 1,
                z,
                theta_i,
                transitionNotchAngle,
                spiralDirection,
                cutPlaneNormal=cutPlaneNormal,
            )
            outerSpiral = spiralCurve(
                r_i,
                t + contactLayerThickness,
                turns + 1,
                z,
                theta_i,
                transitionNotchAngle,
                spiralDirection,
                cutPlaneNormal=cutPlaneNormal,
            )

        self.innerSpiral = innerSpiral
        self.outerSpiral = outerSpiral
        self.turns = turns
        # =============================================================================
        # GENERATING SPIRAL CURVES ENDS ===============================================
        # =============================================================================

        # =============================================================================
        # GENERATING SURFACES STARTS ==================================================
        # =============================================================================
        endLines = []
        endInsLines = []

        # This is used to check if all the contact layers are finished:
        allContactLayersAreFinished = False

        # Itterate over the spline tags:
        for i in range(len(innerSpiral.splineTags)):
            if thinShellApproximation:
                # The current spline will be the inner spline:
                innerSplineTag = spiral.splineTags[i]

                # Find the spline tag of the outer spline by finding the spline tag of
                # the spline that is exactly on the next turn:

                # Note the turn number of the current spline's start point:
                startTurn = spiral.splineTurns[i][0]

                if startTurn + 1 + 1e-4 > turns:
                    # If the current spline is on the outer surface, break the loop,
                    # because the whole surface is finished:
                    break

                # Find the outer spline tag:
                isItBroken = True
                for j, turnTuple in enumerate(spiral.splineTurns):
                    # Equality can not be checked with == because of the floating point
                    # errors:
                    if abs(turnTuple[0] - 1 - startTurn) < 1e-4:
                        outerSplineTag = spiral.splineTags[j]
                        isItBroken = False
                        break

                if isItBroken:
                    raise RuntimeError(
                        "Something went wrong while creating the spiral surface. Outer"
                        f" spline tag of {innerSplineTag} could not be found for TSA."
                    )

            else:
                # Store the tags of the current splines:
                innerSplineTag = innerSpiral.splineTags[i]
                outerSplineTag = outerSpiral.splineTags[i]

                # The current outer spline will be the inner spline of the
                # contact layer:
                innerInsSplineTag = outerSpiral.splineTags[i]

                # Find the spline tag of the contact layer's outer spline by finding the
                # spline tag of the spline that is exactly on the next turn:

                # Note the turn number of the current spline's start point:
                startTurn = outerSpiral.splineTurns[i][0]

                if startTurn + 1 + 1e-4 > turns + 1:
                    # If the current spline is on the outer surface, note that all the
                    # contact layers are finished:
                    allContactLayersAreFinished = True

                # Find the contact layer's outer spline tag:
                for j, turnTuple in enumerate(innerSpiral.splineTurns):
                    if math.isclose(turnTuple[0], 1 + startTurn, abs_tol=1e-6):
                        outerInsSplineTag = innerSpiral.splineTags[j]
                        break

            # Create the lines to connect the two splines so that a surface can be
            # created:

            # Create start line:
            if i == 0:
                # If it is the first spline, start line should be created.

                # Create points:
                isStartPoint = gmsh.model.getBoundary([(1, innerSplineTag)])[1][1]
                if thinShellApproximation:
                    osStartPoint = gmsh.model.getBoundary([(1, outerSplineTag)])[0][1]
                else:
                    osStartPoint = gmsh.model.getBoundary([(1, outerSplineTag)])[1][1]

                # Create the line:
                startLine = gmsh.model.occ.addLine(osStartPoint, isStartPoint)
                firstStartLine = startLine

                # Create lines for the contact layer if the thin shell approximation is not
                # used:
                if not thinShellApproximation and not allContactLayersAreFinished:
                    isInsStartPoint = gmsh.model.getBoundary([(1, innerInsSplineTag)])[
                        1
                    ][1]
                    osInsStartPoint = gmsh.model.getBoundary([(1, outerInsSplineTag)])[
                        0
                    ][1]

                    # Create the line:
                    startInsLine = gmsh.model.occ.addLine(
                        osInsStartPoint, isInsStartPoint
                    )
                    firstInsStartLine = startInsLine

            else:
                # If it is not the first spline, the start line is the end line of the
                # previous surface. This guarantees that the surfaces are connected
                # (conformality).
                startLine = endLines[i - 1]

                # Do the same for the contact layer if the thin shell approximation is not
                # used:
                if not thinShellApproximation and not allContactLayersAreFinished:
                    startInsLine = endInsLines[i - 1]

            # Create end line:

            # Create points:
            # The ifs are used because getBoundary is not consistent somehow.
            if i == 0:
                isEndPoint = gmsh.model.getBoundary([(1, innerSplineTag)])[0][1]
            else:
                isEndPoint = gmsh.model.getBoundary([(1, innerSplineTag)])[1][1]

            if (not i == 0) or thinShellApproximation:
                osEndPoint = gmsh.model.getBoundary([(1, outerSplineTag)])[1][1]
            else:
                osEndPoint = gmsh.model.getBoundary([(1, outerSplineTag)])[0][1]

            # Create the line:
            endLine = gmsh.model.occ.addLine(isEndPoint, osEndPoint)
            endLines.append(endLine)

            # Create lines for the contact layer if the thin shell approximation is not
            # used:
            if not thinShellApproximation and not allContactLayersAreFinished:
                if i == 0:
                    isInsEndPoint = gmsh.model.getBoundary([(1, innerInsSplineTag)])[0][
                        1
                    ]
                else:
                    isInsEndPoint = gmsh.model.getBoundary([(1, innerInsSplineTag)])[1][
                        1
                    ]

                osInsEndPoint = gmsh.model.getBoundary([(1, outerInsSplineTag)])[1][1]

                # Create the line:
                endInsLine = gmsh.model.occ.addLine(isInsEndPoint, osInsEndPoint)
                endInsLines.append(endInsLine)

            # Create the surface:
            curveLoop = gmsh.model.occ.addCurveLoop(
                [startLine, innerSplineTag, endLine, outerSplineTag]
            )
            self.surfaceTags.append(gmsh.model.occ.addPlaneSurface([curveLoop]))

            # Create the surface for the contact layer if the thin shell approximation is
            # not used:
            if not thinShellApproximation and not allContactLayersAreFinished:
                curveLoop = gmsh.model.occ.addCurveLoop(
                    [startInsLine, innerInsSplineTag, endInsLine, outerInsSplineTag]
                )
                self.contactLayerSurfaceTags.append(
                    gmsh.model.occ.addPlaneSurface([curveLoop])
                )

        # =============================================================================
        # GENERATING SURFACES ENDS ====================================================
        # =============================================================================

        # =============================================================================
        # GENERATING CURVE LOOPS STARTS ===============================================
        # =============================================================================

        # Create the inner and outer curve loops (for both TSA == True and TSA == False):

        # VERY IMPORTANT NOTES ABOUT THE DIRECTION OF THE CURVE LOOPS
        # 1- GMSH doesn't like duplicates. Or the user doesn't like duplicates if they
        #     want conformality. Actually, it's a positive thing about debugging because
        #     you can always use `Geometry.remove_all_duplicates()` to see if there are
        #     any duplicates. If there are, the problem is found. Solve it.
        # 2- The problem arises when one uses surface loops or curve loops. Because even
        #     if you think there are no duplicates, GMSH/OCC might create some during
        #     addCurveLoops and addSurfaceLoops operations. Even though
        #     `geometry.remove_all_duplicates()` tells that there are duplicates, the
        #     user doesn't suspect about addCurveLoop and addSurfaceLoop at first,
        #     because it doesn't make sense.
        # 3- How you put the curves in the curve loops is very important! The same curve
        #     loop with the same lines might cause problems if the user puts them in a
        #     different order. For example, to create a plane surface with two curve
        #     loops, the direction of the curve loops should be the same. That's why the
        #     code has both innerCurveLoopTag and innerOppositeCurveLoopTag (the same
        #     thing for the outer curve loop).

        # create the transition layer (notch):
        # Inner curve loop:
        notchStartPoint = innerSpiral.getPointTag(
            1 - transitionNotchAngle / (2 * math.pi)
        )
        notchLeftPoint = innerSpiral.getPointTag(0)
        notchLeftLine = gmsh.model.occ.addLine(notchStartPoint, notchLeftPoint)
        notchRightLine = innerSpiral.getSplineTag(
            1 - transitionNotchAngle / (2 * math.pi)
        )

        if thinShellApproximation:
            innerStartCurves = [firstStartLine]
        else:
            innerStartCurves = [firstInsStartLine, firstStartLine]

        if thinShellApproximation:

            notchCurveLoop = gmsh.model.occ.addCurveLoop(
                [notchLeftLine, notchRightLine] + innerStartCurves
            )
            self.innerNotchSurfaceTags = [
                gmsh.model.occ.addPlaneSurface([notchCurveLoop])
            ]
        else:
            notchMiddlePoint = outerSpiral.getPointTag(0)
            notchMiddleLine = gmsh.model.occ.addLine(notchStartPoint, notchMiddlePoint)

            notchCurveLoop1 = gmsh.model.occ.addCurveLoop(
                [notchLeftLine, notchMiddleLine, firstStartLine]
            )
            notchCurveLoop2 = gmsh.model.occ.addCurveLoop(
                [notchMiddleLine, notchRightLine, firstInsStartLine]
            )
            self.innerNotchSurfaceTags = [
                gmsh.model.occ.addPlaneSurface([notchCurveLoop1]),
                gmsh.model.occ.addPlaneSurface([notchCurveLoop2]),
            ]

        lines = innerSpiral.getSplineTags(
            0, 1 - transitionNotchAngle / (2 * math.pi)
        )  # The first turn of the spline
        innerCurves = lines + [notchLeftLine]
        self.innerNotchLeftLine = notchLeftLine

        self.innerStartCurves = innerStartCurves

        self.innerCurveLoopTag = gmsh.model.occ.addCurveLoop(innerCurves)
        self.innerOppositeCurveLoopTag = gmsh.model.occ.addCurveLoop(innerCurves[::-1])

        # Outer curve loop:
        # The last turn of the spline:
        if thinShellApproximation:
            notchStartPoint = innerSpiral.getPointTag(
                self.turns + transitionNotchAngle / (2 * math.pi) - 1
            )
            notchLeftPoint = innerSpiral.getPointTag(self.turns)
            notchLeftLine = gmsh.model.occ.addLine(notchStartPoint, notchLeftPoint)
            notchRightLine = innerSpiral.getSplineTag(
                self.turns - 1 + transitionNotchAngle / (2 * math.pi), endPoint=True
            )
        else:
            notchStartPoint = outerSpiral.getPointTag(
                self.turns + transitionNotchAngle / (2 * math.pi)
            )
            notchMiddlePoint = innerSpiral.getPointTag(self.turns + 1)
            notchLeftPoint = outerSpiral.getPointTag(self.turns + 1)
            notchLeftLine = gmsh.model.occ.addLine(notchStartPoint, notchLeftPoint)
            notchMiddleLine = gmsh.model.occ.addLine(notchStartPoint, notchMiddlePoint)
            notchRightLine = outerSpiral.getSplineTag(
                self.turns + transitionNotchAngle / (2 * math.pi), self.turns
            )
        if thinShellApproximation:
            lines = outerSpiral.getSplineTags(turns - 1, turns)
        else:
            lines = outerSpiral.getSplineTags(turns, turns + 1)

        if thinShellApproximation:
            outerEndCurves = [endLines[-1]]
        else:
            outerEndCurves = [endInsLines[-1], endLines[-1]]

        if thinShellApproximation:
            notchCurveLoop1 = gmsh.model.occ.addCurveLoop(
                [notchLeftLine, notchRightLine, endLines[-1]]
            )
            self.outerNotchSurfaceTags = [
                gmsh.model.occ.addPlaneSurface([notchCurveLoop1]),
            ]
        else:
            notchCurveLoop1 = gmsh.model.occ.addCurveLoop(
                [notchLeftLine, notchMiddleLine, endLines[-1]]
            )
            notchCurveLoop2 = gmsh.model.occ.addCurveLoop(
                [notchMiddleLine, notchRightLine, endInsLines[-1]]
            )
            self.outerNotchSurfaceTags = [
                gmsh.model.occ.addPlaneSurface([notchCurveLoop1]),
                gmsh.model.occ.addPlaneSurface([notchCurveLoop2]),
            ]

        if thinShellApproximation:
            lines = innerSpiral.getSplineTags(
                self.turns - 1 + transitionNotchAngle / (2 * math.pi), self.turns
            )  # The first turn of the spline
        else:
            lines = outerSpiral.getSplineTags(
                self.turns + transitionNotchAngle / (2 * math.pi), self.turns + 1
            )
        outerCurves = lines + [notchLeftLine]
        self.outerNotchLeftLine = notchLeftLine

        self.outerEndCurves = outerEndCurves

        self.outerCurveLoopTag = gmsh.model.occ.addCurveLoop(outerCurves)
        self.outerOppositeCurveLoopTag = gmsh.model.occ.addCurveLoop(outerCurves[::-1])
        # =============================================================================
        # GENERATING CURVE LOOPS ENDS =================================================
        # =============================================================================

        if not thinShellApproximation:
            surfaceTags = self.surfaceTags
            self.surfaceTags = self.contactLayerSurfaceTags
            self.contactLayerSurfaceTags = surfaceTags

    def getInnerRightPointTag(self):
        """
        Returns the point tag of the inner left point.

        :return: point tag
        :rtype: int
        """
        return self.innerSpiral.getPointTag(0)

    def getInnerUpperPointTag(self):
        """
        Returns the point tag of the inner right point.

        :return: point tag
        :rtype: int
        """
        if self.direction is direction.ccw:
            return self.innerSpiral.getPointTag(0.25)
        else:
            return self.innerSpiral.getPointTag(0.75)

    def getInnerLeftPointTag(self):
        """
        Returns the point tag of the inner upper point.

        :return: point tag
        :rtype: int
        """
        return self.innerSpiral.getPointTag(0.5)

    def getInnerLowerPointTag(self):
        """
        Returns the point tag of the inner lower point.

        :return: point tag
        :rtype: int
        """
        if self.direction is direction.ccw:
            return self.innerSpiral.getPointTag(0.75)
        else:
            return self.innerSpiral.getPointTag(0.25)

    def getOuterRightPointTag(self):
        """
        Returns the point tag of the outer left point.

        :return: point tag
        :rtype: int
        """
        if self.tsa:
            turns = self.turns
        else:
            turns = self.turns + 1
        return self.outerSpiral.getPointTag(turns, endPoint=False)

    def getOuterLowerPointTag(self):
        """
        Returns the point tag of the outer right point.

        :return: point tag
        :rtype: int
        """
        if self.tsa:
            turns = self.turns
        else:
            turns = self.turns + 1
        if self.direction is direction.ccw:
            return self.outerSpiral.getPointTag(turns - 0.25, endPoint=False)
        else:
            return self.outerSpiral.getPointTag(turns - 0.75, endPoint=False)

    def getOuterLeftPointTag(self):
        """
        Returns the point tag of the outer upper point.

        :return: point tag
        :rtype: int
        """
        if self.tsa:
            turns = self.turns
        else:
            turns = self.turns + 1
        return self.outerSpiral.getPointTag(turns - 0.5, endPoint=False)

    def getOuterUpperPointTag(self):
        """
        Returns the point tag of the outer lower point.

        :return: point tag
        :rtype: int
        """
        if self.tsa:
            turns = self.turns
        else:
            turns = self.turns + 1
        if self.direction is direction.ccw:
            return self.outerSpiral.getPointTag(turns - 0.75, endPoint=False)
        else:
            return self.outerSpiral.getPointTag(turns - 0.25, endPoint=False)

    def getInnerUpperRightCurves(self):
        """
        Returns the curve tags of the upper right curves.

        :return: curve tags
        :rtype: list[int]
        """
        if self.direction is direction.ccw:
            curves = self.innerSpiral.getSplineTags(0, 0.25)
        else:
            lines = self.innerSpiral.getSplineTags(
                0.75, 1 - self.transitionNotchAngle / (2 * math.pi)
            )  # The first turn of the spline
            lines = lines + [self.innerNotchLeftLine]

            return lines

        return curves

    def getInnerUpperLeftCurves(self):
        """
        Returns the curve tags of the upper left curves.

        :return: curve tags
        :rtype: list[int]
        """
        if self.direction is direction.ccw:
            curves = self.innerSpiral.getSplineTags(0.25, 0.5)
        else:
            curves = self.innerSpiral.getSplineTags(0.5, 0.75)

        return curves

    def getInnerLowerLeftCurves(self):
        """
        Returns the curve tags of the lower left curves.

        :return: curve tags
        :rtype: list[int]
        """
        if self.direction is direction.ccw:
            curves = self.innerSpiral.getSplineTags(0.5, 0.75)
        else:
            curves = self.innerSpiral.getSplineTags(0.25, 0.5)

        return curves

    def getInnerLowerRightCurves(self):
        """
        Returns the curve tags of the lower right curves.

        :return: curve tags
        :rtype: list[int]
        """
        if self.direction is direction.ccw:
            lines = self.innerSpiral.getSplineTags(
                0.75, 1 - self.transitionNotchAngle / (2 * math.pi)
            )  # The first turn of the spline
            lines = lines + [self.innerNotchLeftLine]

            return lines
        else:
            curves = self.innerSpiral.getSplineTags(0, 0.25)

        return curves

    def getOuterUpperRightCurves(self):
        """
        Returns the curve tags of the upper right curves.

        :return: curve tags
        :rtype: list[int]
        """
        if self.tsa:
            turns = self.turns
        else:
            turns = self.turns + 1

        if self.direction is direction.ccw:
            if self.tsa:
                lines = self.innerSpiral.getSplineTags(
                    self.turns - 1 + self.transitionNotchAngle / (2 * math.pi),
                    self.turns - 0.75,
                )  # The first turn of the spline
            else:
                lines = self.outerSpiral.getSplineTags(
                    self.turns + self.transitionNotchAngle / (2 * math.pi),
                    self.turns + 1 - 0.75,
                )
            lines = lines + [self.outerNotchLeftLine]

            return lines
        else:
            curves = self.outerSpiral.getSplineTags(turns - 0.25, turns)

        return curves

    def getOuterUpperLeftCurves(self):
        """
        Returns the curve tags of the lower right curves.

        :return: curve tags
        :rtype: list[int]
        """
        if self.tsa:
            turns = self.turns
        else:
            turns = self.turns + 1
        if self.direction is direction.ccw:
            curves = self.outerSpiral.getSplineTags(turns - 0.75, turns - 0.5)
        else:
            curves = self.outerSpiral.getSplineTags(turns - 0.5, turns - 0.25)

        return curves

    def getOuterLowerLeftCurves(self):
        """
        Returns the curve tags of the lower left curves.

        :return: curve tags
        :rtype: list[int]
        """
        if self.tsa:
            turns = self.turns
        else:
            turns = self.turns + 1
        if self.direction is direction.ccw:
            curves = self.outerSpiral.getSplineTags(turns - 0.5, turns - 0.25)
        else:
            curves = self.outerSpiral.getSplineTags(turns - 0.75, turns - 0.5)

        return curves

    def getOuterLowerRightCurves(self):
        """
        Returns the curve tags of the upper left curves.

        :return: curve tags
        :rtype: list[int]
        """
        if self.tsa:
            turns = self.turns
        else:
            turns = self.turns + 1
        if self.direction is direction.ccw:
            curves = self.outerSpiral.getSplineTags(turns - 0.25, turns)
        else:
            if self.tsa:
                lines = self.innerSpiral.getSplineTags(
                    self.turns - 1 + self.transitionNotchAngle / (2 * math.pi),
                    self.turns - 0.75,
                )  # The first turn of the spline
            else:
                lines = self.outerSpiral.getSplineTags(
                    self.turns + self.transitionNotchAngle / (2 * math.pi),
                    self.turns + 1 - 0.75,
                )
            lines = lines + [self.outerNotchLeftLine]

            return lines

        return curves

    def getInnerStartCurves(self):
        """
        Returns the curve tags of the start curves.

        :return: curve tags
        :rtype: list[int]
        """
        return self.innerStartCurves

    def getOuterEndCurves(self):
        """
        Returns the curve tags of the end curves.

        :return: curve tags
        :rtype: list[int]
        """
        return self.outerEndCurves

getInnerLeftPointTag()

Returns the point tag of the inner upper point.

Returns:

Type Description
int

point tag

Source code in fiqus/geom_generators/GeometryPancake3D.py
def getInnerLeftPointTag(self):
    """
    Returns the point tag of the inner upper point.

    :return: point tag
    :rtype: int
    """
    return self.innerSpiral.getPointTag(0.5)

getInnerLowerLeftCurves()

Returns the curve tags of the lower left curves.

Returns:

Type Description
list[int]

curve tags

Source code in fiqus/geom_generators/GeometryPancake3D.py
def getInnerLowerLeftCurves(self):
    """
    Returns the curve tags of the lower left curves.

    :return: curve tags
    :rtype: list[int]
    """
    if self.direction is direction.ccw:
        curves = self.innerSpiral.getSplineTags(0.5, 0.75)
    else:
        curves = self.innerSpiral.getSplineTags(0.25, 0.5)

    return curves

getInnerLowerPointTag()

Returns the point tag of the inner lower point.

Returns:

Type Description
int

point tag

Source code in fiqus/geom_generators/GeometryPancake3D.py
def getInnerLowerPointTag(self):
    """
    Returns the point tag of the inner lower point.

    :return: point tag
    :rtype: int
    """
    if self.direction is direction.ccw:
        return self.innerSpiral.getPointTag(0.75)
    else:
        return self.innerSpiral.getPointTag(0.25)

getInnerLowerRightCurves()

Returns the curve tags of the lower right curves.

Returns:

Type Description
list[int]

curve tags

Source code in fiqus/geom_generators/GeometryPancake3D.py
def getInnerLowerRightCurves(self):
    """
    Returns the curve tags of the lower right curves.

    :return: curve tags
    :rtype: list[int]
    """
    if self.direction is direction.ccw:
        lines = self.innerSpiral.getSplineTags(
            0.75, 1 - self.transitionNotchAngle / (2 * math.pi)
        )  # The first turn of the spline
        lines = lines + [self.innerNotchLeftLine]

        return lines
    else:
        curves = self.innerSpiral.getSplineTags(0, 0.25)

    return curves

getInnerRightPointTag()

Returns the point tag of the inner left point.

Returns:

Type Description
int

point tag

Source code in fiqus/geom_generators/GeometryPancake3D.py
def getInnerRightPointTag(self):
    """
    Returns the point tag of the inner left point.

    :return: point tag
    :rtype: int
    """
    return self.innerSpiral.getPointTag(0)

getInnerStartCurves()

Returns the curve tags of the start curves.

Returns:

Type Description
list[int]

curve tags

Source code in fiqus/geom_generators/GeometryPancake3D.py
def getInnerStartCurves(self):
    """
    Returns the curve tags of the start curves.

    :return: curve tags
    :rtype: list[int]
    """
    return self.innerStartCurves

getInnerUpperLeftCurves()

Returns the curve tags of the upper left curves.

Returns:

Type Description
list[int]

curve tags

Source code in fiqus/geom_generators/GeometryPancake3D.py
def getInnerUpperLeftCurves(self):
    """
    Returns the curve tags of the upper left curves.

    :return: curve tags
    :rtype: list[int]
    """
    if self.direction is direction.ccw:
        curves = self.innerSpiral.getSplineTags(0.25, 0.5)
    else:
        curves = self.innerSpiral.getSplineTags(0.5, 0.75)

    return curves

getInnerUpperPointTag()

Returns the point tag of the inner right point.

Returns:

Type Description
int

point tag

Source code in fiqus/geom_generators/GeometryPancake3D.py
def getInnerUpperPointTag(self):
    """
    Returns the point tag of the inner right point.

    :return: point tag
    :rtype: int
    """
    if self.direction is direction.ccw:
        return self.innerSpiral.getPointTag(0.25)
    else:
        return self.innerSpiral.getPointTag(0.75)

getInnerUpperRightCurves()

Returns the curve tags of the upper right curves.

Returns:

Type Description
list[int]

curve tags

Source code in fiqus/geom_generators/GeometryPancake3D.py
def getInnerUpperRightCurves(self):
    """
    Returns the curve tags of the upper right curves.

    :return: curve tags
    :rtype: list[int]
    """
    if self.direction is direction.ccw:
        curves = self.innerSpiral.getSplineTags(0, 0.25)
    else:
        lines = self.innerSpiral.getSplineTags(
            0.75, 1 - self.transitionNotchAngle / (2 * math.pi)
        )  # The first turn of the spline
        lines = lines + [self.innerNotchLeftLine]

        return lines

    return curves

getOuterEndCurves()

Returns the curve tags of the end curves.

Returns:

Type Description
list[int]

curve tags

Source code in fiqus/geom_generators/GeometryPancake3D.py
def getOuterEndCurves(self):
    """
    Returns the curve tags of the end curves.

    :return: curve tags
    :rtype: list[int]
    """
    return self.outerEndCurves

getOuterLeftPointTag()

Returns the point tag of the outer upper point.

Returns:

Type Description
int

point tag

Source code in fiqus/geom_generators/GeometryPancake3D.py
def getOuterLeftPointTag(self):
    """
    Returns the point tag of the outer upper point.

    :return: point tag
    :rtype: int
    """
    if self.tsa:
        turns = self.turns
    else:
        turns = self.turns + 1
    return self.outerSpiral.getPointTag(turns - 0.5, endPoint=False)

getOuterLowerLeftCurves()

Returns the curve tags of the lower left curves.

Returns:

Type Description
list[int]

curve tags

Source code in fiqus/geom_generators/GeometryPancake3D.py
def getOuterLowerLeftCurves(self):
    """
    Returns the curve tags of the lower left curves.

    :return: curve tags
    :rtype: list[int]
    """
    if self.tsa:
        turns = self.turns
    else:
        turns = self.turns + 1
    if self.direction is direction.ccw:
        curves = self.outerSpiral.getSplineTags(turns - 0.5, turns - 0.25)
    else:
        curves = self.outerSpiral.getSplineTags(turns - 0.75, turns - 0.5)

    return curves

getOuterLowerPointTag()

Returns the point tag of the outer right point.

Returns:

Type Description
int

point tag

Source code in fiqus/geom_generators/GeometryPancake3D.py
def getOuterLowerPointTag(self):
    """
    Returns the point tag of the outer right point.

    :return: point tag
    :rtype: int
    """
    if self.tsa:
        turns = self.turns
    else:
        turns = self.turns + 1
    if self.direction is direction.ccw:
        return self.outerSpiral.getPointTag(turns - 0.25, endPoint=False)
    else:
        return self.outerSpiral.getPointTag(turns - 0.75, endPoint=False)

getOuterLowerRightCurves()

Returns the curve tags of the upper left curves.

Returns:

Type Description
list[int]

curve tags

Source code in fiqus/geom_generators/GeometryPancake3D.py
def getOuterLowerRightCurves(self):
    """
    Returns the curve tags of the upper left curves.

    :return: curve tags
    :rtype: list[int]
    """
    if self.tsa:
        turns = self.turns
    else:
        turns = self.turns + 1
    if self.direction is direction.ccw:
        curves = self.outerSpiral.getSplineTags(turns - 0.25, turns)
    else:
        if self.tsa:
            lines = self.innerSpiral.getSplineTags(
                self.turns - 1 + self.transitionNotchAngle / (2 * math.pi),
                self.turns - 0.75,
            )  # The first turn of the spline
        else:
            lines = self.outerSpiral.getSplineTags(
                self.turns + self.transitionNotchAngle / (2 * math.pi),
                self.turns + 1 - 0.75,
            )
        lines = lines + [self.outerNotchLeftLine]

        return lines

    return curves

getOuterRightPointTag()

Returns the point tag of the outer left point.

Returns:

Type Description
int

point tag

Source code in fiqus/geom_generators/GeometryPancake3D.py
def getOuterRightPointTag(self):
    """
    Returns the point tag of the outer left point.

    :return: point tag
    :rtype: int
    """
    if self.tsa:
        turns = self.turns
    else:
        turns = self.turns + 1
    return self.outerSpiral.getPointTag(turns, endPoint=False)

getOuterUpperLeftCurves()

Returns the curve tags of the lower right curves.

Returns:

Type Description
list[int]

curve tags

Source code in fiqus/geom_generators/GeometryPancake3D.py
def getOuterUpperLeftCurves(self):
    """
    Returns the curve tags of the lower right curves.

    :return: curve tags
    :rtype: list[int]
    """
    if self.tsa:
        turns = self.turns
    else:
        turns = self.turns + 1
    if self.direction is direction.ccw:
        curves = self.outerSpiral.getSplineTags(turns - 0.75, turns - 0.5)
    else:
        curves = self.outerSpiral.getSplineTags(turns - 0.5, turns - 0.25)

    return curves

getOuterUpperPointTag()

Returns the point tag of the outer lower point.

Returns:

Type Description
int

point tag

Source code in fiqus/geom_generators/GeometryPancake3D.py
def getOuterUpperPointTag(self):
    """
    Returns the point tag of the outer lower point.

    :return: point tag
    :rtype: int
    """
    if self.tsa:
        turns = self.turns
    else:
        turns = self.turns + 1
    if self.direction is direction.ccw:
        return self.outerSpiral.getPointTag(turns - 0.75, endPoint=False)
    else:
        return self.outerSpiral.getPointTag(turns - 0.25, endPoint=False)

getOuterUpperRightCurves()

Returns the curve tags of the upper right curves.

Returns:

Type Description
list[int]

curve tags

Source code in fiqus/geom_generators/GeometryPancake3D.py
def getOuterUpperRightCurves(self):
    """
    Returns the curve tags of the upper right curves.

    :return: curve tags
    :rtype: list[int]
    """
    if self.tsa:
        turns = self.turns
    else:
        turns = self.turns + 1

    if self.direction is direction.ccw:
        if self.tsa:
            lines = self.innerSpiral.getSplineTags(
                self.turns - 1 + self.transitionNotchAngle / (2 * math.pi),
                self.turns - 0.75,
            )  # The first turn of the spline
        else:
            lines = self.outerSpiral.getSplineTags(
                self.turns + self.transitionNotchAngle / (2 * math.pi),
                self.turns + 1 - 0.75,
            )
        lines = lines + [self.outerNotchLeftLine]

        return lines
    else:
        curves = self.outerSpiral.getSplineTags(turns - 0.25, turns)

    return curves

findOuterOnes(dimTags, findInnerOnes=False)

Finds the outermost surface/curve/point in a list of dimTags. The outermost means the furthest from the origin.

Source code in fiqus/geom_generators/GeometryPancake3D.py
def findOuterOnes(dimTags, findInnerOnes=False):
    """
    Finds the outermost surface/curve/point in a list of dimTags. The outermost means
    the furthest from the origin.
    """
    dim = dimTags[0][0]
    if dim == 2:
        distances = []
        for dimTag in dimTags:
            _, curves = gmsh.model.occ.getCurveLoops(dimTag[1])
            for curve in curves:
                curve = list(curve)
                pointTags = gmsh.model.getBoundary(
                    [(1, curveTag) for curveTag in curve],
                    oriented=False,
                    combined=False,
                )
                # Get the positions of the points:
                points = []
                for dimTag in pointTags:
                    boundingbox1 = gmsh.model.occ.getBoundingBox(0, dimTag[1])[:3]
                    boundingbox2 = gmsh.model.occ.getBoundingBox(0, dimTag[1])[3:]
                    boundingbox = list(map(operator.add, boundingbox1, boundingbox2))
                    points.append(list(map(operator.truediv, boundingbox, (2, 2, 2))))

                distances.append(
                    max([point[0] ** 2 + point[1] ** 2 for point in points])
                )
    elif dim == 1:
        distances = []
        for dimTag in dimTags:
            pointTags = gmsh.model.getBoundary(
                [dimTag],
                oriented=False,
                combined=False,
            )
            # Get the positions of the points:
            points = []
            for dimTag in pointTags:
                boundingbox1 = gmsh.model.occ.getBoundingBox(0, dimTag[1])[:3]
                boundingbox2 = gmsh.model.occ.getBoundingBox(0, dimTag[1])[3:]
                boundingbox = list(map(operator.add, boundingbox1, boundingbox2))
                points.append(list(map(operator.truediv, boundingbox, (2, 2, 2))))

            distances.append(max([point[0] ** 2 + point[1] ** 2 for point in points]))

    if findInnerOnes:
        goalDistance = min(distances)
    else:
        goalDistance = max(distances)

    result = []
    for distance, dimTag in zip(distances, dimTags):
        # Return all the dimTags with the hoal distance:
        if math.isclose(distance, goalDistance, abs_tol=1e-6):
            result.append(dimTag)

    return result

Last update: April 27, 2024