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,