Source code for ansys.aedt.toolkits.antenna.backend.api

# Copyright (C) 2023 - 2024 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.

import gc
import os.path
import re

# isort: off

from ansys.aedt.toolkits.antenna.backend.models import properties

# isort: on

from ansys.aedt.core import generate_unique_name
from ansys.aedt.core.generic.touchstone_parser import find_touchstone_files
from ansys.aedt.toolkits.common.backend.api import AEDTCommon
from ansys.aedt.toolkits.common.backend.logger_handler import logger

from ansys.aedt.toolkits.antenna.backend import antenna_models


[docs] class ToolkitBackend(AEDTCommon): """Provides methods for controlling the toolkit workflow. This class provides methods for creating an AEDT session, connecting to an existing AEDT session, and synthesizing and creating an antenna in HFSS. Examples -------- >>> from ansys.aedt.toolkits.antenna.backend.api import ToolkitBackend >>> import time >>> toolkit = ToolkitBackend() >>> msg1 = toolkit.launch_aedt() >>> toolkit.wait_to_be_idle() >>> toolkit.get_antenna("BowTie") """ def __init__(self): AEDTCommon.__init__(self, properties) self.properties = properties self.oantenna = None self.antenna_type = None self.available_antennas = [] for name, var in vars(antenna_models).items(): # If the variable is a module, print the module's name if isinstance(var, type): self.available_antennas.append(name)
[docs] def get_antenna(self, antenna, synth_only=False): """Synthesize and create an antenna in HFSS. Parameters ---------- antenna : :class: Type of antenna to create. synth_only : bool, optional Whether to only synthesize the anttena. The default is ``False``. Returns ------- bool ``True`` when successful, ``False`` when failed. Examples -------- >>> from ansys.aedt.toolkits.antenna.backend.api import ToolkitBackend >>> import time >>> toolkit = ToolkitBackend() >>> msg1 = toolkit_api.launch_thread(toolkit.launch_aedt) >>> idle = toolkit_api.wait_to_be_idle() >>> toolkit.get_antenna("BowTie") """ if self.oantenna: logger.debug("Antenna is already created.") return False if antenna not in antenna_models.__dir__(): logger.debug("Antenna is not implemented.") return False if not synth_only and not self.aedtapp: if not self.properties.active_design: logger.debug("Not active design.") return False # Connect to AEDT design self.connect_design("HFSS") if not self.aedtapp: logger.debug("HFSS design is not connected.") return False # Get antenna properties freq_units = self.properties.antenna.synthesis.frequency_unit antenna_module = getattr(antenna_models, antenna) self.properties.antenna.model = antenna self.antenna_type = antenna # Create antenna object with default values self.oantenna = antenna_module( self.aedtapp, frequency_unit=freq_units, length_unit=self.properties.antenna.synthesis.length_unit, ) # Update antenna properties for antenna_prop in self.properties.antenna.synthesis.model_fields: if ( antenna_prop == "frequency" and "start_frequency" in self.oantenna.__dir__() and "stop_frequency" in self.oantenna.__dir__() ): pass elif antenna_prop == "material_properties": if self.properties.antenna.synthesis.material_properties: self.oantenna.material_properties["permittivity"] = ( self.properties.antenna.synthesis.material_properties["permittivity"] ) elif getattr(self.properties.antenna.synthesis, antenna_prop): setattr(self.oantenna, antenna_prop, getattr(self.properties.antenna.synthesis, antenna_prop)) self.oantenna._parameters = self.oantenna.synthesis() self.oantenna.update_synthesis_parameters(self.oantenna._parameters) antenna_parameters = {} oantenna_public_parameters = ( name for name in self.oantenna.synthesis_parameters.__dict__ if not name.startswith("_") ) for param in oantenna_public_parameters: antenna_parameters[param] = self.oantenna.synthesis_parameters.__getattribute__(param).value if not synth_only and not self.properties.antenna.is_created: if not self.oantenna.object_list: if not self.oantenna.name: self.oantenna.name = generate_unique_name(self.antenna_type) self.set_properties({"name": self.oantenna.name}) self.oantenna.init_model() self.oantenna.model_hfss() self.oantenna.setup_hfss() self.properties.antenna.is_created = True if self.properties.antenna.setup.lattice_pair: self.oantenna.create_lattice_pair() if self.properties.antenna.setup.component_3d: self.oantenna.create_3dcomponent(replace=True) if self.properties.antenna.setup.create_setup: freq = float(self.oantenna.frequency) setup = self.aedtapp.create_setup() setup.props["Frequency"] = str(freq) + freq_units if int(self.properties.antenna.setup.sweep) > 0: sweep1 = setup.add_sweep() perc_sweep = (int(self.properties.antenna.setup.sweep)) / 100 sweep1.props["RangeStart"] = str(freq * (1 - perc_sweep)) + freq_units sweep1.props["RangeEnd"] = str(freq * (1 + perc_sweep)) + freq_units sweep1.update() elif synth_only: self.oantenna = None if self.aedtapp: self.aedtapp.save_project() self.properties.antenna.parameters = antenna_parameters self.release_aedt(False, False) return antenna_parameters
[docs] def update_hfss_parameters(self, key: str, val: str) -> bool: """Update parameters in HFSS. Parameters ---------- key : str Key. val : str Value. Returns ------- bool ``True`` when successful, ``False`` when failed. Examples -------- >>> from ansys.aedt.toolkits.antenna.backend.api import ToolkitBackend >>> import time >>> toolkit = ToolkitBackend() >>> msg1 = toolkit_api.launch_thread(toolkit.launch_aedt) >>> idle = toolkit_api.wait_to_be_idle() >>> toolkit.get_antenna("BowTie") >>> msg3 = toolkit.update_hfss_parameters() """ if not self.properties.antenna.parameters_hfss: # pragma: no cover logger.debug("Antenna was not created in HFSS.") return True if not self.aedtapp: # Connect to AEDT design self.connect_design() if not self.aedtapp: # pragma: no cover logger.debug("HFSS design is not connected.") return False if ( self.aedtapp and key in self.properties.antenna.parameters_hfss and self.properties.antenna.parameters_hfss[key] in self.aedtapp.variable_manager.independent_variable_names ): ratio_re = re.compile("|".join(["ratio", "coefficient", "points", "number"])) if "angle" in key: # pragma: no cover if "deg" not in val: val = val + "deg" elif ratio_re.search(key): # pragma: no cover val = val else: if self.properties.antenna.synthesis.length_unit not in val: val = val + self.properties.antenna.synthesis.length_unit hfss_parameter = self.properties.antenna.parameters_hfss[key] self.aedtapp[hfss_parameter] = val new_value = self.properties.antenna.parameters new_value[key] = val self.release_aedt(False, False) return True else: logger.debug("Parameter does not exist.") return False
[docs] def analyze(self): """Analyze the design. Launch analysis in batch. AEDT is released once it is opened. Returns ------- bool ``True`` when successful, ``False`` when failed. Examples -------- >>> import time >>> from ansys.aedt.toolkits.antenna.backend.api import ToolkitBackend >>> toolkit = ToolkitBackend() >>> msg1 = toolkit_api.launch_thread(toolkit.launch_aedt) >>> idle = toolkit_api.wait_to_be_idle() >>> toolkit.get_antenna("BowTie") >>> toolkit.analyze() """ if not self.aedtapp: # Connect to AEDT design self.connect_design() if not self.aedtapp: # pragma: no cover logger.debug("HFSS design is not connected.") return False num_cores = properties.antenna.setup.num_cores self.aedtapp.save_project() self.aedtapp.analyze(num_cores=num_cores) gc.collect() self.release_aedt(False, False) return True
[docs] def scattering_results(self): """Get antenna scattering results. Returns ------- bool ``True`` when successful, ``False`` when failed. """ if not self.aedtapp: # Connect to AEDT design self.connect_design() if not self.aedtapp: # pragma: no cover logger.debug("HFSS design is not connected.") return False sol_data = self.aedtapp.post.get_solution_data() self.release_aedt(False, False) if not sol_data: # pragma: no cover return return sol_data.primary_sweep_values, sol_data.data_db20()
[docs] def export_farfield(self, frequencies=None, setup=None, sphere=None, variations=None, encode=True): """Export far field data and then encode the file if the ``encode`` parameter is enabled. Parameters ---------- frequencies : float, list Frequency value or list of frequencies to compute far field data. The default is ``None,`` in which case all available frequencies are computed. setup : str, optional Name of the setup to use. The default is ``None,`` in which case ``nominal_adaptive`` is used. sphere : 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. encode : bool, optional Whether to encode the file. The default is ``True``. Returns ------- list or dict List of eep files or encoded data. """ if not self.aedtapp: self.connect_design() if self.aedtapp: self.aedtapp.save_project() farfield_exporter = self.aedtapp.get_antenna_data( frequencies=frequencies, setup=setup, sphere=sphere, variations=variations ) if encode: encoded_json_file = None encoded_geometry_files = [] encoded_ffd_files = [] encoded_scattering_file = None metadata_file = farfield_exporter.metadata_file metadata_dir = os.path.dirname(metadata_file) if os.path.isfile(metadata_file): serialized_file = self.serialize_obj_base64(metadata_file) encoded_json_file = serialized_file.decode("utf-8") geometry_path = os.path.abspath(os.path.join(metadata_dir, "geometry")) if os.path.exists(geometry_path): for root, _, files in os.walk(geometry_path): for file in files: if file.lower().endswith(".obj"): geometry_file = os.path.abspath(os.path.join(root, file)) serialized_file = self.serialize_obj_base64(geometry_file) encoded_geometry_files.append(serialized_file.decode("utf-8")) for root, _, files in os.walk(metadata_dir): for file in files: if file.lower().endswith(".ffd"): ffd_file = os.path.abspath(os.path.join(root, file)) serialized_file = self.serialize_obj_base64(ffd_file) encoded_ffd_files.append(serialized_file.decode("utf-8")) sNp_files = find_touchstone_files(metadata_dir) if sNp_files: snP_file = list(sNp_files.keys())[0] serialized_file = self.serialize_obj_base64(sNp_files[snP_file]) encoded_scattering_file = serialized_file.decode("utf-8") self.release_aedt(False, False) return encoded_json_file, encoded_geometry_files, encoded_ffd_files, encoded_scattering_file self.release_aedt(False, False) return farfield_exporter.metadata_file, farfield_exporter.frequencies