# -*- coding: utf-8 -*-
#
# Copyright (C) 2023 - 2026 ANSYS, Inc. and/or its affiliates.
# SPDX-License-Identifier: MIT
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from collections import OrderedDict
import math
import ansys.aedt.core.generic.constants as constants
from ansys.aedt.core.generic.general_methods import pyaedt_function_handler
from ansys.aedt.toolkits.common.backend.logger_handler import logger
from ansys.aedt.toolkits.antenna.backend.antenna_models.common import CommonAntenna
from ansys.aedt.toolkits.antenna.backend.antenna_models.common import TransmissionLine
from ansys.aedt.toolkits.antenna.backend.antenna_models.common import properties
class CommonPatch(CommonAntenna):
"""Provides base methods common to patch antenna."""
def __init__(self, _default_input_parameters, *args, **kwargs):
default_input_parameters = properties.antenna.synthesis.model_dump()
default_input_parameters.update(_default_input_parameters)
CommonAntenna.antenna_type = "Patch"
CommonAntenna.__init__(self, default_input_parameters, *args, **kwargs)
if "substrate_height" not in kwargs:
self.substrate_height = constants.unit_converter(
self.substrate_height, "Length", default_input_parameters["length_unit"], self.length_unit
)
self._transmission_line_calculator = TransmissionLine(self.frequency, self.frequency_unit)
@property
def material(self):
"""Substrate material.
Returns
-------
str
"""
return self._input_parameters.material
@material.setter
def material(self, value):
if self._app:
if (
value
and value not in self._app.materials.mat_names_aedt
and value not in self._app.materials.mat_names_aedt_lower
):
logger.debug("Material not defined")
else:
if value != self.material and self.object_list:
for antenna_obj in self.object_list:
if (
self.object_list[antenna_obj].material_name == self.material.lower()
and "coax" not in antenna_obj
):
self.object_list[antenna_obj].material_name = value
self._input_parameters.material = value
parameters = self.synthesis()
self.update_synthesis_parameters(parameters)
self.set_variables_in_hfss()
else:
self._input_parameters.material = value
@property
def material_properties(self):
"""Substrate material properties.
Returns
-------
str
"""
return self._input_parameters.material_properties
@property
def substrate_height(self):
"""Substrate height.
Returns
-------
float
"""
return self._input_parameters.substrate_height
@substrate_height.setter
def substrate_height(self, value):
self._input_parameters.substrate_height = value
if self.object_list:
parameters = self.synthesis()
self.update_synthesis_parameters(parameters)
self.set_variables_in_hfss()
def _set_patch_property(self, parameter_name, value, requires_remodel=False):
self._input_parameters.__setattr__(parameter_name, value)
if self.object_list:
if requires_remodel:
logger.warning("%s updates require remodelling the antenna.", parameter_name)
return
parameters = self.synthesis()
self.update_synthesis_parameters(parameters)
self.set_variables_in_hfss()
@property
def number_of_patches_x(self):
return self._input_parameters.number_of_patches_x
@number_of_patches_x.setter
def number_of_patches_x(self, value):
self._set_patch_property("number_of_patches_x", value, requires_remodel=True)
@property
def number_of_patches_y(self):
return self._input_parameters.number_of_patches_y
@number_of_patches_y.setter
def number_of_patches_y(self, value):
self._set_patch_property("number_of_patches_y", value, requires_remodel=True)
@property
def feed_rotation_angle(self):
return self._input_parameters.feed_rotation_angle
@feed_rotation_angle.setter
def feed_rotation_angle(self, value):
self._set_patch_property("feed_rotation_angle", value, requires_remodel=True)
@property
def element_1_rotation_angle(self):
return self._input_parameters.element_1_rotation_angle
@element_1_rotation_angle.setter
def element_1_rotation_angle(self, value):
self._set_patch_property("element_1_rotation_angle", value, requires_remodel=True)
@property
def element_2_rotation_angle(self):
return self._input_parameters.element_2_rotation_angle
@element_2_rotation_angle.setter
def element_2_rotation_angle(self, value):
self._set_patch_property("element_2_rotation_angle", value, requires_remodel=True)
@property
def element_3_rotation_angle(self):
return self._input_parameters.element_3_rotation_angle
@element_3_rotation_angle.setter
def element_3_rotation_angle(self, value):
self._set_patch_property("element_3_rotation_angle", value, requires_remodel=True)
@property
def element_4_rotation_angle(self):
return self._input_parameters.element_4_rotation_angle
@element_4_rotation_angle.setter
def element_4_rotation_angle(self, value):
self._set_patch_property("element_4_rotation_angle", value, requires_remodel=True)
@property
def element_1_port_phase(self):
return self._input_parameters.element_1_port_phase
@element_1_port_phase.setter
def element_1_port_phase(self, value):
self._set_patch_property("element_1_port_phase", value)
@property
def element_2_port_phase(self):
return self._input_parameters.element_2_port_phase
@element_2_port_phase.setter
def element_2_port_phase(self, value):
self._set_patch_property("element_2_port_phase", value)
@property
def element_3_port_phase(self):
return self._input_parameters.element_3_port_phase
@element_3_port_phase.setter
def element_3_port_phase(self, value):
self._set_patch_property("element_3_port_phase", value)
@property
def element_4_port_phase(self):
return self._input_parameters.element_4_port_phase
@element_4_port_phase.setter
def element_4_port_phase(self, value):
self._set_patch_property("element_4_port_phase", value)
def _material_permittivity(self):
if self._app and (
self.material in self._app.materials.mat_names_aedt
or self.material in self._app.materials.mat_names_aedt_lower
):
mat_props = self._app.materials[self.material]
permittivity = mat_props.permittivity.value
self._input_parameters.material_properties["permittivity"] = permittivity
return permittivity
if self.material_properties:
return self.material_properties["permittivity"]
if self._app:
self._app.logger.warning("Material is not found. Create the material before assigning it.")
return None
def _patch_synthesis_base(self):
freq_hz = constants.unit_converter(self.frequency, "Freq", self.frequency_unit, "Hz")
wavelength = constants.SpeedOfLight / freq_hz
permittivity = self._material_permittivity()
if permittivity is None:
return {}
sub_permittivity = float(permittivity)
sub_meters = constants.unit_converter(self.substrate_height, "Length", self.length_unit, "meter")
patch_width = 3.0e8 / ((2.0 * freq_hz) * math.sqrt((sub_permittivity + 1.0) / 2.0))
eff_permittivity = (sub_permittivity + 1.0) / 2.0 + (sub_permittivity - 1.0) / 2.0 * math.pow(
1.0 + 12.0 * sub_meters / patch_width, -0.5
)
effective_length = 3.0e8 / (2.0 * freq_hz * math.sqrt(eff_permittivity))
top = (eff_permittivity + 0.3) * (patch_width / sub_meters + 0.264)
bottom = (eff_permittivity - 0.258) * (patch_width / sub_meters + 0.8)
delta_length = 0.412 * sub_meters * top / bottom
patch_length = effective_length - 2.0 * delta_length
k = 2.0 * math.pi / eff_permittivity
g = math.pi * patch_width / (120.0 * math.pi * wavelength) * (1.0 - math.pow(k * sub_meters, 2) / 24)
res = 1.0 / (2.0 * g)
offset_pin_pos = patch_length / math.pi * math.asin(math.sqrt(50.0 / res))
return {
"freq_hz": freq_hz,
"patch_width": patch_width,
"patch_length": patch_length,
"resistance": res,
"sub_meters": sub_meters,
"sub_permittivity": sub_permittivity,
"wavelength": wavelength,
"offset_pin_pos": offset_pin_pos,
}
def _ordered_parameters(self, parameters):
my_keys = list(parameters.keys())
my_keys.sort()
return OrderedDict([(i, parameters[i]) for i in my_keys])
@pyaedt_function_handler()
def setup_hfss(self):
"""Set up a patch antenna in HFSS."""
for obj_name in self.object_list.keys():
if obj_name.startswith("PerfE") or obj_name.startswith("gnd_") or obj_name.startswith("ant_"):
bound = self._app.assign_perfecte_to_sheets(obj_name)
bound.name = "PerfE_" + obj_name
self.boundaries[bound.name] = bound
elif obj_name.startswith("coax_"):
obj = self.object_list[obj_name]
face_id = obj.faces[0].edges[0].id
for face in obj.faces:
if len(face.edges) == 2:
face_id = face.id
break
coax_bound = self._app.assign_perfecte_to_sheets(face_id)
coax_bound.name = "PerfE_" + obj_name
self.boundaries[coax_bound.name] = coax_bound
port_count = 1
for item in list(self.object_list.keys()):
terminal_references = []
port_lump = port = port_cap = None
if f"port_lump_{self.name}" in item:
port_lump = self.object_list[item]
terminal_references = [
i
for i in port_lump.touching_objects
if self._app.modeler[i]
and (
self._app.modeler[i].object_type == "Sheet"
or self._app.materials[self._app.modeler[i].material_name].is_conductor()
)
]
if len(terminal_references) > 1:
terminal_references = terminal_references[1:]
elif f"port_{self.name}" in item and not item.startswith("port_cap_"):
port = self.object_list[item]
port_suffix = item.replace(f"port_{self.name}", "", 1)
matching_port_cap = f"port_cap_{self.name}{port_suffix}"
if matching_port_cap in self.object_list:
port_cap = self.object_list[matching_port_cap]
if port_lump:
port1 = self._app.lumped_port(
assignment=item,
reference=terminal_references,
impedance=50,
name="port_" + self.name + "_" + str(port_count),
renormalize=True,
deembed=False,
)
self.excitations[port1.name] = port1
port_count += 1
if self._app.solution_type == "Terminal":
self._CommonAntenna__excitation_type = "Terminal_Lumped"
else:
self._app.solution_type = "Modal_Lumped"
elif port:
if self._app.solution_type == "Terminal" and port_cap:
terminal_references = port_cap.name
port1 = self._app.wave_port(
assignment=port,
reference=terminal_references,
name="port_" + self.name + "_" + str(port_count),
)
self.excitations[port1.name] = port1
port_count += 1
if self._app.solution_type == "Terminal":
self._CommonAntenna__excitation_type = "Terminal_Waveport"
else:
self._app.solution_type = "Modal_Waveport"
self._update_port_sources()
return True
def _update_port_sources(self):
return None
@pyaedt_function_handler()
def synthesis(self):
pass
[docs]
class RectangularPatchProbe(CommonPatch):
"""Manages a rectangular patch antenna with a coaxial probe.
This class is accessible through the ``Hfss`` object [1]_.
Parameters
----------
frequency : float, optional
Center frequency. The default is ``10.0``.
frequency_unit : str, optional
Frequency units. The default is ``"GHz"``.
material : str, optional
Substrate material.
If the material is not defined, a new material, ``parametrized``, is created.
The default is ``"FR4_epoxy"``.
outer_boundary : str, optional
Boundary type to use. The default is ``None``. Options are
``"FEBI"``, ``"PML"``, ``"Radiation"``, and ``None``.
length_unit : str, optional
Length units. The default is ``"mm"``.
substrate_height : float, optional
Substrate height. The default is ``1.575``.
parametrized : bool, optional
Whether to create a parametrized antenna. The default is ``True``.
Returns
-------
:class:`aedt.toolkits.antenna.RectangularPatchProbe`
Patch antenna object.
Notes
-----
.. [1] C. Balanis, "Microstrip Antennas," *Antenna Theory*, 2nd Ed. New York: Wiley, 1997.
Examples
--------
>>> from ansys.aedt.toolkits.antenna.backend.antenna_models.patch import RectangularPatchProbe
>>> import ansys.aedt.core
>>> app = ansys.aedt.core.Hfss()
>>> oantenna1 = RectangularPatchProbe(app)
>>> oantenna1.frequency = 12.0
>>> oantenna1.model_hfss()
>>> oantenna1.setup_hfss()
>>> app.release_desktop(False, False)
"""
_default_input_parameters = {
"name": "",
"origin": [0, 0, 0],
"length_unit": "mm",
"coordinate_system": "Global",
"frequency": 10.0,
"frequency_unit": "GHz",
"material": "FR4_epoxy",
"material_properties": {"permittivity": 4.4},
"outer_boundary": "",
"substrate_height": 1.575,
}
def __init__(self, *args, **kwargs):
CommonPatch.__init__(self, self._default_input_parameters, *args, **kwargs)
self._parameters = self.synthesis()
self.update_synthesis_parameters(self._parameters)
self.antenna_type = "RectangularPatchProbe"
[docs]
@pyaedt_function_handler()
def synthesis(self):
"""Antenna synthesis.
Returns
-------
dict
Analytical parameters.
"""
parameters = {}
length_unit = self.length_unit
light_speed = constants.SpeedOfLight # m/s
freq_hz = constants.unit_converter(self.frequency, "Freq", self.frequency_unit, "Hz")
wavelength = light_speed / freq_hz
if self._app and (
self.material in self._app.materials.mat_names_aedt
or self.material in self._app.materials.mat_names_aedt_lower
):
mat_props = self._app.materials[self.material]
permittivity = mat_props.permittivity.value
self._input_parameters.material_properties["permittivity"] = permittivity
elif self.material_properties:
permittivity = self.material_properties["permittivity"]
else:
self._app.logger.warning("Material is not found. Create the material before assigning it.")
return parameters
sub_permittivity = float(permittivity)
sub_meters = constants.unit_converter(self.substrate_height, "Length", self.length_unit, "meter")
patch_width = 3.0e8 / ((2.0 * freq_hz) * math.sqrt((sub_permittivity + 1.0) / 2.0))
eff_permittivity = (sub_permittivity + 1.0) / 2.0 + (sub_permittivity - 1.0) / 2.0 * math.pow(
1.0 + 12.0 * sub_meters / patch_width, -0.5
)
effective_length = 3.0e8 / (2.0 * freq_hz * math.sqrt(eff_permittivity))
top = (eff_permittivity + 0.3) * (patch_width / sub_meters + 0.264)
bottom = (eff_permittivity - 0.258) * (patch_width / sub_meters + 0.8)
delta_length = 0.412 * sub_meters * top / bottom
patch_length = effective_length - 2.0 * delta_length
# eff_WL_meters = wavelength / math.sqrt(eff_permittivity)
k = 2.0 * math.pi / eff_permittivity
g = math.pi * patch_width / (120.0 * math.pi * wavelength) * (1.0 - math.pow(k * sub_meters, 2) / 24)
# impedance at edge of patch
res = 1.0 / (2.0 * g)
offset_pin_pos = patch_length / math.pi * math.asin(math.sqrt(50.0 / res))
patch_x = constants.unit_converter(patch_width, "Length", "meter", length_unit)
parameters["patch_x"] = patch_x
patch_y = constants.unit_converter(patch_length, "Length", "meter", length_unit)
parameters["patch_y"] = patch_y
feed_x = 0.0
parameters["feed_x"] = feed_x
feed_y = constants.unit_converter(offset_pin_pos, "Length", "meter", length_unit)
parameters["feed_y"] = feed_y
sub_h = self.substrate_height
parameters["sub_h"] = sub_h
sub_x = constants.unit_converter(1.5 * patch_width + 6.0 * sub_meters, "Length", "meter", length_unit)
parameters["sub_x"] = sub_x
sub_y = constants.unit_converter(1.5 * patch_length + 6.0 * sub_meters, "Length", "meter", length_unit)
parameters["sub_y"] = sub_y
coax_inner_rad = constants.unit_converter(0.025 * (1e8 / freq_hz), "Length", "meter", length_unit)
parameters["coax_inner_rad"] = coax_inner_rad
coax_outer_rad = constants.unit_converter(0.085 * (1e8 / freq_hz), "Length", "meter", length_unit)
parameters["coax_outer_rad"] = coax_outer_rad
feed_length = constants.unit_converter(wavelength / 6.0, "Length", "meter", length_unit)
parameters["feed_length"] = feed_length
gnd_x = sub_x
gnd_y = sub_y
parameters["gnd_x"] = gnd_x
parameters["gnd_y"] = gnd_y
parameters["pos_x"] = self.origin[0]
parameters["pos_y"] = self.origin[1]
parameters["pos_z"] = self.origin[2]
my_keys = list(parameters.keys())
my_keys.sort()
parameters_out = OrderedDict([(i, parameters[i]) for i in my_keys])
return parameters_out
[docs]
@pyaedt_function_handler()
def model_hfss(self):
"""
Draw rectangular patch antenna with coaxial probe.
Once the antenna is created, this method will not be used anymore.
"""
if self.object_list:
logger.debug("This antenna already exists")
return False
if (
self.material not in self._app.materials.mat_names_aedt
and self.material not in self._app.materials.mat_names_aedt_lower
):
self._app.logger.warning("Material is not found. Create the material before assigning it.")
return False
self.set_variables_in_hfss()
# Map parameters
patch_x = self.synthesis_parameters.patch_x.hfss_variable
patch_y = self.synthesis_parameters.patch_y.hfss_variable
feed_x = self.synthesis_parameters.feed_x.hfss_variable
feed_y = self.synthesis_parameters.feed_y.hfss_variable
sub_h = self.synthesis_parameters.sub_h.hfss_variable
sub_x = self.synthesis_parameters.sub_x.hfss_variable
sub_y = self.synthesis_parameters.sub_y.hfss_variable
coax_inner_rad = self.synthesis_parameters.coax_inner_rad.hfss_variable
coax_outer_rad = self.synthesis_parameters.coax_outer_rad.hfss_variable
feed_length = self.synthesis_parameters.feed_length.hfss_variable
gnd_x = self.synthesis_parameters.gnd_x.hfss_variable
gnd_y = self.synthesis_parameters.gnd_y.hfss_variable
pos_x = self.synthesis_parameters.pos_x.hfss_variable
pos_y = self.synthesis_parameters.pos_y.hfss_variable
pos_z = self.synthesis_parameters.pos_z.hfss_variable
antenna_name = self.name
coordinate_system = self.coordinate_system
# Substrate
sub = self._app.modeler.create_box(
origin=["-" + sub_x + "/2", "-" + sub_y + "/2", "0"],
sizes=[sub_x, sub_y, sub_h],
name="sub_" + antenna_name,
material=self.material,
new_properties={"Coordinate System": coordinate_system},
)
sub.color = (0, 128, 0)
sub.transparency = 0.8
# Ground
gnd = self._app.modeler.create_rectangle(
orientation=2,
origin=["-" + gnd_x + "/2", "-" + gnd_y + "/2", "0"],
sizes=[gnd_x, gnd_y],
name="gnd_" + antenna_name,
new_properties={"Coordinate System": coordinate_system},
)
gnd.color = (255, 128, 65)
gnd.transparency = 0.1
# Antenna
ant = self._app.modeler.create_rectangle(
orientation=2,
origin=[
"-" + patch_x + "/2",
"-" + patch_y + "/2",
sub_h,
],
sizes=[patch_x, patch_y],
name="ant_" + antenna_name,
new_properties={"Coordinate System": coordinate_system},
)
ant.color = (255, 128, 65)
ant.transparency = 0.1
void = self._app.modeler.create_circle(
orientation=2,
origin=[feed_x, feed_y, "0"],
radius=coax_outer_rad,
name="void_" + antenna_name,
new_properties={"Coordinate System": coordinate_system},
)
self._app.modeler.subtract(gnd, void, False)
feed_pin = self._app.modeler.create_cylinder(
orientation=2,
origin=[feed_x, feed_y, "0"],
radius=coax_inner_rad,
height=sub_h,
name="feed_pin_" + antenna_name,
material="pec",
new_properties={"Coordinate System": coordinate_system},
)
feed_pin.color = (255, 128, 65)
feed_coax = self._app.modeler.create_cylinder(
orientation=2,
origin=[feed_x, feed_y, "0"],
radius=coax_inner_rad,
height="-" + feed_length,
name="feed_coax_" + antenna_name,
material="pec",
new_properties={"Coordinate System": coordinate_system},
)
feed_coax.color = (255, 128, 65)
coax = self._app.modeler.create_cylinder(
orientation=2,
origin=[feed_x, feed_y, "0"],
radius=coax_outer_rad,
height="-" + feed_length,
name="coax_" + antenna_name,
material="Teflon (tm)",
new_properties={"Coordinate System": coordinate_system},
)
coax.color = (128, 255, 255)
port_cap = self._app.modeler.create_cylinder(
orientation=2,
origin=[feed_x, feed_y, "-" + feed_length],
radius=coax_outer_rad,
height="-" + sub_h + "/" + str(10),
name="port_cap_" + antenna_name,
material="pec",
new_properties={"Coordinate System": coordinate_system},
)
port_cap.color = (132, 132, 193)
p1 = self._app.modeler.create_circle(
orientation=2,
origin=[feed_x, feed_y, "-" + feed_length],
radius=coax_outer_rad,
name="port_" + antenna_name,
new_properties={"Coordinate System": coordinate_system},
)
p1.color = (128, 0, 0)
self.object_list[sub.name] = sub
self.object_list[gnd.name] = gnd
self.object_list[ant.name] = ant
self.object_list[feed_pin.name] = feed_pin
self.object_list[feed_coax.name] = feed_coax
self.object_list[coax.name] = coax
self.object_list[port_cap.name] = port_cap
self.object_list[p1.name] = p1
self._app.modeler.move(list(self.object_list.keys()), [pos_x, pos_y, pos_z])
sub.group_name = antenna_name
gnd.group_name = antenna_name
ant.group_name = antenna_name
feed_pin.group_name = antenna_name
feed_coax.group_name = antenna_name
coax.group_name = antenna_name
port_cap.group_name = antenna_name
p1.group_name = antenna_name
return True
[docs]
@pyaedt_function_handler()
def model_disco(self):
"""Model in PyDiscovery. To be implemented."""
pass
[docs]
@pyaedt_function_handler()
def setup_disco(self):
"""Set up the model in PyDiscovery. To be implemented."""
pass
class EllipticalPatchMixin:
def _create_patch_ellipse(self, patch_x, patch_y, sub_h, antenna_name, coordinate_system):
ant = self._app.modeler.create_ellipse(
orientation=2,
origin=[0, 0, sub_h],
major_radius=patch_x + "/2",
ratio=patch_y + "/" + patch_x,
name="ant_" + antenna_name,
new_properties={"Coordinate System": coordinate_system},
)
ant.color = (255, 128, 65)
ant.transparency = 0.1
return ant
class EllipticalEdge(EllipticalPatchMixin, CommonPatch):
"""Manages an elliptical patch antenna with an edge feed."""
_default_input_parameters = {
"name": "",
"origin": [0, 0, 0],
"length_unit": "mm",
"coordinate_system": "Global",
"frequency": 10.0,
"frequency_unit": "GHz",
"material": "FR4_epoxy",
"material_properties": {"permittivity": 4.4},
"outer_boundary": "",
"substrate_height": 1.575,
}
def __init__(self, *args, **kwargs):
CommonPatch.__init__(self, self._default_input_parameters, *args, **kwargs)
self._parameters = self.synthesis()
self.update_synthesis_parameters(self._parameters)
self.antenna_type = "EllipticalEdge"
@pyaedt_function_handler()
def synthesis(self):
parameters = {}
base = self._patch_synthesis_base()
if not base:
return parameters
length_unit = self.length_unit
quarterwave_imped = math.sqrt(50.0 * base["resistance"])
u_strip1 = self._transmission_line_calculator.microstrip_calculator(
base["sub_meters"], base["sub_permittivity"], quarterwave_imped, 90.0
)
u_strip2 = self._transmission_line_calculator.microstrip_calculator(
base["sub_meters"], base["sub_permittivity"], 50.0, 150.0
)
parameters["patch_x"] = constants.unit_converter(base["patch_width"], "Length", "meter", length_unit)
parameters["patch_y"] = constants.unit_converter(base["patch_length"], "Length", "meter", length_unit)
parameters["sub_h"] = self.substrate_height
parameters["sub_x"] = constants.unit_converter(
1.5 * base["patch_width"] + 6.0 * base["sub_meters"], "Length", "meter", length_unit
)
parameters["sub_y"] = constants.unit_converter(
2.1 * (u_strip2[1] + u_strip1[1] + base["patch_length"] / 2), "Length", "meter", length_unit
)
parameters["edge_feed_width"] = constants.unit_converter(u_strip1[0], "Length", "meter", length_unit)
parameters["edge_feed_length"] = constants.unit_converter(u_strip1[1], "Length", "meter", length_unit)
parameters["feed_width"] = constants.unit_converter(u_strip2[0], "Length", "meter", length_unit)
parameters["feed_length"] = constants.unit_converter(u_strip2[1], "Length", "meter", length_unit)
parameters["pos_x"] = self.origin[0]
parameters["pos_y"] = self.origin[1]
parameters["pos_z"] = self.origin[2]
return self._ordered_parameters(parameters)
@pyaedt_function_handler()
def model_hfss(self):
if self.object_list:
self._app.logger.warning("This antenna already exists.")
return False
if (
self.material not in self._app.materials.mat_names_aedt
and self.material not in self._app.materials.mat_names_aedt_lower
):
self._app.logger.warning("Material is not found. Create the material before assigning it.")
return False
self.set_variables_in_hfss()
patch_x = self.synthesis_parameters.patch_x.hfss_variable
patch_y = self.synthesis_parameters.patch_y.hfss_variable
sub_h = self.synthesis_parameters.sub_h.hfss_variable
sub_x = self.synthesis_parameters.sub_x.hfss_variable
sub_y = self.synthesis_parameters.sub_y.hfss_variable
edge_feed_width = self.synthesis_parameters.edge_feed_width.hfss_variable
edge_feed_length = self.synthesis_parameters.edge_feed_length.hfss_variable
feed_width = self.synthesis_parameters.feed_width.hfss_variable
feed_length = self.synthesis_parameters.feed_length.hfss_variable
pos_x = self.synthesis_parameters.pos_x.hfss_variable
pos_y = self.synthesis_parameters.pos_y.hfss_variable
pos_z = self.synthesis_parameters.pos_z.hfss_variable
antenna_name = self.name
coordinate_system = self.coordinate_system
sub = self._app.modeler.create_box(
origin=["-" + sub_x + "/2", "-" + sub_y + "/2", "0"],
sizes=[sub_x, sub_y, sub_h],
name="sub_" + antenna_name,
material=self.material,
new_properties={"Coordinate System": coordinate_system},
)
sub.color = (0, 128, 0)
sub.transparency = 0.8
gnd = self._app.modeler.create_rectangle(
orientation=2,
origin=["-" + sub_x + "/2", "-" + sub_y + "/2", "0"],
sizes=[sub_x, sub_y],
name="gnd_" + antenna_name,
new_properties={"Coordinate System": coordinate_system},
)
gnd.color = (255, 128, 65)
gnd.transparency = 0.1
ant = self._create_patch_ellipse(patch_x, patch_y, sub_h, antenna_name, coordinate_system)
edge_feed = self._app.modeler.create_rectangle(
orientation=2,
origin=["-" + edge_feed_width + "/2", "0", sub_h],
sizes=[edge_feed_width, patch_y + "/2+" + edge_feed_length],
name="edge_feed_" + antenna_name,
new_properties={"Coordinate System": coordinate_system},
)
edge_feed.color = (255, 128, 65)
feed = self._app.modeler.create_rectangle(
orientation=2,
origin=["-" + feed_width + "/2", patch_y + "/2+" + edge_feed_length, sub_h],
sizes=[feed_width, feed_length],
name="feed_" + antenna_name,
new_properties={"Coordinate System": coordinate_system},
)
feed.color = (255, 128, 65)
self._app.modeler.unite([ant, edge_feed, feed])
p1 = self._app.modeler.create_rectangle(
orientation=1,
origin=["-" + feed_width + "/2", patch_y + "/2+" + edge_feed_length + "+" + feed_length, "0"],
sizes=[sub_h, feed_width],
name="port_lump_" + antenna_name,
new_properties={"Coordinate System": coordinate_system},
)
p1.color = (255, 128, 65)
self.object_list[sub.name] = sub
self.object_list[gnd.name] = gnd
self.object_list[ant.name] = ant
self.object_list[p1.name] = p1
self._app.modeler.move(list(self.object_list.keys()), [pos_x, pos_y, pos_z])
for obj in self.object_list.values():
obj.group_name = antenna_name
return True
@pyaedt_function_handler()
def model_disco(self):
pass
@pyaedt_function_handler()
def setup_disco(self):
pass
class EllipticalInset(EllipticalPatchMixin, CommonPatch):
"""Manages an elliptical patch antenna with an inset feed."""
_default_input_parameters = {
"name": "",
"origin": [0, 0, 0],
"length_unit": "mm",
"coordinate_system": "Global",
"frequency": 10.0,
"frequency_unit": "GHz",
"material": "FR4_epoxy",
"material_properties": {"permittivity": 4.4},
"outer_boundary": "",
"substrate_height": 1.575,
}
def __init__(self, *args, **kwargs):
CommonPatch.__init__(self, self._default_input_parameters, *args, **kwargs)
self._parameters = self.synthesis()
self.update_synthesis_parameters(self._parameters)
self.antenna_type = "EllipticalInset"
@pyaedt_function_handler()
def synthesis(self):
parameters = {}
base = self._patch_synthesis_base()
if not base:
return parameters
length_unit = self.length_unit
u_strip = self._transmission_line_calculator.microstrip_calculator(
base["sub_meters"], base["sub_permittivity"], 50.0, 150.0
)
microstrip_width = u_strip[0]
microstrip_length = u_strip[1]
parameters["patch_x"] = constants.unit_converter(base["patch_width"], "Length", "meter", length_unit)
parameters["patch_y"] = constants.unit_converter(base["patch_length"], "Length", "meter", length_unit)
parameters["sub_h"] = self.substrate_height
parameters["sub_x"] = constants.unit_converter(
1.5 * base["patch_width"] + 6.0 * base["sub_meters"], "Length", "meter", length_unit
)
parameters["sub_y"] = constants.unit_converter(
2.1 * (microstrip_length + base["patch_length"] / 2), "Length", "meter", length_unit
)
parameters["inset_distance"] = constants.unit_converter(
base["patch_length"] / 2 - base["offset_pin_pos"], "Length", "meter", length_unit
)
microstrip_width = constants.unit_converter(microstrip_width, "Length", "meter", length_unit)
microstrip_length = constants.unit_converter(microstrip_length, "Length", "meter", length_unit)
parameters["inset_gap"] = round(microstrip_width / 2, 3)
parameters["feed_width"] = round(microstrip_width, 3)
parameters["feed_length"] = round(microstrip_length, 3)
parameters["pos_x"] = self.origin[0]
parameters["pos_y"] = self.origin[1]
parameters["pos_z"] = self.origin[2]
return self._ordered_parameters(parameters)
@pyaedt_function_handler()
def model_hfss(self):
if self.object_list:
self._app.logger.warning("This antenna already exists.")
return False
if (
self.material not in self._app.materials.mat_names_aedt
and self.material not in self._app.materials.mat_names_aedt_lower
):
self._app.logger.warning("Material is not found. Create the material before assigning it.")
return False
self.set_variables_in_hfss()
patch_x = self.synthesis_parameters.patch_x.hfss_variable
patch_y = self.synthesis_parameters.patch_y.hfss_variable
sub_h = self.synthesis_parameters.sub_h.hfss_variable
sub_x = self.synthesis_parameters.sub_x.hfss_variable
sub_y = self.synthesis_parameters.sub_y.hfss_variable
inset_distance = self.synthesis_parameters.inset_distance.hfss_variable
inset_gap = self.synthesis_parameters.inset_gap.hfss_variable
feed_width = self.synthesis_parameters.feed_width.hfss_variable
feed_length = self.synthesis_parameters.feed_length.hfss_variable
pos_x = self.synthesis_parameters.pos_x.hfss_variable
pos_y = self.synthesis_parameters.pos_y.hfss_variable
pos_z = self.synthesis_parameters.pos_z.hfss_variable
antenna_name = self.name
coordinate_system = self.coordinate_system
sub = self._app.modeler.create_box(
origin=["-" + sub_x + "/2", "-" + sub_y + "/2", "0"],
sizes=[sub_x, sub_y, sub_h],
name="sub_" + antenna_name,
material=self.material,
new_properties={"Coordinate System": coordinate_system},
)
sub.color = (0, 128, 0)
sub.transparency = 0.8
gnd = self._app.modeler.create_rectangle(
orientation=2,
origin=["-" + sub_x + "/2", "-" + sub_y + "/2", "0"],
sizes=[sub_x, sub_y],
name="gnd_" + antenna_name,
new_properties={"Coordinate System": coordinate_system},
)
gnd.color = (255, 128, 65)
gnd.transparency = 0.1
ant = self._create_patch_ellipse(patch_x, patch_y, sub_h, antenna_name, coordinate_system)
cutout = self._app.modeler.create_rectangle(
orientation=2,
origin=["-" + feed_width + "/2-" + inset_gap, patch_y + "/2-" + inset_distance, sub_h],
sizes=[feed_width + "+2*" + inset_gap, feed_length],
name="cutout_" + antenna_name,
new_properties={"Coordinate System": coordinate_system},
)
self._app.modeler.subtract(ant, cutout, False)
feed = self._app.modeler.create_rectangle(
orientation=2,
origin=["-" + feed_width + "/2", patch_y + "/2-" + inset_distance, sub_h],
sizes=[feed_width, feed_length + "+" + inset_distance],
name="feed_" + antenna_name,
new_properties={"Coordinate System": coordinate_system},
)
feed.color = (255, 128, 65)
self._app.modeler.unite([ant, feed])
p1 = self._app.modeler.create_rectangle(
orientation=1,
origin=["-" + feed_width + "/2", patch_y + "/2+" + feed_length, "0"],
sizes=[sub_h, feed_width],
name="port_lump_" + antenna_name,
new_properties={"Coordinate System": coordinate_system},
)
p1.color = (255, 128, 65)
self.object_list[sub.name] = sub
self.object_list[gnd.name] = gnd
self.object_list[ant.name] = ant
self.object_list[p1.name] = p1
self._app.modeler.move(list(self.object_list.keys()), [pos_x, pos_y, pos_z])
for obj in self.object_list.values():
obj.group_name = antenna_name
return True
@pyaedt_function_handler()
def model_disco(self):
pass
@pyaedt_function_handler()
def setup_disco(self):
pass
class EllipticalProbe(EllipticalPatchMixin, CommonPatch):
"""Manages an elliptical patch antenna with a coaxial probe."""
_default_input_parameters = {
"name": "",
"origin": [0, 0, 0],
"length_unit": "mm",
"coordinate_system": "Global",
"frequency": 10.0,
"frequency_unit": "GHz",
"material": "FR4_epoxy",
"material_properties": {"permittivity": 4.4},
"outer_boundary": "",
"substrate_height": 1.575,
}
def __init__(self, *args, **kwargs):
CommonPatch.__init__(self, self._default_input_parameters, *args, **kwargs)
self._parameters = self.synthesis()
self.update_synthesis_parameters(self._parameters)
self.antenna_type = "EllipticalProbe"
@pyaedt_function_handler()
def synthesis(self):
parameters = {}
base = self._patch_synthesis_base()
if not base:
return parameters
length_unit = self.length_unit
patch_size = constants.unit_converter(base["patch_length"], "Length", "meter", length_unit)
parameters["patch_x"] = patch_size
parameters["patch_y"] = patch_size
parameters["feed_x"] = constants.unit_converter(base["offset_pin_pos"], "Length", "meter", length_unit)
parameters["feed_y"] = 0.0
parameters["sub_h"] = self.substrate_height
parameters["sub_x"] = constants.unit_converter(
base["patch_length"] + 6.0 * base["sub_meters"], "Length", "meter", length_unit
)
parameters["sub_y"] = constants.unit_converter(
base["patch_length"] + 6.0 * base["sub_meters"], "Length", "meter", length_unit
)
parameters["coax_inner_rad"] = constants.unit_converter(
0.025 * (1e8 / base["freq_hz"]), "Length", "meter", length_unit
)
parameters["coax_outer_rad"] = constants.unit_converter(
0.085 * (1e8 / base["freq_hz"]), "Length", "meter", length_unit
)
parameters["feed_length"] = constants.unit_converter(base["wavelength"] / 6.0, "Length", "meter", length_unit)
parameters["gnd_x"] = parameters["sub_x"]
parameters["gnd_y"] = parameters["sub_y"]
parameters["pos_x"] = self.origin[0]
parameters["pos_y"] = self.origin[1]
parameters["pos_z"] = self.origin[2]
return self._ordered_parameters(parameters)
@pyaedt_function_handler()
def model_hfss(self):
if self.object_list:
logger.debug("This antenna already exists")
return False
if (
self.material not in self._app.materials.mat_names_aedt
and self.material not in self._app.materials.mat_names_aedt_lower
):
self._app.logger.warning("Material is not found. Create the material before assigning it.")
return False
self.set_variables_in_hfss()
patch_x = self.synthesis_parameters.patch_x.hfss_variable
patch_y = self.synthesis_parameters.patch_y.hfss_variable
feed_x = self.synthesis_parameters.feed_x.hfss_variable
feed_y = self.synthesis_parameters.feed_y.hfss_variable
sub_h = self.synthesis_parameters.sub_h.hfss_variable
sub_x = self.synthesis_parameters.sub_x.hfss_variable
sub_y = self.synthesis_parameters.sub_y.hfss_variable
coax_inner_rad = self.synthesis_parameters.coax_inner_rad.hfss_variable
coax_outer_rad = self.synthesis_parameters.coax_outer_rad.hfss_variable
feed_length = self.synthesis_parameters.feed_length.hfss_variable
gnd_x = self.synthesis_parameters.gnd_x.hfss_variable
gnd_y = self.synthesis_parameters.gnd_y.hfss_variable
pos_x = self.synthesis_parameters.pos_x.hfss_variable
pos_y = self.synthesis_parameters.pos_y.hfss_variable
pos_z = self.synthesis_parameters.pos_z.hfss_variable
antenna_name = self.name
coordinate_system = self.coordinate_system
sub = self._app.modeler.create_box(
origin=["-" + sub_x + "/2", "-" + sub_y + "/2", "0"],
sizes=[sub_x, sub_y, sub_h],
name="sub_" + antenna_name,
material=self.material,
new_properties={"Coordinate System": coordinate_system},
)
sub.color = (0, 128, 0)
sub.transparency = 0.8
gnd = self._app.modeler.create_rectangle(
orientation=2,
origin=["-" + gnd_x + "/2", "-" + gnd_y + "/2", "0"],
sizes=[gnd_x, gnd_y],
name="gnd_" + antenna_name,
new_properties={"Coordinate System": coordinate_system},
)
gnd.color = (255, 128, 65)
gnd.transparency = 0.1
ant = self._create_patch_ellipse(patch_x, patch_y, sub_h, antenna_name, coordinate_system)
void = self._app.modeler.create_circle(
orientation=2,
origin=[feed_x, feed_y, "0"],
radius=coax_outer_rad,
name="void_" + antenna_name,
new_properties={"Coordinate System": coordinate_system},
)
self._app.modeler.subtract(gnd, void, False)
feed_pin = self._app.modeler.create_cylinder(
orientation=2,
origin=[feed_x, feed_y, "0"],
radius=coax_inner_rad,
height=sub_h,
name="feed_pin_" + antenna_name,
material="pec",
new_properties={"Coordinate System": coordinate_system},
)
feed_pin.color = (255, 128, 65)
feed_coax = self._app.modeler.create_cylinder(
orientation=2,
origin=[feed_x, feed_y, "0"],
radius=coax_inner_rad,
height="-" + feed_length,
name="feed_coax_" + antenna_name,
material="pec",
new_properties={"Coordinate System": coordinate_system},
)
feed_coax.color = (255, 128, 65)
coax = self._app.modeler.create_cylinder(
orientation=2,
origin=[feed_x, feed_y, "0"],
radius=coax_outer_rad,
height="-" + feed_length,
name="coax_" + antenna_name,
material="Teflon (tm)",
new_properties={"Coordinate System": coordinate_system},
)
coax.color = (128, 255, 255)
port_cap = self._app.modeler.create_cylinder(
orientation=2,
origin=[feed_x, feed_y, "-" + feed_length],
radius=coax_outer_rad,
height="-" + sub_h + "/10",
name="port_cap_" + antenna_name,
material="pec",
new_properties={"Coordinate System": coordinate_system},
)
port_cap.color = (132, 132, 193)
p1 = self._app.modeler.create_circle(
orientation=2,
origin=[feed_x, feed_y, "-" + feed_length],
radius=coax_outer_rad,
name="port_" + antenna_name,
new_properties={"Coordinate System": coordinate_system},
)
p1.color = (128, 0, 0)
self.object_list[sub.name] = sub
self.object_list[gnd.name] = gnd
self.object_list[ant.name] = ant
self.object_list[feed_pin.name] = feed_pin
self.object_list[feed_coax.name] = feed_coax
self.object_list[coax.name] = coax
self.object_list[port_cap.name] = port_cap
self.object_list[p1.name] = p1
self._app.modeler.move(list(self.object_list.keys()), [pos_x, pos_y, pos_z])
for obj in self.object_list.values():
obj.group_name = antenna_name
return True
@pyaedt_function_handler()
def model_disco(self):
pass
@pyaedt_function_handler()
def setup_disco(self):
pass
class MbyNPatchArray(CommonPatch):
"""Manages an M-by-N rectangular patch array."""
_default_input_parameters = {
"name": "",
"origin": [0, 0, 0],
"length_unit": "mm",
"coordinate_system": "Global",
"frequency": 1.0,
"frequency_unit": "GHz",
"material": "Teflon (tm)",
"material_properties": {"permittivity": 2.1},
"outer_boundary": "",
"substrate_height": 1.272,
"number_of_patches_x": 2,
"number_of_patches_y": 3,
}
def __init__(self, *args, **kwargs):
CommonPatch.__init__(self, self._default_input_parameters, *args, **kwargs)
self._parameters = self.synthesis()
self.update_synthesis_parameters(self._parameters)
self.antenna_type = "MbyNPatchArray"
@pyaedt_function_handler()
def synthesis(self):
parameters = {}
base = self._patch_synthesis_base()
if not base:
return parameters
length_unit = self.length_unit
patch_count_x = int(self.number_of_patches_x)
patch_count_y = int(self.number_of_patches_y)
freq_ghz = constants.unit_converter(self.frequency, "Freq", self.frequency_unit, "GHz")
parameters["patch_count_x"] = patch_count_x
parameters["patch_count_y"] = patch_count_y
parameters["patch_x"] = constants.unit_converter(base["patch_width"], "Length", "meter", length_unit)
parameters["patch_y"] = constants.unit_converter(base["patch_length"], "Length", "meter", length_unit)
parameters["feed_x"] = 0.0
parameters["feed_y"] = constants.unit_converter(base["offset_pin_pos"], "Length", "meter", length_unit)
parameters["sub_h"] = self.substrate_height
parameters["sub_x"] = constants.unit_converter(
(patch_count_x - 1) * (1.5 * base["patch_width"] + 6.0 * base["sub_meters"]) + 2.0 * base["patch_width"],
"Length",
"meter",
length_unit,
)
parameters["sub_y"] = constants.unit_converter(
(patch_count_y - 1) * (1.5 * base["patch_length"] + 6.0 * base["sub_meters"]) + 2.0 * base["patch_length"],
"Length",
"meter",
length_unit,
)
parameters["patch_spacing_x"] = constants.unit_converter(
1.5 * base["patch_width"] + 6.0 * base["sub_meters"], "Length", "meter", length_unit
)
parameters["patch_spacing_y"] = constants.unit_converter(
1.5 * base["patch_length"] + 6.0 * base["sub_meters"], "Length", "meter", length_unit
)
parameters["coax_inner_rad"] = constants.unit_converter(0.25 * (10.0 / freq_ghz), "Length", "mm", length_unit)
parameters["coax_outer_rad"] = constants.unit_converter(0.85 * (10.0 / freq_ghz), "Length", "mm", length_unit)
parameters["feed_length"] = constants.unit_converter(base["wavelength"] / 6.0, "Length", "meter", length_unit)
parameters["gnd_x"] = parameters["sub_x"]
parameters["gnd_y"] = parameters["sub_y"]
parameters["pos_x"] = self.origin[0]
parameters["pos_y"] = self.origin[1]
parameters["pos_z"] = self.origin[2]
return self._ordered_parameters(parameters)
@pyaedt_function_handler()
def model_hfss(self):
if self.object_list:
self._app.logger.warning("This antenna already exists.")
return False
if (
self.material not in self._app.materials.mat_names_aedt
and self.material not in self._app.materials.mat_names_aedt_lower
):
self._app.logger.warning("Material is not found. Create the material before assigning it.")
return False
self.set_variables_in_hfss()
patch_x = self.synthesis_parameters.patch_x.hfss_variable
patch_y = self.synthesis_parameters.patch_y.hfss_variable
patch_spacing_x = self.synthesis_parameters.patch_spacing_x.hfss_variable
patch_spacing_y = self.synthesis_parameters.patch_spacing_y.hfss_variable
feed_x = self.synthesis_parameters.feed_x.hfss_variable
feed_y = self.synthesis_parameters.feed_y.hfss_variable
sub_h = self.synthesis_parameters.sub_h.hfss_variable
sub_x = self.synthesis_parameters.sub_x.hfss_variable
sub_y = self.synthesis_parameters.sub_y.hfss_variable
coax_inner_rad = self.synthesis_parameters.coax_inner_rad.hfss_variable
coax_outer_rad = self.synthesis_parameters.coax_outer_rad.hfss_variable
feed_length = self.synthesis_parameters.feed_length.hfss_variable
gnd_x = self.synthesis_parameters.gnd_x.hfss_variable
gnd_y = self.synthesis_parameters.gnd_y.hfss_variable
pos_x = self.synthesis_parameters.pos_x.hfss_variable
pos_y = self.synthesis_parameters.pos_y.hfss_variable
pos_z = self.synthesis_parameters.pos_z.hfss_variable
antenna_name = self.name
coordinate_system = self.coordinate_system
patch_count_x = int(self.number_of_patches_x)
patch_count_y = int(self.number_of_patches_y)
sub = self._app.modeler.create_box(
origin=["-" + sub_x + "/2", "-" + sub_y + "/2", "0"],
sizes=[sub_x, sub_y, sub_h],
name="sub_" + antenna_name,
material=self.material,
new_properties={"Coordinate System": coordinate_system},
)
sub.color = (0, 128, 0)
sub.transparency = 0.8
gnd = self._app.modeler.create_rectangle(
orientation=2,
origin=["-" + gnd_x + "/2", "-" + gnd_y + "/2", "0"],
sizes=[gnd_x, gnd_y],
name="gnd_" + antenna_name,
new_properties={"Coordinate System": coordinate_system},
)
gnd.color = (255, 128, 65)
gnd.transparency = 0.1
self.object_list[sub.name] = sub
self.object_list[gnd.name] = gnd
for i in range(patch_count_x):
for j in range(patch_count_y):
x_index = i - (patch_count_x - 1) / 2
y_index = j - (patch_count_y - 1) / 2
patch_center_x = f"({x_index})*{patch_spacing_x}"
patch_center_y = f"({y_index})*{patch_spacing_y}"
feed_origin = [patch_center_x + "+" + feed_x, patch_center_y + "+" + feed_y, "0"]
ant = self._app.modeler.create_rectangle(
orientation=2,
origin=[patch_center_x + "-" + patch_x + "/2", patch_center_y + "-" + patch_y + "/2", sub_h],
sizes=[patch_x, patch_y],
name=f"ant_{antenna_name}_{i}_{j}",
new_properties={"Coordinate System": coordinate_system},
)
ant.color = (255, 128, 65)
ant.transparency = 0.1
void = self._app.modeler.create_circle(
orientation=2,
origin=feed_origin,
radius=coax_outer_rad,
name=f"void_{antenna_name}_{i}_{j}",
new_properties={"Coordinate System": coordinate_system},
)
self._app.modeler.subtract(gnd, void, False)
feed_pin = self._app.modeler.create_cylinder(
orientation=2,
origin=feed_origin,
radius=coax_inner_rad,
height=sub_h,
name=f"feed_pin_{antenna_name}_{i}_{j}",
material="pec",
new_properties={"Coordinate System": coordinate_system},
)
feed_pin.color = (255, 128, 65)
feed_coax = self._app.modeler.create_cylinder(
orientation=2,
origin=feed_origin,
radius=coax_inner_rad,
height="-" + feed_length,
name=f"feed_coax_{antenna_name}_{i}_{j}",
material="pec",
new_properties={"Coordinate System": coordinate_system},
)
feed_coax.color = (255, 128, 65)
coax = self._app.modeler.create_cylinder(
orientation=2,
origin=feed_origin,
radius=coax_outer_rad,
height="-" + feed_length,
name=f"coax_{antenna_name}_{i}_{j}",
material="Teflon (tm)",
new_properties={"Coordinate System": coordinate_system},
)
coax.color = (128, 255, 255)
port_cap = self._app.modeler.create_cylinder(
orientation=2,
origin=[patch_center_x + "+" + feed_x, patch_center_y + "+" + feed_y, "-" + feed_length],
radius=coax_outer_rad,
height="-" + sub_h + "/10",
name=f"port_cap_{antenna_name}_{i}_{j}",
material="pec",
new_properties={"Coordinate System": coordinate_system},
)
port_cap.color = (132, 132, 193)
port = self._app.modeler.create_circle(
orientation=2,
origin=[patch_center_x + "+" + feed_x, patch_center_y + "+" + feed_y, "-" + feed_length],
radius=coax_outer_rad,
name=f"port_{antenna_name}_{i}_{j}",
new_properties={"Coordinate System": coordinate_system},
)
port.color = (128, 0, 0)
for obj in [ant, feed_pin, feed_coax, coax, port_cap, port]:
self.object_list[obj.name] = obj
self._app.modeler.move(list(self.object_list.keys()), [pos_x, pos_y, pos_z])
for obj in self.object_list.values():
obj.group_name = antenna_name
return True
@pyaedt_function_handler()
def model_disco(self):
pass
@pyaedt_function_handler()
def setup_disco(self):
pass
class SeqRotated2Patch(CommonPatch):
"""Manages a sequentially rotated four-patch array."""
_default_input_parameters = {
"name": "",
"origin": [0, 0, 0],
"length_unit": "mm",
"coordinate_system": "Global",
"frequency": 5.0,
"frequency_unit": "GHz",
"material": "Teflon (tm)",
"material_properties": {"permittivity": 2.1},
"outer_boundary": "",
"substrate_height": 1.272,
"feed_rotation_angle": 45.0,
"element_1_rotation_angle": 0.0,
"element_2_rotation_angle": -90.0,
"element_3_rotation_angle": -180.0,
"element_4_rotation_angle": -270.0,
"element_1_port_phase": 0.0,
"element_2_port_phase": 90.0,
"element_3_port_phase": 180.0,
"element_4_port_phase": 270.0,
}
def __init__(self, *args, **kwargs):
CommonPatch.__init__(self, self._default_input_parameters, *args, **kwargs)
self._parameters = self.synthesis()
self.update_synthesis_parameters(self._parameters)
self.antenna_type = "SeqRotated2Patch"
@pyaedt_function_handler()
def synthesis(self):
parameters = {}
freq_ghz = constants.unit_converter(self.frequency, "Freq", self.frequency_unit, "GHz")
scale = 5.0 / freq_ghz
length_unit = self.length_unit
def scaled(value):
return constants.unit_converter(scale * value, "Length", "mm", length_unit)
parameters["sub_h"] = self.substrate_height
parameters["sub_x"] = scaled(80.0)
parameters["sub_y"] = scaled(80.0)
parameters["patch_diameter"] = scaled(23.53)
parameters["patch_spacing_x"] = scaled(35.29)
parameters["patch_spacing_y"] = scaled(35.29)
parameters["notch_length"] = scaled(1.502)
parameters["notch_width"] = scaled(3.004)
parameters["feed_pin_offset"] = scaled(3.663)
parameters["coax_inner_rad"] = constants.unit_converter(0.25, "Length", "mm", length_unit)
parameters["coax_outer_rad"] = constants.unit_converter(0.85, "Length", "mm", length_unit)
parameters["feed_length"] = constants.unit_converter(2.5, "Length", "mm", length_unit)
parameters["feed_rotation_angle"] = self.feed_rotation_angle
parameters["element_1_rotation_angle"] = self.element_1_rotation_angle
parameters["element_2_rotation_angle"] = self.element_2_rotation_angle
parameters["element_3_rotation_angle"] = self.element_3_rotation_angle
parameters["element_4_rotation_angle"] = self.element_4_rotation_angle
parameters["element_1_port_phase"] = self.element_1_port_phase
parameters["element_2_port_phase"] = self.element_2_port_phase
parameters["element_3_port_phase"] = self.element_3_port_phase
parameters["element_4_port_phase"] = self.element_4_port_phase
parameters["pos_x"] = self.origin[0]
parameters["pos_y"] = self.origin[1]
parameters["pos_z"] = self.origin[2]
return self._ordered_parameters(parameters)
@pyaedt_function_handler()
def model_hfss(self):
if self.object_list:
self._app.logger.warning("This antenna already exists.")
return False
if (
self.material not in self._app.materials.mat_names_aedt
and self.material not in self._app.materials.mat_names_aedt_lower
):
self._app.logger.warning("Material is not found. Create the material before assigning it.")
return False
self.set_variables_in_hfss()
sub_h = self.synthesis_parameters.sub_h.hfss_variable
sub_x = self.synthesis_parameters.sub_x.hfss_variable
sub_y = self.synthesis_parameters.sub_y.hfss_variable
patch_diameter = self.synthesis_parameters.patch_diameter.hfss_variable
patch_spacing_x = self.synthesis_parameters.patch_spacing_x.hfss_variable
patch_spacing_y = self.synthesis_parameters.patch_spacing_y.hfss_variable
notch_length = self.synthesis_parameters.notch_length.hfss_variable
notch_width = self.synthesis_parameters.notch_width.hfss_variable
feed_pin_offset = self.synthesis_parameters.feed_pin_offset.hfss_variable
coax_inner_rad = self.synthesis_parameters.coax_inner_rad.hfss_variable
coax_outer_rad = self.synthesis_parameters.coax_outer_rad.hfss_variable
feed_length = self.synthesis_parameters.feed_length.hfss_variable
feed_rotation_angle = self.synthesis_parameters.feed_rotation_angle.hfss_variable
pos_x = self.synthesis_parameters.pos_x.hfss_variable
pos_y = self.synthesis_parameters.pos_y.hfss_variable
pos_z = self.synthesis_parameters.pos_z.hfss_variable
rotations = [
self.synthesis_parameters.element_1_rotation_angle.hfss_variable,
self.synthesis_parameters.element_2_rotation_angle.hfss_variable,
self.synthesis_parameters.element_3_rotation_angle.hfss_variable,
self.synthesis_parameters.element_4_rotation_angle.hfss_variable,
]
centers = [
[patch_spacing_x + "/2", patch_spacing_y + "/2", "0"],
["-" + patch_spacing_x + "/2", patch_spacing_y + "/2", "0"],
["-" + patch_spacing_x + "/2", "-" + patch_spacing_y + "/2", "0"],
[patch_spacing_x + "/2", "-" + patch_spacing_y + "/2", "0"],
]
antenna_name = self.name
coordinate_system = self.coordinate_system
sub = self._app.modeler.create_box(
origin=["-" + sub_x + "/2", "-" + sub_y + "/2", "0"],
sizes=[sub_x, sub_y, sub_h],
name="sub_" + antenna_name,
material=self.material,
new_properties={"Coordinate System": coordinate_system},
)
sub.color = (0, 128, 0)
sub.transparency = 0.8
gnd = self._app.modeler.create_rectangle(
orientation=2,
origin=["-" + sub_x + "/2", "-" + sub_y + "/2", "0"],
sizes=[sub_x, sub_y],
name="gnd_" + antenna_name,
new_properties={"Coordinate System": coordinate_system},
)
gnd.color = (255, 128, 65)
gnd.transparency = 0.1
self.object_list[sub.name] = sub
self.object_list[gnd.name] = gnd
for idx, (center, rotation_angle) in enumerate(zip(centers, rotations)):
ant = self._app.modeler.create_circle(
orientation=2,
origin=[0, 0, sub_h],
radius=patch_diameter + "/2",
name=f"ant_{antenna_name}_{idx}",
new_properties={"Coordinate System": coordinate_system},
)
notch1 = self._app.modeler.create_rectangle(
orientation=2,
origin=[patch_diameter + "/2", "-" + notch_width + "/2", sub_h],
sizes=["-" + notch_length, notch_width],
name=f"notch_1_{antenna_name}_{idx}",
new_properties={"Coordinate System": coordinate_system},
)
notch2 = self._app.modeler.create_rectangle(
orientation=2,
origin=["-" + patch_diameter + "/2", "-" + notch_width + "/2", sub_h],
sizes=[notch_length, notch_width],
name=f"notch_2_{antenna_name}_{idx}",
new_properties={"Coordinate System": coordinate_system},
)
self._app.modeler.subtract(ant, [notch1, notch2], False)
self._app.modeler.rotate(ant, "Z", rotation_angle)
self._app.modeler.move(ant, center)
ant.color = (255, 128, 65)
ant.transparency = 0.1
rotation_expr = feed_rotation_angle + "+" + rotation_angle
feed_x = feed_pin_offset + "*cos((" + rotation_expr + ")*pi/180)"
feed_y = feed_pin_offset + "*sin((" + rotation_expr + ")*pi/180)"
feed_origin = [feed_x, feed_y, "0"]
feed_pin = self._app.modeler.create_cylinder(
orientation=2,
origin=feed_origin,
radius=coax_inner_rad,
height=sub_h,
name=f"feed_pin_{antenna_name}_{idx}",
material="pec",
new_properties={"Coordinate System": coordinate_system},
)
feed_pin.color = (255, 128, 65)
feed_coax = self._app.modeler.create_cylinder(
orientation=2,
origin=feed_origin,
radius=coax_inner_rad,
height="-" + feed_length,
name=f"feed_coax_{antenna_name}_{idx}",
material="pec",
new_properties={"Coordinate System": coordinate_system},
)
feed_coax.color = (255, 128, 65)
coax = self._app.modeler.create_cylinder(
orientation=2,
origin=feed_origin,
radius=coax_outer_rad,
height="-" + feed_length,
name=f"coax_{antenna_name}_{idx}",
material="Teflon (tm)",
new_properties={"Coordinate System": coordinate_system},
)
coax.color = (128, 255, 255)
port_cap = self._app.modeler.create_cylinder(
orientation=2,
origin=[feed_x, feed_y, "-" + feed_length],
radius=coax_outer_rad,
height="-" + sub_h + "/10",
name=f"port_cap_{antenna_name}_{idx}",
material="pec",
new_properties={"Coordinate System": coordinate_system},
)
port_cap.color = (132, 132, 193)
port = self._app.modeler.create_circle(
orientation=2,
origin=[feed_x, feed_y, "-" + feed_length],
radius=coax_outer_rad,
name=f"port_{antenna_name}_{idx}",
new_properties={"Coordinate System": coordinate_system},
)
port.color = (128, 0, 0)
void = self._app.modeler.create_circle(
orientation=2,
origin=feed_origin,
radius=coax_outer_rad,
name=f"void_{antenna_name}_{idx}",
new_properties={"Coordinate System": coordinate_system},
)
self._app.modeler.move(
[feed_pin.name, feed_coax.name, coax.name, port_cap.name, port.name, void.name],
center,
)
self._app.modeler.subtract(gnd, void, False)
for obj in [ant, feed_pin, feed_coax, coax, port_cap, port]:
self.object_list[obj.name] = obj
self._app.modeler.move(list(self.object_list.keys()), [pos_x, pos_y, pos_z])
for obj in self.object_list.values():
obj.group_name = antenna_name
return True
def _update_port_sources(self):
if len(self.excitations) != 4:
return None
phases = [
self.element_1_port_phase,
self.element_2_port_phase,
self.element_3_port_phase,
self.element_4_port_phase,
]
assignments = {
port_name: (1, f"{phase}deg") for port_name, phase in zip(sorted(self.excitations.keys()), phases)
}
self._app.edit_sources(assignments)
return None
@pyaedt_function_handler()
def model_disco(self):
pass
@pyaedt_function_handler()
def setup_disco(self):
pass
[docs]
class RectangularPatchInset(CommonPatch):
"""Manages a rectangular patch antenna inset fed.
This class is accessible through the ``Hfss`` object [1]_.
Parameters
----------
frequency : float, optional
Center frequency. The default is ``10.0``.
frequency_unit : str, optional
Frequency units. The default is ``"GHz"``.
material : str, optional
Substrate material. If the material is not defined, a new
material, ``parametrized``, is created. The default is ``"FR4_epoxy"``.
outer_boundary : str, optional
Boundary type to use. The default is ``None``. Options are
``"FEBI"``, ``"PML"``, ``"Radiation"``, and ``None``.
length_unit : str, optional
Length units. The default is ``"mm"``.
substrate_height : float, optional
Substrate height. The default is ``1.575``.
parametrized : bool, optional
Whether to create a parametrized antenna. The default is ``True``.
Returns
-------
:class:`aedt.toolkits.antenna.RectangularPatchInset`
Patch antenna object.
Notes
-----
.. [1] C. Balanis, "Microstrip Antennas," *Antenna Theory*, 2nd Ed. New York: Wiley, 1997.
Examples
--------
>>> from ansys.aedt.toolkits.antenna.backend.antenna_models.patch import RectangularPatchInset
>>> import ansys.aedt.core
>>> app = ansys.aedt.core.Hfss()
>>> oantenna1 = RectangularPatchInset(app)
>>> oantenna1.frequency = 12.0
>>> oantenna1.model_hfss()
>>> oantenna1.setup_hfss()
>>> app.release_desktop(False, False)
"""
_default_input_parameters = {
"name": "",
"origin": [0, 0, 0],
"length_unit": "mm",
"coordinate_system": "Global",
"frequency": 10.0,
"frequency_unit": "GHz",
"material": "FR4_epoxy",
"material_properties": {"permittivity": 4.4},
"outer_boundary": "",
"substrate_height": 1.575,
}
def __init__(self, *args, **kwargs):
CommonPatch.__init__(self, self._default_input_parameters, *args, **kwargs)
self._parameters = self.synthesis()
self.update_synthesis_parameters(self._parameters)
self.antenna_type = "RectangularPatchInset"
[docs]
@pyaedt_function_handler()
def synthesis(self):
"""Antenna synthesis.
Returns
-------
dict
Analytical parameters.
"""
parameters = {}
length_unit = self.length_unit
light_speed = constants.SpeedOfLight # m/s
freq_hz = constants.unit_converter(self.frequency, "Freq", self.frequency_unit, "Hz")
wavelength = light_speed / freq_hz
if self._app and (
self.material in self._app.materials.mat_names_aedt
or self.material in self._app.materials.mat_names_aedt_lower
):
mat_props = self._app.materials[self.material]
permittivity = mat_props.permittivity.value
self._input_parameters.material_properties["permittivity"] = permittivity
elif self.material_properties:
permittivity = self.material_properties["permittivity"]
else:
self._app.logger.warning("Material is not found. Create the material before assigning it.")
return parameters
sub_permittivity = float(permittivity)
patch_width = 3.0e8 / ((2.0 * freq_hz) * math.sqrt((sub_permittivity + 1.0) / 2.0))
sub_meters = constants.unit_converter(self.substrate_height, "Length", self.length_unit, "meter")
eff_permittivity = (sub_permittivity + 1.0) / 2.0 + (sub_permittivity - 1.0) / 2.0 * math.pow(
1.0 + 12.0 * sub_meters / patch_width, -0.5
)
effective_length = 3.0e8 / (2.0 * freq_hz * math.sqrt(eff_permittivity))
top = (eff_permittivity + 0.3) * (patch_width / sub_meters + 0.264)
bottom = (eff_permittivity - 0.258) * (patch_width / sub_meters + 0.8)
delta_length = 0.412 * sub_meters * top / bottom
patch_length = effective_length - 2.0 * delta_length
# eff_WL_meters = wavelength / math.sqrt(eff_permittivity)
k = 2.0 * math.pi / eff_permittivity
g = math.pi * patch_width / (120.0 * math.pi * wavelength) * (1.0 - math.pow(k * sub_meters, 2) / 24)
# impedance at edge of patch
res = 1.0 / (2.0 * g)
inset_distance_meter = patch_length / math.pi * math.asin(math.sqrt(50.0 / res))
# quarterwave_imped = math.sqrt(50.0 * res)
u_strip = self._transmission_line_calculator.microstrip_calculator(sub_meters, sub_permittivity, 50.0, 150.0)
microstrip_width = u_strip[0]
microstrip_length = u_strip[1]
patch_x = constants.unit_converter(patch_width, "Length", "meter", length_unit)
parameters["patch_x"] = patch_x
patch_y = constants.unit_converter(patch_length, "Length", "meter", length_unit)
parameters["patch_y"] = patch_y
sub_h = self.substrate_height
parameters["sub_h"] = sub_h
sub_x = constants.unit_converter(1.5 * patch_width + 6.0 * sub_meters, "Length", "meter", length_unit)
parameters["sub_x"] = sub_x
sub_y = constants.unit_converter(2.1 * (microstrip_length + patch_length / 2), "Length", "meter", length_unit)
parameters["sub_y"] = sub_y
inset_distance = constants.unit_converter(
patch_length / 2 - inset_distance_meter, "Length", "meter", length_unit
)
parameters["inset_distance"] = inset_distance
microstrip_length = constants.unit_converter(microstrip_length, "Length", "meter", length_unit)
microstrip_width = constants.unit_converter(microstrip_width, "Length", "meter", length_unit)
inset_gap = round(microstrip_width / 2, 3)
parameters["inset_gap"] = inset_gap
feed_width = round(microstrip_width, 3)
parameters["feed_width"] = feed_width
feed_length = round(microstrip_length, 3)
parameters["feed_length"] = feed_length
parameters["pos_x"] = self.origin[0]
parameters["pos_y"] = self.origin[1]
parameters["pos_z"] = self.origin[2]
my_keys = list(parameters.keys())
my_keys.sort()
parameters_out = OrderedDict([(i, parameters[i]) for i in my_keys])
return parameters_out
[docs]
@pyaedt_function_handler()
def model_hfss(self):
"""Draw a rectangular patch antenna inset fed.
Once the antenna is created, this method is not used anymore.
"""
if self.object_list:
self._app.logger.warning("This antenna already exists.")
return False
if (
self.material not in self._app.materials.mat_names_aedt
and self.material not in self._app.materials.mat_names_aedt_lower
):
self._app.logger.warning("Material is not found. Create the material before assigning it.")
return False
self.set_variables_in_hfss()
# Map parameters
patch_x = self.synthesis_parameters.patch_x.hfss_variable
patch_y = self.synthesis_parameters.patch_y.hfss_variable
sub_h = self.synthesis_parameters.sub_h.hfss_variable
sub_x = self.synthesis_parameters.sub_x.hfss_variable
sub_y = self.synthesis_parameters.sub_y.hfss_variable
inset_distance = self.synthesis_parameters.inset_distance.hfss_variable
inset_gap = self.synthesis_parameters.inset_gap.hfss_variable
feed_width = self.synthesis_parameters.feed_width.hfss_variable
feed_length = self.synthesis_parameters.feed_length.hfss_variable
pos_x = self.synthesis_parameters.pos_x.hfss_variable
pos_y = self.synthesis_parameters.pos_y.hfss_variable
pos_z = self.synthesis_parameters.pos_z.hfss_variable
antenna_name = self.name
coordinate_system = self.coordinate_system
# Substrate
sub = self._app.modeler.create_box(
origin=["-" + sub_x + "/2", "-" + sub_y + "/2", "0"],
sizes=[sub_x, sub_y, sub_h],
name="sub_" + antenna_name,
material=self.material,
new_properties={"Coordinate System": coordinate_system},
)
sub.color = (0, 128, 0)
sub.transparency = 0.8
# Ground
gnd = self._app.modeler.create_rectangle(
orientation=2,
origin=["-" + sub_x + "/2", "-" + sub_y + "/2", "0"],
sizes=[sub_x, sub_y],
name="gnd_" + antenna_name,
new_properties={"Coordinate System": coordinate_system},
)
gnd.color = (255, 128, 65)
gnd.transparency = 0.1
# Antenna
ant = self._app.modeler.create_rectangle(
orientation=2,
origin=[
"-" + patch_x + "/2",
"-" + patch_y + "/2",
sub_h,
],
sizes=[patch_x, patch_y],
name="ant_" + antenna_name,
new_properties={"Coordinate System": coordinate_system},
)
ant.color = (255, 128, 65)
ant.transparency = 0.1
cutout = self._app.modeler.create_rectangle(
orientation=2,
origin=[
"-" + feed_width + "/2" + "-" + inset_gap,
patch_y + "/2" + "-" + inset_distance,
sub_h,
],
sizes=[feed_width + "+2*" + inset_gap, feed_length],
name="cutout_" + antenna_name,
new_properties={"Coordinate System": coordinate_system},
)
cutout.color = (255, 128, 65)
self._app.modeler.subtract(ant, cutout, False)
feed = self._app.modeler.create_rectangle(
orientation=2,
origin=[
"-" + feed_width + "/2",
patch_y + "/2-" + inset_distance,
sub_h,
],
sizes=[feed_width, feed_length + "+" + inset_distance],
name="feed_" + antenna_name,
new_properties={"Coordinate System": coordinate_system},
)
feed.color = (255, 128, 65)
self._app.modeler.unite([ant, feed])
p1 = self._app.modeler.create_rectangle(
orientation=1,
origin=[
"-" + feed_width + "/2",
patch_y + "/2" + "+" + feed_length,
"0",
],
sizes=[sub_h, feed_width],
name="port_lump_" + antenna_name,
new_properties={"Coordinate System": coordinate_system},
)
p1.color = (255, 128, 65)
self.object_list[sub.name] = sub
self.object_list[gnd.name] = gnd
self.object_list[ant.name] = ant
self.object_list[p1.name] = p1
self._app.modeler.move(list(self.object_list.keys()), [pos_x, pos_y, pos_z])
sub.group_name = antenna_name
gnd.group_name = antenna_name
ant.group_name = antenna_name
p1.group_name = antenna_name
return True
[docs]
@pyaedt_function_handler()
def model_disco(self):
"""Model in PyDiscovery. To be implemented."""
pass
[docs]
@pyaedt_function_handler()
def setup_disco(self):
"""Set up the model in PyDiscovery. To be implemented."""
pass
[docs]
class RectangularPatchEdge(CommonPatch):
"""Manages a rectangular patch edge antenna.
This class is accessible through the ``Hfss`` object [1]_.
Parameters
----------
frequency : float, optional
Center frequency. The default is ``10.0``.
frequency_unit : str, optional
Frequency units. The default is ``"GHz"``.
material : str, optional
Substrate material. If the material is not defined,
a new material, ``parametrized``, is created.
The default is ``"FR4_epoxy"``.
outer_boundary : str, optional
Boundary type to use. The default is ``None``. Options are
``"FEBI"``, ``"PML"``, ``"Radiation"``, and ``None``.
length_unit : str, optional
Length units. The default is ``"mm"``.
substrate_height : float, optional
Substrate height. The default is ``1.575``.
parametrized : bool, optional
Whether to create a parametrized antenna. The default is ``True``.
Returns
-------
:class:`aedt.toolkits.antenna.RectangularPatchEdge`
Patch antenna object.
Notes
-----
.. [1] C. Balanis, "Microstrip Antennas," *Antenna Theory*, 2nd Ed. New York: Wiley, 1997.
Examples
--------
>>> from ansys.aedt.toolkits.antenna.backend.antenna_models.patch import RectangularPatchEdge
>>> import ansys.aedt.core
>>> app = ansys.aedt.core.Hfss()
>>> oantenna1 = RectangularPatchEdge(app)
>>> oantenna1.frequency = 12.0
>>> oantenna1.model_hfss()
>>> oantenna1.setup_hfss()
>>> app.release_desktop(False, False)
"""
_default_input_parameters = {
"name": "",
"origin": [0, 0, 0],
"length_unit": "mm",
"coordinate_system": "Global",
"frequency": 10.0,
"frequency_unit": "GHz",
"material": "FR4_epoxy",
"material_properties": {"permittivity": 4.4},
"outer_boundary": "",
"substrate_height": 1.575,
}
def __init__(self, *args, **kwargs):
CommonPatch.__init__(self, self._default_input_parameters, *args, **kwargs)
self._parameters = self.synthesis()
self.update_synthesis_parameters(self._parameters)
self.antenna_type = "RectangularPatchEdge"
[docs]
@pyaedt_function_handler()
def synthesis(self):
"""Antenna synthesis.
Returns
-------
dict
Analytical parameters.
"""
parameters = {}
length_unit = self.length_unit
light_speed = constants.SpeedOfLight # m/s
freq_hz = constants.unit_converter(self.frequency, "Freq", self.frequency_unit, "Hz")
wavelength = light_speed / freq_hz
if self._app and (
self.material in self._app.materials.mat_names_aedt
or self.material in self._app.materials.mat_names_aedt_lower
):
mat_props = self._app.materials[self.material]
permittivity = mat_props.permittivity.value
self._input_parameters.material_properties["permittivity"] = permittivity
elif self.material_properties:
permittivity = self.material_properties["permittivity"]
else:
self._app.logger.warning("Material is not found. Create the material before assigning it.")
return parameters
sub_permittivity = float(permittivity)
patch_width = 3.0e8 / ((2.0 * freq_hz) * math.sqrt((sub_permittivity + 1.0) / 2.0))
sub_meters = constants.unit_converter(self.substrate_height, "Length", self.length_unit, "meter")
eff_permittivity = (sub_permittivity + 1.0) / 2.0 + (sub_permittivity - 1.0) / 2.0 * math.pow(
1.0 + 12.0 * sub_meters / patch_width, -0.5
)
effective_length = 3.0e8 / (2.0 * freq_hz * math.sqrt(eff_permittivity))
top = (eff_permittivity + 0.3) * (patch_width / sub_meters + 0.264)
bottom = (eff_permittivity - 0.258) * (patch_width / sub_meters + 0.8)
delta_length = 0.412 * sub_meters * top / bottom
patch_length = effective_length - 2.0 * delta_length
# eff_WL_meters = wavelength / math.sqrt(eff_permittivity)
k = 2.0 * math.pi / eff_permittivity
g = math.pi * patch_width / (120.0 * math.pi * wavelength) * (1.0 - math.pow(k * sub_meters, 2) / 24)
# impedance at edge of patch
res = 1.0 / (2.0 * g)
# offset_pin_pos = patch_length / math.pi * math.asin(math.sqrt(50.0 / res))
quarterwave_imped = math.sqrt(50.0 * res)
u_strip1 = self._transmission_line_calculator.microstrip_calculator(
sub_meters, sub_permittivity, quarterwave_imped, 90.0
)
microstrip_edge_width = u_strip1[0]
microstrip_edge_length = u_strip1[1]
u_strip2 = self._transmission_line_calculator.microstrip_calculator(sub_meters, sub_permittivity, 50.0, 150.0)
microstrip_width = u_strip2[0]
microstrip_length = u_strip2[1]
patch_x = constants.unit_converter(patch_width, "Length", "meter", length_unit)
parameters["patch_x"] = patch_x
patch_y = constants.unit_converter(patch_length, "Length", "meter", length_unit)
parameters["patch_y"] = patch_y
sub_h = self.substrate_height
parameters["sub_h"] = sub_h
sub_x = constants.unit_converter(1.5 * patch_width + 6.0 * sub_meters, "Length", "meter", length_unit)
parameters["sub_x"] = sub_x
sub_y = constants.unit_converter(
2.1 * (microstrip_length + microstrip_edge_length + patch_length / 2),
"Length",
"meter",
length_unit,
)
parameters["sub_y"] = sub_y
edge_feed_width = constants.unit_converter(microstrip_edge_width, "Length", "meter", length_unit)
parameters["edge_feed_width"] = edge_feed_width
edge_feed_length = constants.unit_converter(microstrip_edge_length, "Length", "meter", length_unit)
parameters["edge_feed_length"] = edge_feed_length
feed_width = constants.unit_converter(microstrip_width, "Length", "meter", length_unit)
parameters["feed_width"] = feed_width
feed_length = constants.unit_converter(microstrip_length, "Length", "meter", length_unit)
parameters["feed_length"] = feed_length
parameters["pos_x"] = self.origin[0]
parameters["pos_y"] = self.origin[1]
parameters["pos_z"] = self.origin[2]
my_keys = list(parameters.keys())
my_keys.sort()
parameters_out = OrderedDict([(i, parameters[i]) for i in my_keys])
return parameters_out
[docs]
@pyaedt_function_handler()
def model_hfss(self):
"""Draw a rectangular patch edge antenna inset fed.
Once the antenna is created, this method is not used anymore.
"""
if self.object_list:
self._app.logger.warning("This antenna already exists.")
return False
if (
self.material not in self._app.materials.mat_names_aedt
and self.material not in self._app.materials.mat_names_aedt_lower
):
self._app.logger.warning("Material is not found. Create the material before assigning it.")
return False
self.set_variables_in_hfss()
# Map parameters
patch_x = self.synthesis_parameters.patch_x.hfss_variable
patch_y = self.synthesis_parameters.patch_y.hfss_variable
sub_h = self.synthesis_parameters.sub_h.hfss_variable
sub_x = self.synthesis_parameters.sub_x.hfss_variable
sub_y = self.synthesis_parameters.sub_y.hfss_variable
edge_feed_width = self.synthesis_parameters.edge_feed_width.hfss_variable
edge_feed_length = self.synthesis_parameters.edge_feed_length.hfss_variable
feed_width = self.synthesis_parameters.feed_width.hfss_variable
feed_length = self.synthesis_parameters.feed_length.hfss_variable
pos_x = self.synthesis_parameters.pos_x.hfss_variable
pos_y = self.synthesis_parameters.pos_y.hfss_variable
pos_z = self.synthesis_parameters.pos_z.hfss_variable
antenna_name = self.name
coordinate_system = self.coordinate_system
# Substrate
sub = self._app.modeler.create_box(
origin=["-" + sub_x + "/2", "-" + sub_y + "/2", "0"],
sizes=[sub_x, sub_y, sub_h],
name="sub_" + antenna_name,
material=self.material,
new_properties={"Coordinate System": coordinate_system},
)
sub.color = (0, 128, 0)
sub.transparency = 0.8
# Ground
gnd = self._app.modeler.create_rectangle(
orientation=2,
origin=["-" + sub_x + "/2", "-" + sub_y + "/2", "0"],
sizes=[sub_x, sub_y],
name="gnd_" + antenna_name,
new_properties={"Coordinate System": coordinate_system},
)
gnd.color = (255, 128, 65)
gnd.transparency = 0.1
# Antenna
ant = self._app.modeler.create_rectangle(
orientation=2,
origin=[
"-" + patch_x + "/2",
"-" + patch_y + "/2",
sub_h,
],
sizes=[patch_x, patch_y],
name="ant_" + antenna_name,
new_properties={"Coordinate System": coordinate_system},
)
ant.color = (255, 128, 65)
ant.transparency = 0.1
edge_feed = self._app.modeler.create_rectangle(
orientation=2,
origin=[
"-" + edge_feed_width + "/2",
"0",
sub_h,
],
sizes=[edge_feed_width, patch_y + "/2" + "+" + edge_feed_length],
name="cutout_" + antenna_name,
new_properties={"Coordinate System": coordinate_system},
)
edge_feed.color = (255, 128, 65)
feed = self._app.modeler.create_rectangle(
orientation=2,
origin=[
"-" + feed_width + "/2",
patch_y + "/2" + "+" + edge_feed_length,
sub_h,
],
sizes=[feed_width, feed_length],
name="feed_" + antenna_name,
new_properties={"Coordinate System": coordinate_system},
)
feed.color = (255, 128, 65)
self._app.modeler.unite([ant, edge_feed, feed])
p1 = self._app.modeler.create_rectangle(
orientation=1,
origin=[
"-" + feed_width + "/2",
patch_y + "/2" + "+" + edge_feed_length + "+" + feed_length,
"0",
],
sizes=[sub_h, feed_width],
name="port_lump_" + antenna_name,
new_properties={"Coordinate System": coordinate_system},
)
p1.color = (255, 128, 65)
self.object_list[sub.name] = sub
self.object_list[gnd.name] = gnd
self.object_list[ant.name] = ant
self.object_list[p1.name] = p1
self._app.modeler.move(list(self.object_list.keys()), [pos_x, pos_y, pos_z])
sub.group_name = antenna_name
gnd.group_name = antenna_name
ant.group_name = antenna_name
p1.group_name = antenna_name
return True
[docs]
@pyaedt_function_handler()
def model_disco(self):
"""Model in PyDiscovery. To be implemented."""
pass
[docs]
@pyaedt_function_handler()
def setup_disco(self):
"""Set up the model in PyDiscovery. To be implemented."""
pass