"""This module contains these classes: ``Hfss`` and ``BoundaryType``."""

from __future__ import absolute_import  # noreorder

import ast
from collections import OrderedDict
import math
import os
import tempfile
import warnings

from pyaedt.application.Analysis3D import FieldAnalysis3D
from pyaedt.generic.DataHandlers import _dict2arg
from pyaedt.generic.DataHandlers import str_to_bool
from pyaedt.generic.constants import INFINITE_SPHERE_TYPE
from pyaedt.generic.general_methods import generate_unique_name
from pyaedt.generic.general_methods import open_file
from pyaedt.generic.general_methods import parse_excitation_file
from pyaedt.generic.general_methods import pyaedt_function_handler
from pyaedt.generic.general_methods import read_configuration_file
from pyaedt.generic.settings import settings
from pyaedt.modeler import cad
from pyaedt.modeler.cad.component_array import ComponentArray
from pyaedt.modeler.cad.components_3d import UserDefinedComponent
from pyaedt.modeler.geometry_operators import GeometryOperators
from pyaedt.modules.Boundary import BoundaryObject
from pyaedt.modules.Boundary import FarFieldSetup
from pyaedt.modules.Boundary import NativeComponentObject
from pyaedt.modules.Boundary import NearFieldSetup
from pyaedt.modules.SetupTemplates import SetupKeys


