Source code for ansys.aedt.toolkits.antenna.backend.antenna_models.custom

# -*- 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 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.patch import CommonPatch


[docs] class GPSPatchCeramic(CommonPatch): """Manages the ACT-derived ceramic GPS patch antenna. This antenna is modeled after the ACT (ANSYS Customization Toolkit) reference design for a GPS L1-band (1.575 GHz) ceramic patch antenna with a coaxial feed. Parameters ---------- *args : list Positional arguments forwarded to :class:`CommonPatch`. **kwargs : dict Keyword arguments forwarded to :class:`CommonPatch`. Examples -------- Create a GPS ceramic patch antenna at the default frequency of 1.575 GHz: >>> from ansys.aedt.toolkits.antenna.backend.antenna_models.custom import GPSPatchCeramic >>> antenna = GPSPatchCeramic() """ _default_input_parameters = { "name": "", "origin": [0, 0, 0], "length_unit": "mm", "coordinate_system": "Global", "frequency": 1.575, "frequency_unit": "GHz", "material": "Ceramic", "material_properties": {"permittivity": 68.0}, "outer_boundary": "", "substrate_height": 2.0, } _reference_frequency_ghz = 1.575 _reference_dimensions_mm = { "patch_x": 12.0, "patch_y": 12.0, "cutout": 1.1, "feed_x": -0.4, "feed_y": 0.9, "coax_inner_rad": 0.167, "coax_outer_rad": 0.565, "feed_length": 5.0, "sub_h": 2.0, "sub_x": 13.0, "sub_y": 13.0, "gnd_x": 60.0, "gnd_y": 60.0, } def __init__(self, *args, **kwargs): """Initialize the GPS ceramic patch antenna and compute synthesis parameters.""" CommonPatch.__init__(self, self._default_input_parameters, *args, **kwargs) self._parameters = self.synthesis() self.update_synthesis_parameters(self._parameters) self.antenna_type = "GPSPatchCeramic"
[docs] @pyaedt_function_handler() def synthesis(self): """Scale the ACT reference geometry from the nominal GPS center frequency. All reference dimensions defined in ``_reference_dimensions_mm`` are scaled by the ratio of the reference frequency (1.575 GHz) to the requested operating frequency and then converted to the active ``length_unit``. Returns ------- collections.OrderedDict Alphabetically sorted mapping of parameter names to their scaled values expressed in ``self.length_unit``. Also includes the position keys ``pos_x``, ``pos_y``, and ``pos_z`` taken directly from ``self.origin``. """ freq_ghz = constants.unit_converter(self.frequency, "Freq", self.frequency_unit, "GHz") scale = self._reference_frequency_ghz / freq_ghz length_unit = self.length_unit parameters = {} for name, value in self._reference_dimensions_mm.items(): scaled_value = value * scale parameters[name] = constants.unit_converter(scaled_value, "Length", "mm", length_unit) 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() return OrderedDict([(i, parameters[i]) for i in my_keys])
def _ensure_material(self): """Ensure the ceramic substrate material exists in the active HFSS project. If the material named by ``self.material`` is not already present in the AEDT material library, a new material is created and its ``permittivity`` and ``dielectric_loss_tangent`` properties are set from ``self.material_properties``. """ if not self._app: return if ( self.material in self._app.materials.mat_names_aedt or self.material in self._app.materials.mat_names_aedt_lower ): return material = self._app.materials.add_material(self.material) if self.material_properties: if "permittivity" in self.material_properties: material.permittivity = self.material_properties["permittivity"] if "dielectric_loss_tangent" in self.material_properties: material.dielectric_loss_tangent = self.material_properties["dielectric_loss_tangent"]
[docs] @pyaedt_function_handler() def model_hfss(self): """Draw the ACT-derived ceramic GPS patch antenna in HFSS. Creates the following objects in the active HFSS design: * **sub** – ceramic dielectric substrate box. * **gnd** – ground-plane rectangle with a coaxial void. * **ant** – rectangular patch with a triangular polarisation cutout. * **feed_pin** – inner conductor cylinder through the substrate. * **feed_coax** – inner conductor extension below the ground plane. * **coax** – Teflon-filled outer coaxial cylinder. * **port_cap** – PEC end-cap closing the coaxial feed. * **port** – circular lumped-port face. All objects are moved to ``self.origin`` and grouped under ``self.name`` before the modeler view is fitted. Returns ------- bool ``True`` when the antenna geometry was created successfully. ``False`` if the antenna already exists in ``self.object_list``. """ if self.object_list: logger.debug("This antenna already exists") return False self._ensure_material() self.set_variables_in_hfss() patch_x = self.synthesis_parameters.patch_x.hfss_variable patch_y = self.synthesis_parameters.patch_y.hfss_variable cutout = self.synthesis_parameters.cutout.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._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_triangle = self._app.modeler.create_polyline( [ ["-" + patch_x + "/2", patch_y + "/2", sub_h], ["-" + patch_x + "/2+" + cutout, patch_y + "/2", sub_h], ["-" + patch_x + "/2", patch_y + "/2-" + cutout, sub_h], ["-" + patch_x + "/2", patch_y + "/2", sub_h], ], cover_surface=True, name="cutout_" + antenna_name, ) # Set coordinate system of polyline cutout_obj = self._app.get_oo_object(self._app.oeditor, cutout_triangle.name) self._app.set_oo_property_value( aedt_object=cutout_obj, object_name="CreatePolyline:1", prop_name="Coordinate System", value=coordinate_system, ) self._app.modeler.subtract(ant, cutout_triangle, False) 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="-1mm", name="port_cap_" + antenna_name, 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="port_" + antenna_name, new_properties={"Coordinate System": coordinate_system}, ) port.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[port.name] = port self._app.modeler.move(list(self.object_list.keys()), [pos_x, pos_y, pos_z]) for antenna_obj in self.object_list.values(): antenna_obj.group_name = antenna_name self._app.modeler.fit_all() return True
[docs] @pyaedt_function_handler() def model_disco(self): """Model the antenna in PyDiscovery. .. note:: Not yet implemented. """ pass
[docs] @pyaedt_function_handler() def setup_disco(self): """Set up the antenna model in PyDiscovery. .. note:: Not yet implemented. """ pass