class Hfss(FieldAnalysis3D, object):
    """Provides the HFSS application interface.

    This class allows you to create an interactive instance of HFSS and
    connect to an existing HFSS design or create a new HFSS design if
    one does not exist.

    Parameters
    ----------
    projectname : str, optional
        Name of the project to select or the full path to the project
        or AEDTZ archive to open. The default is ``None``, in which
        case an attempt is made to get an active project. If no
        projects are present, an empty project is created.
    designname : str, optional
        Name of the design to select. The default is ``None``, in
        which case an attempt is made to get an active design. If no
        designs are present, an empty design is created.
    solution_type : str, optional
        Solution type to apply to the design. The default is
        ``None``, in which case the user-defined
        default type is applied.
        Options are:

        - "Terminal"
        - "Modal"
        - "SBR+"
        - "Transient"
        - "Eigenmode"

    setup_name : str, optional
        Name of the setup to use as the nominal. The default is
        ``None``, in which case the active setup is used or
        nothing is used.
    specified_version : str, int, float, optional
        Version of AEDT to use. The default is ``None``, in which case
        the active version or latest installed version is used.
        This parameter is ignored when a script is launched within AEDT.
        Examples of input values are ``232``, ``23.2``,``2023.2``,``"2023.2"``.
    non_graphical : bool, optional
        Whether to run AEDT in non-graphical mode. The default
        is ``False``, in which case AEDT is launched in graphical mode.
        This parameter is ignored when a script is launched within AEDT.
    new_desktop_session : bool, optional
        Whether to launch an instance of AEDT in a new thread, even if
        another instance of the ``specified_version`` is active on the
        machine. The default is ``False``. This parameter is ignored when
        a script is launched within AEDT.
    close_on_exit : bool, optional
        Whether to release AEDT on exit. The default is ``False``.
    student_version : bool, optional
        Whether to open the AEDT student version. The default is
        ``False``. This parameter is ignored when a script is launched
        within AEDT.
    machine : str, optional
        Machine name to connect the oDesktop session to. This parameter works only on
        2022 R2 or later. The remote Server must be up and running with the command
        `"ansysedt.exe -grpcsrv portnum"`. If the machine is `"localhost"`, the server
        starts if it is not present.
    port : int, optional
        Port number on which to start the oDesktop communication on an already existing server.
        This parameter is ignored when creating a new server. It works only in 2022 R2 or later.
        The remote server must be up and running with the command `"ansysedt.exe -grpcsrv portnum"`.
    aedt_process_id : int, optional
        Process ID for the instance of AEDT to point PyAEDT at. The default is
        ``None``. This parameter is only used when ``new_desktop_session = False``.

    Examples
    --------
    Create an instance of HFSS and connect to an existing HFSS
    design or create a new HFSS design if one does not exist.

    >>> from pyaedt import Hfss
    >>> hfss = Hfss()
    PyAEDT INFO: No project is defined...
    PyAEDT INFO: Active design is set to...


    Create an instance of HFSS and link to a project named
    ``HfssProject``. If this project does not exist, create one with
    this name.

    >>> hfss = Hfss("HfssProject")
    PyAEDT INFO: Project HfssProject has been created.
    PyAEDT INFO: No design is present. Inserting a new design.
    PyAEDT INFO: Added design ...


    Create an instance of HFSS and link to a design named
    ``HfssDesign1`` in a project named ``HfssProject``.

    >>> hfss = Hfss("HfssProject","HfssDesign1")
    PyAEDT INFO: Added design 'HfssDesign1' of type HFSS.


    Create an instance of HFSS and open the specified project,
    which is named ``"myfile.aedt"``.

    >>> hfss = Hfss("myfile.aedt")
    PyAEDT INFO: Project myfile has been created.
    PyAEDT INFO: No design is present. Inserting a new design.
    PyAEDT INFO: Added design...


    Create an instance of HFSS using the 2023 R2 release and open
    the specified project, which is named ``"myfile2.aedt"``.

    >>> hfss = Hfss(specified_version=232, projectname="myfile2.aedt")
    PyAEDT INFO: Project myfile2 has been created.
    PyAEDT INFO: No design is present. Inserting a new design.
    PyAEDT INFO: Added design...


    Create an instance of HFSS using the 2023 R2 student version and open
    the specified project, which is named ``"myfile3.aedt"``.

    >>> hfss = Hfss(specified_version="2023.2", projectname="myfile3.aedt", student_version=True)
    PyAEDT INFO: Project myfile3 has been created.
    PyAEDT INFO: No design is present. Inserting a new design.
    PyAEDT INFO: Added design...

    """

    # def __repr__(self):
    #     try:
    #         return "HFSS {} {}. ProjectName:{} DesignName:{} ".format(
    #             self._aedt_version, self.solution_type, self.project_name, self.design_name
    #         )
    #     except:
    #         return "HFSS Module"

    def __init__(
        self,
        projectname=None,
        designname=None,
        solution_type=None,
        setup_name=None,
        specified_version=None,
        non_graphical=False,
        new_desktop_session=False,
        close_on_exit=False,
        student_version=False,
        machine="",
        port=0,
        aedt_process_id=None,
    ):
        FieldAnalysis3D.__init__(
            self,
            "HFSS",
            projectname,
            designname,
            solution_type,
            setup_name,
            specified_version,
            non_graphical,
            new_desktop_session,
            close_on_exit,
            student_version,
            machine,
            port,
            aedt_process_id,
        )
        self._field_setups = []
        self.component_array = {}
        self.component_array_names = list(self.get_oo_name(self.odesign, "Model"))
        for component_array in self.component_array_names:
            self.component_array[component_array] = ComponentArray(self, component_array)

    def _init_from_design(self, *args, **kwargs):
        self.__init__(*args, **kwargs)

    @property
    def field_setups(self):
        """List of AEDT radiation fields.

        Returns
        -------
        List of :class:`pyaedt.modules.Boundary.FarFieldSetup` and :class:`pyaedt.modules.Boundary.NearFieldSetup`
        """
        if not self._field_setups:
            self._field_setups = self._get_rad_fields()
        return self._field_setups

    @property
    def field_setup_names(self):
        """List of AEDT radiation field names.

        Returns
        -------
        List of str
        """
        return self.odesign.GetChildObject("Radiation").GetChildNames()

    class BoundaryType(object):
        """Creates and manages boundaries."""

        (PerfectE, PerfectH, Aperture, Radiation, Impedance, LayeredImp, LumpedRLC, FiniteCond, Hybrid, FEBI) = range(
            0, 10
        )

    @property
    def hybrid(self):
        """HFSS hybrid mode for the active solution.

        For instance, it must be set to ``True`` to define the solution type as 'HFSS with Hybrid and Arrays'.

        Returns
        -------
        bool
        """
        return self.design_solutions.hybrid

    @hybrid.setter
    def hybrid(self, value):
        if value != self.design_solutions.hybrid and isinstance(value, bool):
            self.design_solutions.hybrid = value

    @property
    def composite(self):
        """HFSS composite mode for the active solution.

        Returns
        -------
        bool
        """
        return self.design_solutions.composite

    @composite.setter
    def composite(self, value):
        self.design_solutions.composite = value

    @pyaedt_function_handler()
    def set_auto_open(self, enable=True, boundary_type="Radiation"):
        """Set the HFSS auto open type.

        Parameters
        ----------
        enable : bool, optional
            Whether to enable the HFSS auto open option. The default is ``True``.
        boundary_type : str, optional
            Boundary type to use with auto open. Options are ``"Radiation"``,
            ``"FEBI"``, and ``"PML"``. The default is ``"Radiation"``.

        Returns
        -------
        bool
            ``True`` when successful, ``False`` when failed.

        Examples
        --------
        Enable auto open type for the PML boundary.

        >>> hfss.set_auto_open(True, "PML")
        """
        if enable and boundary_type not in ["Radiation", "FEBI", "PML"]:
            raise AttributeError("Wrong boundary type. Check Documentation for valid inputs")
        return self.design_solutions.set_auto_open(enable=enable, boundary_type=boundary_type)

    @pyaedt_function_handler()
    def _get_unique_source_name(self, source_name, root_name):
        if not source_name:
            source_name = generate_unique_name(root_name)
        elif source_name in self.excitations or source_name + ":1" in self.excitations:
            source_name = generate_unique_name(source_name)
        return source_name

    @pyaedt_function_handler()
    def _get_rad_fields(self):
        if not self.design_properties:
            return []
        fields = []
        if self.design_properties.get("RadField"):
            if self.design_properties["RadField"].get("FarFieldSetups"):
                for val in self.design_properties["RadField"]["FarFieldSetups"]:
                    p = self.design_properties["RadField"]["FarFieldSetups"][val]
                    if isinstance(p, (dict, OrderedDict)) and p.get("Type") == "Infinite Sphere":
                        fields.append(FarFieldSetup(self, val, p, "FarFieldSphere"))
            if self.design_properties["RadField"].get("NearFieldSetups"):
                for val in self.design_properties["RadField"]["NearFieldSetups"]:
                    p = self.design_properties["RadField"]["NearFieldSetups"][val]
                    if isinstance(p, (dict, OrderedDict)):
                        if p["Type"] == "Near Rectangle":
                            fields.append(NearFieldSetup(self, val, p, "NearFieldRectangle"))
                        elif p["Type"] == "Near Line":
                            fields.append(NearFieldSetup(self, val, p, "NearFieldLine"))
                        elif p["Type"] == "Near Box":
                            fields.append(NearFieldSetup(self, val, p, "NearFieldBox"))
                        elif p["Type"] == "Near Sphere":
                            fields.append(NearFieldSetup(self, val, p, "NearFieldSphere"))
        return fields

    @pyaedt_function_handler()
    def _create_boundary(self, name, props, boundary_type):
        """Create a boundary.

        Parameters
        ----------
        name : str
            Name of the boundary.
        props : list
            List of properties for the boundary.
        boundary_type :
            Type of the boundary.

        Returns
        -------
        :class:`pyaedt.modules.Boundary.BoundaryObject`
            Boundary object.

        """

        bound = BoundaryObject(self, name, props, boundary_type)
        result = bound.create()
        if result:
            self._boundaries[bound.name] = bound
            self.logger.info("Boundary %s %s has been correctly created.", boundary_type, name)
            return bound
        self.logger.error("Error in boundary creation for %s %s.", boundary_type, name)

        return result

    @pyaedt_function_handler()
    def _create_lumped_driven(self, objectname, int_line_start, int_line_stop, impedance, portname, renorm, deemb):
        start = [str(i) + self.modeler.model_units for i in int_line_start]
        stop = [str(i) + self.modeler.model_units for i in int_line_stop]
        props = OrderedDict({})
        if isinstance(objectname, str):
            props["Objects"] = [objectname]
        else:
            props["Faces"] = [objectname]
        props["DoDeembed"] = deemb
        props["RenormalizeAllTerminals"] = renorm
        if renorm:
            props["Modes"] = OrderedDict(
                {
                    "Mode1": OrderedDict(
                        {
                            "ModeNum": 1,
                            "UseIntLine": True,
                            "IntLine": OrderedDict({"Start": start, "End": stop}),
                            "AlignmentGroup": 0,
                            "CharImp": "Zpi",
                            "RenormImp": str(impedance) + "ohm",
                        }
                    )
                }
            )
        else:
            props["Modes"] = OrderedDict(
                {
                    "Mode1": OrderedDict(
                        {
                            "ModeNum": 1,
                            "UseIntLine": True,
                            "IntLine": OrderedDict({"Start": start, "End": stop}),
                            "AlignmentGroup": 0,
                            "CharImp": "Zpi",
                        }
                    )
                }
            )
        props["ShowReporterFilter"] = False
        props["ReporterFilter"] = [True]
        props["Impedance"] = str(impedance) + "ohm"
        return self._create_boundary(portname, props, "Lumped Port")

    @pyaedt_function_handler()
    def _create_port_terminal(
        self,
        objectname,
        int_line_stop,
        portname,
        renorm=True,
        deembed=None,
        iswaveport=False,
        impedance=None,
        terminals_rename=True,
    ):
        ref_conductors = self.modeler.convert_to_selections(int_line_stop, True)
        props = OrderedDict()
        props["Faces"] = int(objectname)
        props["IsWavePort"] = iswaveport
        props["ReferenceConductors"] = ref_conductors
        props["RenormalizeModes"] = True
        ports = list(self.oboundary.GetExcitationsOfType("Terminal"))
        boundary = self._create_boundary(portname, props, "AutoIdentify")
        if boundary:
            new_ports = list(self.oboundary.GetExcitationsOfType("Terminal"))
            terminals = [i for i in new_ports if i not in ports]
            for count, terminal in enumerate(terminals, start=1):
                props_terminal = OrderedDict()
                props_terminal["TerminalResistance"] = "50ohm"
                props_terminal["ParentBndID"] = boundary.name
                terminal_name = terminal

                if impedance:
                    props_terminal["TerminalResistance"] = str(impedance) + "ohm"
                    properties = [
                        "NAME:AllTabs",
                        [
                            "NAME:HfssTab",
                            ["NAME:PropServers", "BoundarySetup:" + terminal],
                            [
                                "NAME:ChangedProps",
                                ["NAME:Terminal Renormalizing Impedance", "Value:=", str(impedance) + "ohm"],
                            ],
                        ],
                    ]
                    try:
                        self.odesign.ChangeProperty(properties)
                    except:  # pragma: no cover
                        self.logger.warning("Failed to change terminal impedance.")
                if not renorm:
                    properties = [
                        "NAME:AllTabs",
                        [
                            "NAME:HfssTab",
                            ["NAME:PropServers", "BoundarySetup:" + boundary.name],
                            [
                                "NAME:ChangedProps",
                                ["NAME:Renorm All Terminals", "Value:=", False],
                            ],
                        ],
                    ]
                    try:
                        self.odesign.ChangeProperty(properties)
                    except:  # pragma: no cover
                        self.logger.warning("Failed to change normalization.")
                if terminals_rename:
                    new_name = portname + "_T" + str(count)
                    terminal_name = new_name
                    properties = [
                        "NAME:AllTabs",
                        [
                            "NAME:HfssTab",
                            ["NAME:PropServers", "BoundarySetup:" + terminal],
                            ["NAME:ChangedProps", ["NAME:Name", "Value:=", new_name]],
                        ],
                    ]
                    try:
                        self.odesign.ChangeProperty(properties)
                    except:  # pragma: no cover
                        self.logger.warning("Failed to rename terminal {}.".format(terminal))
                bound = BoundaryObject(self, terminal_name, props_terminal, "Terminal")
                self._boundaries[terminal_name] = bound

            if iswaveport:
                boundary.type = "Wave Port"
            else:
                boundary.type = "Lumped Port"
            props["Faces"] = [objectname]
            if iswaveport:
                props["NumModes"] = 1
                props["UseLineModeAlignment"] = 1
            if deembed is None:
                props["DoDeembed"] = False
                if iswaveport:
                    props["DeembedDist"] = self.modeler._arg_with_dim(0)
            else:
                props["DoDeembed"] = True
                if iswaveport:
                    props["DeembedDist"] = self.modeler._arg_with_dim(deembed)
            props["RenormalizeAllTerminals"] = renorm
            props["ShowReporterFilter"] = False
            props["UseAnalyticAlignment"] = False
            boundary.auto_update = False
            boundary.props.update(props)
            boundary.auto_update = True
            boundary.update()

        return boundary

    @pyaedt_function_handler()
    def _create_circuit_port(self, edgelist, impedance, name, renorm, deemb, renorm_impedance=""):
        edgelist = self.modeler.convert_to_selections(edgelist, True)
        props = OrderedDict(
            {
                "Edges": edgelist,
                "Impedance": str(impedance) + "ohm",
                "DoDeembed": deemb,
                "RenormalizeAllTerminals": renorm,
            }
        )

        if "Modal" in self.solution_type:
            if renorm:
                if isinstance(renorm_impedance, (int, float)) or "i" not in renorm_impedance:
                    renorm_imp = str(renorm_impedance) + "ohm"
                else:
                    renorm_imp = "(" + renorm_impedance + ") ohm"
            else:
                renorm_imp = "0ohm"
            props["RenormImp"] = renorm_imp
        else:
            props["TerminalIDList"] = []
        return self._create_boundary(name, props, "Circuit Port")

    @pyaedt_function_handler()
    def _create_waveport_driven(
        self,
        objectname,
        int_line_start=None,
        int_line_stop=None,
        impedance=50,
        portname="",
        renorm=True,
        nummodes=1,
        deemb_distance=0,
    ):
        start = None
        stop = None
        if int_line_start and int_line_stop:  # Allow non-numeric arguments
            start = [str(i) + self.modeler.model_units if type(i) in (int, float) else i for i in int_line_start]
            stop = [str(i) + self.modeler.model_units if type(i) in (int, float) else i for i in int_line_stop]
            useintline = True
        else:
            useintline = False

        props = OrderedDict({})  # Used to create the argument to pass to native api: oModule.AssignWavePort()
        if isinstance(objectname, int):  # Assumes a Face ID is passed in objectname
            props["Faces"] = [objectname]
        elif isinstance(objectname, list):  # Assume [x, y, z] point is passed in objectname
            props["Faces"] = self.modeler.get_faceid_from_position(objectname)
        else:
            props["Objects"] = [objectname]
        props["NumModes"] = nummodes
        props["UseLineModeAlignment"] = False

        if deemb_distance != 0:
            props["DoDeembed"] = True
            props["DeembedDist"] = self.modeler._arg_with_dim(deemb_distance)
        else:
            props["DoDeembed"] = False
        props["RenormalizeAllTerminals"] = renorm
        modes = OrderedDict({})
        arg2 = []
        arg2.append("NAME:Modes")
        i = 1
        report_filter = []
        while i <= nummodes:
            if i == 1:
                mode = OrderedDict({})
                mode["ModeNum"] = i
                mode["UseIntLine"] = useintline
                if useintline:
                    mode["IntLine"] = OrderedDict({"Start": start, "End": stop})
                mode["AlignmentGroup"] = 0
                mode["CharImp"] = "Zpi"
                if renorm:
                    mode["RenormImp"] = str(impedance) + "ohm"
                modes["Mode1"] = mode
            else:
                mode = OrderedDict({})

                mode["ModeNum"] = i
                mode["UseIntLine"] = False
                mode["AlignmentGroup"] = 0
                mode["CharImp"] = "Zpi"
                if renorm:
                    mode["RenormImp"] = str(impedance) + "ohm"
                modes["Mode" + str(i)] = mode
            report_filter.append(True)
            i += 1
        props["Modes"] = modes
        props["ShowReporterFilter"] = False
        props["ReporterFilter"] = report_filter
        props["UseAnalyticAlignment"] = False
        return self._create_boundary(portname, props, "Wave Port")

    @pyaedt_function_handler()
    def assign_coating(
        self,
        obj,
        mat=None,
        cond=58000000,
        perm=1,
        usethickness=False,
        thickness="0.1mm",
        roughness="0um",
        isinfgnd=False,
        istwoside=False,
        isInternal=True,
        issheelElement=False,
        usehuray=False,
        radius="0.5um",
        ratio="2.9",
    ):
        """Assign finite conductivity to one or more objects or faces of a given material.

        Parameters
        ----------
        obj : str or list
            One or more objects or faces to assign finite conductivity to.
        mat : str, optional
            Material to use. The default is ``None``.
        cond : float, optional
            If no material is provided, a conductivity value must be supplied. The default is ``58000000``.
        perm : float, optional
            If no material is provided, a permittivity value must be supplied. The default is ``1``.
        usethickness : bool, optional
            Whether to use thickness. The default is ``False``.
        thickness : str, optional
            Thickness value if ``usethickness=True``. The default is ``"0.1mm"``.
        roughness : str, optional
            Roughness value  with units. The default is ``"0um"``.
        isinfgnd : bool, optional
            Whether the finite conductivity is an infinite ground. The default is ``False``.
        istwoside : bool, optional
            Whether the finite conductivity is two-sided. The default is ``False``.
        isInternal : bool, optional
            Whether the finite conductivity is internal. The default is ``True``.
        issheelElement : bool, optional
            The default is ``False``.
        usehuray : bool, optional
            Whether to use an Huray coefficient. The default is ``False``.
        radius : str, optional
            Radius value if ``usehuray=True``. The default is ``"0.5um"``.
        ratio : str, optional
            Ratio value if ``usehuray=True``. The default is ``"2.9"``.

        Returns
        -------
        :class:`pyaedt.modules.Boundary.BoundaryObject`
            Boundary object.

        References
        ----------

        >>> oModule.AssignFiniteCond

        Examples
        --------

        Create two cylinders in the XY working plane and assign a copper coating of 0.2 mm to the inner cylinder and
        outer face.
        >>> from pyaedt import Hfss
        >>> hfss = Hfss()
        >>> origin = hfss.modeler.Position(0, 0, 0)
        >>> inner = hfss.modeler.create_cylinder(
        ...     hfss.PLANE.XY, origin, 3, 200, 0, "inner"
        ... )
        >>> outer = hfss.modeler.create_cylinder(
        ...     hfss.PLANE.XY, origin, 4, 200, 0, "outer"
        ... )
        >>> coat = hfss.assign_coating(["inner", outer.faces[2].id], "copper", usethickness=True, thickness="0.2mm")

        """

        userlst = self.modeler.convert_to_selections(obj, True)
        lstobj = []
        lstface = []
        for selection in userlst:
            if selection in self.modeler.model_objects:
                lstobj.append(selection)
            elif isinstance(selection, int) and self.modeler._find_object_from_face_id(selection):
                lstface.append(selection)

        if not lstface and not lstobj:
            self.logger.warning("Objects or Faces selected do not exist in the design.")
            return False
        listobjname = ""
        props = {}
        if lstobj:
            listobjname = listobjname + "_" + "_".join(lstobj)
            props["Objects"] = lstobj
        if lstface:
            props["Faces"] = lstface
            lstface = [str(i) for i in lstface]
            listobjname = listobjname + "_" + "_".join(lstface)
        if mat:
            if self.materials[mat]:
                props["UseMaterial"] = True
                props["Material"] = self.materials[mat].name
            else:
                return False
        else:
            props["UseMaterial"] = False
            props["Conductivity"] = str(cond)
            props["Permeability"] = str(str(perm))
        props["UseThickness"] = usethickness
        if usethickness:
            props["Thickness"] = thickness
        if usehuray:
            props["Radius"] = str(radius)
            props["Ratio"] = str(ratio)
            props["InfGroundPlane"] = False
        else:
            props["Roughness"] = roughness
            props["InfGroundPlane"] = isinfgnd
        props["IsTwoSided"] = istwoside

        if istwoside:
            props["IsShellElement"] = issheelElement
        else:
            props["IsInternal"] = isInternal
        return self._create_boundary("Coating_" + listobjname[1:], props, "Finite Conductivity")

    # TODO: Extract name and type from **kwargs to pass them to create_setup() as setuptype and setupname

    @pyaedt_function_handler()
    def create_setup(self, setupname="MySetupAuto", setuptype=None, **kwargs):
        """Create an analysis setup for HFSS.
        Optional arguments are passed along with ``setuptype`` and ``setupname``.  Keyword
        names correspond to the ``setuptype``
        corresponding to the native AEDT API.  The list of
        keywords here is not exhaustive.

        .. note::
           This method overrides the ``Analysis.setup()`` method for the HFSS app.

        Parameters
        ----------
        setuptype : str, optional
            Type of the setup. Based on the solution type, options are
            ``"HFSSDrivenAuto"``, ``"HFSSDrivenDefault"``, ``"HFSSEigen"``, ``"HFSSTransient"``,
            and ``"HFSSSBR"``. The default is ``"HFSSDrivenAuto"``.
        setupname : str, optional
            Name of the setup. The default is ``"Setup1"``.
        **kwargs : dict, optional
            Extra arguments to set up the circuit.
            Available keys depend on the setup chosen.
            For more information, see
            :doc:`../SetupTemplatesHFSS`.


        Returns
        -------
        :class:`pyaedt.modules.SolveSetup.SetupHFSS`, :class:`pyaedt.modules.SolveSetup.SetupHFSSAuto`
            3D Solver Setup object.

        References
        ----------

        >>> oModule.InsertSetup

        Examples
        --------

        >>> from pyaedt import Hfss
        >>> hfss = Hfss()
        >>> hfss.create_setup(setupname="Setup1", setuptype="HFSSDriven", Frequency="10GHz")

        """
        if setuptype is None:
            setuptype = self.design_solutions.default_setup
        elif setuptype in SetupKeys.SetupNames:
            setuptype = SetupKeys.SetupNames.index(setuptype)
        setup = self._create_setup(setupname=setupname, setuptype=setuptype)
        setup.auto_update = False
        for arg_name, arg_value in kwargs.items():
            if setup[arg_name] is not None:
                if arg_name == "MultipleAdaptiveFreqsSetup":
                    setup[arg_name].delete_all()
                    if isinstance(arg_value, list):
                        for i in arg_value:
                            setup[arg_name][i] = [0.02]
                    else:
                        for i, k in arg_value.items():
                            setup[arg_name][i] = [k]
                    setup.props["SolveType"] = "MultiFrequency"
                else:
                    setup[arg_name] = arg_value
        setup.auto_update = True
        setup.update()
        return setup

    @pyaedt_function_handler()
    def create_linear_count_sweep(
        self,
        setupname,
        unit,
        freqstart,
        freqstop,
        num_of_freq_points=None,
        sweepname=None,
        save_fields=True,
        save_rad_fields=False,
        sweep_type="Discrete",
        interpolation_tol=0.5,
        interpolation_max_solutions=250,
    ):
        """Create a sweep with a specified number of points.

        Parameters
        ----------
        setupname : str
            Name of the setup.
        unit : str
            Unit of the frequency. For example, ``"MHz"`` or ``"GHz"``.
        freqstart : float
            Starting frequency of the sweep, such as ``1``.
        freqstop : float
            Stopping frequency of the sweep.
        num_of_freq_points : int
            Number of frequency points in the range.
            The default is ``401`` for ``sweep_type = "Interpolating"``. The defaults
            are "Fast"`` and ``5`` for ``sweep_type = ""Discrete"``.
        sweepname : str, optional
            Name of the sweep. The default is ``None``, in which
            case the default name is automatically assigned.
        save_fields : bool, optional
            Whether to save the fields. The default is ``True``.
        save_rad_fields : bool, optional
            Whether to save the radiating fields. The default is ``False``.
        sweep_type : str, optional
            Type of the sweep. Options are ``"Fast"``, ``"Interpolating"``,
            and ``"Discrete"``. The default is ``"Discrete"``.
        interpolation_tol : float, optional
            Error tolerance threshold for the interpolation process. The default
            is ``0.5``.
        interpolation_max_solutions : int, optional
            Maximum number of solutions evaluated for the interpolation process.
            The default is ``250``.

        Returns
        -------
        :class:`pyaedt.modules.SolveSweeps.SweepHFSS` or bool
            Sweep object if successful, ``False`` otherwise.

        References
        ----------

        >>> oModule.InsertFrequencySweep

        Examples
        --------

        Create a setup named ``"LinearCountSetup"`` and use it in a linear count sweep
        named ``"LinearCountSweep"``.

        >>> setup = hfss.create_setup("LinearCountSetup")
        >>> linear_count_sweep = hfss.create_linear_count_sweep(setupname="LinearCountSetup",
        ...                                                     sweepname="LinearCountSweep",
        ...                                                     unit="MHz", freqstart=1.1e3,
        ...                                                     freqstop=1200.1, num_of_freq_points=1658)
        >>> type(linear_count_sweep)
        <class 'pyaedt.modules.SetupTemplates.SweepHFSS'>

        """
        if sweep_type in ["Interpolating", "Fast"]:
            if num_of_freq_points == None:
                num_of_freq_points = 401
        elif sweep_type == "Discrete":
            if num_of_freq_points == None:
                num_of_freq_points = 5
        else:
            raise AttributeError(
                "Invalid value for `sweep_type`. The value must be 'Discrete', 'Interpolating', or 'Fast'."
            )

        if sweepname is None:
            sweepname = generate_unique_name("Sweep")

        if setupname not in self.setup_names:
            return False
        for s in self.setups:
            if s.name == setupname:
                setupdata = s
                if sweepname in [sweep.name for sweep in setupdata.sweeps]:
                    oldname = sweepname
                    sweepname = generate_unique_name(oldname)
                    self.logger.warning(
                        "Sweep %s is already present. Sweep has been renamed in %s.", oldname, sweepname
                    )
                sweepdata = setupdata.add_sweep(sweepname, sweep_type)
                if not sweepdata:
                    return False
                sweepdata.props["RangeType"] = "LinearCount"
                sweepdata.props["RangeStart"] = str(freqstart) + unit
                sweepdata.props["RangeEnd"] = str(freqstop) + unit
                sweepdata.props["RangeCount"] = num_of_freq_points
                sweepdata.props["Type"] = sweep_type
                if sweep_type == "Interpolating":
                    sweepdata.props["InterpTolerance"] = interpolation_tol
                    sweepdata.props["InterpMaxSolns"] = interpolation_max_solutions
                    sweepdata.props["InterpMinSolns"] = 0
                    sweepdata.props["InterpMinSubranges"] = 1
                sweepdata.props["SaveFields"] = save_fields
                sweepdata.props["SaveRadFields"] = save_rad_fields
                sweepdata.update()
                self.logger.info("Linear count sweep {} has been correctly created.".format(sweepname))
                return sweepdata
        return False

    @pyaedt_function_handler()
    def create_linear_step_sweep(
        self,
        setupname,
        unit,
        freqstart,
        freqstop,
        step_size,
        sweepname=None,
        save_fields=True,
        save_rad_fields=False,
        sweep_type="Discrete",
    ):
        """Create a sweep with a specified frequency step.

        Parameters
        ----------
        setupname : str
            Name of the setup.
        unit : str
            Unit of the frequency. For example, ``"MHz"`` or ``"GHz"``.
        freqstart : float
            Starting frequency of the sweep.
        freqstop : float
            Stopping frequency of the sweep.
        step_size : float
            Frequency size of the step.
        sweepname : str, optional
            Name of the sweep. The default is ``None``.
        save_fields : bool, optional
            Whether to save fields. The default is ``True``.
        save_rad_fields : bool, optional
            Whether to save radiating fields. The default is ``False``.
        sweep_type : str, optional
            Type of the sweep. Options are ``"Discrete"``,``"Interpolating"`` and
            ``"Fast"``. The default is ``"Discrete"``.

        Returns
        -------
        :class:`pyaedt.modules.SolveSweeps.SweepHFSS` or bool
            Sweep object if successful, ``False`` otherwise.

        References
        ----------

        >>> oModule.InsertFrequencySweep

        Examples
        --------

        Create a setup named ``"LinearStepSetup"`` and use it in a linear step sweep
        named ``"LinearStepSweep"``.

        >>> setup = hfss.create_setup("LinearStepSetup")
        >>> linear_step_sweep = hfss.create_linear_step_sweep(setupname="LinearStepSetup",
        ...                                                   sweepname="LinearStepSweep",
        ...                                                   unit="MHz", freqstart=1.1e3,
        ...                                                   freqstop=1200.1, step_size=153.8)
        >>> type(linear_step_sweep)
        <class 'pyaedt.modules.SetupTemplates.SweepHFSS'>

        """
        if sweep_type not in ["Discrete", "Interpolating", "Fast"]:
            raise AttributeError(
                "Invalid value for `sweep_type`. The value must be 'Discrete', 'Interpolating', or 'Fast'."
            )
        if sweepname is None:
            sweepname = generate_unique_name("Sweep")

        if setupname not in self.setup_names:
            return False
        for s in self.setups:
            if s.name == setupname:
                return s.create_linear_step_sweep(
                    unit=unit,
                    freqstart=freqstart,
                    freqstop=freqstop,
                    step_size=step_size,
                    sweepname=sweepname,
                    save_fields=save_fields,
                    save_rad_fields=save_rad_fields,
                    sweep_type=sweep_type,
                )
        return False

    @pyaedt_function_handler()
    def create_single_point_sweep(
        self,
        setupname,
        unit,
        freq,
        sweepname=None,
        save_single_field=True,
        save_fields=False,
        save_rad_fields=False,
    ):
        """Create a sweep with a single frequency point.

        Parameters
        ----------
        setupname : str
            Name of the setup.
        unit : str
            Unit of the frequency. For example, ``"MHz"`` or ``"GHz"``.
        freq : float, list
            Frequency of the single point or list of frequencies to create distinct single points.
        sweepname : str, optional
            Name of the sweep. The default is ``None``.
        save_single_field : bool, list, optional
            Whether to save the fields of the single point. The default is ``True``.
            If a list is specified, the length must be the same as the list of frequencies.
        save_fields : bool, optional
            Whether to save fields for all points and subranges defined in the sweep.
            The default is ``False``.
        save_rad_fields : bool, optional
            Whether to save only radiating fields. The default is ``False``.

        Returns
        -------
        :class:`pyaedt.modules.SolveSweeps.SweepHFSS` or bool
            Sweep object if successful, ``False`` otherwise.

        References
        ----------

        >>> oModule.InsertFrequencySweep

        Examples
        --------

        Create a setup named ``"LinearStepSetup"`` and use it in a single point sweep
        named ``"SinglePointSweep"``.

        >>> setup = hfss.create_setup("LinearStepSetup")
        >>> single_point_sweep = hfss.create_single_point_sweep(setupname="LinearStepSetup",
        ...                                                   sweepname="SinglePointSweep",
        ...                                                   unit="MHz", freq=1.1e3)
        >>> type(single_point_sweep)
        <class 'pyaedt.modules.SetupTemplates.SweepHFSS'>

        """
        if sweepname is None:
            sweepname = generate_unique_name("SinglePoint")

        if isinstance(save_single_field, list):
            if not isinstance(freq, list) or len(save_single_field) != len(freq):
                raise AttributeError("The length of save_single_field must be the same as the frequency length.")

        add_subranges = False
        if isinstance(freq, list):
            if not freq:
                raise AttributeError("Frequency list is empty. Specify at least one frequency point.")
            freq0 = freq.pop(0)
            if freq:
                add_subranges = True
        else:
            freq0 = freq

        if isinstance(save_single_field, list):
            save0 = save_single_field.pop(0)
        else:
            save0 = save_single_field
            if add_subranges:
                save_single_field = [save0] * len(freq)

        if setupname not in self.setup_names:
            return False
        for s in self.setups:
            if s.name == setupname:
                return s.create_single_point_sweep(
                    unit=unit,
                    freq=freq,
                    sweepname=sweepname,
                    save_single_field=save_single_field,
                    save_fields=save_fields,
                    save_rad_fields=save_rad_fields,
                )
        return False

    @pyaedt_function_handler()
    def create_sbr_linked_antenna(
        self,
        source_object,
        target_cs="Global",
        solution=None,
        fieldtype="nearfield",
        use_composite_ports=False,
        use_global_current=True,
        current_conformance="Disable",
        thin_sources=True,
        power_fraction="0.95",
        visible=True,
    ):
        """Create a linked antennas.

        Parameters
        ----------
        source_object : pyaedt.Hfss
            Source object.
        target_cs : str, optional
            Target coordinate system. The default is ``"Global"``.
        solution : optional
            The default is ``None``.
        fieldtype : str, optional
            The default is ``"nearfield"``.
        use_composite_ports : bool, optional
            Whether to use composite ports. The default is ``False``.
        use_global_current : bool, optional
            Whether to use the global current. The default is ``True``.
        current_conformance, str optional
            The default is ``"Disable"``.
        thin_sources : bool, optional
             The default is ``True``.
        power_fraction : str, optional
             The default is ``"0.95"``.
        visible : bool, optional.
            Visualize source objects in target design. The default is ``True``.

        References
        ----------

        >>> oEditor.InsertNativeComponent

        Examples
        --------
        >>> from pyaedt import Hfss
        >>> target_project = "my/path/to/targetProject.aedt"
        >>> source_project = "my/path/to/sourceProject.aedt"
        >>> target = Hfss(projectname=target_project, solution_type="SBR+",
        ...               specified_version="2021.2", new_desktop_session=False)  # doctest: +SKIP
        >>> source = Hfss(projectname=source_project, designname="feeder",
        ...               specified_version="2021.2", new_desktop_session=False)  # doctest: +SKIP
        >>> target.create_sbr_linked_antenna(source, target_cs="feederPosition",
        ...                                  fieldtype="farfield")  # doctest: +SKIP

        """
        if self.solution_type != "SBR+":
            self.logger.error("Native components only apply to the SBR+ solution.")
            return False
        compName = source_object.design_name
        uniquename = generate_unique_name(compName)
        if source_object.project_name == self.project_name:
            project_name = "This Project*"
        else:
            project_name = os.path.join(source_object.project_path, source_object.project_name + ".aedt")
        design_name = source_object.design_name
        if not solution:
            solution = source_object.nominal_adaptive
        params = OrderedDict({})
        pars = source_object.available_variations.nominal_w_values_dict
        for el in pars:
            params[el] = pars[el]
        native_props = OrderedDict(
            {
                "Type": "Linked Antenna",
                "Unit": self.modeler.model_units,
                "Is Parametric Array": False,
                "Project": project_name,
                "Product": "HFSS",
                "Design": design_name,
                "Soln": solution,
                "Params": params,
                "ForceSourceToSolve": True,
                "PreservePartnerSoln": True,
                "PathRelativeTo": "TargetProject",
                "FieldType": fieldtype,
                "UseCompositePort": use_composite_ports,
                "SourceBlockageStructure": OrderedDict({"NonModelObject": []}),
            }
        )
        if fieldtype == "nearfield":
            native_props["UseGlobalCurrentSrcOption"] = use_global_current
            native_props["Current Source Conformance"] = current_conformance
            native_props["Thin Sources"] = thin_sources
            native_props["Power Fraction"] = power_fraction
        if visible:
            native_props["VisualizationObjects"] = source_object.modeler.solid_names
        return self._create_native_component(
            "Linked Antenna", target_cs, self.modeler.model_units, native_props, uniquename
        )

    @pyaedt_function_handler()
    def _create_native_component(
        self, antenna_type, target_cs=None, model_units=None, parameters_dict=None, antenna_name=None
    ):
        from pyaedt.modeler.cad.Modeler import CoordinateSystem

        if antenna_name is None:
            antenna_name = generate_unique_name(antenna_type.replace(" ", "").replace("-", ""))
        if not model_units:
            model_units = self.modeler.model_units

        native_props = OrderedDict(
            {"NativeComponentDefinitionProvider": OrderedDict({"Type": antenna_type, "Unit": model_units})}
        )
        if isinstance(target_cs, CoordinateSystem):
            target_cs = target_cs.name
        native_props["TargetCS"] = target_cs
        if isinstance(parameters_dict, dict):
            for el in parameters_dict:
                if (
                    el not in ["antenna_type", "offset", "rotation", "rotation_axis", "mode"]
                    and parameters_dict[el] is not None
                ):
                    native_props["NativeComponentDefinitionProvider"][el.replace("_", " ").title()] = parameters_dict[
                        el
                    ]
        native = NativeComponentObject(self, antenna_type, antenna_name, native_props)
        if native.create():
            user_defined_component = UserDefinedComponent(
                self.modeler, native.name, native_props["NativeComponentDefinitionProvider"], antenna_type
            )
            self.modeler.user_defined_components[native.name] = user_defined_component
            self._native_components.append(native)
            self.logger.info("Native component %s %s has been correctly created.", antenna_type, antenna_name)
            return native
        self.logger.error("Error in native component creation for %s %s.", antenna_type, antenna_name)

        return None

    class SbrAntennas:
        (
            ConicalHorn,
            CrossDipole,
            HalfWaveDipole,
            HorizontalDipole,
            ParametricBeam,
            ParametricSlot,
            PyramidalHorn,
            QuarterWaveMonopole,
            ShortDipole,
            SmallLoop,
            WireDipole,
            WireMonopole,
        ) = (
            "Conical Horn",
            "Cross Dipole",
            "Half-Wave Dipole",
            "Horizontal Dipole",
            "Parametric Beam",
            "Parametric Slot",
            "Pyramidal Horn",
            "Quarter-Wave Monopole",
            "Short Dipole",
            "Small Loop",
            "Wire Dipole",
            "Wire Monopole",
        )

    class SBRAntennaDefaults:
        _conical = OrderedDict(
            {
                "Is Parametric Array": False,
                "MatchedPortImpedance": "50ohm",
                "Polarization": "Vertical",
                "Representation": "Far Field",
                "Mouth Diameter": "0.3meter",
                "Flare Half Angle": "20deg",
            }
        )
        _cross = OrderedDict(
            {
                "Is Parametric Array": False,
                "MatchedPortImpedance": "50ohm",
                "Polarization": "RHCP",
                "Representation": "Current Source",
                "Density": "1",
                "UseGlobalCurrentSrcOption": True,
                "Resonant Frequency": "0.3GHz",
                "Wire Length": "499.654096666667mm",
                "Mode": 0,
            }
        )
        _horizontal = OrderedDict(
            {
                "Is Parametric Array": False,
                "MatchedPortImpedance": "50ohm",
                "Polarization": "Vertical",
                "Representation": "Current Source",
                "Density": "1",
                "UseGlobalCurrentSrcOption": False,
                "Resonant Frequency": "0.3GHz",
                "Wire Length": "499.654096666667mm",
                "Height Over Ground Plane": "249.827048333333mm",
                "Use Default Height": True,
            }
        )
        _parametricbeam = OrderedDict(
            {
                "Is Parametric Array": False,
                "Size": "0.1meter",
                "MatchedPortImpedance": "50ohm",
                "Polarization": "Vertical",
                "Representation": "Far Field",
                "Vertical BeamWidth": "30deg",
                "Horizontal BeamWidth": "60deg",
            }
        )
        _slot = OrderedDict(
            {
                "Is Parametric Array": False,
                "MatchedPortImpedance": "50ohm",
                "Representation": "Far Field",
                "Resonant Frequency": "0.3GHz",
                "Slot Length": "499.654096666667mm",
            }
        )
        _horn = OrderedDict(
            {
                "Is Parametric Array": False,
                "MatchedPortImpedance": "50ohm",
                "Representation": "Far Field",
                "Mouth Width": "0.3meter",
                "Mouth Height": "0.5meter",
                "Waveguide Width": "0.15meter",
                "Width Flare Half Angle": "20deg",
                "Height Flare Half Angle": "35deg",
            }
        )
        _dipole = OrderedDict(
            {
                "Is Parametric Array": False,
                "Size": "1mm",
                "MatchedPortImpedance": "50ohm",
                "Representation": "Far Field",
            }
        )
        _smallloop = OrderedDict(
            {
                "Is Parametric Array": False,
                "MatchedPortImpedance": "50ohm",
                "Polarization": "Vertical",
                "Representation": "Current Source",
                "Density": "1",
                "UseGlobalCurrentSrcOption": False,
                "Current Source Conformance": "Disable",
                "Thin Sources": True,
                "Power Fraction": "0.95",
                "Mouth Diameter": "0.3meter",
                "Flare Half Angle": "20deg",
            }
        )
        _wiredipole = OrderedDict(
            {
                "Is Parametric Array": False,
                "MatchedPortImpedance": "50ohm",
                "Representation": "Far Field",
                "Resonant Frequency": "0.3GHz",
                "Wire Length": "499.654096666667mm",
            }
        )
        parameters = {
            "Conical Horn": _conical,
            "Cross Dipole": _cross,
            "Half-Wave Dipole": _dipole,
            "Horizontal Dipole": _horizontal,
            "Parametric Beam": _parametricbeam,
            "Parametric Slot": _slot,
            "Pyramidal Horn": _horn,
            "Quarter-Wave Monopole": _dipole,
            "Short Dipole": _dipole,
            "Small Loop": _dipole,
            "Wire Dipole": _wiredipole,
            "Wire Monopole": _wiredipole,
        }
        default_type_id = {
            "Conical Horn": 11,
            "Cross Dipole": 12,
            "Half-Wave Dipole": 3,
            "Horizontal Dipole": 13,
            "Parametric Beam": 0,
            "Parametric Slot": 7,
            "Pyramidal Horn": _horn,
            "Quarter-Wave Monopole": 4,
            "Short Dipole": 1,
            "Small Loop": 2,
            "Wire Dipole": 5,
            "Wire Monopole": 6,
            "File Based Antenna": 8,
        }

    @pyaedt_function_handler()
    def create_sbr_antenna(
        self,
        antenna_type=SbrAntennas.ConicalHorn,
        target_cs=None,
        model_units=None,
        parameters_dict=None,
        use_current_source_representation=False,
        is_array=False,
        antenna_name=None,
    ):
        """Create a parametric beam antennas in SBR+.

        Parameters
        ----------
        antenna_type : str, `SbrAntennas.ConicalHorn`
            Name of the antennas type. The enumerator ``SbrAntennas`` can also be used.
            The default is ``"SbrAntennas.Conical Horn"``.
        target_cs : str, optional
            Target coordinate system. The default is ``None``, in which case
            the active coodiante system is used.
        model_units : str, optional
            Model units to apply to the object. The default is
            ``None``, in which case the active modeler units are applied.
        parameters_dict : dict, optional
            Dictionary of parameters. The default is ``None``.
        use_current_source_representation : bool, optional
            Whether to use the current source representation. The default is ``False``.
        is_array : bool, optional
            The default is ``False``.
        antenna_name : str, optional
            Name of the 3D component. The default is ``None``, in which case the
            name is auto-generated based on the antennas type.

        Returns
        -------
        :class:`pyaedt.modules.Boundary.NativeComponentObject`
            NativeComponentObject object.

        References
        ----------

        >>> oEditor.InsertNativeComponent

        Examples
        --------
        >>> from pyaedt import Hfss
        >>> hfss = Hfss(solution_type="SBR+")  # doctest: +SKIP
        PyAEDT INFO: Added design 'HFSS_IPO' of type HFSS.
        >>> parm = {"polarization": "Vertical"}  # doctest: +SKIP
        >>> par_beam = hfss.create_sbr_antenna(hfss.SbrAntennas.ShortDipole,
        ...                                    parameters_dict=parm,
        ...                                    antenna_name="TX1")  # doctest: +SKIP

        """
        if self.solution_type != "SBR+":
            self.logger.error("This native component only applies to a SBR+ solution.")
            return False
        if target_cs is None:
            target_cs = self.modeler.oeditor.GetActiveCoordinateSystem()
        parameters_defaults = self.SBRAntennaDefaults.parameters[antenna_type].copy()
        if use_current_source_representation and antenna_type in [
            "Conical Horn",
            "Horizontal Dipole",
            "Parametric Slot",
            "Pyramidal Horn",
            "Wire Dipole",
            "Wire Monopole",
        ]:
            parameters_defaults["Representation"] = "Current Source"
            parameters_defaults["Density"] = "1"
            parameters_defaults["UseGlobalCurrentSrcOption"] = False
            parameters_defaults["Current Source Conformance"] = "Disable"
            parameters_defaults["Thin Sources"] = False
            parameters_defaults["Power Fraction"] = "0.95"
        if is_array:
            parameters_defaults["Is Parametric Array"] = True
            parameters_defaults["Array Element Type"] = self.SBRAntennaDefaults.default_type_id[antenna_type]
            parameters_defaults["Array Element Angle Phi"] = ("0deg",)
            parameters_defaults["Array Element Angle Theta"] = ("0deg",)
            parameters_defaults["Array Element Offset X"] = "0meter"
            parameters_defaults["Array Element Offset Y"] = "0meter"
            parameters_defaults["Array Element Offset Z"] = "0meter"
            parameters_defaults["Array Element Conformance Type"] = 0
            parameters_defaults["Array Element Conformance Type"] = 0
            parameters_defaults["Array Element Conformance Type"] = 0
            parameters_defaults["Array Element Conform Orientation"] = False
            parameters_defaults["Array Design Frequency"] = "1GHz"
            parameters_defaults["Array Layout Type"] = 1
            parameters_defaults["Array Specify Design In Wavelength"] = True
            parameters_defaults["Array Element Num"] = 5
            parameters_defaults["Array Length"] = "1meter"
            parameters_defaults["Array Width"] = "1meter"
            parameters_defaults["Array Length Spacing"] = "0.1meter"
            parameters_defaults["Array Width Spacing"] = "0.1meter"
            parameters_defaults["Array Length In Wavelength"] = "3"
            parameters_defaults["Array Width In Wavelength"] = "4"
            parameters_defaults["Array Length Spacing In Wavelength"] = "0.5"
            parameters_defaults["Array Stagger Type"] = 0
            parameters_defaults["Array Stagger Angle"] = "0deg"
            parameters_defaults["Array Symmetry Type"] = 0
            parameters_defaults["Array Weight Type"] = 3
            parameters_defaults["Array Beam Angle Theta"] = "0deg"
            parameters_defaults["Array Weight Edge TaperX"] = -200
            parameters_defaults["Array Weight Edge TaperY"] = -200
            parameters_defaults["Array Weight Cosine Exp"] = 1
            parameters_defaults["Array Differential Pattern Type"] = 0
            if is_array:
                antenna_name = generate_unique_name("pAntArray")
        if parameters_dict:
            for el, value in parameters_dict.items():
                parameters_defaults[el] = value
        return self._create_native_component(antenna_type, target_cs, model_units, parameters_defaults, antenna_name)

    @pyaedt_function_handler()
    def create_sbr_file_based_antenna(
        self,
        ffd_full_path,
        antenna_size="1mm",
        antenna_impedance="50ohm",
        representation_type="Far Field",
        target_cs=None,
        model_units=None,
        antenna_name=None,
    ):
        """Create a linked antennas.

        Parameters
        ----------
        ffd_full_path : str
            Full path to the FFD file.
        antenna_size : str, optional
            Antenna size with units. The default is ``"1mm"``.
        antenna_impedance : str, optional
            Antenna impedance with units. The default is ``"50ohm"``.
        representation_type : str, optional
            Type of the antennas. Options are ``"Far Field"`` and ``"Near Field"``.
            The default is ``"Far Field"``.
        target_cs : str, optional
            Target coordinate system. The default is ``None``, in which case the
            active coordinate system is used.
        model_units : str, optional
            Model units to apply to the object. The default is
            ``None``, in which case the active modeler units are applied.
        antenna_name : str, optional
            Name of the 3D component. The default is ``None``, in which case
            the name is auto-generated based on the antennas type.

        Returns
        -------
        :class:`pyaedt.modules.Boundary.NativeComponentObject`

        References
        ----------

        >>> oEditor.InsertNativeComponent

        Examples
        --------
        >>> from pyaedt import Hfss
        >>> hfss = Hfss(solution_type="SBR+")  # doctest: +SKIP
        >>> ffd_file = "full_path/to/ffdfile.ffd"
        >>> par_beam = hfss.create_sbr_file_based_antenna(ffd_file)  # doctest: +SKIP

        """
        if self.solution_type != "SBR+":
            self.logger.error("This native component only applies to a SBR+ solution.")
            return False
        if target_cs is None:
            target_cs = self.modeler.oeditor.GetActiveCoordinateSystem()

        par_dicts = OrderedDict(
            {
                "Size": antenna_size,
                "MatchedPortImpedance": antenna_impedance,
                "Representation": representation_type,
                "ExternalFile": ffd_full_path,
            }
        )
        if not antenna_name:
            antenna_name = generate_unique_name(os.path.basename(ffd_full_path).split(".")[0])

        return self._create_native_component("File Based Antenna", target_cs, model_units, par_dicts, antenna_name)

    @pyaedt_function_handler()
    def set_sbr_txrx_settings(self, txrx_settings):
        """Set SBR+ TX RX antennas settings.

        Parameters
        ----------
        txrx_settings : dict
            Dictionary containing the TX as key and RX as values.

        Returns
        -------
        :class:`pyaedt.modules.Boundary.BoundaryObject`
            Boundary object.

        References
        ----------

        >>> oModule.SetSBRTxRxSettings
        """
        if self.solution_type != "SBR+":
            self.logger.error("This boundary only applies to a SBR+ solution.")
            return False
        id_ = 0
        props = OrderedDict({})
        for el, val in txrx_settings.items():
            props["Tx/Rx List " + str(id_)] = OrderedDict({"Tx Antenna": el, "Rx Antennas": txrx_settings[el]})
            id_ += 1
        return self._create_boundary("SBRTxRxSettings", props, "SBRTxRxSettings")

    @pyaedt_function_handler()
    def create_circuit_port_between_objects(
        self, startobj, endobject, axisdir=0, impedance=50, portname=None, renorm=True, renorm_impedance=50, deemb=False
    ):
        """Create a circuit port taking the closest edges of two objects.

        .. deprecated:: 0.6.70
        Use :func:`circuit_port` method instead.

        Parameters
        ----------
        startobj :
            Starting object for the integration line.
        endobject :
            Ending object for the integration line.
        axisdir : int or :class:`pyaedt.application.Analysis.Analysis.AxisDir`, optional
            Position of the port. It should be one of the values for ``Application.AxisDir``,
            which are: ``XNeg``, ``YNeg``, ``ZNeg``, ``XPos``, ``YPos``, and ``ZPos``.
            The default is ``Application.AxisDir.XNeg``.
        impedance : float, optional
            Port impedance. The default is ``50``.
        portname : str, optional
            Name of the port. The default is ``None``.
        renorm : bool, optional
            Whether to renormalize the mode. The default is ``True``.
        renorm_impedance : float or str, optional
            Renormalize impedance. The default is ``50``.
        deemb : bool, optional
            Whether to deembed the port. The default is ``False``.

        Returns
        -------
        :class:`pyaedt.modules.Boundary.BoundaryObject`
            Boundary object.

        References
        ----------

        >>> oModule.AssignCircuitPort

        Examples
        --------

        Create two boxes for creating a circuit port named ``'CircuitExample'``.

        >>> box1 = hfss.modeler.create_box([0, 0, 80], [10, 10, 5],
        ...                                "BoxCircuit1", "copper")
        >>> box2 = hfss.modeler.create_box([0, 0, 100], [10, 10, 5],
        ...                                "BoxCircuit2", "copper")
        >>> hfss.create_circuit_port_between_objects("BoxCircuit1", "BoxCircuit2",
        ...                                          hfss.AxisDir.XNeg, 50,
        ...                                          "CircuitExample", True, 50, False)
        'CircuitExample'

        """
        warnings.warn("Use :func:`circuit_port` method instead.", DeprecationWarning)
        return self.circuit_port(
            signal=startobj,
            reference=endobject,
            port_location=axisdir,
            impedance=impedance,
            name=portname,
            renormalize=renorm,
            renorm_impedance=renorm_impedance,
            deembed=deemb,
        )

    @pyaedt_function_handler()
    def create_lumped_port_between_objects(
        self, startobj, endobject, axisdir=0, impedance=50, portname=None, renorm=True, deemb=False, port_on_plane=True
    ):
        """Create a lumped port taking the closest edges of two objects.

        .. deprecated:: 0.6.70
        Use :func:`lumped_port` method instead.

        Parameters
        ----------
        startobj :
            Starting object for the integration line.
        endobject :
            Ending object for the integration line.
        axisdir : int or :class:`pyaedt.application.Analysis.Analysis.AxisDir`, optional
            Position of the port. It should be one of the values for ``Application.AxisDir``,
            which are: ``XNeg``, ``YNeg``, ``ZNeg``, ``XPos``, ``YPos``, and ``ZPos``.
            The default is ``Application.AxisDir.XNeg``.
        impedance : float, optional
            Port impedance. The default is ``50``.
        portname : str, optional
            Name of the port. The default is ``None``.
        renorm : bool, optional
            Whether to renormalize the mode. The default is ``True``.
        deemb : bool, optional
            Whether to deembed the port. The default is ``False``.
        port_on_plane : bool, optional
            Whether to create the source on the plane orthogonal to ``AxisDir``.
            The default is ``True``.

        Returns
        -------
        :class:`pyaedt.modules.Boundary.BoundaryObject`
            Boundary object.

        References
        ----------

        >>> oModule.AssignLumpedPort

        Examples
        --------

        Create two boxes that will be used to create a lumped port
        named ``'LumpedPort'``.

        >>> box1 = hfss.modeler.create_box([0, 0, 50], [10, 10, 5],
        ...                                "BoxLumped1","copper")
        >>> box2 = hfss.modeler.create_box([0, 0, 60], [10, 10, 5],
        ...                                "BoxLumped2", "copper")
        >>> hfss.create_lumped_port_between_objects("BoxLumped1", "BoxLumped2",
        ...                                         hfss.AxisDir.XNeg, 50,
        ...                                         "LumpedPort", True, False)
        PyAEDT INFO: Connection Correctly created
        'LumpedPort'

        """
        warnings.warn("Use :func:`lumped_port` method instead.", DeprecationWarning)
        return self.lumped_port(
            signal=startobj,
            reference=endobject,
            create_port_sheet=True,
            port_on_plane=port_on_plane,
            integration_line=axisdir,
            impedance=impedance,
            name=portname,
            renormalize=renorm,
            deembed=deemb,
        )

    @pyaedt_function_handler()
    def create_spiral_lumped_port(self, start_object, end_object, port_width=None):
        """Create a spiral lumped port between two adjacent objects.

        The two objects must have two adjacent, parallel, and identical faces.
        The faces must be a polygon (not a circle).
        The closest faces must be aligned with the main planes of the reference system.

        Parameters
        ----------
        start_object : str or int or :class:`pyaedt.modeler.cad.object3d.Object3d`
            First solid connected to the spiral port.

        end_object : str or int or :class:`pyaedt.modeler.cad.object3d.Object3d`
            Second object connected to the spiral port.

        port_width : float, optional
            Width of the spiral port.
            If not specified the width will be calculated based on the object dimensions.
            The default is ``None``.

        Returns
        -------
        :class:`pyaedt.modules.Boundary.BoundaryObject`
            Boundary object.

        Examples
        --------
        >>> aedtapp = Hfss()
        >>> aedtapp.insert_design("Design_Terminal_2")
        >>> aedtapp.solution_type = "Terminal"
        >>> box1 = aedtapp.modeler.create_box([-100, -100, 0], [200, 200, 5], name="gnd2z", matname="copper")
        >>> box2 = aedtapp.modeler.create_box([-100, -100, 20], [200, 200, 25], name="sig2z", matname="copper")
        >>> aedtapp.modeler.fit_all()
        >>> portz = aedtapp.create_spiral_lumped_port(box1, box2)
        """
        if not "Terminal" in self.solution_type:
            raise Exception("This method can be used only in Terminal solutions.")
        start_object = self.modeler.convert_to_selections(start_object)
        end_object = self.modeler.convert_to_selections(end_object)

        # find the closest faces (based on face center)
        closest_distance = 1e9
        closest_faces = []
        for face1 in self.modeler[start_object].faces:
            for face2 in self.modeler[end_object].faces:
                facecenter_distance = GeometryOperators.points_distance(face1.center, face2.center)
                if facecenter_distance <= closest_distance:
                    closest_distance = facecenter_distance
                    closest_faces = [face1, face2]

        # check if the faces are parallel
        if not GeometryOperators.is_collinear(closest_faces[0].normal, closest_faces[1].normal):
            raise AttributeError("The two objects must have parallel adjacent faces.")
        if GeometryOperators.is_collinear(closest_faces[0].normal, [1, 0, 0]):
            plane = "X"
        elif GeometryOperators.is_collinear(closest_faces[0].normal, [0, 1, 0]):
            plane = "Y"
        elif GeometryOperators.is_collinear(closest_faces[0].normal, [0, 0, 1]):
            plane = "Z"
        else:
            raise AttributeError(
                "The closest faces of the two objects must be aligned with the main planes of the reference system."
            )

        # check if the faces are identical (actually checking only the area, not the shape)
        if abs(closest_faces[0].area - closest_faces[1].area) > 1e-10:
            raise AttributeError("The closest faces of the two objects must be identical in shape.")

        # evaluate the vector to move from face0 to the middle distance between the faces
        move_vector = GeometryOperators.v_sub(closest_faces[1].center, closest_faces[0].center)
        move_vector_mid = GeometryOperators.v_prod(0.5, move_vector)

        # fmt: off
        if port_width:
            spiral_width = port_width
            filling = 1.5
        else:
            # get face bounding box
            face_bb = [1e15, 1e15, 1e15, -1e15, -1e15, -1e15]
            for i in range(3):
                for v in closest_faces[0].vertices:
                    face_bb[i] = min(face_bb[i], v.position[i])
                    face_bb[i+3] = max(face_bb[i+3], v.position[i])
            # get the ratio in 2D
            bb_dim = [abs(face_bb[i]-face_bb[i+3]) for i in range(3) if abs(face_bb[i]-face_bb[i+3]) > 1e-12]
            bb_ratio = max(bb_dim)/min(bb_dim)
            if bb_ratio > 2:
                spiral_width = min(bb_dim) / 12
                filling = -0.2828*bb_ratio**2 + 3.4141*bb_ratio - 4.197
                print(filling)
            else:
                vertex_coordinates = []
                for v in closest_faces[0].vertices:
                    vertex_coordinates.append(v.position)
                segments_lengths = []
                for vc in vertex_coordinates:
                    segments_lengths.append(GeometryOperators.points_distance(vc, closest_faces[0].center))
                spiral_width = min(segments_lengths) / 15
                filling = 1.5
        # fmt: on

        name = generate_unique_name("P", n=3)

        spiral = self.modeler.create_spiral_on_face(closest_faces[0], spiral_width, filling_factor=filling)
        spiral.name = name
        spiral.move(move_vector_mid)
        spiral_center = GeometryOperators.get_mid_point(closest_faces[0].center, closest_faces[1].center)

        # get the polyline center point (before width operation). They need to be moved as well.
        poly_points = [GeometryOperators.v_sum(i, move_vector_mid) for i in spiral.points]

        # get the vertices of the spiral created. These need to be divided in two lists, one following the external
        # contour (p1) and one following the internal contour (p2). We use poly_points to discern the points.
        poly_v = [[v.position[0], v.position[1], v.position[2]] for v in spiral.vertices]

        p1 = []
        p2 = []
        for p in poly_points:
            cp = GeometryOperators.find_closest_points(poly_v, p)
            if len(cp) > 2:
                raise Exception("Internal error in spiral creation, please review the port_width parameter.")
            if GeometryOperators.points_distance(cp[0], spiral_center) > GeometryOperators.points_distance(
                cp[1], spiral_center
            ):
                p1.append(cp[0])
                p2.append(cp[1])
            else:
                p1.append(cp[1])
                p2.append(cp[0])

        # move the p1 down and the p2 up
        move_vector_quarter = GeometryOperators.v_prod(0.25, move_vector)
        p1_down = [GeometryOperators.v_sub(i, move_vector_quarter) for i in p1]
        p2_up = [GeometryOperators.v_sum(i, move_vector_quarter) for i in p2]

        # create first polyline to join spiral with conductor face
        dx = abs(p1_down[0][0] - p1_down[1][0])
        dy = abs(p1_down[0][1] - p1_down[1][1])
        dz = abs(p1_down[0][2] - p1_down[1][2])
        if plane == "X":
            orient = "Y" if (dy < dz) else "Z"
        elif plane == "Y":
            orient = "X" if (dx < dz) else "Z"
        else:
            orient = "X" if (dx < dy) else "Y"

        poly1 = self.modeler.create_polyline(
            p1_down,
            xsection_type="Line",
            xsection_orient=orient,
            xsection_width=closest_distance / 2,
            name=start_object + "_sheet",
        )

        # create second polyline to join spiral with conductor face
        dx = abs(p2_up[0][0] - p2_up[1][0])
        dy = abs(p2_up[0][1] - p2_up[1][1])
        dz = abs(p2_up[0][2] - p2_up[1][2])
        if plane == "X":
            orient = "Y" if (dy < dz) else "Z"
        elif plane == "Y":
            orient = "X" if (dx < dz) else "Z"
        else:
            orient = "X" if (dx < dy) else "Y"
        poly2 = self.modeler.create_polyline(
            p2_up,
            xsection_type="Line",
            xsection_orient=orient,
            xsection_width=closest_distance / 2,
            name=end_object + "_sheet",
        )

        # assign pec to created polylines
        self.assign_perfecte_to_sheets(poly1, sourcename=start_object)
        self.assign_perfecte_to_sheets(poly2, sourcename=end_object)

        # create lumped port on spiral
        port = self.lumped_port(spiral, reference=[poly2.name], name=name)

        return port

    @pyaedt_function_handler()
    def create_voltage_source_from_objects(self, startobj, endobject, axisdir=0, sourcename=None, source_on_plane=True):
        """Create a voltage source taking the closest edges of two objects.

        Parameters
        ----------
        startobj :
            Starting object for the integration line.
        endobject :
            Ending object for the integration line.
        axisdir : int or :class:`pyaedt.application.Analysis.Analysis.AxisDir`, optional
            Position of the port. It should be one of the values for
            ``Application.AxisDir``, which are: ``XNeg``, ``YNeg``,
            ``ZNeg``, ``XPos``, ``YPos``, and ``ZPos``.  The default
            is ``Application.AxisDir.XNeg``.
        sourcename : str, optional
            Name of the source. The default is ``None``.
        source_on_plane : bool, optional
            Whether to create the source on the plane orthogonal to
            ``AxisDir``. The default is ``True``.

        Returns
        -------
        :class:`pyaedt.modules.Boundary.BoundaryObject`
            Boundary object.

        References
        ----------

        >>> oModule.AssignVoltage

        Examples
        --------

        Create two boxes for creating a voltage source named ``'VoltageSource'``.

        >>> box1 = hfss.modeler.create_box([30, 0, 0], [40, 10, 5],
        ...                                "BoxVolt1", "copper")
        >>> box2 = hfss.modeler.create_box([30, 0, 10], [40, 10, 5],
        ...                                "BoxVolt2", "copper")
        >>> v1 = hfss.create_voltage_source_from_objects("BoxVolt1", "BoxVolt2",
        ...                                         hfss.AxisDir.XNeg,
        ...                                         "VoltageSource")
        PyAEDT INFO: Connection Correctly created

        """

        if not self.modeler.does_object_exists(startobj) or not self.modeler.does_object_exists(endobject):
            self.logger.error("One or both objects doesn't exists. Check and retry")
            return False
        if self.solution_type in ["Modal", "Terminal", "Transient Network"]:
            sheet_name, point0, point1 = self.modeler._create_sheet_from_object_closest_edge(
                startobj, endobject, axisdir, source_on_plane
            )
            sourcename = self._get_unique_source_name(sourcename, "Voltage")
            return self.create_source_excitation(sheet_name, point0, point1, sourcename, sourcetype="Voltage")
        return False  # pragma: no cover

    @pyaedt_function_handler()
    def create_current_source_from_objects(self, startobj, endobject, axisdir=0, sourcename=None, source_on_plane=True):
        """Create a current source taking the closest edges of two objects.

        Parameters
        ----------
        startobj :
            Starting object for the integration line.
        endobject :
            Ending object for the integration line.
        axisdir : int or :class:`pyaedt.application.Analysis.Analysis.AxisDir`, optional
            Position of the port. It should be one of the values for ``Application.AxisDir``,
            which are: ``XNeg``, ``YNeg``, ``ZNeg``, ``XPos``, ``YPos``, and ``ZPos``.
            The default is ``Application.AxisDir.XNeg``.
        sourcename : str, optional
            Name of the source. The default is ``None``.
        source_on_plane : bool, optional
            Whether to create the source on the plane orthogonal to ``axisdir``. The default is ``True``.

        Returns
        -------
        :class:`pyaedt.modules.Boundary.BoundaryObject`
            Boundary object.

        References
        ----------

        >>> oModule.AssignCurrent

        Examples
        --------

        Create two boxes for creating a current source named ``'CurrentSource'``.

        >>> box1 = hfss.modeler.create_box([30, 0, 20], [40, 10, 5],
        ...                                "BoxCurrent1", "copper")
        >>> box2 = hfss.modeler.create_box([30, 0, 30], [40, 10, 5],
        ...                                "BoxCurrent2", "copper")
        >>> i1 = hfss.create_current_source_from_objects("BoxCurrent1", "BoxCurrent2",
        ...                                         hfss.AxisDir.XPos,
        ...                                         "CurrentSource")
        PyAEDT INFO: Connection created 'CurrentSource' correctly.

        """

        if not self.modeler.does_object_exists(startobj) or not self.modeler.does_object_exists(endobject):
            self.logger.error("One or both objects do not exist. Check and retry.")
            return False
        if self.solution_type in ["Modal", "Terminal", "Transient Network"]:
            sheet_name, point0, point1 = self.modeler._create_sheet_from_object_closest_edge(
                startobj, endobject, axisdir, source_on_plane
            )
            sourcename = self._get_unique_source_name(sourcename, "Current")
            return self.create_source_excitation(sheet_name, point0, point1, sourcename, sourcetype="Current")
        return False  # pragma: no cover

    @pyaedt_function_handler()
    def create_source_excitation(self, sheet_name, point1, point2, sourcename, sourcetype="Voltage"):
        """Create a source excitation.

        Parameters
        ----------
        sheet_name : str
            Name of the sheet.
        point1 :

        point2 :

        sourcename : str
            Name of the source.

        sourcetype : str, optional
            Type of the source. The default is ``"Voltage"``.

        Returns
        -------
        :class:`pyaedt.modules.Boundary.BoundaryObject`
            Boundary object.

        References
        ----------

        >>> oModule.AssignVoltage
        >>> oModule.AssignCurrent
        """

        props = OrderedDict({"Objects": [sheet_name], "Direction": OrderedDict({"Start": point1, "End": point2})})
        return self._create_boundary(sourcename, props, sourcetype)

    @pyaedt_function_handler()
    def create_wave_port_between_objects(
        self,
        startobj,
        endobject,
        axisdir=0,
        impedance=50,
        nummodes=1,
        portname=None,
        renorm=True,
        deembed_dist=0,
        port_on_plane=True,
        add_pec_cap=False,
    ):
        """Create a waveport taking the closest edges of two objects.

        .. deprecated:: 0.6.62
           Use :func:`wave_port` metho instead.

        Parameters
        ----------
        startobj :
            Starting object for the integration line.
        endobject :
            Ending object for the integration line.
        axisdir : int or :class:`pyaedt.application.Analysis.Analysis.AxisDir`, optional
            Position of the port. It should be one of the values for ``Application.AxisDir``,
            which are: ``XNeg``, ``YNeg``, ``ZNeg``, ``XPos``, ``YPos``, and ``ZPos``.
            The default is ``Application.AxisDir.XNeg``.
        impedance : float, optional
            Port impedance. The default is ``50``.
        nummodes : int, optional
            Number of modes. The default is ``1``.
        portname : str, optional
            Name of the port. The default is ``None``.
        renorm : bool, optional
            Whether to renormalize the mode. The default is ``True``.
        deembed_dist : float, optional
            Deembed distance in millimeters. The default is ``0``,
            in which case deembed is disabled.
        port_on_plane : bool, optional
            Whether to create the port on the plane orthogonal to ``AxisDir``. The default is ``True``.
        add_pec_cap : bool, optional
             The default is ``False``.

        Returns
        -------
        :class:`pyaedt.modules.Boundary.BoundaryObject`
            Boundary object.

        References
        ----------

        >>> oModule.AssignWavePort

        Examples
        --------

        Create two boxes that will be used to create a wave port
        named ``'Wave Port'``.

        >>> box1 = hfss.modeler.create_box([0,0,0], [10,10,5],
        ...                                           "BoxWave1", "copper")
        >>> box2 = hfss.modeler.create_box([0, 0, 10], [10, 10, 5],
        ...                                           "BoxWave2", "copper")
        >>> wave_port = hfss.create_wave_port_between_objects("BoxWave1", "BoxWave2",
        ...                                                   hfss.AxisDir.XNeg, 50, 1,
        ...                                                   "Wave Port", False)
        PyAEDT INFO: Connection Correctly created

        """
        warnings.warn(
            "`create_wave_port_between_objects` is deprecated. Use `wave_port` property instead.", DeprecationWarning
        )
        return self.wave_port(
            signal=startobj,
            reference=endobject,
            integration_line=axisdir,
            create_port_sheet=True,
            impedance=impedance,
            num_modes=nummodes,
            name=portname,
            renormalize=renorm,
            deembed=deembed_dist,
            port_on_plane=port_on_plane,
            add_pec_cap=add_pec_cap,
        )

    @pyaedt_function_handler()
    def create_floquet_port(
        self,
        face,
        lattice_origin=None,
        lattice_a_end=None,
        lattice_b_end=None,
        nummodes=2,
        portname=None,
        renorm=True,
        deembed_dist=0,
        reporter_filter=True,
        lattice_cs="Global",
    ):
        """Create a floquet port on a face.

        Parameters
        ----------
        face :
            Face or sheet to apply the floquet port to.
        lattice_origin : list
            List of ``[x,y,z]`` coordinates for the lattice A-B origin. The default is ``None``,
            in which case the method tries to compute the A-B automatically.
        lattice_a_end : list
            List of ``[x,y,z]`` coordinates for the lattice A end point. The default is ``None``,
            in which case the method tries to compute the A-B automatically.
        lattice_b_end : list
            List of ``[x,y,z]`` coordinates for the lattice B end point. The default is ``None``,
            in which case the method tries to compute the A-B automatically.
        nummodes : int, optional
            Number of modes. The default is ``2``.
        portname : str, optional
            Name of the port. The default is ``None``.
        renorm : bool, optional
            Whether to renormalize the mode. The default is ``True``.
        deembed_dist : float, str, optional
            Deembed distance in millimeters. The default is ``0``,
            in which case deembed is disabled.
        reporter_filter : bool, list of bool
            Whether to include modes in the report. The default is ``True``. If a single
            Boolean value is specified, it applies to all modes. If a list of Boolean values is specified, it applies
            to each mode in the list. A list must have ``nummodes`` elements.
        lattice_cs : str, optional
            Coordinate system for the lattice A-B vector reference. The default is ``Global``.

        Returns
        -------
        :class:`pyaedt.modules.Boundary.BoundaryObject`
            Boundary object.


        References
        ----------

        >>> oModule.AssignFloquetPort
        """
        face_id = self.modeler.convert_to_selections(face, True)
        props = OrderedDict({})
        if isinstance(face_id[0], int):
            props["Faces"] = face_id
        else:
            props["Objects"] = face_id

        props["NumModes"] = nummodes
        if deembed_dist:
            props["DoDeembed"] = True
            props["DeembedDist"] = self.modeler._arg_with_dim(deembed_dist)
        else:
            props["DoDeembed"] = False
            props["DeembedDist"] = "0mm"
        props["RenormalizeAllTerminals"] = renorm
        props["Modes"] = OrderedDict({})
        for i in range(1, 1 + nummodes):
            props["Modes"]["Mode{}".format(i)] = OrderedDict({})
            props["Modes"]["Mode{}".format(i)]["ModeNum"] = i
            props["Modes"]["Mode{}".format(i)]["UseIntLine"] = False
            props["Modes"]["Mode{}".format(i)]["CharImp"] = "Zpi"
        props["ShowReporterFilter"] = True
        if isinstance(reporter_filter, bool):
            props["ReporterFilter"] = [reporter_filter for i in range(nummodes)]
        else:
            props["ReporterFilter"] = reporter_filter
        if not lattice_a_end or not lattice_origin or not lattice_b_end:
            result, output = self.modeler._find_perpendicular_points(face_id[0])
            lattice_origin = output[0]
            lattice_a_end = output[1]
            lattice_b_end = output[2]
        props["LatticeAVector"] = OrderedDict({})
        props["LatticeAVector"]["Coordinate System"] = lattice_cs
        props["LatticeAVector"]["Start"] = lattice_origin
        props["LatticeAVector"]["End"] = lattice_a_end
        props["LatticeBVector"] = OrderedDict({})
        props["LatticeBVector"]["Coordinate System"] = lattice_cs
        props["LatticeBVector"]["Start"] = lattice_origin
        props["LatticeBVector"]["End"] = lattice_b_end
        if not portname:
            portname = generate_unique_name("Floquet")
        return self._create_boundary(portname, props, "Floquet Port")

    @pyaedt_function_handler()
    def assign_lattice_pair(
        self,
        face_couple,
        reverse_v=False,
        phase_delay="UseScanAngle",
        phase_delay_param1="0deg",
        phase_delay_param2="0deg",
        pair_name=None,
    ):
        """Assign a lattice pair to a couple of faces.

        Parameters
        ----------
        face_couple : list
            List of two faces to assign the lattice pair to.
        reverse_v : bool, optional
            Whether to reverse the V vector. The default is `False`.
        phase_delay : str, optional
            Phase delay approach. Options are ``"UseScanAngle"``,
            ``"UseScanUV"``, and ``"InputPhaseDelay"``. The default is
            ``"UseScanAngle"``.
        phase_delay_param1 : str, optional
            Value for the first phase delay parameter, which depends on the approach:

            - Phi angle if the approach is ``"UseScanAngle"``.
            - U value if the approach is ``"UseScanUV"``.
            - Phase if the approach is ``"InputPhaseDelay"``.

            The default is ``0deg``.

        phase_delay_param2 :  str, optional
            Value for the second phase delay parameter, which depends on the approach:

            - Theta angle if the approach is "``UseScanAngle"``.
            - V value if the approach is ``"UseScanUV"``.

            The default is ``0deg``.
        pair_name : str, optional
            Boundary name.

        Returns
        -------
        :class:`pyaedt.modules.Boundary.BoundaryObject`
            Boundary object.

        References
        ----------

        >>> oModule.AssignLatticePair
        """
        props = OrderedDict({})
        face_id = self.modeler.convert_to_selections(face_couple, True)
        props["Faces"] = face_id
        props["ReverseV"] = reverse_v

        props["PhaseDelay"] = phase_delay
        if phase_delay == "UseScanAngle":
            props["Phi"] = phase_delay_param1
            props["Theta"] = phase_delay_param2
        elif phase_delay == "UseScanUV":
            props["ScanU"] = phase_delay_param1
            props["ScanV"] = phase_delay_param2
        else:
            props["Phase"] = phase_delay_param1
        if not pair_name:
            pair_name = generate_unique_name("LatticePair")
        return self._create_boundary(pair_name, props, "Lattice Pair")

    @pyaedt_function_handler()
    def auto_assign_lattice_pairs(self, object_to_assign, coordinate_system="Global", coordinate_plane="XY"):
        """Assign lattice pairs to a geometry automatically.

        Parameters
        ----------
        object_to_assign : str, Object3d
            Object to assign a lattice to.
        coordinate_system : str, optional
            Coordinate system to look for the lattice on.
        coordinate_plane : str, optional
            Plane to look for the lattice on. Options are ``"XY"``, ``"XZ"``, and
            ``"YZ"``. The default is ``"XY"``.

        Returns
        -------
        list of str
            List of created pair names.

        References
        ----------

        >>> oModule.AutoIdentifyLatticePair
        """
        objectname = self.modeler.convert_to_selections(object_to_assign, True)
        boundaries = list(self.oboundary.GetBoundaries())
        self.oboundary.AutoIdentifyLatticePair("{}:{}".format(coordinate_system, coordinate_plane), objectname[0])
        boundaries = [i for i in list(self.oboundary.GetBoundaries()) if i not in boundaries]
        bounds = [i for i in boundaries if boundaries.index(i) % 2 == 0]
        return bounds

    @pyaedt_function_handler()
    def assign_secondary(
        self,
        face,
        primary_name,
        u_start,
        u_end,
        reverse_v=False,
        phase_delay="UseScanAngle",
        phase_delay_param1="0deg",
        phase_delay_param2="0deg",
        coord_name="Global",
        secondary_name=None,
    ):
        """Assign the secondary boundary condition.

        Parameters
        ----------
        face : int, FacePrimitive
            Face to assign the lattice pair to.
        primary_name : str
            Name of the primary boundary to couple.
        u_start : list
            List of ``[x,y,z]`` values for the starting point of the U vector.
        u_end : list
            List of ``[x,y,z]`` values for the ending point of the U vector.
        reverse_v : bool, optional
            Whether to reverse the V vector. The default is ``False``.
        phase_delay : str, optional
            Phase delay approach. Options are ``"UseScanAngle"``,
            ``"UseScanUV"``, and ``"InputPhaseDelay"``. The default is
            ``"UseScanAngle"``.
        phase_delay_param1 : str, optional
            Value for the first phase delay parameter, which depends on the approach:

            - Phi angle if the approach is ``"UseScanAngle"``.
            - U value if the approach is ``"UseScanUV"``.
            - Phase if the approach is ``"InputPhaseDelay"``.

            The default is ``0deg``.
        phase_delay_param2 :  str, optional
            Value for the second phase delay parameter, which depends on the approach:

            - Theta angle if the approach is "``UseScanAngle"``.
            - V value if the approach is ``"UseScanUV"``.

            The default is ``0deg``.
        coord_name : str, optional
            Name of the coordinate system for U coordinates.
        secondary_name : str, optional
            Name of the boundary. The default is ``None``.

        Returns
        -------
        :class:`pyaedt.modules.Boundary.BoundaryObject`
            Boundary object.

        References
        ----------

        >>> oModule.AssignSecondary
        """
        props = OrderedDict({})
        face_id = self.modeler.convert_to_selections(face, True)
        if isinstance(face_id[0], str):
            props["Objects"] = face_id

        else:
            props["Faces"] = face_id

        props["CoordSysVector"] = OrderedDict({})
        props["CoordSysVector"]["Coordinate System"] = coord_name
        props["CoordSysVector"]["Origin"] = u_start
        props["CoordSysVector"]["UPos"] = u_end
        props["ReverseV"] = reverse_v

        props["Primary"] = primary_name
        props["PhaseDelay"] = phase_delay
        if phase_delay == "UseScanAngle":
            props["Phi"] = phase_delay_param1
            props["Theta"] = phase_delay_param2
        elif phase_delay == "UseScanUV":
            props["ScanU"] = phase_delay_param1
            props["ScanV"] = phase_delay_param2
        else:
            props["Phase"] = phase_delay_param1
        if not secondary_name:
            secondary_name = generate_unique_name("Secondary")
        return self._create_boundary(secondary_name, props, "Secondary")

    @pyaedt_function_handler()
    def assign_primary(self, face, u_start, u_end, reverse_v=False, coord_name="Global", primary_name=None):
        """Assign the primary boundary condition.

        Parameters
        ----------
        face : int, FacePrimitive
            Face to assign the lattice pair to.
        u_start : list
            List of ``[x,y,z]`` values for the starting point of the U vector.
        u_end : list
            List of ``[x,y,z]`` values for the ending point of the U vector.
        reverse_v : bool, optional
            Whether to reverse the V vector. The default is `False`.
        coord_name : str, optional
            Name of the coordinate system for the U coordinates. The
            default is ``"Global"``.
        primary_name : str, optional
            Name of the boundary. The default is ``None``.  # TODO: Add names of allowed values to docstring.

        Returns
        -------
        :class:`pyaedt.modules.Boundary.BoundaryObject`
            Boundary object.

        References
        ----------

        >>> oModule.AssignPrimary
        """
        props = OrderedDict({})
        face_id = self.modeler.convert_to_selections(face, True)
        if isinstance(face_id[0], str):
            props["Objects"] = face_id

        else:
            props["Faces"] = face_id
        props["ReverseV"] = reverse_v
        props["CoordSysVector"] = OrderedDict({})
        props["CoordSysVector"]["Coordinate System"] = coord_name
        props["CoordSysVector"]["Origin"] = u_start
        props["CoordSysVector"]["UPos"] = u_end
        if not primary_name:
            primary_name = generate_unique_name("Primary")
        return self._create_boundary(primary_name, props, "Primary")

    def _create_pec_cap(self, sheet_name, obj_name, pecthick):
        """Create a PEC object to back a wave port.


        Parameters
        ----------
        sheet_name : str
            Name of the sheet object touching the port surface.
        obj_name : str
            Name of the 3D object touching the port surface.
        pecthick : float
            Thickness of the PEC cap

        Returns
        -------
        bool
           ``True`` when successful, ``False`` when failed.

        """
        if isinstance(sheet_name, str) and isinstance(obj_name, cad.elements3d.FacePrimitive):
            obj = obj_name.create_object()  # Create face object of type cad.object3d.Object3d from FacePrimitive
            oname = obj_name._object3d.name
            bounding1 = self.modeler[oname].bounding_box
        else:
            obj = self.modeler[sheet_name].clone()
            bounding1 = self.modeler[obj_name].bounding_box
        out_obj = self.modeler.thicken_sheet(obj, pecthick, False)
        bounding2 = out_obj.bounding_box
        tol = 1e-9
        i = 0

        # Check that the pec cap is internal by comparing the bounding box
        # of the cap with the bounding box of obj_name.
        internal = False
        for a, b in zip(bounding1, bounding2):
            if i < 3:
                if (b - a) > tol:
                    internal = True
            elif (b - a) < tol:
                internal = True
            i += 1
        if internal:
            self.odesign.Undo()
            self.modeler.cleanup_objects()
            out_obj = self.modeler.thicken_sheet(obj, -pecthick, False)

        out_obj.material_name = "pec"
        return True

    @pyaedt_function_handler()
    def create_wave_port_microstrip_between_objects(
        self,
        startobj,
        endobject,
        axisdir=0,
        impedance=50,
        nummodes=1,
        portname=None,
        renorm=True,
        deembed_dist=0,
        vfactor=3,
        hfactor=5,
    ):
        """Create a waveport taking the closest edges of two objects.

        .. deprecated:: 0.6.62
            `create_wave_port_microstrip_between_objects` is deprecated. Use `wave_port` property instead.

        Parameters
        ----------
        startobj :
            Starting object for the integration line. This is typically the reference plane.
        endobject :
            Ending object for the integration line.
        axisdir : int or :class:`pyaedt.application.Analysis.Analysis.AxisDir`, optional
            Position of the port. It should be one of the values for ``Application.AxisDir``,
            which are: ``XNeg``, ``YNeg``, ``ZNeg``, ``XPos``, ``YPos``, and ``ZPos``.
            The default is ``Application.AxisDir.XNeg``.
        impedance : float, optional
            Port impedance. The default is ``50``.
        nummodes : int, optional
            Number of modes. The default is ``1``.
        portname : str, optional
            Name of the port. The default is ``None``.
        renorm : bool, optional
            Whether to renormalize the mode. The default is ``True``.
        deembed_dist : float, optional
            Deembed distance in millimeters. The default is ``0``,
            in which case deembed is disabled.
        vfactor : int, optional
            Port vertical factor. The default is ``3``.
        hfactor : int, optional
            Port horizontal factor. The default is ``5``.

        Returns
        -------
        :class:`pyaedt.modules.Boundary.BoundaryObject`
            Port object.

        References
        ----------

        >>> oModule.AssignWavePort

        Examples
        --------

        Create a wave port supported by a microstrip line.

        >>> ms = hfss.modeler.create_box([4, 5, 0], [1, 100, 0.2],
        ...                               name="MS1", matname="copper")
        >>> sub = hfss.modeler.create_box([0, 5, -2], [20, 100, 2],
        ...                               name="SUB1", matname="FR4_epoxy")
        >>> gnd = hfss.modeler.create_box([0, 5, -2.2], [20, 100, 0.2],
        ...                               name="GND1", matname="FR4_epoxy")
        >>> port = hfss.create_wave_port_microstrip_between_objects("GND1", "MS1",
        ...                                                         portname="MS1",
        ...                                                         axisdir=1)
        PyAEDT INFO: Connection correctly created.

        """
        warnings.warn(
            "`create_wave_port_microstrip_between_objects` is deprecated. Use `wave_port` property instead.",
            DeprecationWarning,
        )
        return self.wave_port(
            signal=startobj,
            reference=endobject,
            integration_line=axisdir,
            create_port_sheet=True,
            impedance=impedance,
            num_modes=nummodes,
            name=portname,
            renormalize=renorm,
            deembed=deembed_dist,
            is_microstrip=True,
            vfactor=vfactor,
            hfactor=hfactor,
        )

    @pyaedt_function_handler()
    def create_perfecte_from_objects(
        self, startobj, endobject, axisdir=0, sourcename=None, is_infinite_gnd=False, bound_on_plane=True
    ):
        """Create a Perfect E taking the closest edges of two objects.

        Parameters
        ----------
        startobj :
            Starting object for the integration line.
        endobject :
           Ending object for the integration line.
        axisdir : int or :class:`pyaedt.application.Analysis.Analysis.AxisDir`, optional
            Position of the port. It should be one of the values for
            ``Application.AxisDir``, which are: ``XNeg``, ``YNeg``,
            ``ZNeg``, ``XPos``, ``YPos``, and ``ZPos``.  The default
            is ``Application.AxisDir.XNeg``.
        sourcename : str, optional
            Perfect E name. The default is ``None``.
        is_infinite_gnd : bool, optional
            Whether the Perfect E is an infinite ground. The default is ``False``.
        bound_on_plane : bool, optional
            Whether to create the Perfect E on the plane orthogonal to
            ``AxisDir``. The default is ``True``.

        Returns
        -------
        :class:`pyaedt.modules.Boundary.BoundaryObject` or bool
            Boundary object if successful, ``False`` otherwise.

        References
        ----------

        >>> oModule.AssignPerfectE

        Examples
        --------

        Create two boxes for creating a Perfect E named ``'PerfectE'``.

        >>> box1 = hfss.modeler.create_box([0,0,0], [10,10,5],
        ...                                "perfect1", "Copper")
        >>> box2 = hfss.modeler.create_box([0, 0, 10], [10, 10, 5],
        ...                                "perfect2", "copper")
        >>> perfect_e = hfss.create_perfecte_from_objects("perfect1", "perfect2",
        ...                                               hfss.AxisDir.ZNeg, "PerfectE")
        PyAEDT INFO: Connection Correctly created
        >>> type(perfect_e)
        <class 'pyaedt.modules.Boundary.BoundaryObject'>

        """

        if not self.modeler.does_object_exists(startobj) or not self.modeler.does_object_exists(endobject):
            self.logger.error("One or both objects do not exist. Check and retry.")
            return False
        if self.solution_type in ["Modal", "Terminal", "Transient Network"]:
            sheet_name, point0, point1 = self.modeler._create_sheet_from_object_closest_edge(
                startobj, endobject, axisdir, bound_on_plane
            )

            if not sourcename:
                sourcename = generate_unique_name("PerfE")
            elif sourcename in self.modeler.get_boundaries_name():
                sourcename = generate_unique_name(sourcename)
            return self.create_boundary(self.BoundaryType.PerfectE, sheet_name, sourcename, is_infinite_gnd)
        return False

    @pyaedt_function_handler()
    def create_perfecth_from_objects(self, startobj, endobject, axisdir=0, sourcename=None, bound_on_plane=True):
        """Create a Perfect H taking the closest edges of two objects.

        Parameters
        ----------
        startobj :
            Starting object for the integration line.
        endobject :
            Ending object for the integration line.
        axisdir : int or :class:`pyaedt.application.Analysis.Analysis.AxisDir`, optional
            Position of the port. It should be one of the values for ``Application.AxisDir``,
            which are: ``XNeg``, ``YNeg``, ``ZNeg``, ``XPos``, ``YPos``, and ``ZPos``.
            The default is ``Application.AxisDir.XNeg``.
        sourcename : str, optional
            Perfect H name. The default is ``None``.
        bound_on_plane : bool, optional
            Whether to create the Perfect H on the plane orthogonal to ``AxisDir``. The default is ``True``.

        Returns
        -------
        :class:`pyaedt.modules.Boundary.BoundaryObject` or bool
            Boundary object if successful, ``False`` otherwise.

        References
        ----------

        >>> oModule.AssignPerfectH

        Examples
        --------

        Create two boxes for creating a Perfect H named ``'PerfectH'``.

        >>> box1 = hfss.modeler.create_box([0,0,20], [10,10,5],
        ...                                "perfect1", "Copper")
        >>> box2 = hfss.modeler.create_box([0, 0, 30], [10, 10, 5],
        ...                                "perfect2", "copper")
        >>> perfect_h = hfss.create_perfecth_from_objects("perfect1", "perfect2",
        ...                                               hfss.AxisDir.ZNeg, "Perfect H")
        PyAEDT INFO: Connection Correctly created
        >>> type(perfect_h)
        <class 'pyaedt.modules.Boundary.BoundaryObject'>

        """

        if not self.modeler.does_object_exists(startobj) or not self.modeler.does_object_exists(endobject):
            self.logger.error("One or both objects do not exist. Check and retry.")
            return False
        if self.solution_type in ["Modal", "Terminal", "Transient Network"]:
            sheet_name, point0, point1 = self.modeler._create_sheet_from_object_closest_edge(
                startobj, endobject, axisdir, bound_on_plane
            )

            if not sourcename:
                sourcename = generate_unique_name("PerfH")
            elif sourcename in self.modeler.get_boundaries_name():
                sourcename = generate_unique_name(sourcename)
            return self.create_boundary(self.BoundaryType.PerfectH, sheet_name, sourcename)
        return None

    @pyaedt_function_handler()
    def sar_setup(self, Tissue_object_List_ID=-1, TissueMass=1, MaterialDensity=1, voxel_size=1, Average_SAR_method=0):
        """Define SAR settings.

        Parameters
        ----------
        Tissue_object_List_ID : int, optional
           The default is ``-1`` to not specify the object.
        TissueMass : float, optional
            The default is ``1``.
        MaterialDensity : optional
            The default is ``1``.
        voxel_size : optional
            The default is ``1``.
        Average_SAR_method : optional
            The default is ``0``.

        Returns
        -------
        bool
            ``True`` when successful, ``False`` when failed.

        References
        ----------

        >>> oDesign.SARSetup
        """
        self.odesign.SARSetup(TissueMass, MaterialDensity, Tissue_object_List_ID, voxel_size, Average_SAR_method)
        self.logger.info("SAR Settings correctly applied.")
        return True

    @pyaedt_function_handler()
    def create_open_region(self, Frequency="1GHz", Boundary="Radiation", ApplyInfiniteGP=False, GPAXis="-z"):
        """Create an open region on the active editor.

        Parameters
        ----------
        Frequency : str, optional
            Frequency with units. The default is ``"1GHz"``.
        Boundary : str, optional
            Type of the boundary. The default is ``"Radiation"``.
        ApplyInfiniteGP : bool, optional
            Whether to apply an infinite ground plane. The default is ``False``.
        GPAXis : str, optional
            The default is ``"-z"``.

        Returns
        -------
        bool
            ``True`` when successful, ``False`` when failed.

        References
        ----------

        >>> oModule.CreateOpenRegion
        """
        vars = ["NAME:Settings", "OpFreq:=", Frequency, "Boundary:=", Boundary, "ApplyInfiniteGP:=", ApplyInfiniteGP]
        if ApplyInfiniteGP:
            vars.append("Direction:=")
            vars.append(GPAXis)

        self.omodelsetup.CreateOpenRegion(vars)
        self.logger.info("Open Region correctly created.")
        return True

    @pyaedt_function_handler()
    def create_lumped_rlc_between_objects(
        self,
        startobj,
        endobject,
        axisdir=0,
        sourcename=None,
        rlctype="Parallel",
        Rvalue=None,
        Lvalue=None,
        Cvalue=None,
        bound_on_plane=True,
    ):
        """Create a lumped RLC taking the closest edges of two objects.

        Parameters
        ----------
        startobj :
            Starting object for the integration line.
        endobject :
            Ending object for the integration line.
        axisdir : int or :class:`pyaedt.application.Analysis.Analysis.AxisDir`, optional
            Position of the port. It should be one of the values for ``Application.AxisDir``,
            which are: ``XNeg``, ``YNeg``, ``ZNeg``, ``XPos``, ``YPos``, and ``ZPos``.
            The default is ``Application.AxisDir.XNeg``.
        sourcename : str, optional
            Perfect H name. The default is ``None``.
        rlctype : str, optional
            Type of the RLC. Options are ``"Parallel"`` and ``"Serial"``.
            The default is ``"Parallel"``.
        Rvalue : optional
            Resistance value in ohms. The default is ``None``,
            in which case this parameter is disabled.
        Lvalue : optional
            Inductance value in H. The default is ``None``,
            in which case this parameter is disabled.
        Cvalue : optional
            Capacitance value in F. The default is ``None``,
            in which case this parameter is disabled.
        bound_on_plane : bool, optional
            Whether to create the boundary on the plane orthogonal
            to ``AxisDir``. The default is ``True``.

        Returns
        -------
        :class:`pyaedt.modules.Boundary.BoundaryObject` or bool
            Boundary object if successful, ``False`` otherwise.

        References
        ----------

        >>> oModule.AssignLumpedRLC

        Examples
        --------

        Create two boxes for creating a lumped RLC named ``'LumpedRLC'``.

        >>> box1 = hfss.modeler.create_box([0, 0, 50], [10, 10, 5],
        ...                                           "rlc1", "copper")
        >>> box2 = hfss.modeler.create_box([0, 0, 60], [10, 10, 5],
        ...                                           "rlc2", "copper")
        >>> rlc = hfss.create_lumped_rlc_between_objects("rlc1", "rlc2", hfss.AxisDir.XPos,
        ...                                              "Lumped RLC", Rvalue=50,
        ...                                              Lvalue=1e-9, Cvalue = 1e-6)
        PyAEDT INFO: Connection Correctly created

        """

        if not self.modeler.does_object_exists(startobj) or not self.modeler.does_object_exists(endobject):
            self.logger.error("One or both objects do not exist. Check and retry.")
            return False
        if self.solution_type in ["Modal", "Terminal", "Transient Network"] and (Rvalue or Lvalue or Cvalue):
            sheet_name, point0, point1 = self.modeler._create_sheet_from_object_closest_edge(
                startobj, endobject, axisdir, bound_on_plane
            )

            if not sourcename:
                sourcename = generate_unique_name("Lump")
            elif sourcename in self.modeler.get_boundaries_name():
                sourcename = generate_unique_name(sourcename)
            start = [str(i) + self.modeler.model_units for i in point0]
            stop = [str(i) + self.modeler.model_units for i in point1]

            props = OrderedDict()
            props["Objects"] = [sheet_name]
            props["CurrentLine"] = OrderedDict({"Start": start, "End": stop})
            props["RLC Type"] = rlctype
            if Rvalue:
                props["UseResist"] = True
                props["Resistance"] = str(Rvalue) + "ohm"
            if Lvalue:
                props["UseInduct"] = True
                props["Inductance"] = str(Lvalue) + "H"
            if Cvalue:
                props["UseCap"] = True
                props["Capacitance"] = str(Cvalue) + "farad"

            return self._create_boundary(sourcename, props, "Lumped RLC")
        return False

    @pyaedt_function_handler()
    def create_impedance_between_objects(
        self,
        startobj,
        endobject,
        axisdir=0,
        sourcename=None,
        resistance=50,
        reactance=0,
        is_infground=False,
        bound_on_plane=True,
    ):
        """Create an impedance taking the closest edges of two objects.

        Parameters
        ----------
        startobj :
            Starting object for the integration line.
        endobject :
            Ending object for the integration line.
        axisdir : int or :class:`pyaedt.application.Analysis.Analysis.AxisDir`, optional
            Position of the port. It should be one of the values for ``Application.AxisDir``,
            which are: ``XNeg``, ``YNeg``, ``ZNeg``, ``XPos``, ``YPos``, and ``ZPos``.
            The default is ``Application.AxisDir.XNeg``.
        sourcename : str, optional
            Name of the impedance. The default is ``None``.
        resistance : float, optional
            Resistance value in ohms. The default is ``50``. If ``None``,
            this parameter is disabled.
        reactance : optional
            Reactance value in ohms. The default is ``0``. If ``None``,
            this parameter is disabled.
        is_infground : bool, optional
            Whether the impendance is an infinite ground. The default is ``False``.
        bound_on_plane : bool, optional
            Whether to create the impedance on the plane orthogonal to ``AxisDir``.
            The default is ``True``.

        Returns
        -------
        :class:`pyaedt.modules.Boundary.BoundaryObject` or bool
            Boundary object if successful, ``False`` otherwise.

        References
        ----------

        >>> oModule.AssignImpedance

        Examples
        --------

        Create two boxes for creating an impedance named ``'ImpedanceExample'``.

        >>> box1 = hfss.modeler.create_box([0, 0, 70], [10, 10, 5],
        ...                                           "box1", "copper")
        >>> box2 = hfss.modeler.create_box([0, 0, 80], [10, 10, 5],
        ...                                           "box2", "copper")
        >>> impedance = hfss.create_impedance_between_objects("box1", "box2", hfss.AxisDir.XPos,
        ...                                                   "ImpedanceExample", 100, 50)
        PyAEDT INFO: Connection Correctly created

        """

        if not self.modeler.does_object_exists(startobj) or not self.modeler.does_object_exists(endobject):
            self.logger.error("One or both objects do not exist. Check and retry.")
            return False
        if self.solution_type in ["Modal", "Terminal", "Transient Network"]:
            sheet_name, point0, point1 = self.modeler._create_sheet_from_object_closest_edge(
                startobj, endobject, axisdir, bound_on_plane
            )

            if not sourcename:
                sourcename = generate_unique_name("Imped")
            elif sourcename in self.modeler.get_boundaries_name():
                sourcename = generate_unique_name(sourcename)
            props = OrderedDict(
                {
                    "Objects": [sheet_name],
                    "Resistance": str(resistance),
                    "Reactance": str(reactance),
                    "InfGroundPlane": is_infground,
                }
            )
            return self._create_boundary(sourcename, props, "Impedance")
        return False

    @pyaedt_function_handler()
    def create_boundary(
        self, boundary_type=BoundaryType.PerfectE, sheet_name=None, boundary_name="", is_infinite_gnd=False
    ):
        """Create a boundary given specific inputs.

        Parameters
        ----------
        boundary_type : str, optional
            Boundary type object. Options are ``"Perfect E"``, ``"Perfect H"``, ``"Aperture"``, and
            ``"Radiation"``. The default is ``PerfectE``.
        sheet_name : in, str, or list, optional
            Name of the sheet. It can be an integer (face ID), a string (sheet), or a list of integers
            and strings. The default is ``None``.
        boundary_name : str, optional
            Name of the boundary. The default is ``""``.
        is_infinite_gnd : bool, optional
            Whether the boundary is an infinite ground. The default is ``False``.

        Returns
        -------
        :class:`pyaedt.modules.Boundary.BoundaryObject`
            Boundary object.

        """

        props = {}
        sheet_name = self.modeler.convert_to_selections(sheet_name, True)
        if type(sheet_name) is list:
            if type(sheet_name[0]) is str:
                props["Objects"] = sheet_name
            else:
                props["Faces"] = sheet_name

        if boundary_type == self.BoundaryType.PerfectE:
            props["InfGroundPlane"] = is_infinite_gnd
            boundary_type = "Perfect E"
        elif boundary_type == self.BoundaryType.PerfectH:
            boundary_type = "Perfect H"
        elif boundary_type == self.BoundaryType.Aperture:
            boundary_type = "Aperture"
        elif boundary_type == self.BoundaryType.Radiation:
            props["IsFssReference"] = False
            props["IsForPML"] = False
            boundary_type = "Radiation"
        elif boundary_type == self.BoundaryType.Hybrid:
            props["IsLinkedRegion"] = False
            props["Type"] = "SBR+"
            boundary_type = "Hybrid"
        elif boundary_type == self.BoundaryType.FEBI:
            boundary_type = "FE-BI"
        else:
            return None
        return self._create_boundary(boundary_name, props, boundary_type)

    @pyaedt_function_handler()
    def _get_reference_and_integration_points(self, sheet, axisdir, obj_name=None):
        if isinstance(sheet, int):
            objID = [sheet]
            sheet = obj_name
        else:
            objID = self.modeler.oeditor.GetFaceIDs(sheet)
        face_edges = self.modeler.get_face_edges(objID[0])
        mid_points = [self.modeler.get_edge_midpoint(i) for i in face_edges]
        if axisdir < 3:
            min_point = [1e6, 1e6, 1e6]
            max_point = [-1e6, -1e6, -1e6]
            for el in mid_points:
                if el[axisdir] < min_point[axisdir]:
                    min_point = el
                if el[axisdir] > max_point[axisdir]:
                    max_point = el
        else:
            min_point = [-1e6, -1e6, -1e6]
            max_point = [1e6, 1e6, 1e6]
            for el in mid_points:
                if el[axisdir - 3] > min_point[axisdir - 3]:
                    min_point = el
                if el[axisdir - 3] < max_point[axisdir - 3]:
                    max_point = el

        refid = self.modeler.get_bodynames_from_position(min_point)
        refid.remove(sheet)
        diels = self.get_all_dielectrics_names()
        for el in refid:
            if el in diels:
                refid.remove(el)

        int_start = None
        int_stop = None
        if min_point != max_point:
            int_start = min_point
            int_stop = max_point
        return refid, int_start, int_stop

    @pyaedt_function_handler()
    def create_wave_port_from_sheet(
        self,
        sheet,
        deemb=0,
        axisdir=None,
        impedance=50,
        nummodes=1,
        portname=None,
        renorm=True,
        terminal_references=None,
    ):
        """Create a waveport on sheet objects created starting from sheets.

        .. deprecated:: 0.6.62
            `create_wave_port_from_sheet` is deprecated. Use `wave_port` property instead.

        Parameters
        ----------
        sheet : str or int or list or :class:`pyaedt.modeler.cad.object3d.Object3d`
            Name of the sheet.
        deemb : float, optional
            Deembedding value distance in model units. The default is ``0``.
        axisdir : int or :class:`pyaedt.application.Analysis.Analysis.AxisDir`, optional
            Position of the port. It is used to auto evaluate the integration line.
            If set to ``None`` the integration line is not defined.
            It should be one of the values for ``Application.AxisDir``,
            which are: ``XNeg``, ``YNeg``, ``ZNeg``, ``XPos``, ``YPos``, and ``ZPos``.
            The default is ``None`` and no integration line is defined.
        impedance : float, optional
            Port impedance. The default is ``50``.
        nummodes : int, optional
            Number of modes. The default is ``1``.
        portname : str, optional
            Name of the port. The default is ``None``.
        renorm : bool, optional
            Whether to renormalize the mode. The default is ``True``.
        terminal_references : list, optional
            For a driven-terminal simulation, list of conductors for port terminal definitions.

        Returns
        -------
        :class:`pyaedt.modules.Boundary.BoundaryObject`
            Boundary object.

        References
        ----------

        >>> oModule.AssignWavePort

        Examples
        --------

        Create a circle sheet for creating a wave port named ``'WavePortFromSheet'``.

        >>> origin_position = hfss.modeler.Position(0, 0, 0)
        >>> circle = hfss.modeler.create_circle(hfss.PLANE.YZ,
        ...                                                origin_position, 10, name="WaveCircle")
        >>> hfss.solution_type = "Modal"
        >>> port = hfss.create_wave_port_from_sheet(circle, 5, hfss.AxisDir.XNeg, 40, 2,
        ...                                         "WavePortFromSheet", True)
        >>> port[0].name
        'WavePortFromSheet'

        """
        warnings.warn(
            "`create_wave_port_from_sheet` is deprecated. Use `wave_port` property instead.", DeprecationWarning
        )
        return self.wave_port(
            signal=sheet,
            reference=terminal_references,
            integration_line=axisdir,
            create_port_sheet=False,
            impedance=impedance,
            num_modes=nummodes,
            name=portname,
            renormalize=renorm,
            deembed=deemb,
        )

    @pyaedt_function_handler()
    def create_wave_port(
        self,
        port_item,  # Item to use for wave port creation
        int_start,
        int_stop,
        deemb=0,
        axisdir=None,
        impedance=50,
        nummodes=1,
        portname=None,
        renorm=True,
        terminal_references=None,
    ):
        """Assign a wave port to a face given a point on the face.

        .. deprecated:: 0.6.62
            `create_wave_port` is deprecated. Use `wave_port` property instead.

        Parameters
        ----------
        port_item : list, int
            Item for defining where to create the port.
            If a list is passed, then Cartesian [x,y,z] coordinates of a point on the face are
            expected. If an integer is passed, it is assumed to be a face ID.
        deemb : float, optional
            Deembedding value distance in model units. The default is ``0``.
        axisdir : int or :class:`pyaedt.application.Analysis.Analysis.AxisDir`, optional
            Position of the port. This parameter is used to automatically evaluate
            the integration line. The default is ``None``, in which case no integration
            line is defined. This parameter should be set to one of the values
            for ``Application.AxisDir``,  which are: ``XNeg``, ``YNeg``, ``ZNeg``,
            ``XPos``, ``YPos``, and ``ZPos``.

        impedance : float, optional
            Port impedance. The default is ``50``.
        nummodes : int, optional
            Number of modes. The default is ``1``.
        portname : str, optional
            Name of the port. The default is ``None``.
        renorm : bool, optional
            Whether to renormalize the mode. The default is ``True``.
        terminal_references : list, optional
            For a driven-terminal simulation, list of conductors for port terminal definitions.

        Returns
        -------
        :class:`pyaedt.modules.Boundary.BoundaryObject`
            Boundary object.

        References
        ----------

        >>> oModule.AssignWavePort

        Examples
        --------

        Create a circle sheet for creating a wave port named ``'WavePortFromSheet'``.

        >>> hfss.modeler.model_units("in")
        >>> hfss.modeler.create_box([-0.2, -0.45, -1], [0.4, 0.9, 2], name="Xband_WG", matname="vacuum")
        >>> setup = hfss.create_setup("Setup1")
        >>> setup["Frequency"] = "10GHz"
        >>> ports = [ hfss.create_wave_port([0, "a/2", "-wg_len/2"], portname="Port1", deembed=False),
        >>>  ...      hfss.create_wave_port([0, "a/2", "wg_len/2"], portname="Port2", deembed=False) ]
        >>> [print(name) for p.name in ports]

        """
        warnings.warn("`create_wave_port` is deprecated. Use `wave_port` property instead.", DeprecationWarning)
        return self.wave_port(
            signal=port_item,
            reference=terminal_references,
            integration_line=[int_start, int_stop],
            create_port_sheet=True,
            impedance=impedance,
            num_modes=nummodes,
            name=portname,
            renormalize=renorm,
            deembed=deemb,
        )

    @pyaedt_function_handler()
    def create_lumped_port_to_sheet(
        self, sheet_name, axisdir=0, impedance=50, portname=None, renorm=True, deemb=False, reference_object_list=[]
    ):
        """Create a lumped port taking one sheet.

        .. deprecated:: 0.6.62
            `create_lumped_port_to_sheet` is deprecated. Use `lumped` property instead.

        Parameters
        ----------
        sheet_name : str
            Name of the sheet.
        axisdir : int, :class:`pyaedt.application.Analysis.Analysis.AxisDir` or list, optional
            Direction of the integration line. It should be one of the values for ``Application.AxisDir``,
            which are: ``XNeg``, ``YNeg``, ``ZNeg``, ``XPos``, ``YPos``, and ``ZPos``. It also accepts the list
            of the start point and end point with the format [[xstart, ystart, zstart], [xend, yend, zend]].
            The default is ``Application.AxisDir.XNeg``.
        impedance : float, optional
            Port impedance. The default is ``50``.
        portname : str, optional
            Name of the port. The default is ``None``.
        renorm : bool, optional
            Whether to renormalize the mode. The default is ``True``.
        deemb : bool, optional
            Whether to deembed the port. The default is ``False``.
        reference_object_list : list, optional
            For a driven terminal solution only, a list of reference conductors. The default is ``[]``.

        Returns
        -------
        :class:`pyaedt.modules.Boundary.BoundaryObject`
            Boundary object.

        References
        ----------

        >>> oModule.AssignLumpedPort

        Examples
        --------

        Create a rectangle sheet for creating a lumped port named ``'LumpedPortFromSheet'``.

        >>> rectangle = hfss.modeler.create_rectangle(hfss.PLANE.XY,
        ...                                                      [0, 0, 0], [10, 2], name="lump_port",
        ...                                                      matname="copper")
        >>> h1 = hfss.create_lumped_port_to_sheet(rectangle.name, hfss.AxisDir.XNeg, 50,
        ...                                  "LumpedPortFromSheet", True, False)
        >>> h2 = hfss.create_lumped_port_to_sheet(rectangle.name, [rectangle.bottom_edge_x.midpoint,
        ...                                     rectangle.bottom_edge_y.midpoint], 50, "LumpedPortFromSheet", True,
        ...                                     False)

        """
        warnings.warn(
            "`create_lumped_port_to_sheet` is deprecated. Use `lumped_port` property instead.", DeprecationWarning
        )
        return self.lumped_port(
            signal=sheet_name,
            reference=reference_object_list,
            integration_line=axisdir,
            create_port_sheet=False,
            impedance=impedance,
            name=portname,
            renormalize=renorm,
            deembed=deemb,
        )

    @pyaedt_function_handler()
    def assign_voltage_source_to_sheet(self, sheet_name, axisdir=0, sourcename=None):
        """Create a voltage source taking one sheet.

        Parameters
        ----------
        sheet_name : str
            Name of the sheet to apply the boundary to.
        axisdir : int, :class:`pyaedt.application.Analysis.Analysis.AxisDir` or list, optional
            Direction of the integration line. It should be one of the values for ``Application.AxisDir``,
            which are: ``XNeg``, ``YNeg``, ``ZNeg``, ``XPos``, ``YPos``, and ``ZPos``. It also accepts the list
            of the start point and end point with the format [[xstart, ystart, zstart], [xend, yend, zend]]
            The default is ``Application.AxisDir.XNeg``.
        sourcename : str, optional
            Name of the source. The default is ``None``.

        Returns
        -------
        :class:`pyaedt.modules.Boundary.BoundaryObject`
            Boundary object.

        References
        ----------

        >>> oModule.AssignVoltage

        Examples
        --------

        Create a sheet and assign to it some voltage.

        >>> sheet = hfss.modeler.create_rectangle(hfss.PLANE.XY,
        ...                                                  [0, 0, -70], [10, 2], name="VoltageSheet",
        ...                                                  matname="copper")
        >>> v1 = hfss.assign_voltage_source_to_sheet(sheet.name, hfss.AxisDir.XNeg, "VoltageSheetExample")
        >>> v2 = hfss.assign_voltage_source_to_sheet(sheet.name, [sheet.bottom_edge_x.midpoint,
        ...                                     sheet.bottom_edge_y.midpoint], 50, "LumpedPortFromSheet", True,
        ...                                     False)

        """

        if self.solution_type in ["Modal", "Terminal", "Transient Network"]:
            if isinstance(axisdir, list):
                if len(axisdir) != 2 or len(axisdir[0]) != len(axisdir[1]):
                    self.logger.error("List of coordinates is not set correctly")
                    return False
                point0 = axisdir[0]
                point1 = axisdir[1]
            else:
                point0, point1 = self.modeler.get_mid_points_on_dir(sheet_name, axisdir)
            sourcename = self._get_unique_source_name(sourcename, "Voltage")
            return self.create_source_excitation(sheet_name, point0, point1, sourcename, sourcetype="Voltage")
        return False

    @pyaedt_function_handler()
    def assign_current_source_to_sheet(self, sheet_name, axisdir=0, sourcename=None):
        """Create a current source taking one sheet.

        Parameters
        ----------
        sheet_name : str
            Name of the sheet to apply the boundary to.
        axisdir : int, :class:`pyaedt.application.Analysis.Analysis.AxisDir` or list, optional
            Direction of the integration line. It should be one of the values for ``Application.AxisDir``,
            which are: ``XNeg``, ``YNeg``, ``ZNeg``, ``XPos``, ``YPos``, and ``ZPos``. It also accepts the list
            of the start point and end point with the format [[xstart, ystart, zstart], [xend, yend, zend]]
            The default is ``Application.AxisDir.XNeg``.
        sourcename : str, optional
            Name of the source. The default is ``None``.

        Returns
        -------
        :class:`pyaedt.modules.Boundary.BoundaryObject`
            Boundary object.

        References
        ----------

        >>> oModule.AssignCurrent

        Examples
        --------

        Create a sheet and assign some current to it.

        >>> sheet = hfss.modeler.create_rectangle(hfss.PLANE.XY, [0, 0, -50],
        ...                                                  [5, 1], name="CurrentSheet", matname="copper")
        >>> hfss.assign_current_source_to_sheet(sheet.name, hfss.AxisDir.XNeg, "CurrentSheetExample")
        'CurrentSheetExample'
        >>> c1 = hfss.assign_current_source_to_sheet(sheet.name, [sheet.bottom_edge_x.midpoint,
        ...                                     sheet.bottom_edge_y.midpoint])

        """

        if self.solution_type in ["Modal", "Terminal", "Transient Network"]:
            if isinstance(axisdir, list):
                if len(axisdir) != 2 or len(axisdir[0]) != len(axisdir[1]):
                    self.logger.error("List of coordinates is not set correctly")
                    return False
                point0 = axisdir[0]
                point1 = axisdir[1]
            else:
                point0, point1 = self.modeler.get_mid_points_on_dir(sheet_name, axisdir)
            sourcename = self._get_unique_source_name(sourcename, "Current")
            return self.create_source_excitation(sheet_name, point0, point1, sourcename, sourcetype="Current")
        return False

    @pyaedt_function_handler()
    def assign_perfecte_to_sheets(self, sheet_list, sourcename=None, is_infinite_gnd=False):
        """Create a Perfect E taking one sheet.

        Parameters
        ----------
        sheet_list : str or list
            Name of the sheet or list to apply the boundary to.
        sourcename : str, optional
            Name of the Perfect E source. The default is ``None``.
        is_infinite_gnd : bool, optional
            Whether the Perfect E is an infinite ground. The default is ``False``.

        Returns
        -------
        :class:`pyaedt.modules.Boundary.BoundaryObject`
            Boundary object.

        References
        ----------

        >>> oModule.AssignPerfectE

        Examples
        --------

        Create a sheet and use it to create a Perfect E.

        >>> sheet = hfss.modeler.create_rectangle(hfss.PLANE.XY, [0, 0, -90],
        ...                                       [10, 2], name="PerfectESheet", matname="Copper")
        >>> perfect_e_from_sheet = hfss.assign_perfecte_to_sheets(sheet.name, "PerfectEFromSheet")
        >>> type(perfect_e_from_sheet)
        <class 'pyaedt.modules.Boundary.BoundaryObject'>

        """
        sheet_list = self.modeler.convert_to_selections(sheet_list, True)
        if self.solution_type in ["Modal", "Terminal", "Transient Network", "SBR+", "Eigenmode"]:
            if not sourcename:
                sourcename = generate_unique_name("PerfE")
            elif sourcename in self.modeler.get_boundaries_name():
                sourcename = generate_unique_name(sourcename)
            return self.create_boundary(self.BoundaryType.PerfectE, sheet_list, sourcename, is_infinite_gnd)
        return None

    @pyaedt_function_handler()
    def assign_perfecth_to_sheets(self, sheet_list, sourcename=None):
        """Assign a Perfect H to sheets.

        Parameters
        ----------
        sheet_list : list
            List of sheets to apply the boundary to.
        sourcename : str, optional
            Perfect H name. The default is ``None``.

        Returns
        -------
        :class:`pyaedt.modules.Boundary.BoundaryObject`
            Boundary object.

        References
        ----------

        >>> oModule.AssignPerfectH

        Examples
        --------

        Create a sheet and use it to create a Perfect H.

        >>> sheet = hfss.modeler.create_rectangle(hfss.PLANE.XY, [0, 0, -90],
        ...                                       [10, 2], name="PerfectHSheet", matname="Copper")
        >>> perfect_h_from_sheet = hfss.assign_perfecth_to_sheets(sheet.name, "PerfectHFromSheet")
        >>> type(perfect_h_from_sheet)
        <class 'pyaedt.modules.Boundary.BoundaryObject'>

        """

        if self.solution_type in ["Modal", "Terminal", "Transient Network", "SBR+", "Eigenmode"]:
            if not sourcename:
                sourcename = generate_unique_name("PerfH")
            elif sourcename in self.modeler.get_boundaries_name():
                sourcename = generate_unique_name(sourcename)
            return self.create_boundary(self.BoundaryType.PerfectH, sheet_list, sourcename)
        return None

    @pyaedt_function_handler()
    def assign_lumped_rlc_to_sheet(
        self, sheet_name, axisdir=0, sourcename=None, rlctype="Parallel", Rvalue=None, Lvalue=None, Cvalue=None
    ):
        """Create a lumped RLC taking one sheet.

        Parameters
        ----------
        sheet_name : str
            Name of the sheet to apply the boundary to.
        axisdir : int, :class:`pyaedt.application.Analysis.Analysis.AxisDir` or list, optional
            Direction of the integration line. It should be one of the values for ``Application.AxisDir``,
            which are: ``XNeg``, ``YNeg``, ``ZNeg``, ``XPos``, ``YPos``, and ``ZPos``. It also accepts the list
            of the start point and end point with the format [[xstart, ystart, zstart], [xend, yend, zend]]
            The default is ``Application.AxisDir.XNeg``.
        sourcename : str, optional
            Lumped RLC name. The default is ``None``.
        rlctype : str, optional
            Type of the RLC. Options are ``"Parallel"`` and ``"Serial"``. The default is ``"Parallel"``.
        Rvalue : float, optional
            Resistance value in ohms. The default is ``None``, in which
            case this parameter is disabled.
        Lvalue : optional
            Inductance value in Henry (H). The default is ``None``, in which
            case this parameter is disabled.
        Cvalue : optional
            Capacitance value in  farads (F). The default is ``None``, in which
            case this parameter is disabled.

        Returns
        -------
        :class:`pyaedt.modules.Boundary.BoundaryObject`
            Boundary object if successful, ``False`` otherwise.

        References
        ----------

        >>> oModule.AssignLumpedRLC

        Examples
        --------

        Create a sheet and use it to create a lumped RLC.

        >>> sheet = hfss.modeler.create_rectangle(hfss.PLANE.XY,
        ...                                       [0, 0, -90], [10, 2], name="RLCSheet",
        ...                                        matname="Copper")
        >>> lumped_rlc_to_sheet = hfss.assign_lumped_rlc_to_sheet(sheet.name, hfss.AxisDir.XPos,
        ...                                                       Rvalue=50, Lvalue=1e-9,
        ...                                                       Cvalue=1e-6)
        >>> type(lumped_rlc_to_sheet)
        <class 'pyaedt.modules.Boundary.BoundaryObject'>
        >>> h2 = hfss.assign_lumped_rlc_to_sheet(sheet.name, [sheet.bottom_edge_x.midpoint,
        ...                                     sheet.bottom_edge_y.midpoint], Rvalue=50, Lvalue=1e-9, Cvalue=1e-6)

        """

        if self.solution_type in ["Eigenmode", "Modal", "Terminal", "Transient Network", "SBR+"] and (
            Rvalue or Lvalue or Cvalue
        ):
            if isinstance(axisdir, list):
                if len(axisdir) != 2 or len(axisdir[0]) != len(axisdir[1]):
                    self.logger.error("List of coordinates is not set correctly")
                    return False
                point0 = axisdir[0]
                point1 = axisdir[1]
            else:
                point0, point1 = self.modeler.get_mid_points_on_dir(sheet_name, axisdir)

            if not sourcename:
                sourcename = generate_unique_name("Lump")
            elif sourcename in self.modeler.get_boundaries_name():
                sourcename = generate_unique_name(sourcename)
            start = [str(i) + self.modeler.model_units for i in point0]
            stop = [str(i) + self.modeler.model_units for i in point1]
            props = OrderedDict()
            props["Objects"] = [sheet_name]
            props["CurrentLine"] = OrderedDict({"Start": start, "End": stop})
            props["RLC Type"] = rlctype
            if Rvalue:
                props["UseResist"] = True
                props["Resistance"] = str(Rvalue) + "ohm"
            if Lvalue:
                props["UseInduct"] = True
                props["Inductance"] = str(Lvalue) + "H"
            if Cvalue:
                props["UseCap"] = True
                props["Capacitance"] = str(Cvalue) + "F"
            return self._create_boundary(sourcename, props, "Lumped RLC")
        return False

    @pyaedt_function_handler()
    def assign_impedance_to_sheet(self, sheet_name, sourcename=None, resistance=50, reactance=0, is_infground=False):
        """Create an impedance taking one sheet.

        Parameters
        ----------
        sheet_name : str
            Name of the sheet to apply the boundary to.
        sourcename : str, optional
            Name of the impedance. The default is ``None``.
        resistance : optional
            Resistance value in ohms. The default is ``50``. If ``None``,
            this parameter is disabled.
        reactance : optional
            Reactance value in ohms. The default is ``0``. If ``None``,
            this parameter is disabled.
        is_infground : bool, optional
            Whether the impedance is an infinite ground. The default is ``False``.

        Returns
        -------
        :class:`pyaedt.modules.Boundary.BoundaryObject`
            Boundary object if successful, ``False`` otherwise.

        References
        ----------

        >>> oModule.AssignImpedance

        Examples
        --------

        Create a sheet and use it to create an impedance.

        >>> sheet = hfss.modeler.create_rectangle(hfss.PLANE.XY,
        ...                                       [0, 0, -90], [10, 2], name="ImpedanceSheet",
        ...                                        matname="Copper")
        >>> impedance_to_sheet = hfss.assign_impedance_to_sheet(sheet.name, "ImpedanceFromSheet", 100, 50)
        >>> type(impedance_to_sheet)
        <class 'pyaedt.modules.Boundary.BoundaryObject'>

        """

        if self.solution_type in ["Modal", "Terminal", "Transient Network"]:
            if not sourcename:
                sourcename = generate_unique_name("Imped")
            elif sourcename in self.modeler.get_boundaries_name():
                sourcename = generate_unique_name(sourcename)
            props = OrderedDict(
                {
                    "Objects": [sheet_name],
                    "Resistance": str(resistance),
                    "Reactance": str(reactance),
                    "InfGroundPlane": is_infground,
                }
            )
            return self._create_boundary(sourcename, props, "Impedance")
        return False

    @pyaedt_function_handler()
    def create_circuit_port_from_edges(
        self,
        edge_signal,
        edge_gnd,
        port_name="",
        port_impedance="50",
        renormalize=False,
        renorm_impedance="50",
        deembed=False,
    ):
        """Create a circuit port from two edges.
        The integration line is from edge 2 to edge 1.

        .. deprecated:: 0.6.70
        Use :func:`circuit_port` method instead.

        Parameters
        ----------
        edge_signal : int
            Edge ID of the signal.
        edge_gnd : int
            Edge ID of the ground.
        port_name : str, optional
            Name of the port. The default is ``""``.
        port_impedance : int, str, or float, optional
            Impedance. The default is ``"50"``. You can also
            enter a string that looks like this: ``"50+1i*55"``.
        renormalize : bool, optional
            Whether to renormalize the mode. The default is ``False``.
            This parameter is ignored for a driven terminal.
        renorm_impedance :  str, optional
            Impedance. The default is ``50``.
        deembed : bool, optional
            Whether to deembed the port. The default is ``False``.

        Returns
        -------
        :class:`pyaedt.modules.Boundary.BoundaryObject`
            Boundary object.

        References
        ----------

        >>> oModule.AssignCircuitPort

        Examples
        --------

        Create two rectangles in the XY plane.
        Select the first edge of each rectangle created previously.
        Create a circuit port from the first edge of the first rectangle
        toward the first edge of the second rectangle.

        >>> plane = hfss.PLANE.XY
        >>> rectangle1 = hfss.modeler.create_rectangle(plane, [10, 10, 10], [10, 10],
        ...                                            name="rectangle1_for_port")
        >>> edges1 = hfss.modeler.get_object_edges(rectangle1.id)
        >>> first_edge = edges1[0]
        >>> rectangle2 = hfss.modeler.create_rectangle(plane, [30, 10, 10], [10, 10],
        ...                                            name="rectangle2_for_port")
        >>> edges2 = hfss.modeler.get_object_edges(rectangle2.id)
        >>> second_edge = edges2[0]
        >>> hfss.solution_type = "Modal"
        >>> hfss.create_circuit_port_from_edges(first_edge, second_edge, port_name="PortExample",
        ...                                     port_impedance=50.1, renormalize=False,
        ...                                     renorm_impedance="50")
        'PortExample'

        """
        warnings.warn("Use :func:`circuit_port` method instead.", DeprecationWarning)
        return self.circuit_port(
            signal=edge_signal,
            reference=edge_gnd,
            port_location=0,
            impedance=port_impedance,
            name=port_name,
            renormalize=renormalize,
            renorm_impedance=renorm_impedance,
            deembed=deembed,
        )

    @pyaedt_function_handler()
    def edit_sources(
        self, excitations, include_port_post_processing=True, max_available_power=None, use_incident_voltage=False
    ):
        """Set up the power loaded for HFSS postprocessing in multiple sources simultaneously.

        Parameters
        ----------
        excitations : dict
            Dictionary of input sources to modify module and phase.
            Dictionary values can be:
            - 1 value to setup 0deg as default
            - 2 values tuple or list (magnitude and phase) or
            - 3 values (magnitude, phase, and termination flag) for Terminal solution in case of incident voltage usage.

        Returns
        -------
        bool

        Examples
        --------
        >>> sources = {"Port1:1": ("0W", "0deg"), "Port2:1": ("1W", "90deg")}
        >>> hfss.edit_sources(sources, include_port_post_processing=True)

        >>> sources = {"Box2_T1": ("0V", "0deg", True), "Box1_T1": ("1V", "90deg")}
        >>> hfss.edit_sources(sources, max_available_power="2W", use_incident_voltage=True)
        """
        data = {i: ("0W", "0deg", False) for i in self.excitations}
        for key, value in excitations.items():
            data[key] = value
        setting = []
        for key, vals in data.items():
            if isinstance(vals, str):
                power = vals
                phase = "0deg"
            else:
                power = vals[0]
                if len(vals) == 1:
                    phase = "0deg"
                else:
                    phase = vals[1]
            if isinstance(vals, (list, tuple)) and len(vals) == 3:
                terminated = vals[2]
            else:
                terminated = False
            if use_incident_voltage and self.solution_type == "Terminal":
                setting.append(["Name:=", key, "Terminated:=", terminated, "Magnitude:=", power, "Phase:=", phase])
            else:
                setting.append(["Name:=", key, "Magnitude:=", power, "Phase:=", phase])
        argument = []
        if self.solution_type == "Terminal":
            argument.extend(["UseIncidentVoltage:=", use_incident_voltage])

        argument.extend(
            [
                "IncludePortPostProcessing:=",
                include_port_post_processing,
                "SpecifySystemPower:=",
                True if max_available_power else False,
            ]
        )

        if max_available_power:
            argument.append("Incident Power:=")
            argument.append(max_available_power)

        args = [argument]
        args.extend(setting)
        for arg in args:
            self.osolution.EditSources(arg)
        return True

    @pyaedt_function_handler()
    def edit_source(self, portandmode=None, powerin="1W", phase="0deg"):
        """Set up the power loaded for HFSS postprocessing.

        Parameters
        ----------
        portandmode : str, optional
            Port name and mode. For example, ``"Port1:1"``.
            The port name must be defined if the solution type is other than Eigenmodal. This parameter
            is ignored if the solution type is Eigenmodal.
        powerin : str, optional
            Power in watts (W) or the project variable to put as stored energy in the project.
            The default is ``"1W"``.
        phase : str, optional
            Phase of the excitation. The default is ``"0deg"``.

        Returns
        -------
        bool
            ``True`` when successful, ``False`` when failed.

        References
        ----------

        >>> oModule.EditSources

        Examples
        --------

        Create a circle sheet and use it to create a wave port.
        Set up the thermal power for this wave port.

        >>> sheet = hfss.modeler.create_circle(hfss.PLANE.YZ,
        ...                                    [-20, 0, 0], 10,
        ...                                    name="sheet_for_source")
        >>> hfss.solution_type = "Modal"
        >>> wave_port = hfss.create_wave_port_from_sheet(sheet, 5, hfss.AxisDir.XNeg, 40,
        ...                                              2, "SheetWavePort", True)
        >>> hfss.edit_source("SheetWavePort" + ":1", "10W")
        PyAEDT INFO: Setting up power to "SheetWavePort:1" = 10W
        True

        """

        if self.solution_type != "Eigenmode":
            if portandmode is None:
                self.logger.error("Port and mode must be defined for solution type {}".format(self.solution_type))
                return False
            self.logger.info('Setting up power to "{}" = {}'.format(portandmode, powerin))
            self.osolution.EditSources(
                [
                    ["IncludePortPostProcessing:=", True, "SpecifySystemPower:=", False],
                    ["Name:=", portandmode, "Magnitude:=", powerin, "Phase:=", phase],
                ]
            )
        else:
            self.logger.info("Setting up power to Eigenmode = {}".format(powerin))
            self.osolution.EditSources(
                [["FieldType:=", "EigenStoredEnergy"], ["Name:=", "Modes", "Magnitudes:=", [powerin]]]
            )
        return True

    @pyaedt_function_handler()
    def edit_source_from_file(
        self,
        portandmode,
        file_name,
        is_time_domain=True,
        x_scale=1,
        y_scale=1,
        impedance=50,
        data_format="Power",
        encoding="utf-8",
    ):
        """Edit a source from file data.
        File data is a csv containing either frequency data or time domain data that will be converted through FFT.

        Parameters
        ----------
        portandmode : str
            Port name and mode. For example, ``"Port1:1"``.
            The port name must be defined if the solution type is other than Eigenmodal.
        file_name : str
            Full name of the input file.
        is_time_domain : bool, optional
            Either if the input data is Time based or Frequency Based. Frequency based data are Mag/Phase (deg).
        x_scale : float, optional
            Scaling factor for x axis.
        y_scale : float, optional
            Scaling factor for y axis.
        impedance : float, optional
            Excitation impedance. Default is `50`.
        data_format : str, optional
            Either `"Power"`, `"Current"` or `"Voltage"`.
        encoding : str, optional
            Csv file encoding.


        Returns
        -------
        bool
        """
        if self.solution_type == "Modal":
            out = "Power"
        else:
            out = "Voltage"
        freq, mag, phase = parse_excitation_file(
            file_name=file_name,
            is_time_domain=is_time_domain,
            x_scale=x_scale,
            y_scale=y_scale,
            impedance=impedance,
            data_format=data_format,
            encoding=encoding,
            out_mag=out,
        )
        ds_name_mag = "ds_" + portandmode.replace(":", "_mode_") + "_Mag"
        ds_name_phase = "ds_" + portandmode.replace(":", "_mode_") + "_Angle"
        if self.dataset_exists(ds_name_mag, False):
            self.design_datasets[ds_name_mag].x = freq
            self.design_datasets[ds_name_mag].y = mag
            self.design_datasets[ds_name_mag].update()
        else:
            self.create_dataset1d_design(ds_name_mag, freq, mag, xunit="Hz")
        if self.dataset_exists(ds_name_phase, False):
            self.design_datasets[ds_name_phase].x = freq
            self.design_datasets[ds_name_phase].y = phase
            self.design_datasets[ds_name_phase].update()

        else:
            self.create_dataset1d_design(ds_name_phase, freq, phase, xunit="Hz", yunit="deg")
        self.osolution.EditSources(
            [
                ["IncludePortPostProcessing:=", True, "SpecifySystemPower:=", False],
                [
                    "Name:=",
                    portandmode,
                    "Magnitude:=",
                    "pwl({}, Freq)".format(ds_name_mag),
                    "Phase:=",
                    "pwl({}, Freq)".format(ds_name_phase),
                ],
            ]
        )
        self.logger.info("Source Excitation updated with Dataset.")
        return True

    @pyaedt_function_handler()
    def edit_sources_from_file(self, file_name):
        """Update all sources from a csv.

        Parameters
        ----------
        file_name : str
            Filen name.

        Returns
        -------
        bool
        """
        self.osolution.LoadSourceWeights(file_name)
        return True

    @pyaedt_function_handler()
    def thicken_port_sheets(self, inputlist, value, internalExtr=True, internalvalue=1):
        """Create thickened sheets over a list of input port sheets.

        This method is built to work with the output of ``modeler.find_port_faces``.

        Parameters
        ----------
        inputlist : list
            List of the sheets to thicken.
        value :
            Value in millimeters for thickening the faces.
        internalExtr : bool, optional
            Whether to extrude the sheets internally (vgoing into the model).
            The default is ``True``.
        internalvalue : optional
            Value in millimeters for thickening the sheets internally if ``internalExtr=True``.
            The default is ``1``.

        Returns
        -------
        Dict
            For each input sheet, returns the port IDs where thickened sheets were created
            if the name contains the word "Vacuum".

        References
        ----------

        >>> oEditor.ThickenSheet

        Examples
        --------

        Create a circle sheet and use it to create a wave port.
        Set the thickness of this circle sheet to ``"2 mm"``.

        >>> sheet_for_thickness = hfss.modeler.create_circle(hfss.PLANE.YZ,
        ...                                                  [60, 60, 60], 10,
        ...                                                  name="SheetForThickness")
        >>> port_for_thickness = hfss.create_wave_port_from_sheet(sheet_for_thickness, 5, hfss.AxisDir.XNeg,
        ...                                                       40, 2, "WavePortForThickness", True)
        >>> hfss.thicken_port_sheets(["SheetForThickness"], 2)
        PyAEDT INFO: done
        {}

        """

        tol = 1e-6
        ports_ID = {}
        aedt_bounding_box = self.modeler.get_model_bounding_box()
        aedt_bounding_dim = self.modeler.get_bounding_dimension()
        directions = {}
        for el in inputlist:
            objID = self.modeler.oeditor.GetFaceIDs(el)
            faceCenter = self.modeler.oeditor.GetFaceCenter(int(objID[0]))
            directionfound = False
            l = min(aedt_bounding_dim) / 2
            while not directionfound:
                self.modeler.oeditor.ThickenSheet(
                    ["NAME:Selections", "Selections:=", el, "NewPartsModelFlag:=", "Model"],
                    ["NAME:SheetThickenParameters", "Thickness:=", str(l) + "mm", "BothSides:=", False],
                )
                # aedt_bounding_box2 = self.oeditor.GetModelBoundingBox()
                aedt_bounding_box2 = self.modeler.get_model_bounding_box()
                self._odesign.Undo()
                if aedt_bounding_box != aedt_bounding_box2:
                    directions[el] = "External"
                    directionfound = True
                self.modeler.oeditor.ThickenSheet(
                    ["NAME:Selections", "Selections:=", el, "NewPartsModelFlag:=", "Model"],
                    ["NAME:SheetThickenParameters", "Thickness:=", "-" + str(l) + "mm", "BothSides:=", False],
                )
                # aedt_bounding_box2 = self.oeditor.GetModelBoundingBox()
                aedt_bounding_box2 = self.modeler.get_model_bounding_box()

                self._odesign.Undo()

                if aedt_bounding_box != aedt_bounding_box2:
                    directions[el] = "Internal"
                    directionfound = True
                else:
                    l = l + min(aedt_bounding_dim) / 2
        for el in inputlist:
            objID = self.modeler.oeditor.GetFaceIDs(el)
            maxarea = 0
            for f in objID:
                faceArea = self.modeler.get_face_area(int(f))
                if faceArea > maxarea:
                    maxarea = faceArea
                    faceCenter = self.modeler.oeditor.GetFaceCenter(int(f))
            if directions[el] == "Internal":
                self.modeler.oeditor.ThickenSheet(
                    ["NAME:Selections", "Selections:=", el, "NewPartsModelFlag:=", "Model"],
                    ["NAME:SheetThickenParameters", "Thickness:=", "-" + str(value) + "mm", "BothSides:=", False],
                )
            else:
                self.modeler.oeditor.ThickenSheet(
                    ["NAME:Selections", "Selections:=", el, "NewPartsModelFlag:=", "Model"],
                    ["NAME:SheetThickenParameters", "Thickness:=", str(value) + "mm", "BothSides:=", False],
                )
            if "Vacuum" in el:
                newfaces = self.modeler.oeditor.GetFaceIDs(el)
                for f in newfaces:
                    try:
                        fc2 = self.modeler.oeditor.GetFaceCenter(f)
                        fc2 = [float(i) for i in fc2]
                        fa2 = self.modeler.get_face_area(int(f))
                        faceoriginal = [float(i) for i in faceCenter]
                        # dist = mat.sqrt(sum([(a*a-b*b) for a,b in zip(faceCenter, fc2)]))
                        if abs(fa2 - maxarea) < tol**2 and (
                            abs(faceoriginal[2] - fc2[2]) > tol
                            or abs(faceoriginal[1] - fc2[1]) > tol
                            or abs(faceoriginal[0] - fc2[0]) > tol
                        ):
                            ports_ID[el] = int(f)

                        # if (abs(faceoriginal[0] - fc2[0]) < tol and abs(faceoriginal[1] - fc2[1]) < tol and abs(
                        #         faceoriginal[2] - fc2[2]) > tol) or (
                        #         abs(faceoriginal[0] - fc2[0]) < tol and abs(faceoriginal[1] - fc2[1]) > tol and abs(
                        #         faceoriginal[2] - fc2[2]) < tol) or (
                        #         abs(faceoriginal[0] - fc2[0]) > tol and abs(faceoriginal[1] - fc2[1]) < tol and abs(
                        #         faceoriginal[2] - fc2[2]) < tol):
                        #     ports_ID[el] = int(f)
                    except:
                        pass
            if internalExtr:
                objID2 = self.modeler.oeditor.GetFaceIDs(el)
                for fid in objID2:
                    try:
                        faceCenter2 = self.modeler.oeditor.GetFaceCenter(int(fid))
                        if faceCenter2 == faceCenter:
                            self.modeler.oeditor.MoveFaces(
                                ["NAME:Selections", "Selections:=", el, "NewPartsModelFlag:=", "Model"],
                                [
                                    "NAME:Parameters",
                                    [
                                        "NAME:MoveFacesParameters",
                                        "MoveAlongNormalFlag:=",
                                        True,
                                        "OffsetDistance:=",
                                        str(internalvalue) + "mm",
                                        "MoveVectorX:=",
                                        "0mm",
                                        "MoveVectorY:=",
                                        "0mm",
                                        "MoveVectorZ:=",
                                        "0mm",
                                        "FacesToMove:=",
                                        [int(fid)],
                                    ],
                                ],
                            )
                    except:
                        self.logger.info("done")
                        # self.modeler_oproject.ClearMessages()
        return ports_ID

    @pyaedt_function_handler()
    def validate_full_design(self, dname=None, outputdir=None, ports=None):
        """Validate a design based on an expected value and save information to the log file.


        Parameters
        ----------
        dname : str,  optional
            Name of the design to validate. The default is ``None``, in which case
            the current design is used.
        outputdir : str, optional
            Directory to save the log file to. The default is ``None``,
            in which case the current project path is used.
        ports : int, optional
            Number of excitations (sum of modes) that is expected. The default is ``None``.

        Returns
        -------
        list of str
            List of all the validation information for later use.
        bool
            ``True`` if the validation was successful, ``False`` otherwise.

        References
        ----------

        >>> oDesign.ValidateDesign

        Examples
        --------

        Validate the current design and save the log file in the current project directory.

        >>> validation = hfss.validate_full_design()
        PyAEDT INFO: Design Validation Checks
        >>> validation[1]
        False

        """

        self.logger.info("Design validation checks.")
        validation_ok = True
        val_list = []
        if not dname:
            dname = self.design_name
        if not outputdir:
            outputdir = self.working_directory
        pname = self.project_name
        validation_log_file = os.path.join(outputdir, pname + "_" + dname + "_validation.log")

        # Desktop Messages
        msg = "Desktop messages:"
        val_list.append(msg)
        temp_msg = list(self._desktop.GetMessages(pname, dname, 0))
        if temp_msg:
            temp2_msg = [i.strip("Project: " + pname + ", Design: " + dname + ", ").strip("\r\n") for i in temp_msg]
            val_list.extend(temp2_msg)

        # Run design validation and write out the lines to the log.
        temp_dir = tempfile.gettempdir()
        temp_val_file = os.path.join(temp_dir, "val_temp.log")
        simple_val_return = self.validate_simple(temp_val_file)
        if simple_val_return == 1:
            msg = "Design validation check PASSED."
        elif simple_val_return == 0:
            msg = "Design validation check ERROR."
            validation_ok = False
        val_list.append(msg)
        msg = "Design validation messages:"
        val_list.append(msg)
        if os.path.isfile(temp_val_file) or settings.remote_rpc_session:
            with open_file(temp_val_file, "r") as df:
                temp = df.read().splitlines()
                val_list.extend(temp)
            os.remove(temp_val_file)
        else:
            msg = "** No design validation file is found. **"
            self.logger.info(msg)
            val_list.append(msg)
        msg = "** End of design validation messages. **"
        val_list.append(msg)

        # Find the excitations and check or list them out
        msg = "Excitations check:"
        val_list.append(msg)
        if self.solution_type != "Eigenmode":
            detected_excitations = self.excitations
            if ports:
                if ports != len(detected_excitations):
                    msg = "** Port number error. Check the model. **"
                    self.logger.error(msg)
                    val_list.append(msg)
                    validation_ok = False
                else:
                    msg1 = "Solution type: " + str(self.solution_type)
                    msg2 = "Ports Requested: " + str(ports)
                    msg3 = "Defined excitations number: " + str(len(detected_excitations))
                    msg4 = "Defined excitations names: " + str(detected_excitations)
                    val_list.append(msg1)
                    val_list.append(msg2)
                    val_list.append(msg3)
                    val_list.append(msg4)
        else:
            msg = "Eigen model is detected. No excitatons are defined."
            self.logger.info(msg)
            val_list.append(msg)

        # Find the number of analysis setups and output the info.
        msg = "Analysis setup messages:"
        val_list.append(msg)
        setups = self.oanalysis.GetSetups()
        if setups:
            setups = list(setups)
            msg = "Detected setup and sweep: "
            val_list.append(msg)
            for setup in setups:
                msg = str(setup)
                val_list.append(msg)
                if self.solution_type.lower() != "eigenmode":
                    sweepsname = self.oanalysis.GetSweeps(setup)
                    if sweepsname:
                        for sw in sweepsname:
                            msg = " |__ " + sw
                            val_list.append(msg)
        else:
            msg = "No setup is detected."
            val_list.append(msg)

        with open_file(validation_log_file, "w") as f:
            for item in val_list:
                f.write("%s\n" % item)
        return val_list, validation_ok  # Return all the information in a list for later use.

    @pyaedt_function_handler()
    def create_scattering(
        self, plot_name="S Parameter Plot Nominal", sweep_name=None, port_names=None, port_excited=None, variations=None
    ):
        """Create an S-parameter report.

        Parameters
        ----------
        plot_name : str, optional
             Name of the plot. The default is ``"S Parameter Plot Nominal"``.
        sweep_name : str, optional
             Name of the sweep. The default is ``None``.
        port_names : list, optional
             List of port names. The first index, i, in S[i,j].
             The default is ``None``. (include only self-terms)
        port_excited : list or str, optional
             List of port names. The seconds index, j in S[i,j].
             The default is ``None``. (include only self-terms)
        variations : str, optional
             The default is ``None``.

        Returns
        -------
        bool
            ``True`` when successful, ``False`` when failed.

        References
        ----------

        >>> oModule.CreateReport

        Examples
        --------

        Create ad S-parameter plot named ``"S Parameter Plot Nominal"`` for a 3-port network.
        plotting S11, S21, S31.  The port names are ``P1``, ``P2``, and ``P3``.

        >>> hfss.create_scattering(port_names=["P1", "P2", "P3"], port_excited=["P1", "P1", "P1"])
        True

        """

        solution_data = "Standard"
        if "Modal" in self.solution_type:
            solution_data = "Modal Solution Data"
        elif "Terminal" in self.solution_type:
            solution_data = "Terminal Solution Data"
        if not port_names:
            port_names = self.excitations
        if not port_excited:
            port_excited = port_names
        traces = ["dB(S(" + p + "," + q + "))" for p, q in zip(list(port_names), list(port_excited))]
        return self.post.create_report(
            traces, sweep_name, variations=variations, report_category=solution_data, plotname=plot_name
        )

    @pyaedt_function_handler()
    def create_qfactor_report(self, project_dir, outputlist, setupname, plotname, Xaxis="X"):
        """Export a CSV file of the EigenQ plot.

        Parameters
        ----------
        project_dir : str
            Directory to export the CSV file to.
        outputlist : list
            Output quantity, which in this case is the Q-factor.
        setupname : str
            Name of the setup to generate the report from.
        plotname : str
            Name of the plot.
        Xaxis : str, optional
            Value for the X axis. The default is ``"X"``.

        Returns
        -------
        bool
            ``True`` when successful, ``False`` when failed.

        References
        ----------

        >>> oModule.CreateReport

        """
        npath = project_dir

        # Setup arguments list for createReport function
        args = [Xaxis + ":=", ["All"]]
        args2 = ["X Component:=", Xaxis, "Y Component:=", outputlist]

        self.post.post_oreport_setup.CreateReport(
            plotname, "Eigenmode Parameters", "Rectangular Plot", setupname + " : LastAdaptive", [], args, args2, []
        )
        return True

    @pyaedt_function_handler()
    def export_touchstone(
        self,
        setup_name=None,
        sweep_name=None,
        file_name=None,
        variations=None,
        variations_value=None,
        renormalization=False,
        impedance=None,
        comments=False,
    ):
        """Export the Touchstone file to a local folder.

        Parameters
        ----------
        setup_name : str, optional
            Name of the setup that has been solved.
        sweep_name : str, optional
            Name of the sweep that has been solved.
        file_name : str, optional
            Full path and name for the Touchstone file.
            The default is ``None``, in which case the file is exported to the working directory.
        variations : list, optional
            List of all parameter variations. For example, ``["$AmbientTemp", "$PowerIn"]``.
            The default is ``None``.
        variations_value : list, optional
            List of all parameter variation values. For example, ``["22cel", "100"]``.
            The default is ``None``.
        renormalization : bool, optional
            Perform renormalization before export.
            The default is ``False``.
        impedance : float, optional
            Real impedance value in ohm, for renormalization, if not specified considered 50 ohm.
            The default is ``None``.
        comments : bool, optional
            Include Gamma and Impedance values in comments.
            The default is ``False``.

        Returns
        -------
        bool
            ``True`` when successful, ``False`` when failed.
        """
        return self._export_touchstone(
            setup_name=setup_name,
            sweep_name=sweep_name,
            file_name=file_name,
            variations=variations,
            variations_value=variations_value,
            renormalization=renormalization,
            impedance=impedance,
            comments=comments,
        )

    @pyaedt_function_handler()
    def set_export_touchstone(self, activate, export_dir=""):
        """Set automatic export of the Touchstone file after simulation.

        Parameters
        ----------
        activate : bool
            Whether to export the Touchstone file after simulation finishes.
        export_dir : str, optional
            Directory to export the Touchstone file to. The default is ``""``,
            in which case the Touchstone file is exported to the working directory.

        Returns
        -------
        bool
            ``True`` when successful, ``False`` when failed.

        References
        ----------

        >>> oDesign.SetDesignSettings
        """
        settings = []
        if activate:
            settings.append("NAME:Design Settings Data")
            settings.append("Export After Simulation:=")
            settings.append(True)
            settings.append("Export Dir:=")
            settings.append(export_dir)
        elif not activate:
            settings.append("NAME:Design Settings Data")
            settings.append("Export After Simulation:=")
            settings.append(False)
        self.odesign.SetDesignSettings(settings)
        return True

    @pyaedt_function_handler()
    def assign_radiation_boundary_to_objects(self, obj_names, boundary_name=""):
        """Assign a radiation boundary to one or more objects (usually airbox objects).

        Parameters
        ----------
        obj_names : str or list or int or :class:`pyaedt.modeler.cad.object3d.Object3d`
            One or more object names or IDs.
        boundary_name : str, optional
            Name of the boundary. The default is ``""``.

        Returns
        -------
        :class:`pyaedt.modules.Boundary.BoundaryObject`
            Boundary object.

        References
        ----------

        >>> oModule.AssignRadiation

        Examples
        --------

        Create a box and assign a radiation boundary to it.

        >>> radiation_box = hfss.modeler.create_box([0, -200, -200], [200, 200, 200],
        ...                                         name="Radiation_box")
        >>> radiation = hfss.assign_radiation_boundary_to_objects("Radiation_box")
        >>> type(radiation)
        <class 'pyaedt.modules.Boundary.BoundaryObject'>

        """

        object_list = self.modeler.convert_to_selections(obj_names, return_list=True)
        if boundary_name:
            rad_name = boundary_name
        else:
            rad_name = generate_unique_name("Rad_")
        return self.create_boundary(self.BoundaryType.Radiation, object_list, rad_name)

    @pyaedt_function_handler()
    def assign_hybrid_region(self, obj_names, boundary_name="", hybrid_region="SBR+"):
        """Assign a hybrid region to one or more objects.

        Parameters
        ----------
        obj_names : str or list or int or :class:`pyaedt.modeler.cad.object3d.Object3d`
            One or more object names or IDs.
        boundary_name : str, optional
            Name of the boundary. The default is ``""``.
        hybrid_region : str, optional
            Hybrid region to assign. Options are ``"SBR+"``, ``"IE"``, ``"PO"``. The default is `"SBR+"``.

        Returns
        -------
        :class:`pyaedt.modules.Boundary.BoundaryObject`
            Boundary object.

        References
        ----------

        >>> oModule.AssignHybridRegion

        Examples
        --------

        Create a box and assign a hybrid boundary to it.

        >>> box = hfss.modeler.create_box([0, -200, -200], [200, 200, 200],
        ...                                         name="Radiation_box")
        >>> sbr_box = hfss.assign_hybrid_region("Radiation_box")
        >>> type(sbr_box)
        <class 'pyaedt.modules.Boundary.BoundaryObject'>

        """

        object_list = self.modeler.convert_to_selections(obj_names, return_list=True)
        if boundary_name:
            region_name = boundary_name
        else:
            region_name = generate_unique_name("Hybrid_")
        bound = self.create_boundary(self.BoundaryType.Hybrid, object_list, region_name)
        if hybrid_region != "SBR+":
            bound.props["Type"] = hybrid_region
        return bound

    @pyaedt_function_handler()
    def assign_febi(self, obj_names, boundary_name=""):
        """Assign an FE-BI region to one or more objects.

        Parameters
        ----------
        obj_names : str or list or int or :class:`pyaedt.modeler.cad.object3d.Object3d`
            One or more object names or IDs.
        boundary_name : str, optional
            Name of the boundary. The default is ``""``, in which case a name is automatically assigned.

        Returns
        -------
        :class:`pyaedt.modules.Boundary.BoundaryObject`
            Boundary object.

        References
        ----------

        >>> oModule.AssignFEBI

        Examples
        --------

        Create a box and assign an FE-BI boundary to it.

        >>> box = hfss.modeler.create_box([0, -200, -200], [200, 200, 200],
        ...                                         name="Radiation_box")
        >>> febi_box = hfss.assign_febi("Radiation_box")
        >>> type(febi_box)
        <class 'pyaedt.modules.Boundary.BoundaryObject'>

        """

        object_list = self.modeler.convert_to_selections(obj_names, return_list=True)
        if boundary_name:
            region_name = boundary_name
        else:
            region_name = generate_unique_name("FEBI_")
        bound = self.create_boundary(self.BoundaryType.FEBI, object_list, region_name)

        return bound

    @pyaedt_function_handler()
    def assign_radiation_boundary_to_faces(self, faces_id, boundary_name=""):
        """Assign a radiation boundary to one or more faces.

        Parameters
        ----------
        faces_id :
            Face ID to assign the boundary condition to.
        boundary_name : str, optional
            Name of the boundary. The default is ``""``.

        Returns
        -------
        :class:`pyaedt.modules.Boundary.BoundaryObject`
            Boundary object.

        References
        ----------

        >>> oModule.AssignRadiation

        Examples
        --------

        Create a box. Select the faces of this box and assign a radiation
        boundary to them.

        >>> radiation_box = hfss.modeler.create_box([0 , -100, 0], [200, 200, 200],
        ...                                         name="RadiationForFaces")
        >>> ids = [i.id for i in hfss.modeler["RadiationForFaces"].faces]
        >>> radiation = hfss.assign_radiation_boundary_to_faces(ids)
        >>> type(radiation)
        <class 'pyaedt.modules.Boundary.BoundaryObject'>

        """
        faces_list = self.modeler.convert_to_selections(faces_id, True)
        if boundary_name:
            rad_name = boundary_name
        else:
            rad_name = generate_unique_name("Rad_")
        return self.create_boundary(self.BoundaryType.Radiation, faces_list, rad_name)

    @pyaedt_function_handler()
    def _create_sbr_doppler_setup(
        self,
        setup_type,
        time_var,
        center_freq,
        resolution,
        period,
        velocity_resolution,
        min_velocity,
        max_velocity,
        ray_density_per_wavelength,
        max_bounces,
        setup_name,
        include_coupling_effects=False,
        doppler_ad_sampling_rate=20,
    ):
        setup1 = self.create_setup(setup_name, 4)
        setup1.auto_update = False
        setup1.props["IsSbrRangeDoppler"] = True
        del setup1.props["PTDUTDSimulationSettings"]
        del setup1.props["ComputeFarFields"]
        del setup1.props["Sweeps"]
        if setup_type == "ChirpIQ":
            setup1.props["SbrRangeDopplerWaveformType"] = "ChirpSeqFmcw"
            setup1.props["ChannelConfiguration"] = "IQChannels"
        elif setup_type == "ChirpI":
            setup1.props["SbrRangeDopplerWaveformType"] = "ChirpSeqFmcw"
            setup1.props["ChannelConfiguration"] = "IChannelOnly"
        else:
            setup1.props["SbrRangeDopplerWaveformType"] = setup_type
        setup1.props["SbrRangeDopplerTimeVariable"] = time_var
        setup1.props["SbrRangeDopplerCenterFreq"] = self.modeler._arg_with_dim(center_freq, "GHz")
        setup1.props["SbrRangeDopplerRangeResolution"] = self.modeler._arg_with_dim(resolution, "meter")
        setup1.props["SbrRangeDopplerRangePeriod"] = self.modeler._arg_with_dim(period, "meter")
        setup1.props["SbrRangeDopplerVelocityResolution"] = self.modeler._arg_with_dim(velocity_resolution, "m_per_sec")
        setup1.props["SbrRangeDopplerVelocityMin"] = self.modeler._arg_with_dim(min_velocity, "m_per_sec")
        setup1.props["SbrRangeDopplerVelocityMax"] = self.modeler._arg_with_dim(max_velocity, "m_per_sec")
        setup1.props["DopplerRayDensityPerWavelength"] = ray_density_per_wavelength
        setup1.props["MaxNumberOfBounces"] = max_bounces
        if setup_type != "PulseDoppler":
            setup1.props["IncludeRangeVelocityCouplingEffect"] = include_coupling_effects
            setup1.props["SbrRangeDopplerA/DSamplingRate"] = self.modeler._arg_with_dim(doppler_ad_sampling_rate, "MHz")
        setup1.update()
        setup1.auto_update = True
        return setup1

    @pyaedt_function_handler()
    def _create_sbr_doppler_sweep(self, setupname, time_var, tstart, tstop, tsweep, parametric_name):
        time_start = self.modeler._arg_with_dim(tstart, "s")
        time_sweep = self.modeler._arg_with_dim(tsweep, "s")
        time_stop = self.modeler._arg_with_dim(tstop, "s")
        sweep_range = "LIN {} {} {}".format(time_start, time_stop, time_sweep)
        return self.parametrics.add(
            time_var, tstart, time_stop, tsweep, "LinearStep", setupname, parametricname=parametric_name
        )

    @pyaedt_function_handler()
    def create_sbr_chirp_i_doppler_setup(
        self,
        time_var=None,
        sweep_time_duration=0,
        center_freq=76.5,
        resolution=1,
        period=200,
        velocity_resolution=0.4,
        min_velocity=-20,
        max_velocity=20,
        ray_density_per_wavelength=0.2,
        max_bounces=5,
        include_coupling_effects=False,
        doppler_ad_sampling_rate=20,
        setup_name=None,
    ):
        """Create an SBR+ Chirp I Setup.

        Parameters
        ----------
        time_var : str, optional
            Name of the time variable. The default is ``None``, in which case
            a search for the first time variable is performed.
        sweep_time_duration : float, optional
            Duration for the sweep time. The default is ``0.`` If a value greater
            than ``0`` is specified, a parametric sweep is created.
        center_freq : float, optional
            Center frequency in gigahertz (GHz). The default is ``76.5``.
        resolution : float, optional
            Doppler resolution in meters (m). The default is ``1``.
        period : float, optional
            Period of analysis in meters (m). The default is ``200``.
        velocity_resolution : float, optional
            Doppler velocity resolution in meters per second (m/s). The default is ``0.4``.
        min_velocity : str, optional
            Minimum Doppler velocity in meters per second (m/s). The default is ``-20``.
        max_velocity : str, optional
            Maximum Doppler velocity in meters per second (m/s). The default is ``20``.
        ray_density_per_wavelength : float, optional
            Doppler ray density per wavelength. The default is ``0.2``.
        max_bounces : int, optional
            Maximum number of bounces. The default is ``5``.
        include_coupling_effects : float, optional
            Whether to include coupling effects. The default is ``False``.
        doppler_ad_sampling_rate : float, optional
            Doppler AD sampling rate to use if ``include_coupling_effects``
            is ``True``. The default is ``20``.
        setup_name : str, optional
            Name of the setup. The default is ``None``, in which case the active setup is used.

        Returns
        -------
        tuple
            The tuple contains: (:class:`pyaedt.modules.SolveSetup.Setup`,
            :class:`pyaedt.modules.DesignXPloration.ParametericsSetups.Optimetrics`).

        References
        ----------

        >>> oModule.InsertSetup

        """
        if self.solution_type != "SBR+":
            self.logger.error("Method applies only to the SBR+ solution.")
            return False, False
        if not setup_name:
            setup_name = generate_unique_name("ChirpI")
            parametric_name = generate_unique_name("PulseSweep")
        else:
            parametric_name = generate_unique_name(setup_name)

        if not time_var:
            for var_name, var in self.variable_manager.independent_variables.items():
                if var.unit_system == "Time":
                    time_var = var_name
                    break
            if not time_var:
                self.logger.error(
                    "No time variable is found. Set up or explicitly assign a time variable to the method."
                )
                raise ValueError("No time variable is found.")
        setup = self._create_sbr_doppler_setup(
            "ChirpI",
            time_var=time_var,
            center_freq=center_freq,
            resolution=resolution,
            period=period,
            velocity_resolution=velocity_resolution,
            min_velocity=min_velocity,
            max_velocity=max_velocity,
            ray_density_per_wavelength=ray_density_per_wavelength,
            max_bounces=max_bounces,
            include_coupling_effects=include_coupling_effects,
            doppler_ad_sampling_rate=doppler_ad_sampling_rate,
            setup_name=setup_name,
        )
        if sweep_time_duration > 0:
            sweeptime = math.ceil(300000000 / (2 * center_freq * 1000000000 * velocity_resolution) * 1000) / 1000
            sweep = self._create_sbr_doppler_sweep(
                setup.name, time_var, 0, sweep_time_duration, sweeptime, parametric_name
            )
            return setup, sweep
        return setup, False

    @pyaedt_function_handler()
    def create_sbr_chirp_iq_doppler_setup(
        self,
        time_var=None,
        sweep_time_duration=0,
        center_freq=76.5,
        resolution=1,
        period=200,
        velocity_resolution=0.4,
        min_velocity=-20,
        max_velocity=20,
        ray_density_per_wavelength=0.2,
        max_bounces=5,
        include_coupling_effects=False,
        doppler_ad_sampling_rate=20,
        setup_name=None,
    ):
        """Create an SBR+ Chirp IQ Setup.

        Parameters
        ----------
        time_var : str, optional
            Name of the time variable. The default is ``None``, in which case
            a search for the first time variable is performed.
        sweep_time_duration : float, optional
            Duration of the sweep time. The default is ``0``. If a value greater
            than ``0`` is specified, a parametric sweep is created.
        center_freq : float, optional
            Center frequency in gighertz (GHz). The default is ``76.5``.
        resolution : float, optional
            Doppler resolution in meters (m). The default is ``1``.
        period : float, optional
            Period of analysis in meters (m). The default is ``200``.
        velocity_resolution : float, optional
            Doppler velocity resolution in meters per second (m/s). The default is ``0.4``.
        min_velocity : str, optional
            Minimum Doppler velocity in meters per second (m/s). The default is ``-20``.
        max_velocity : str, optional
            Maximum Doppler velocity in meters per second (m/s). The default is ``20``.
        ray_density_per_wavelength : float, optional
            Doppler ray density per wavelength. The default is ``0.2``.
        max_bounces : int, optional
            Maximum number of bounces. The default is ``5``.
        include_coupling_effects : float, optional
            Whether to include coupling effects. The default is ``False``.
        doppler_ad_sampling_rate : float, optional
            Doppler AD sampling rate to use if ``include_coupling_effects`` is
            ``True``. The default is ``20``.
        setup_name : str, optional
            Name of the setup. The default is ``None``, in which case the active
            setup is used.

        Returns
        -------
        tuple
            The tuple contains: (:class:`pyaedt.modules.SolveSetup.Setup`,
            :class:`pyaedt.modules.DesignXPloration.ParametericsSetups.Optimetrics`).

        References
        ----------

        >>> oModule.InsertSetup
        """
        if self.solution_type != "SBR+":
            self.logger.error("Method applies only to the SBR+ solution.")
            return False, False
        if not setup_name:
            setup_name = generate_unique_name("ChirpIQ")
            parametric_name = generate_unique_name("PulseSweep")
        else:
            parametric_name = generate_unique_name(setup_name)
        if not time_var:
            for var_name, var in self.variable_manager.independent_variables.items():
                if var.unit_system == "Time":
                    time_var = var_name
                    break
            if not time_var:
                raise ValueError("No Time Variable Found")
        setup = self._create_sbr_doppler_setup(
            "ChirpIQ",
            time_var=time_var,
            center_freq=center_freq,
            resolution=resolution,
            period=period,
            velocity_resolution=velocity_resolution,
            min_velocity=min_velocity,
            max_velocity=max_velocity,
            ray_density_per_wavelength=ray_density_per_wavelength,
            max_bounces=max_bounces,
            include_coupling_effects=include_coupling_effects,
            doppler_ad_sampling_rate=doppler_ad_sampling_rate,
            setup_name=setup_name,
        )
        if sweep_time_duration > 0:
            sweeptime = math.ceil(300000000 / (2 * center_freq * 1000000000 * velocity_resolution) * 1000) / 1000
            sweep = self._create_sbr_doppler_sweep(
                setup.name, time_var, 0, sweep_time_duration, sweeptime, parametric_name
            )
            return setup, sweep
        return setup, False

    @pyaedt_function_handler()
    def create_sbr_pulse_doppler_setup(
        self,
        time_var=None,
        sweep_time_duration=0,
        center_freq=76.5,
        resolution=1,
        period=200,
        velocity_resolution=0.4,
        min_velocity=-20,
        max_velocity=20,
        ray_density_per_wavelength=0.2,
        max_bounces=5,
        setup_name=None,
    ):
        """Create an SBR+ pulse Doppler setup.

        Parameters
        ----------
        time_var : str, optional
            Name of the time variable. The default is ``None``, in which case
            a search for the first time variable is performed.
        sweep_time_duration : float, optional
            Duration of the sweep time. The default is ``0``. If a value greater
            than ``0`` is specified, a parametric sweep is created.
        center_freq : float, optional
            Center frequency in gigahertz (GHz). The default is ``76.5``.
        resolution : float, optional
            Doppler resolution in meters (m). The default is ``1``.
        period : float, optional
            Period of analysis in meters (m). The default is ``200``.
        velocity_resolution : float, optional
            Doppler velocity resolution in meters per second (m/s).
            The default is ``0.4``.
        min_velocity : str, optional
            Minimum Doppler velocity in meters per second (m/s). The default
            is ``-20``.
        max_velocity : str, optional
            Maximum Doppler velocity in meters per second (m/s). The default
            is ``20``.
        ray_density_per_wavelength : float, optional
            Doppler ray density per wavelength. The default is ``0.2``.
        max_bounces : int, optional
            Maximum number of bounces. The default is ``5``.
        setup_name : str, optional
            Name of the setup. The default is ``None``, in which case the active
            setup is used.

        Returns
        -------
        tuple
            The tuple contains: (:class:`pyaedt.modules.SolveSetup.Setup`,
            :class:`pyaedt.modules.DesignXPloration.ParametericsSetups.Optimetrics`).

        References
        ----------

        >>> oModule.InsertSetup
        """
        if self.solution_type != "SBR+":
            self.logger.error("Method Applies only to SBR+ Solution.")
            return False, False
        if not setup_name:
            setup_name = generate_unique_name("PulseSetup")
            parametric_name = generate_unique_name("PulseSweep")
        else:
            parametric_name = generate_unique_name(setup_name)

        if not time_var:
            for var_name, var in self.variable_manager.independent_variables.items():
                if var.unit_system == "Time":
                    time_var = var_name
                    break
            if not time_var:
                raise ValueError("No Time Variable Found")
        setup = self._create_sbr_doppler_setup(
            "PulseDoppler",
            time_var=time_var,
            center_freq=center_freq,
            resolution=resolution,
            period=period,
            velocity_resolution=velocity_resolution,
            min_velocity=min_velocity,
            max_velocity=max_velocity,
            ray_density_per_wavelength=ray_density_per_wavelength,
            max_bounces=max_bounces,
            setup_name=setup_name,
        )
        if sweep_time_duration > 0:
            sweeptime = math.ceil(300000000 / (2 * center_freq * 1000000000 * velocity_resolution) * 1000) / 1000
            sweep = self._create_sbr_doppler_sweep(
                setup.name, time_var, 0, sweep_time_duration, sweeptime, parametric_name
            )
            return setup, sweep
        return setup, False

    @pyaedt_function_handler()
    def create_sbr_radar_from_json(
        self, radar_file, radar_name, offset=[0, 0, 0], speed=0.0, use_relative_cs=False, relative_cs_name=None
    ):
        """Create an SBR+ radar from a JSON file.

        Example of input JSON file:

          .. code-block:: json

            {
                "name": "Example_1Tx_1Rx",
                "version": 1,
                "number_tx":"1",
                "number_rx":"1",
                "units":"mm",
                "antennas": {
                    "tx1": {
                        "antenna_type":"parametric",
                        "mode":"tx",
                        "offset":["0" ,"0" ,"0"],
                        "rotation_axis":null,
                        "rotation":null,
                        "beamwidth_elevation":"10deg",
                        "beamwidth_azimuth":"60deg",
                        "polarization":"Vertical"
                        },
                    "rx1": {
                        "antenna_type":"parametric",
                        "mode":"rx",
                        "offset":["0" ,"1.8" ,"0"],
                        "rotation_axis":null,
                        "rotation":null,
                        "beamwidth_elevation":"10deg",
                        "beamwidth_azimuth":"60deg",
                        "polarization":"Vertical"
                        }
                }
            }

        Parameters
        ----------
        radar_file : str
            Path to the directory with the radar file.
        radar_name : str
            Name of the radar file.
        offset : list, optional
            Offset relative to the global coordinate system.
        speed : float, optional
            Radar movement speed relative to the global coordinate system if greater than ``0``.
        use_relative_cs : bool, optional
            Whether to use the relative coordinate system. The default is ``False``.
        relative_cs_name : str
            Name of the relative coordinate system to link the radar to.
            The default is ``None``, in which case the global coordinate system is used.

        Returns
        -------
        :class:`pyaedt.modeler.actors.Radar`
            Radar  class object.

        References
        ----------
        AEDT API Commands.

        >>> oEditor.CreateRelativeCS
        >>> oModule.SetSBRTxRxSettings
        >>> oEditor.CreateGroup
        """
        from pyaedt.modeler.advanced_cad.actors import Radar

        self.modeler._initialize_multipart()
        if self.solution_type != "SBR+":
            self.logger.error("Method applies only to SBR+ solution.")
            return False
        use_motion = abs(speed) > 0.0
        r = Radar(
            radar_file,
            name=radar_name,
            motion=use_motion,
            offset=offset,
            speed=speed,
            use_relative_cs=(use_relative_cs or use_motion),
            relative_cs_name=relative_cs_name,
        )
        r.insert(self, abs(speed) > 0)
        return r

    @pyaedt_function_handler()
    def insert_infinite_sphere(
        self,
        definition=INFINITE_SPHERE_TYPE.ThetaPhi,
        x_start=0,
        x_stop=180,
        x_step=10,
        y_start=0,
        y_stop=180,
        y_step=10,
        units="deg",
        custom_radiation_faces=None,
        custom_coordinate_system=None,
        use_slant_polarization=False,
        polarization_angle=45,
        name=None,
    ):
        """Create an infinite sphere.

        .. note::
           This method is not supported by HFSS ``EigenMode`` and ``CharacteristicMode`` solution types.

        Parameters
        ----------
        definition : str
            Coordinate definition type. The default is ``"Theta-Phi"``.
            It can be a ``pyaedt.generic.constants.INFINITE_SPHERE_TYPE`` enumerator value.
        x_start : float, str, optional
            First angle start value. The default is ``0``.
        x_stop : float, str, optional
            First angle stop value. The default is ``180``.
        x_step : float, str, optional
            First angle step value. The default is ``10``.
        y_start : float, str, optional
            Second angle start value. The default is ``0``.
        y_stop : float, str, optional
            Second angle stop value. The default is ``180``.
        y_step : float, str, optional
            Second angle step value. The default is ``10``.
        units : str
            Angle units. The default is ``"deg"``.
        custom_radiation_faces : str, optional
            List of radiation faces to use for far field computation. The default is ``None``.
        custom_coordinate_system : str, optional
            Local coordinate system to use for far field computation. The default is
            ``None``.
        use_slant_polarization : bool, optional
            Whether to use slant polarization. The default is ``False``.
        polarization_angle : float, str, optional
            Slant angle value. The default is ``45``.
        name : str, optional
            Name of the sphere. The default is ``None``.

        Returns
        -------
        :class:`pyaedt.modules.Boundary.FarFieldSetup`
        """
        if not self.oradfield:
            self.logger.error("Radiation Field not available in this solution.")
        if not name:
            name = generate_unique_name("Infinite")

        props = OrderedDict({"UseCustomRadiationSurface": custom_radiation_faces is not None})
        if custom_radiation_faces:
            props["CustomRadiationSurface"] = custom_radiation_faces
        else:
            props["CustomRadiationSurface"] = ""
        props["CSDefinition"] = definition
        if use_slant_polarization:
            props["Polarization"] = "Slant"
        else:
            props["Polarization"] = "Linear"
        props["SlantAngle"] = self.modeler._arg_with_dim(polarization_angle, units)

        if definition == "Theta-Phi":
            defs = ["ThetaStart", "ThetaStop", "ThetaStep", "PhiStart", "PhiStop", "PhiStep"]
        elif definition == "El Over Az":
            defs = ["AzimuthStart", "AzimuthStop", "AzimuthStep", "ElevationStart", "ElevationStop", "ElevationStep"]
        else:
            defs = ["ElevationStart", "ElevationStop", "ElevationStep", "AzimuthStart", "AzimuthStop", "AzimuthStep"]
        props[defs[0]] = self.modeler._arg_with_dim(x_start, units)
        props[defs[1]] = self.modeler._arg_with_dim(x_stop, units)
        props[defs[2]] = self.modeler._arg_with_dim(x_step, units)
        props[defs[3]] = self.modeler._arg_with_dim(y_start, units)
        props[defs[4]] = self.modeler._arg_with_dim(y_stop, units)
        props[defs[5]] = self.modeler._arg_with_dim(y_step, units)
        props["UseLocalCS"] = custom_coordinate_system is not None
        if custom_coordinate_system:
            props["CoordSystem"] = custom_coordinate_system
        else:
            props["CoordSystem"] = ""
        bound = FarFieldSetup(self, name, props, "FarFieldSphere", units)
        if bound.create():
            self.field_setups.append(bound)
            return bound
        return False

    @pyaedt_function_handler()
    def insert_near_field_sphere(
        self,
        radius=20,
        radius_units="mm",
        x_start=0,
        x_stop=180,
        x_step=10,
        y_start=0,
        y_stop=180,
        y_step=10,
        angle_units="deg",
        custom_radiation_faces=None,
        custom_coordinate_system=None,
        name=None,
    ):
        """Create a near field sphere.

        .. note::
           This method is not supported by HFSS ``EigenMode`` and ``CharacteristicMode`` solution types.

        Parameters
        ----------
        radius : float, str, optional
            Sphere radius. The default is ``20``.
        radius_units : str
            Radius units. The default is ``"mm"``.
        x_start : float, str, optional
            First angle start value. The default is ``0``.
        x_stop : float, str, optional
            First angle stop value. The default is ``180``.
        x_step : float, str, optional
            First angle step value. The default is ``10``.
        y_start : float, str, optional
            Second angle start value. The default is ``0``.
        y_stop : float, str, optional
            Second angle stop value. The default is ``180``.
        y_step : float, str, optional
            Second angle step value. The default is ``10``.
        angle_units : str
            Angle units. The default is ``"deg"``.
        custom_radiation_faces : str, optional
            List of radiation faces to use for far field computation. The default is ``None``.
        custom_coordinate_system : str, optional
            Local coordinate system to use for far field computation. The default is ``None``.
        name : str, optional
            Name of the sphere. The default is ``None``.

        Returns
        -------
        :class:`pyaedt.modules.Boundary.NearFieldSetup`
        """
        if not self.oradfield:
            self.logger.error("Radiation Field not available in this solution.")
        if not name:
            name = generate_unique_name("Sphere")

        props = OrderedDict({"UseCustomRadiationSurface": custom_radiation_faces is not None})
        if custom_radiation_faces:
            props["CustomRadiationSurface"] = custom_radiation_faces
        else:
            props["CustomRadiationSurface"] = ""

        props["Radius"] = self.modeler._arg_with_dim(radius, radius_units)

        defs = ["ThetaStart", "ThetaStop", "ThetaStep", "PhiStart", "PhiStop", "PhiStep"]
        props[defs[0]] = self.modeler._arg_with_dim(x_start, angle_units)
        props[defs[1]] = self.modeler._arg_with_dim(x_stop, angle_units)
        props[defs[2]] = self.modeler._arg_with_dim(x_step, angle_units)
        props[defs[3]] = self.modeler._arg_with_dim(y_start, angle_units)
        props[defs[4]] = self.modeler._arg_with_dim(y_stop, angle_units)
        props[defs[5]] = self.modeler._arg_with_dim(y_step, angle_units)
        props["UseLocalCS"] = custom_coordinate_system is not None
        if custom_coordinate_system:
            props["CoordSystem"] = custom_coordinate_system
        else:
            props["CoordSystem"] = ""
        bound = NearFieldSetup(self, name, props, "NearFieldSphere")
        if bound.create():
            self.field_setups.append(bound)
            return bound
        return False

    @pyaedt_function_handler()
    def insert_near_field_box(
        self,
        u_length=20,
        u_samples=21,
        v_length=20,
        v_samples=21,
        w_length=20,
        w_samples=21,
        units="mm",
        custom_radiation_faces=None,
        custom_coordinate_system=None,
        name=None,
    ):
        """Create a near field box.

        .. note::
           This method is not supported by HFSS ``EigenMode`` and ``CharacteristicMode`` solution types.

        Parameters
        ----------
        u_length : float, str, optional
            U axis length. The default is ``20``.
        u_samples : float, str, optional
            U axis samples. The default is ``21``.
        v_length : float, str, optional
            V axis length. The default is ``20``.
        v_samples : float, str, optional
            V axis samples. The default is ``21``.
        w_length : float, str, optional
            W axis length. The default is ``20``.
        w_samples : float, str, optional
            W axis samples. The default is ``21``.
        units : str
            Length units. The default is ``"mm"``.
        custom_radiation_faces : str, optional
            List of radiation faces to use for far field computation. The default is ``None``.
        custom_coordinate_system : str, optional
            Local coordinate system to use for far field computation. The default is ``None``.
        name : str, optional
            Name of the sphere. The default is ``None``.

        Returns
        -------
        :class:`pyaedt.modules.Boundary.NearFieldSetup`
        """
        if not self.oradfield:
            self.logger.error("Radiation Field not available in this solution.")
        if not name:
            name = generate_unique_name("Box")

        props = OrderedDict({"UseCustomRadiationSurface": custom_radiation_faces is not None})
        if custom_radiation_faces:
            props["CustomRadiationSurface"] = custom_radiation_faces
        else:
            props["CustomRadiationSurface"] = ""

        defs = ["U Size", "V Size", "W Size", "U Samples", "V Samples", "W Samples"]
        props[defs[0]] = self.modeler._arg_with_dim(u_length, units)
        props[defs[1]] = self.modeler._arg_with_dim(v_length, units)
        props[defs[2]] = self.modeler._arg_with_dim(w_length, units)
        props[defs[3]] = self.modeler._arg_with_dim(u_samples, units)
        props[defs[4]] = self.modeler._arg_with_dim(v_samples, units)
        props[defs[5]] = self.modeler._arg_with_dim(w_samples, units)

        if custom_coordinate_system:
            props["CoordSystem"] = custom_coordinate_system
        else:
            props["CoordSystem"] = "Global"
        bound = NearFieldSetup(self, name, props, "NearFieldBox")
        if bound.create():
            self.field_setups.append(bound)
            return bound
        return False

    @pyaedt_function_handler()
    def insert_near_field_rectangle(
        self,
        u_length=20,
        u_samples=21,
        v_length=20,
        v_samples=21,
        units="mm",
        custom_radiation_faces=None,
        custom_coordinate_system=None,
        name=None,
    ):
        """Create a near field rectangle.

        .. note::
           This method is not supported by HFSS ``EigenMode`` and ``CharacteristicMode`` solution types.

        Parameters
        ----------
        u_length : float, str, optional
            U axis length. The default is ``20``.
        u_samples : float, str, optional
            U axis samples. The default is ``21``.
        v_length : float, str, optional
            V axis length. The default is ``20``.
        v_samples : float, str, optional
            V axis samples. The default is ``21``.
        units : str
            Length units. The default is ``"mm"``.
        custom_radiation_faces : str, optional
            List of radiation faces to use for far field computation. The default is ``None``.
        custom_coordinate_system : str, optional
            Local coordinate system to use for far field computation. The default is ``None``.
        name : str, optional
            Name of the sphere. The default is ``None``.

        Returns
        -------
        :class:`pyaedt.modules.Boundary.NearFieldSetup`
        """
        if not self.oradfield:
            self.logger.error("Radiation Field not available in this solution.")
        if not name:
            name = generate_unique_name("Rectangle")

        props = OrderedDict({"UseCustomRadiationSurface": custom_radiation_faces is not None})
        if custom_radiation_faces:
            props["CustomRadiationSurface"] = custom_radiation_faces
        else:
            props["CustomRadiationSurface"] = ""

        defs = ["Length", "Width", "LengthSamples", "WidthSamples"]
        props[defs[0]] = self.modeler._arg_with_dim(u_length, units)
        props[defs[1]] = self.modeler._arg_with_dim(v_length, units)
        props[defs[2]] = u_samples
        props[defs[3]] = v_samples

        if custom_coordinate_system:
            props["CoordSystem"] = custom_coordinate_system
        else:
            props["CoordSystem"] = "Global"
        bound = NearFieldSetup(self, name, props, "NearFieldRectangle")
        if bound.create():
            self.field_setups.append(bound)
            return bound
        return False

    @pyaedt_function_handler()
    def insert_near_field_line(
        self,
        line,
        points=1000,
        custom_radiation_faces=None,
        name=None,
    ):
        """Create a near field line.

        .. note::
           This method is not supported by HFSS ``EigenMode`` and ``CharacteristicMode`` solution types.

        Parameters
        ----------
        line : str
            Polyline name.
        points : float, str, optional
            Number of points. The default value is ``1000``.
        custom_radiation_faces : str, optional
            List of radiation faces to use for far field computation. The default is ``None``.
        name : str, optional
            Name of the sphere. The default is ``None``.

        Returns
        -------
        :class:`pyaedt.modules.Boundary.NearFieldSetup`
        """
        if not self.oradfield:
            self.logger.error("Radiation Field not available in this solution.")
        if not name:
            name = generate_unique_name("Line")

        props = OrderedDict({"UseCustomRadiationSurface": custom_radiation_faces is not None})
        if custom_radiation_faces:
            props["CustomRadiationSurface"] = custom_radiation_faces
        else:
            props["CustomRadiationSurface"] = ""

        props["NumPts"] = points
        props["Line"] = line

        bound = NearFieldSetup(self, name, props, "NearFieldLine")
        if bound.create():
            self.field_setups.append(bound)
            return bound
        return False

    @pyaedt_function_handler()
    def set_sbr_current_sources_options(self, conformance=False, thin_sources=False, power_fraction=0.95):
        """Set SBR+ setup options for the current source.

        Parameters
        ----------
        conformance : bool, optional
            Whether to enable current source conformance. The default is ``False``.
        thin_sources : bool, optional
            Whether to enable thin sources. The default is ``False``.
        power_fraction : float or str, optional
            Power fraction to use if ``thin_sources=True``. The default is ``0.95``.

        Returns
        -------
        bool
            ``True`` when successful, ``False`` when failed.

        References
        ----------

        >>> oModule.EditGlobalCurrentSourcesOption
        """
        if self.solution_type != "SBR+":
            self.logger.error("Method Applies only to SBR+ Solution.")
            return False
        current_conformance = "Disable"
        if conformance:
            current_conformance = "Enable"
        arg = [
            "NAME:CurrentSourceOption",
            "Current Source Conformance:=",
            current_conformance,
            "Thin Sources:=",
            thin_sources,
        ]
        if thin_sources:
            arg.append("Power Fraction:=")
            arg.append(str(power_fraction))
        self.oboundary.EditGlobalCurrentSourcesOption(arg)
        self.logger.info("SBR+ current source options correctly applied.")
        return True

    @pyaedt_function_handler()
    def set_differential_pair(
        self,
        positive_terminal,
        negative_terminal,
        common_name=None,
        diff_name=None,
        common_ref_z=25,
        diff_ref_z=100,
        active=True,
        matched=False,
    ):
        """Add a differential pair definition.

        Differential pairs can be defined only in Terminal and Transient solution types.
        The differential pair is created from an existing port definition having at least two
        terminals.

        Parameters
        ----------
        positive_terminal : str
            Name of the terminal to use as the positive terminal.
        negative_terminal : str
            Name of the terminal to use as the negative terminal.
        common_name : str, optional
            Name for the common mode. The default is ``None``, in which case a unique name is assigned.
        diff_name : str, optional
            Name for the differential mode. The default is ``None``, in which case a unique name is assigned.
        common_ref_z : float, optional
            Reference impedance for the common mode in ohms. The default is ``25``.
        diff_ref_z : float, optional
            Reference impedance for the differential mode in ohms. The default is ``100``.
        active : bool, optional
            Whether the differential pair is active. The default is ``True``.
        matched : bool, optional
            Whether the differential pair is matched. The default is ``False``.

        Returns
        -------
        bool
            ``True`` when successful, ``False`` when failed.

        References
        ----------
        >>> oModule.EditDiffPairs
        """

        if self.solution_type not in ["Transient Network", "Terminal"]:  # pragma: no cover
            raise AttributeError("Differential pairs can be defined only in Terminal and Transient solution types.")

        props = OrderedDict()
        props["PosBoundary"] = positive_terminal
        props["NegBoundary"] = negative_terminal
        if not common_name:
            common_name = generate_unique_name("Comm")
        props["CommonName"] = common_name
        props["CommonRefZ"] = str(common_ref_z) + "ohm"
        if not diff_name:
            diff_name = generate_unique_name("Diff")
        props["DiffName"] = diff_name
        props["DiffRefZ"] = str(diff_ref_z) + "ohm"
        props["IsActive"] = active
        props["UseMatched"] = matched
        arg = ["NAME:" + generate_unique_name("Pair")]
        _dict2arg(props, arg)

        arg2 = ["NAME:EditDiffPairs", arg]

        existing_pairs = self.oboundary.GetDiffPairs()
        # Native API returns Boolean values as strings. Therefore, map to Boolean.
        num_old_pairs = len(existing_pairs)
        if existing_pairs:
            for i, p in enumerate(existing_pairs):
                tmp_p = list(map(str_to_bool, p))
                tmp_p.insert(0, "NAME:Pair_" + str(i))
                arg2.append(tmp_p)

        self.oboundary.EditDiffPairs(arg2)

        if len(self.oboundary.GetDiffPairs()) == num_old_pairs + 1:
            return True
        else:
            return False

    @pyaedt_function_handler()
    def add_3d_component_array_from_json(self, json_file, array_name=None):
        """Add or edit a new 3D component array from a JSON file or TOML file.
        The 3D component is placed in the layout if it is not present.

        Parameters
        ----------
        json_file : str, dict
            Full path to either the JSON file or dictionary containing the array information.
        array_name : str, optional
            Name of the boundary to create or edit.

        Returns
        -------
        class:`pyaedt.modeler.cad.component_array.ComponentArray`

        Examples
        --------
        Add a 3D component array from a json file.
        Below is the content of a json file that will be used in the following code sample.

        >>> {
        >>> "primarylattice": "MyFirstLattice",
        >>> "secondarylattice": "MySecondLattice",
        >>> "useairobjects": true,
        >>> "rowdimension": 4,
        >>> "columndimension": 4,
        >>> "visible": true,
        >>> "showcellnumber": true,
        >>> "paddingcells": 0,
        >>> "referencecsid": 1,
        >>> "MyFirstCell": "path/to/firstcell.a3dcomp", # optional to insert 3d comp
        >>> "MySecondCell": "path/to/secondcell.a3dcomp",# optional to insert 3d comp
        >>> "MyThirdCell": "path/to/thirdcell.a3dcomp",  # optional to insert 3d comp
        >>> "cells": { "(1,1)": {
        >>>            "name" : "MyFirstCell",
        >>>            "color" : "(255,0,20)", #optional
        >>>            "active" : true, #optional
        >>>            "postprocessing" : true #optional
        >>>            "rotation" : 0.0  #optional
        >>>             },
        >>>            "(1,2)": {
        >>>            "name" : "MySecondCell",
        >>>            "rotation" : 90.0
        >>>             }
        >>>             # continue
        >>> }

        >>> from pyaedt import Hfss
        >>> from pyaedt.generic.general_methods import read_configuration_file
        >>> hfss_app = Hfss()
        >>> dict_in = read_configuration_file(path\to\json_file)
        >>> component_array = hfss_app.add_3d_component_array_from_json(dict_in)
        """
        self.hybrid = True
        if isinstance(json_file, dict):
            json_dict = json_file
        else:
            json_dict = read_configuration_file(json_file)
        if not array_name and self.omodelsetup.IsArrayDefined():
            array_name = self.omodelsetup.GetArrayNames()[0]
        elif not array_name:
            array_name = generate_unique_name("Array")

        cells_names = {}
        cells_color = {}
        cells_active = []
        cells_rotation = {}
        cells_post = {}
        for k, v in json_dict["cells"].items():
            if isinstance(k, (list, tuple)):
                k1 = str(list(k))
            else:
                k1 = str(list(ast.literal_eval(k)))
            if v["name"] in cells_names:
                cells_names[v["name"]].append(k1)
            else:
                def_names = self.oeditor.Get3DComponentDefinitionNames()
                if v["name"] not in def_names and v["name"][:-1] not in def_names and v["name"][:-2] not in def_names:
                    if v["name"] not in json_dict:
                        self.logger.error(
                            "3D component array is not present in design and not defined correctly in the JSON file."
                        )
                        return False

                    geometryparams = self.get_components3d_vars(json_dict[v["name"]])

                    self.modeler.insert_3d_component(json_dict[v["name"]], geometryparams)
                cells_names[v["name"]] = [k1]
            if v.get("color", None):
                cells_color[v["name"]] = v.get("color", None)
            if str(v.get("rotation", "0.0")) in cells_rotation:
                cells_rotation[str(v.get("rotation", "0.0"))].append(k1)
            else:
                cells_rotation[str(v.get("rotation", "0.0"))] = [k1]
            if v.get("active", True) in cells_active:
                cells_active.append(k1)

            if v.get("postprocessing", False):
                cells_post[v["name"]] = k1

        primary_lattice = json_dict.get("primarylattice", None)
        secondary_lattice = json_dict.get("secondarylattice", None)
        if not primary_lattice:
            primary_lattice = self.omodelsetup.GetLatticeVectors()[0]
        if not secondary_lattice:
            secondary_lattice = self.omodelsetup.GetLatticeVectors()[1]

        args = [
            "NAME:" + array_name,
            "Name:=",
            array_name,
            "UseAirObjects:=",
            json_dict.get("useairobjects", True),
            "RowPrimaryBnd:=",
            primary_lattice,
            "ColumnPrimaryBnd:=",
            secondary_lattice,
            "RowDimension:=",
            json_dict.get("rowdimension", 4),
            "ColumnDimension:=",
            json_dict.get("columndimension", 4),
            "Visible:=",
            json_dict.get("visible", True),
            "ShowCellNumber:=",
            json_dict.get("showcellnumber", True),
            "RenderType:=",
            0,
            "Padding:=",
            json_dict.get("paddingcells", 0),
            "ReferenceCSID:=",
            json_dict.get("referencecsid", 1),
        ]

        cells = ["NAME:Cells"]
        for k, v in cells_names.items():
            cells.append(k + ":=")
            cells.append([", ".join(v)])
        rotations = ["NAME:Rotation"]
        for k, v in cells_rotation.items():
            if float(k) != 0.0:
                rotations.append(k + " deg:=")
                rotations.append([", ".join(v)])
        args.append(cells)
        args.append(rotations)
        args.append("Active:=")
        if cells_active:
            args.append(", ".join(cells_active))
        else:
            args.append("All")
        post = ["NAME:PostProcessingCells"]
        for k, v in cells_post.items():
            post.append(k + ":=")
            post.append(str(ast.literal_eval(v)))
        args.append(post)
        args.append("Colors:=")
        col = []
        for k, v in cells_color.items():
            col.append(k)
            col.append(str(v).replace(",", " "))
        args.append(col)
        if self.omodelsetup.IsArrayDefined():
            self.omodelsetup.EditArray(args)
            if settings.aedt_version < "2024.1":
                self.save_project()
        else:
            self.omodelsetup.AssignArray(args)
            if settings.aedt_version < "2024.1":
                self.save_project()
            self.component_array[array_name] = ComponentArray(self, array_name)
        self.component_array_names = [array_name]
        return self.component_array[array_name]

    @pyaedt_function_handler()
    def get_antenna_ffd_solution_data(
        self,
        frequencies,
        setup_name=None,
        sphere_name=None,
        variations=None,
        overwrite=True,
    ):
        """Export antennas parameters to Far Field Data (FFD) files and return the ``FfdSolutionDataExporter`` object.

        For phased array cases, only one phased array is calculated.

        Parameters
        ----------
        frequencies : float, list
            Frequency value or list of frequencies to compute far field data.
        setup_name : str, optional
            Name of the setup to use. The default is ``None,`` in which case ``nominal_adaptive`` is used.
        sphere_name : str, optional
            Infinite sphere to use. The default is ``None``, in which case an existing sphere is used or a new
            one is created.
        variations : dict, optional
            Variation dictionary.
        overwrite : bool, optional
            Whether to overwrite FFD files. The default is ``True``.

        Returns
        -------
        :class:`pyaedt.modules.solutions.FfdSolutionDataExporter`
            SolutionData object.
        """
        from pyaedt.modules.solutions import FfdSolutionDataExporter

        if not variations:
            variations = self.available_variations.nominal_w_values_dict_w_dependent
        if not setup_name:
            setup_name = self.nominal_adaptive
        if sphere_name:
            names = [i.name for i in self.field_setups]
            if sphere_name in names:
                self.logger.info("Far field sphere %s is assigned", sphere_name)

            else:
                self.insert_infinite_sphere(
                    x_start=0, x_stop=180, x_step=5, y_start=-180, y_stop=180, y_step=5, name=sphere_name
                )
                self.logger.info("Far field sphere %s is created.", sphere_name)
        elif self.field_setups:
            sphere_name = self.field_setups[0].name
            self.logger.info("No far field sphere is defined. Using %s", sphere_name)
        else:
            sphere_name = "Infinite Sphere1"
            self.insert_infinite_sphere(
                x_start=0, x_stop=180, x_step=5, y_start=-180, y_stop=180, y_step=5, name=sphere_name
            )
            self.logger.info("Far field sphere %s is created.", setup_name)

        return FfdSolutionDataExporter(
            self,
            sphere_name=sphere_name,
            setup_name=setup_name,
            frequencies=frequencies,
            variations=variations,
            overwrite=overwrite,
        )

    @pyaedt_function_handler()
    def set_material_threshold(self, threshold=100000):
        """Set the material conductivity threshold.

        Parameters
        ----------
        threshold : float, optional
            Conductivity threshold. The default value is ``100000``.

        Returns
        -------
        bool
            ``True`` when successful, ``False`` when failed.
        """
        try:
            self.odesign.SetSolveInsideThreshold(threshold)
            return True
        except:
            return False

    @pyaedt_function_handler()
    def assign_symmetry(self, entity_list, symmetry_name=None, is_perfect_e=True):
        """Assign symmetry to planar entities.

        Parameters
        ----------
        entity_list : list
            List of IDs or :class:`pyaedt.modeler.Object3d.FacePrimitive`.
        symmetry_name : str, optional
            Name of the boundary.
            If not provided it's automatically generated.
        is_perfect_e : bool, optional
            Type of symmetry plane the boundary represents: Perfect E or Perfect H.
            The default value is ``True`` (Perfect E).

        Returns
        -------
        :class:`pyaedt.modules.Boundary.BoundaryObject`
            Boundary object.

        References
        ----------

        >>> oModule.AssignSymmetry

        Examples
        --------

        Create a box. Select the faces of this box and assign a symmetry.

        >>> symmetry_box = hfss.modeler.create_box([0 , -100, 0], [200, 200, 200],
        ...                                         name="SymmetryForFaces")
        >>> ids = [i.id for i in hfss.modeler["SymmetryForFaces"].faces]
        >>> symmetry = hfss.assign_symmetry(ids)
        >>> type(symmetry)
        <class 'pyaedt.modules.Boundary.BoundaryObject'>

        """
        try:
            if self.solution_type not in ["Modal", "Eigenmode"]:
                self.logger.error("Symmetry is only available with 'Modal' and 'Eigenmode' solution types.")
                return False

            if symmetry_name is None:
                symmetry_name = generate_unique_name("Symmetry")

            if not isinstance(entity_list, list):
                self.logger.error("Entities have to be provided as a list.")
                return False

            entity_list = self.modeler.convert_to_selections(entity_list, True)

            props = OrderedDict({"Name": symmetry_name, "Faces": entity_list, "IsPerfectE": is_perfect_e})
            return self._create_boundary(symmetry_name, props, "Symmetry")
        except:
            return False

    @pyaedt_function_handler()
    def set_impedance_multiplier(self, multiplier):
        # type: (float) -> bool
        """Set impedance multiplier.

        Parameters
        ----------
        multiplier : float
            Impedance Multiplier.

        Returns
        -------
        bool
            ``True`` when successful, ``False`` when failed.

        References
        ----------

        >>> oModule.ChangeImpedanceMult

        Examples
        --------

        Create a box. Select the faces of this box and assign a symmetry.

        >>> symmetry_box = hfss.modeler.create_box([0 , -100, 0], [200, 200, 200],
        ...                                         name="SymmetryForFaces")
        >>> ids = [i.id for i in hfss.modeler["SymmetryForFaces"].faces]
        >>> symmetry = hfss.assign_symmetry(ids)
        >>> hfss.set_impedance_multiplier(2.0)

        """
        try:
            if self.solution_type not in ["Modal"]:
                self.logger.error("Symmetry is only available with 'Modal' solution type.")
                return False
            self.oboundary.ChangeImpedanceMult(multiplier)
            return True
        except:
            return False

    @pyaedt_function_handler()
    def set_phase_center_per_port(self, coordinate_system=None):
        # type: (list) -> bool
        """Set phase center per port.

        Parameters
        ----------
        coordinate_system : list
            List of the coordinate system per port. The default is ``None``, in which case the
            default port location is assigned.

        Returns
        -------
        bool
            ``True`` when successful, ``False`` when failed.

        References
        ----------

        >>> oModule.SetPhaseCenterPerPort

        Examples
        --------

        Set phase center of an antenna with two ports.

        >>> hfss.set_phase_center_per_port(["Global", "Global"])

        """

        if not self.desktop_class.is_grpc_api:  # pragma: no cover
            self.logger.warning("Set phase center is not supported by AEDT COM API. Set phase center manually.")
            return False

        port_names = []
        for exc in self.design_excitations:
            port_names.append(exc.name)

        if not port_names:  # pragma: no cover
            return False

        if not coordinate_system:
            coordinate_system = ["<-Port Location->"] * len(port_names)
        elif not isinstance(coordinate_system, list):
            return False
        elif len(coordinate_system) != len(port_names):
            return False

        cont = 0
        arg = []
        for port in port_names:
            arg.append(["NAME:" + port, "Coordinate System:=", coordinate_system[cont]])
            cont += 1

        try:
            self.oboundary.SetPhaseCenterPerPort(arg)
        except:
            return False
        return True

    @pyaedt_function_handler()
    def get_touchstone_data(self, setup_name, sweep_name=None, variation_dict=None):
        """
        Return a Touchstone data plot.

        Parameters
        ----------
        setup_name : list
            List of the curves to plot.
        sweep_name : str, optional
            Name of the solution. The default value is ``None``.
        variation_dict : dict, optional
            Dictionary of variation names. The default value is ``None``.

        Returns
        -------
        :class:`pyaedt.generic.touchstone_parser.TouchstoneData`
           Class containing all requested data.

        References
        ----------

        >>> oModule.GetSolutionDataPerVariation
        """
        from pyaedt.generic.touchstone_parser import TouchstoneData

        if not setup_name:
            setup_name = self.setups[0].name

        if not sweep_name:
            for setup in self.setups:
                if setup.name == setup_name:
                    sweep_name = setup.sweeps[0].name
        s_parameters = []
        solution = "{} : {}".format(setup_name, sweep_name)
        expression = self.get_traces_for_plot(category="S")
        sol_data = self.post.get_solution_data(expression, solution, variations=variation_dict)
        for i in range(sol_data.number_of_variations):
            sol_data.set_active_variation(i)
            s_parameters.append(TouchstoneData(solution_data=sol_data))
        return s_parameters

    @pyaedt_function_handler()
    def parse_hdm_file(self, filename):
        """Parse an HFSS SBR+ or Creeping Waves ``hdm`` file.

        Parameters
        ----------
        filename : str
            File to parse.

        Returns
        -------
        :class:`pyaedt.modules.hdm_parser.Parser`
        """

        from pyaedt.sbrplus.hdm_parser import Parser

        if os.path.exists(filename):
            return Parser(filename).parse_message()
        return False

    @pyaedt_function_handler()
    def get_hdm_plotter(self, filename=None):
        """Get the ``HDMPlotter``.

        Parameters
        ----------
        filename : str, optional


        Returns
        -------
        :class:`pyaedt.sbrplus.plot.HDMPlotter`

        """
        from pyaedt.sbrplus.plot import HDMPlotter

        hdm = HDMPlotter()
        files = self.post.export_model_obj(
            export_as_single_objects=True,
            air_objects=False,
        )
        for file in files:
            hdm.add_cad_model(file[0], file[1], file[2], self.modeler.model_units)
        hdm.add_hdm_bundle_from_file(filename)
        return hdm

    @pyaedt_function_handler()
    def circuit_port(
        self,
        signal,
        reference,
        port_location=0,
        impedance=50,
        name=None,
        renormalize=True,
        renorm_impedance=50,
        deembed=False,
    ):
        """Create a circuit port from two objects.

        The integration line is from edge 2 to edge 1.

        Parameters
        ----------
        signal : int or :class:`pyaedt.modeler.cad.object3d.Object3d` or
         :class:`pyaedt.modeler.cad.FacePrimitive`or :class:`pyaedt.modeler.cad.EdgePrimitive`
            Signal object.
        reference : int or :class:`pyaedt.modeler.cad.object3d.Object3d` or
         :class:`pyaedt.modeler.cad.FacePrimitive`or :class:`pyaedt.modeler.cad.EdgePrimitive`
            Reference object.
        port_location : int or :class:`pyaedt.application.Analysis.Analysis.AxisDir`, optional
            Position of the port when an object different from an edge is provided.
            It should be one of the values for ``Application.AxisDir``,
            which are: ``XNeg``, ``YNeg``, ``ZNeg``, ``XPos``, ``YPos``, and ``ZPos``.
            The default is ``Application.AxisDir.XNeg``.
        name : str, optional
            Name of the port. The default is ``""``.
        impedance : int, str, or float, optional
            Impedance. The default is ``"50"``. You can also
            enter a string that looks like this: ``"50+1i*55"``.
        renormalize : bool, optional
            Whether to renormalize the mode. The default is ``False``.
            This parameter is ignored for a driven terminal.
        renorm_impedance :  str, optional
            Impedance. The default is ``50``.
        deembed : bool, optional
            Whether to deembed the port. The default is ``False``.

        Returns
        -------
        :class:`pyaedt.modules.Boundary.BoundaryObject`
            Boundary object.

        References
        ----------

        >>> oModule.AssignCircuitPort

        Examples
        --------

        Create two rectangles in the XY plane.
        Select the first edge of each rectangle created previously.
        Create a circuit port from the first edge of the first rectangle
        toward the first edge of the second rectangle.

        >>> plane = hfss.PLANE.XY
        >>> rectangle1 = hfss.modeler.create_rectangle(plane, [10, 10, 10], [10, 10],
        ...                                            name="rectangle1_for_port")
        >>> edges1 = hfss.modeler.get_object_edges(rectangle1.id)
        >>> first_edge = edges1[0]
        >>> rectangle2 = hfss.modeler.create_rectangle(plane, [30, 10, 10], [10, 10],
        ...                                            name="rectangle2_for_port")
        >>> edges2 = hfss.modeler.get_object_edges(rectangle2.id)
        >>> second_edge = edges2[0]
        >>> hfss.solution_type = "Modal"
        >>> hfss.circuit_port(first_edge, second_edge, name="PortExample",
        ...                   impedance=50.1, renormalize=False,
        ...                   renorm_impedance="50")
        'PortExample'
        """
        if self.solution_type in ["Modal", "Terminal", "Transient Network"]:
            if not self.modeler.does_object_exists(signal) or not self.modeler.does_object_exists(reference):
                out = self.modeler.convert_to_selections([signal, reference], True)
                if isinstance(out[0], str) or isinstance(out[1], str):
                    self.logger.error("Failed to create circuit port.")
                    return False
            else:
                out, parallel = self.modeler.find_closest_edges(signal, reference, port_location)
            name = self._get_unique_source_name(name, "Port")
            return self._create_circuit_port(
                out, impedance, name, renormalize, deembed, renorm_impedance=renorm_impedance
            )
        return False

    @pyaedt_function_handler()
    def lumped_port(
        self,
        signal,
        reference=None,
        create_port_sheet=False,
        port_on_plane=True,
        integration_line=0,
        impedance=50,
        name=None,
        renormalize=True,
        deembed=False,
        terminals_rename=True,
    ):
        """Create a waveport taking the closest edges of two objects.

        Parameters
        ----------
        signal : str, int, list, :class:`pyaedt.modeler.cad.object3d.Object3d` or
            :class:`pyaedt.modeler.elements3d.FacePrimitive`
            Main object for port creation or starting object for the integration line.
        reference : int, list or :class:`pyaedt.modeler.cad.object3d.Object3d`
            Ending object for the integration line or reference for Terminal solution. Can be multiple objects.
        create_port_sheet : bool, optional
            Whether to create a port sheet or use given start_object as port sheet.
        integration_line : int or :class:`pyaedt.application.Analysis.Analysis.AxisDir`, optional
            Position of the port. It should be one of the values for ``Application.AxisDir``,
            which are: ``XNeg``, ``YNeg``, ``ZNeg``, ``XPos``, ``YPos``, and ``ZPos``.
            The default is ``Application.AxisDir.XNeg``.
            It can also be a list of 2 points.
        port_on_plane : bool, optional
            Whether to create the source on the plane orthogonal to ``AxisDir``.
            The default is ``True``.
        impedance : float, optional
            Port impedance. The default is ``50``.
        name : str, optional
            Name of the port. The default is ``None``.
        renormalize : bool, optional
            Whether to renormalize the mode. The default is ``True``.
        deembed : float, optional
            Deembed distance in millimeters. The default is ``0``, in which case deembed is disabled.
        terminals_rename : bool, optional
            Modify terminals name with the port name plus the terminal number. The default value is ``True``.

        Returns
        -------
        :class:`pyaedt.modules.Boundary.BoundaryObject`
            Port object.

        Examples
        --------

        Create two boxes that will be used to create a lumped port
        named ``'LumpedPort'``.

        >>> box1 = hfss.modeler.create_box([0, 0, 50], [10, 10, 5],
        ...                                "BoxLumped1","copper")
        >>> box2 = hfss.modeler.create_box([0, 0, 60], [10, 10, 5],
        ...                                "BoxLumped2", "copper")
        >>> hfss.lumped_port("BoxLumped1", "BoxLumped2",
        ...                  hfss.AxisDir.XNeg, 50,
        ...                  "LumpedPort", True, False)
        PyAEDT INFO: Connection Correctly created
        'LumpedPort'

        """
        if create_port_sheet:
            signal = self.modeler.convert_to_selections(signal)
            reference = self.modeler.convert_to_selections(reference)
            if not self.modeler.does_object_exists(signal) or not self.modeler.does_object_exists(reference):
                self.logger.error("One or both objects do not exist. Check and retry.")
                return False
            sheet_name, point0, point1 = self.modeler._create_sheet_from_object_closest_edge(
                signal, reference, integration_line, port_on_plane
            )
        else:
            if isinstance(signal, list):
                objs = self.modeler.get_faceid_from_position(signal)
                if len(objs) == 1:
                    signal = objs[0]
                elif len(objs) > 1:
                    self.logger.warning("More than 1 face found. Getting first.")
                    signal = objs[0]
                else:
                    self.logger.error("No Faces found on given location.")
                    return False
            sheet_name = self.modeler.convert_to_selections(signal, False)
            if isinstance(integration_line, list):
                if len(integration_line) != 2 or len(integration_line[0]) != len(integration_line[1]):
                    self.logger.error("List of coordinates is not set correctly")
                    return False
                point0 = integration_line[0]
                point1 = integration_line[1]
            else:
                point0, point1 = self.modeler.get_mid_points_on_dir(sheet_name, integration_line)
        if self.solution_type in ["Modal", "Terminal", "Transient Network"]:
            name = self._get_unique_source_name(name, "Port")

            if "Modal" in self.solution_type:
                return self._create_lumped_driven(sheet_name, point0, point1, impedance, name, renormalize, deembed)
            else:
                faces = self.modeler.get_object_faces(sheet_name)
                if deembed:
                    deembed = 0
                else:
                    deembed = None
                return self._create_port_terminal(
                    faces[0],
                    reference,
                    name,
                    renorm=renormalize,
                    deembed=deembed,
                    iswaveport=False,
                    impedance=impedance,
                    terminals_rename=terminals_rename,
                )
        return False

    @pyaedt_function_handler()
    def wave_port(
        self,
        signal,
        reference=None,
        create_port_sheet=False,
        create_pec_cap=False,
        integration_line=0,
        port_on_plane=True,
        num_modes=1,
        impedance=50,
        name=None,
        renormalize=True,
        deembed=0,
        is_microstrip=False,
        vfactor=3,
        hfactor=5,
        terminals_rename=True,
    ):
        """Create a waveport from a sheet (``start_object``) or taking the closest edges of two objects.

        Parameters
        ----------
        signal : int, str, :class:`pyaedt.modeler.cad.object3d.Object3d` or
         :class:`pyaedt.modeler.elements3d.FacePrimitive`
            Main object for port creation or starting object for the integration line.
        reference : int, str, list or :class:`pyaedt.modeler.cad.object3d.Object3d`
            Ending object for the integration line or reference for Terminal solution. Can be multiple objects.
        create_port_sheet : bool, optional
            Whether to create a port sheet or use the start object as the surface
            to create the port. The default is ``False``.
        create_pec_cap : bool, False
            Whether to create a port cap. The default is ``False``.
        integration_line : list or int or :class:`pyaedt.application.Analysis.Analysis.AxisDir`, optional
            Position of the integration. It should be one of the values for ``Application.AxisDir``,
            which are: ``XNeg``, ``YNeg``, ``ZNeg``, ``XPos``, ``YPos``, and ``ZPos``
            The default is ``Application.AxisDir.XNeg``.
            It can also be a list of 2 points.
        port_on_plane : bool, optional
            Whether to create the source on the plane orthogonal to ``AxisDir``.
            The default is ``True``.
        impedance : float, optional
            Port impedance. The default is ``50``.
        num_modes : int, optional
            Number of modes. The default is ``1``.
        name : str, optional
            name of the port. The default is ``None``.
        renormalize : bool, optional
            Whether to renormalize the mode. The default is ``True``.
        deembed : float, optional
            Deembed distance in millimeters. The default is ``0``.
        is_microstrip : bool, optional
            Whether if the wave port will be created and is a microstrip port.
            The default is ``False``.
        vfactor : int, optional
            Port vertical factor. Only valid if ``is_microstrip`` is enabled. The default is ``3``.
        hfactor : int, optional
            Port horizontal factor. Only valid if ``is_microstrip`` is enabled. The default is ``5``.
        terminals_rename : bool, optional
            Modify terminals name with the port name plus the terminal number. The default is ``True``.

        Returns
        -------
        :class:`pyaedt.modules.Boundary.BoundaryObject`
            Port object.

        References
        ----------

        >>> oModule.AssignWavePort

        Examples
        --------

        Create a wave port supported by a microstrip line.

        >>> ms = hfss.modeler.create_box([4, 5, 0], [1, 100, 0.2],
        ...                               name="MS1", matname="copper")
        >>> sub = hfss.modeler.create_box([0, 5, -2], [20, 100, 2],
        ...                               name="SUB1", matname="FR4_epoxy")
        >>> gnd = hfss.modeler.create_box([0, 5, -2.2], [20, 100, 0.2],
        ...                               name="GND1", matname="FR4_epoxy")
        >>> port = hfss.wave_port("GND1", "MS1",
        ...                       name="MS1",
        ...                        integration_line=1)
        PyAEDT INFO: Connection correctly created.

        """
        oname = ""

        if create_port_sheet:
            if not self.modeler.does_object_exists(signal) or not self.modeler.does_object_exists(reference):
                self.logger.error("One or both objects do not exist. Check and retry.")
                return False
            elif isinstance(signal, cad.elements3d.FacePrimitive):
                port_sheet = signal.create_object()
                oname = port_sheet.name
            if is_microstrip:
                sheet_name, int_start, int_stop = self.modeler._create_microstrip_sheet_from_object_closest_edge(
                    signal, reference, integration_line, vfactor, hfactor
                )
            else:
                sheet_name, int_start, int_stop = self.modeler._create_sheet_from_object_closest_edge(
                    signal, reference, integration_line, port_on_plane
                )
        else:
            if isinstance(signal, list):
                objs = self.modeler.get_faceid_from_position(signal)
                if len(objs) == 1:
                    signal = objs[0]
                elif len(objs) > 1:
                    self.logger.warning("More than 1 face found. Getting first.")
                    signal = objs[0]
                else:
                    self.logger.error("No Faces found on given location.")
                    return False
            sheet_name = self.modeler.convert_to_selections(signal, True)[0]
            if isinstance(sheet_name, int):
                try:
                    # NOte: if isinstance(sheet_name, cad.elements3d.FacePrimitive) then
                    # the name of the 3d object is returned.
                    # TODO: Need to improve the way a FacePrimitive is handled.
                    oname = self.modeler.oeditor.GetObjectNameByFaceID(sheet_name)
                except:
                    oname = ""
            if reference:
                reference = self.modeler.convert_to_selections(reference, True)
            #  TODO: integration_line == self.aedtapp.AxisDir.XNeg will be False in next line. Fix this.
            if integration_line:
                if isinstance(integration_line, list):
                    if len(integration_line) != 2 or len(integration_line[0]) != len(integration_line[1]):
                        self.logger.error("List of coordinates is not set correctly")
                        return False
                    int_start = integration_line[0]
                    int_stop = integration_line[1]
                else:
                    # Get two points on the port surface: if only the direction is given.
                    # int_start and int_stop.
                    try:
                        _, int_start, int_stop = self._get_reference_and_integration_points(
                            sheet_name, integration_line, oname
                        )
                    except (IndexError, TypeError):
                        int_start = int_stop = None
            else:
                int_start = int_stop = None
        if self.solution_type in ["Modal", "Terminal", "Transient Network"]:
            if create_pec_cap:
                if oname:
                    #  if isinstance(signal, cad.elements3d.FacePrimitive):
                    #      pec_face = signal.create_object()
                    #      face = pec_face.id
                    #  else:
                    face = oname
                else:
                    face = sheet_name
                dist = math.sqrt(self.modeler[face].faces[0].area)  # TODO: Move this into _create_pec_cap
                if settings.aedt_version > "2022.2":
                    self._create_pec_cap(face, signal, -dist / 10)
                else:
                    self._create_pec_cap(face, signal, dist / 10)
            name = self._get_unique_source_name(name, "Port")

            if "Modal" in self.solution_type:
                return self._create_waveport_driven(
                    sheet_name, int_start, int_stop, impedance, name, renormalize, num_modes, deembed
                )
            elif reference:
                if isinstance(sheet_name, int):
                    faces = [sheet_name]
                else:
                    faces = self.modeler.get_object_faces(sheet_name)
                if deembed == 0:
                    deembed = None
                else:
                    deembed = deembed

                # Draw terminal lumped port between two objects.
                return self._create_port_terminal(
                    faces[0],
                    reference,
                    name,
                    renorm=renormalize,
                    deembed=deembed,
                    iswaveport=True,
                    impedance=impedance,
                    terminals_rename=terminals_rename,
                )
            else:
                self.logger.error("Reference conductors are missing.")
                return False
        return False

    @pyaedt_function_handler()
    def set_radiated_power_calc_method(self, method="Auto"):
        """Set the radiated power calculation method in Hfss.

        method : str, optional
            Radiated power calculation method.
            The options are ``"Auto"``, ``"Radiation Surface Integral"`` and ``"Far Field Integral"``.

        Returns
        -------
        bool
            ``True`` when successful, ``False`` when failed.
        """
        self.oradfield.EditRadiatedPowerCalculationMethod(method)
        return True

    @pyaedt_function_handler()
    def set_mesh_fusion_settings(self, component=None, volume_padding=None, priority=None):
        # type: (list|str, list, list) -> bool

        """Set mesh fusion settings in Hfss.

        component : list, optional
            List of active 3D Components.
            The default is ``None``, in which case components are disabled.
        volume_padding : list, optional
            List of mesh envelope padding, the format is ``[+x, -x, +y, -y, +z, -z]``.
            The default is ``None``, in which case all zeros are applied.
        priority : list, optional
            List of components with the priority flag enabled. The default is ``None``.

        Returns
        -------
        bool
            ``True`` when successful, ``False`` when failed.

        References
        ----------

        >>> oDesign.SetDoMeshAssembly

        Examples
        --------

        >>> import pyaedt
        >>> app = pyaedt.Hfss()
        >>> app.set_mesh_fusion_settings(component=["Comp1", "Comp2"],
         ... volume_padding=[[0,0,0,0,0,0], [0,0,5,0,0,0]],
         ... priority=["Comp1"])
        """
        arg = ["NAME:AllSettings"]
        arg2 = ["NAME:MeshAssembly"]
        arg3 = ["NAME:Priority Components"]

        if component and not isinstance(component, list):
            component = [component]

        if not volume_padding and component:
            for comp in component:
                if comp in self.modeler.user_defined_component_names:
                    mesh_assembly_arg = ["NAME:" + comp]
                    mesh_assembly_arg.append("MeshAssemblyBoundingVolumePadding:=")
                    mesh_assembly_arg.append(["0", "0", "0", "0", "0", "0"])
                    arg2.append(mesh_assembly_arg)
                else:
                    self.logger.warning(comp + " does not exist.")

        elif component and isinstance(volume_padding, list) and len(volume_padding) == len(component):
            count = 0
            for comp in component:
                padding = [str(pad) for pad in volume_padding[count]]
                if comp in self.modeler.user_defined_component_names:
                    mesh_assembly_arg = ["NAME:" + comp]
                    mesh_assembly_arg.append("MeshAssemblyBoundingVolumePadding:=")
                    mesh_assembly_arg.append(padding)
                    arg2.append(mesh_assembly_arg)
                else:
                    self.logger.warning("{0} does not exist".format(str(comp)))
                count += 1
        elif component and isinstance(volume_padding, list) and len(volume_padding) != len(component):
            self.logger.error("Volume padding length is different than component list length.")
            return False

        if priority and not isinstance(priority, list):
            priority = [priority]

        if component and priority:
            for p in priority:
                if p in self.modeler.user_defined_component_names:
                    arg3.append(p)
                else:
                    self.logger.warning("{0} does not exist".format(str(p)))

        arg.append(arg2)
        arg.append(arg3)
        self.odesign.SetDoMeshAssembly(arg)
        return True
