From ca577424546317b4cdde0509b1da86398915e762 Mon Sep 17 00:00:00 2001 From: Eduardo Blanco Date: Mon, 1 Sep 2025 19:10:56 +0200 Subject: [PATCH 01/22] Add WavePortObject class --- .../core/modules/boundary/hfss_boundary.py | 274 ++++++++++++++++++ 1 file changed, 274 insertions(+) diff --git a/src/ansys/aedt/core/modules/boundary/hfss_boundary.py b/src/ansys/aedt/core/modules/boundary/hfss_boundary.py index 7379a957e09..e0a9ceb73a3 100644 --- a/src/ansys/aedt/core/modules/boundary/hfss_boundary.py +++ b/src/ansys/aedt/core/modules/boundary/hfss_boundary.py @@ -26,6 +26,7 @@ from ansys.aedt.core.generic.general_methods import pyaedt_function_handler from ansys.aedt.core.modeler.cad.elements_3d import BinaryTreeNode from ansys.aedt.core.modules.boundary.common import BoundaryCommon +from ansys.aedt.core.modules.boundary.common import BoundaryObject from ansys.aedt.core.modules.boundary.common import BoundaryProps @@ -529,3 +530,276 @@ class NearFieldSetup(FieldSetup, object): def __init__(self, app, component_name, props, component_type): FieldSetup.__init__(self, app, component_name, props, component_type) + + +class WavePortObject(BoundaryObject): + """Manages HFSS Wave Port boundary objects. + + This class provides specialized functionality for wave port + boundaries in HFSS, including analytical alignment settings. + + Examples + -------- + >>> from ansys.aedt.core import Hfss + >>> hfss = Hfss() + >>> wave_port = hfss.wave_port( + ... + ... ) + >>> wave_port.set_analytical_alignment(True) + """ + + def __init__(self, app, name, props, btype): + """Initialize a wave port boundary object. + + Parameters + ---------- + app : :class:`ansys.aedt.core.application.analysis_3d.FieldAnalysis3D` + The AEDT application instance. + name : str + Name of the boundary. + props : dict + Dictionary of boundary properties. + btype : str + Type of the boundary. + """ + super().__init__(app, name, props, btype) + + @pyaedt_function_handler() + def set_analytical_alignment(self, u_axis_line=None, analytic_reverse_v=False, coordinate_system="Global", alignment_group=None): + """Set the analytical alignment property for the wave port. + + Parameters + ---------- + u_axis_line : list + List containing start and end points for the U-axis line. + Format: [[x1, y1, z1], [x2, y2, z2]] + analytic_reverse_v : bool, optional + Whether to reverse the V direction. Default is False. + coordinate_system : str, optional + Coordinate system to use. Default is "Global". + alignment_group : int, list or None, optional + Alignment group number(s) for the wave port. If None, the default group is used. + If a single integer is provided, it is applied to all modes. + + Returns + ------- + bool + True if the operation was successful, False otherwise. + + Examples + -------- + >>> u_line = [[0, 0, 0], [1, 0, 0]] + >>> wave_port.set_analytical_alignment(u_line, analytic_reverse_v=True) + True + """ + try: + # Go through all modes and set the alignment group if provided + if alignment_group is None: + alignment_group = [0] * len(self.props["Modes"]) + elif isinstance(alignment_group, int): + alignment_group = [alignment_group] * len(self.props["Modes"]) + elif not (isinstance(alignment_group, list) and all(isinstance(x, (int, float)) for x in alignment_group)): + raise ValueError("alignment_group must be a list of numbers or None.") + if len(alignment_group) != len(self.props["Modes"]): + raise ValueError("alignment_group length must match the number of modes.") + if not (isinstance(u_axis_line, list) and len(u_axis_line) == 2 and all(isinstance(pt, list) and len(pt) == 3 for pt in u_axis_line)): + raise ValueError("u_axis_line must be a list of two 3-element lists.") + if not isinstance(analytic_reverse_v, bool): + raise ValueError("analytic_reverse_v must be a boolean.") + if not isinstance(coordinate_system, str): + raise ValueError("coordinate_system must be a string.") + + for i, mode_key in enumerate(self.props["Modes"]): + self.props["Modes"][mode_key]["AlignmentGroup"] = i + analytic_u_line = {} + analytic_u_line["Coordinate System"] = coordinate_system + analytic_u_line["Start"] = [str(i) + self._app.modeler.model_units for i in u_axis_line[0]] + analytic_u_line["End"] = [str(i) + self._app.modeler.model_units for i in u_axis_line[1]] + self.props["AnalyticULine"] = analytic_u_line + self.props["AnalyticReverseV"] = analytic_reverse_v + self.props["UseAnalyticAlignment"] = True + return self.update() + except Exception as e: + self._app.logger.error( + f"Failed to set analytical alignment: {str(e)}" + ) + return False + + @pyaedt_function_handler() + def set_integration_line_alignment( + self, + integration_lines=None, + coordinate_system="Global", + alignment_groups=None + ): + """Set the integration line alignment property for the wave port modes. + + This method configures integration lines for wave port modes, + which are used for modal excitation and field calculation. At + least the first 2 modes should have integration lines defined, + and at least 1 alignment group should exist. + + Parameters + ---------- + integration_lines : list of lists, optional + List of integration lines for each mode. Each integration + line is defined as [[start_x, start_y, start_z], + [end_x, end_y, end_z]]. If None, integration lines will + be disabled for all modes. + Format: [[[x1, y1, z1], [x2, y2, z2]], [[x3, y3, z3], ...]] + coordinate_system : str, optional + Coordinate system to use for the integration lines. + Default is "Global". + alignment_groups : list of int, optional + Alignment group numbers for each mode. If None, default + groups will be assigned. At least one alignment group + should exist. If a single integer is provided, it will be + applied to all modes with integration lines. + + Returns + ------- + bool + True if the operation was successful, False otherwise. + + Examples + -------- + >>> # Define integration lines for first two modes + >>> int_lines = [ + ... [[0, 0, 0], [10, 0, 0]], # Mode 1 integration line + ... [[0, 0, 0], [0, -9, 0]] # Mode 2 integration line + ... ] + >>> # Mode 1 in group 1, Mode 2 in group 0 + >>> alignment_groups = [1, 0] + >>> wave_port.set_integration_line_alignment( + ... int_lines, "Global", alignment_groups + ... ) + True + + >>> # Disable integration lines for all modes + >>> wave_port.set_integration_line_alignment() + True + """ + try: + num_modes = len(self.props["Modes"]) + + if integration_lines is None: + # Disable integration lines for all modes + for mode_key in self.props["Modes"]: + self.props["Modes"][mode_key]["UseIntLine"] = False + if "IntLine" in self.props["Modes"][mode_key]: + del self.props["Modes"][mode_key]["IntLine"] + return self.update() + + # Validate integration_lines parameter + if not isinstance(integration_lines, list): + raise ValueError( + "integration_lines must be a list of integration line " + "definitions." + ) + + # Ensure at least the first 2 modes have integration lines + if len(integration_lines) < min(2, num_modes): + raise ValueError( + "At least the first 2 modes should have integration " + "lines defined." + ) + + # Validate each integration line format + for i, line in enumerate(integration_lines): + if not (isinstance(line, list) and len(line) == 2 and + all(isinstance(pt, list) and len(pt) == 3 + for pt in line)): + raise ValueError( + f"Integration line {i+1} must be a list of two " + f"3-element lists [[x1,y1,z1], [x2,y2,z2]]." + ) + + # Validate coordinate_system + if not isinstance(coordinate_system, str): + raise ValueError("coordinate_system must be a string.") + + # Handle alignment_groups parameter + if alignment_groups is None: + # Default: modes with integration lines get group 1, + # others get group 0 + alignment_groups = [ + 1 if i < len(integration_lines) else 0 + for i in range(num_modes) + ] + elif isinstance(alignment_groups, int): + # Single group for modes with integration lines + alignment_groups = [ + (alignment_groups if i < len(integration_lines) + else 0) for i in range(num_modes) + ] + elif isinstance(alignment_groups, list): + # Validate alignment_groups list + if not all(isinstance(x, (int, float)) + for x in alignment_groups): + raise ValueError( + "alignment_groups must be a list of integers." + ) + # Extend or truncate to match number of modes + if len(alignment_groups) < num_modes: + alignment_groups.extend( + [0] * (num_modes - len(alignment_groups)) + ) + elif len(alignment_groups) > num_modes: + alignment_groups = alignment_groups[:num_modes] + else: + raise ValueError( + "alignment_groups must be an integer, list of " + "integers, or None." + ) + + # Ensure at least one alignment group exists (non-zero) + if all(group == 0 for group in + alignment_groups[:len(integration_lines)]): + self._app.logger.warning( + "No non-zero alignment groups defined. Setting " + "first mode to alignment group 1." + ) + if len(alignment_groups) > 0: + alignment_groups[0] = 1 + + # Configure each mode + + mode_keys = list(self.props["Modes"].keys()) + for i, mode_key in enumerate(mode_keys): + # Set alignment group + self.props["Modes"][mode_key]["AlignmentGroup"] = ( + alignment_groups[i] + ) + if i < len(integration_lines): + # Mode has an integration line + + # Create IntLine structure + int_line = { + "Coordinate System": coordinate_system, + "Start": [ + f"{coord}{self._app.modeler.model_units}" + for coord in integration_lines[i][0] + ], + "End": [ + f"{coord}{self._app.modeler.model_units}" + for coord in integration_lines[i][1] + ] + } + self.props["Modes"][mode_key]["IntLine"] = ( + int_line + ) + self.props["Modes"][mode_key]["UseIntLine"] = True + else: + # Mode does not have an integration line + self.props["Modes"][mode_key]["UseIntLine"] = ( + False + ) + if "IntLine" in self.props["Modes"][mode_key]: + del self.props["Modes"][mode_key]["IntLine"] + self.props["UseLineModeAlignment"] = True + return self.update() + except Exception as e: + self._app.logger.error( + f"Failed to set integration line alignment: {str(e)}" + ) + return False From 4d1264ea8c09b93dc276a45c74ee65049faea4cd Mon Sep 17 00:00:00 2001 From: Eduardo Blanco Date: Mon, 1 Sep 2025 19:11:19 +0200 Subject: [PATCH 02/22] Use WavePortObject for waveports --- src/ansys/aedt/core/hfss.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/ansys/aedt/core/hfss.py b/src/ansys/aedt/core/hfss.py index 998058efd98..ef668c9c3cc 100644 --- a/src/ansys/aedt/core/hfss.py +++ b/src/ansys/aedt/core/hfss.py @@ -48,6 +48,7 @@ from ansys.aedt.core.generic.numbers_utils import is_number from ansys.aedt.core.generic.settings import settings from ansys.aedt.core.internal.errors import AEDTRuntimeError +from ansys.aedt.core.internal.errors import GrpcApiError from ansys.aedt.core.mixins import CreateBoundaryMixin from ansys.aedt.core.modeler import cad from ansys.aedt.core.modeler.cad.component_array import ComponentArray @@ -56,6 +57,7 @@ from ansys.aedt.core.modules.boundary.common import BoundaryObject from ansys.aedt.core.modules.boundary.hfss_boundary import FarFieldSetup from ansys.aedt.core.modules.boundary.hfss_boundary import NearFieldSetup +from ansys.aedt.core.modules.boundary.hfss_boundary import WavePortObject from ansys.aedt.core.modules.boundary.layout_boundary import NativeComponentObject from ansys.aedt.core.modules.setup_templates import SetupKeys @@ -240,6 +242,19 @@ def _init_from_design(self, *args, **kwargs): @pyaedt_function_handler # NOTE: Extend Mixin behaviour to handle near field setups def _create_boundary(self, name, props, boundary_type): + # Wave Port cases - return specialized WavePortObject + if boundary_type == "Wave Port": + try: + bound = WavePortObject(self, name, props, boundary_type) + if not bound.create(): + raise AEDTRuntimeError(f"Failed to create boundary {boundary_type} {name}") + + self._boundaries[bound.name] = bound + self.logger.info(f"Boundary {boundary_type} {name} has been created.") + return bound + except GrpcApiError as e: + raise AEDTRuntimeError(f"Failed to create boundary {boundary_type} {name}") from e + # No-near field cases if boundary_type not in ( "NearFieldSphere", From 363abb2479e7d1845268b54a54ffd740d5bb4c07 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 1 Sep 2025 17:13:11 +0000 Subject: [PATCH 03/22] CHORE: Auto fixes from pre-commit hooks --- src/ansys/aedt/core/hfss.py | 2 +- .../core/modules/boundary/hfss_boundary.py | 132 +++++++----------- 2 files changed, 48 insertions(+), 86 deletions(-) diff --git a/src/ansys/aedt/core/hfss.py b/src/ansys/aedt/core/hfss.py index ef668c9c3cc..0d91b9ebdac 100644 --- a/src/ansys/aedt/core/hfss.py +++ b/src/ansys/aedt/core/hfss.py @@ -254,7 +254,7 @@ def _create_boundary(self, name, props, boundary_type): return bound except GrpcApiError as e: raise AEDTRuntimeError(f"Failed to create boundary {boundary_type} {name}") from e - + # No-near field cases if boundary_type not in ( "NearFieldSphere", diff --git a/src/ansys/aedt/core/modules/boundary/hfss_boundary.py b/src/ansys/aedt/core/modules/boundary/hfss_boundary.py index e0a9ceb73a3..89c96a47f0d 100644 --- a/src/ansys/aedt/core/modules/boundary/hfss_boundary.py +++ b/src/ansys/aedt/core/modules/boundary/hfss_boundary.py @@ -534,10 +534,10 @@ def __init__(self, app, component_name, props, component_type): class WavePortObject(BoundaryObject): """Manages HFSS Wave Port boundary objects. - - This class provides specialized functionality for wave port + + This class provides specialized functionality for wave port boundaries in HFSS, including analytical alignment settings. - + Examples -------- >>> from ansys.aedt.core import Hfss @@ -550,7 +550,7 @@ class WavePortObject(BoundaryObject): def __init__(self, app, name, props, btype): """Initialize a wave port boundary object. - + Parameters ---------- app : :class:`ansys.aedt.core.application.analysis_3d.FieldAnalysis3D` @@ -563,11 +563,13 @@ def __init__(self, app, name, props, btype): Type of the boundary. """ super().__init__(app, name, props, btype) - + @pyaedt_function_handler() - def set_analytical_alignment(self, u_axis_line=None, analytic_reverse_v=False, coordinate_system="Global", alignment_group=None): + def set_analytical_alignment( + self, u_axis_line=None, analytic_reverse_v=False, coordinate_system="Global", alignment_group=None + ): """Set the analytical alignment property for the wave port. - + Parameters ---------- u_axis_line : list @@ -585,7 +587,7 @@ def set_analytical_alignment(self, u_axis_line=None, analytic_reverse_v=False, c ------- bool True if the operation was successful, False otherwise. - + Examples -------- >>> u_line = [[0, 0, 0], [1, 0, 0]] @@ -602,7 +604,11 @@ def set_analytical_alignment(self, u_axis_line=None, analytic_reverse_v=False, c raise ValueError("alignment_group must be a list of numbers or None.") if len(alignment_group) != len(self.props["Modes"]): raise ValueError("alignment_group length must match the number of modes.") - if not (isinstance(u_axis_line, list) and len(u_axis_line) == 2 and all(isinstance(pt, list) and len(pt) == 3 for pt in u_axis_line)): + if not ( + isinstance(u_axis_line, list) + and len(u_axis_line) == 2 + and all(isinstance(pt, list) and len(pt) == 3 for pt in u_axis_line) + ): raise ValueError("u_axis_line must be a list of two 3-element lists.") if not isinstance(analytic_reverse_v, bool): raise ValueError("analytic_reverse_v must be a boolean.") @@ -620,18 +626,11 @@ def set_analytical_alignment(self, u_axis_line=None, analytic_reverse_v=False, c self.props["UseAnalyticAlignment"] = True return self.update() except Exception as e: - self._app.logger.error( - f"Failed to set analytical alignment: {str(e)}" - ) + self._app.logger.error(f"Failed to set analytical alignment: {str(e)}") return False - + @pyaedt_function_handler() - def set_integration_line_alignment( - self, - integration_lines=None, - coordinate_system="Global", - alignment_groups=None - ): + def set_integration_line_alignment(self, integration_lines=None, coordinate_system="Global", alignment_groups=None): """Set the integration line alignment property for the wave port modes. This method configures integration lines for wave port modes, @@ -665,14 +664,12 @@ def set_integration_line_alignment( -------- >>> # Define integration lines for first two modes >>> int_lines = [ - ... [[0, 0, 0], [10, 0, 0]], # Mode 1 integration line - ... [[0, 0, 0], [0, -9, 0]] # Mode 2 integration line + ... [[0, 0, 0], [10, 0, 0]], # Mode 1 integration line + ... [[0, 0, 0], [0, -9, 0]], # Mode 2 integration line ... ] >>> # Mode 1 in group 1, Mode 2 in group 0 >>> alignment_groups = [1, 0] - >>> wave_port.set_integration_line_alignment( - ... int_lines, "Global", alignment_groups - ... ) + >>> wave_port.set_integration_line_alignment(int_lines, "Global", alignment_groups) True >>> # Disable integration lines for all modes @@ -681,7 +678,7 @@ def set_integration_line_alignment( """ try: num_modes = len(self.props["Modes"]) - + if integration_lines is None: # Disable integration lines for all modes for mode_key in self.props["Modes"]: @@ -692,114 +689,79 @@ def set_integration_line_alignment( # Validate integration_lines parameter if not isinstance(integration_lines, list): - raise ValueError( - "integration_lines must be a list of integration line " - "definitions." - ) + raise ValueError("integration_lines must be a list of integration line definitions.") # Ensure at least the first 2 modes have integration lines if len(integration_lines) < min(2, num_modes): - raise ValueError( - "At least the first 2 modes should have integration " - "lines defined." - ) + raise ValueError("At least the first 2 modes should have integration lines defined.") # Validate each integration line format for i, line in enumerate(integration_lines): - if not (isinstance(line, list) and len(line) == 2 and - all(isinstance(pt, list) and len(pt) == 3 - for pt in line)): + if not ( + isinstance(line, list) + and len(line) == 2 + and all(isinstance(pt, list) and len(pt) == 3 for pt in line) + ): raise ValueError( - f"Integration line {i+1} must be a list of two " - f"3-element lists [[x1,y1,z1], [x2,y2,z2]]." + f"Integration line {i + 1} must be a list of two 3-element lists [[x1,y1,z1], [x2,y2,z2]]." ) # Validate coordinate_system if not isinstance(coordinate_system, str): raise ValueError("coordinate_system must be a string.") - + # Handle alignment_groups parameter if alignment_groups is None: # Default: modes with integration lines get group 1, # others get group 0 - alignment_groups = [ - 1 if i < len(integration_lines) else 0 - for i in range(num_modes) - ] + alignment_groups = [1 if i < len(integration_lines) else 0 for i in range(num_modes)] elif isinstance(alignment_groups, int): # Single group for modes with integration lines - alignment_groups = [ - (alignment_groups if i < len(integration_lines) - else 0) for i in range(num_modes) - ] + alignment_groups = [(alignment_groups if i < len(integration_lines) else 0) for i in range(num_modes)] elif isinstance(alignment_groups, list): # Validate alignment_groups list - if not all(isinstance(x, (int, float)) - for x in alignment_groups): - raise ValueError( - "alignment_groups must be a list of integers." - ) + if not all(isinstance(x, (int, float)) for x in alignment_groups): + raise ValueError("alignment_groups must be a list of integers.") # Extend or truncate to match number of modes if len(alignment_groups) < num_modes: - alignment_groups.extend( - [0] * (num_modes - len(alignment_groups)) - ) + alignment_groups.extend([0] * (num_modes - len(alignment_groups))) elif len(alignment_groups) > num_modes: alignment_groups = alignment_groups[:num_modes] else: - raise ValueError( - "alignment_groups must be an integer, list of " - "integers, or None." - ) + raise ValueError("alignment_groups must be an integer, list of integers, or None.") # Ensure at least one alignment group exists (non-zero) - if all(group == 0 for group in - alignment_groups[:len(integration_lines)]): + if all(group == 0 for group in alignment_groups[: len(integration_lines)]): self._app.logger.warning( - "No non-zero alignment groups defined. Setting " - "first mode to alignment group 1." + "No non-zero alignment groups defined. Setting first mode to alignment group 1." ) if len(alignment_groups) > 0: alignment_groups[0] = 1 - + # Configure each mode - + mode_keys = list(self.props["Modes"].keys()) for i, mode_key in enumerate(mode_keys): # Set alignment group - self.props["Modes"][mode_key]["AlignmentGroup"] = ( - alignment_groups[i] - ) + self.props["Modes"][mode_key]["AlignmentGroup"] = alignment_groups[i] if i < len(integration_lines): # Mode has an integration line # Create IntLine structure int_line = { "Coordinate System": coordinate_system, - "Start": [ - f"{coord}{self._app.modeler.model_units}" - for coord in integration_lines[i][0] - ], - "End": [ - f"{coord}{self._app.modeler.model_units}" - for coord in integration_lines[i][1] - ] + "Start": [f"{coord}{self._app.modeler.model_units}" for coord in integration_lines[i][0]], + "End": [f"{coord}{self._app.modeler.model_units}" for coord in integration_lines[i][1]], } - self.props["Modes"][mode_key]["IntLine"] = ( - int_line - ) + self.props["Modes"][mode_key]["IntLine"] = int_line self.props["Modes"][mode_key]["UseIntLine"] = True else: # Mode does not have an integration line - self.props["Modes"][mode_key]["UseIntLine"] = ( - False - ) + self.props["Modes"][mode_key]["UseIntLine"] = False if "IntLine" in self.props["Modes"][mode_key]: del self.props["Modes"][mode_key]["IntLine"] self.props["UseLineModeAlignment"] = True return self.update() except Exception as e: - self._app.logger.error( - f"Failed to set integration line alignment: {str(e)}" - ) + self._app.logger.error(f"Failed to set integration line alignment: {str(e)}") return False From f6a330d67fe8ee4af0f60b45f3472de5fbe0a711 Mon Sep 17 00:00:00 2001 From: pyansys-ci-bot <92810346+pyansys-ci-bot@users.noreply.github.com> Date: Tue, 2 Sep 2025 06:53:47 +0000 Subject: [PATCH 04/22] chore: adding changelog file 6595.added.md [dependabot-skip] --- doc/changelog.d/6595.added.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/changelog.d/6595.added.md diff --git a/doc/changelog.d/6595.added.md b/doc/changelog.d/6595.added.md new file mode 100644 index 00000000000..5d25fbe24d0 --- /dev/null +++ b/doc/changelog.d/6595.added.md @@ -0,0 +1 @@ +Waveport object alignment From 0d21f957d55be1536aec713f66865ccb9c75166c Mon Sep 17 00:00:00 2001 From: Eduardo Blanco Date: Tue, 2 Sep 2025 10:49:59 +0200 Subject: [PATCH 05/22] Added set mode polarity using integration lines --- .../core/modules/boundary/hfss_boundary.py | 146 +++++++++++++++++- 1 file changed, 143 insertions(+), 3 deletions(-) diff --git a/src/ansys/aedt/core/modules/boundary/hfss_boundary.py b/src/ansys/aedt/core/modules/boundary/hfss_boundary.py index 89c96a47f0d..c18aa805e45 100644 --- a/src/ansys/aedt/core/modules/boundary/hfss_boundary.py +++ b/src/ansys/aedt/core/modules/boundary/hfss_boundary.py @@ -630,7 +630,7 @@ def set_analytical_alignment( return False @pyaedt_function_handler() - def set_integration_line_alignment(self, integration_lines=None, coordinate_system="Global", alignment_groups=None): + def set_alignment_integration_line(self, integration_lines=None, coordinate_system="Global", alignment_groups=None): """Set the integration line alignment property for the wave port modes. This method configures integration lines for wave port modes, @@ -669,11 +669,11 @@ def set_integration_line_alignment(self, integration_lines=None, coordinate_syst ... ] >>> # Mode 1 in group 1, Mode 2 in group 0 >>> alignment_groups = [1, 0] - >>> wave_port.set_integration_line_alignment(int_lines, "Global", alignment_groups) + >>> wave_port.set_alignment_integration_line(int_lines, "Global", alignment_groups) True >>> # Disable integration lines for all modes - >>> wave_port.set_integration_line_alignment() + >>> wave_port.set_alignment_integration_line() True """ try: @@ -765,3 +765,143 @@ def set_integration_line_alignment(self, integration_lines=None, coordinate_syst except Exception as e: self._app.logger.error(f"Failed to set integration line alignment: {str(e)}") return False + + @pyaedt_function_handler() + def set_polarity_integration_line( + self, integration_lines=None, coordinate_system="Global" + ): + """Set polarity integration lines for the wave port modes. + + This method configures integration lines for wave port modes + with polarity alignment. When integration lines are provided, + they are used to define the field polarization direction for + each mode. The alignment mode is set to polarity + (UseLineModeAlignment=False). + + Parameters + ---------- + integration_lines : list of lists, optional + List of integration lines for each mode. Each integration + line is defined as [[start_x, start_y, start_z], + [end_x, end_y, end_z]]. If None, integration lines will + be disabled for all modes. + Format: [[[x1, y1, z1], [x2, y2, z2]], [[x3, y3, z3], ...]] + coordinate_system : str, optional + Coordinate system to use for the integration lines. + Default is "Global". + + Returns + ------- + bool + True if the operation was successful, False otherwise. + + Examples + -------- + >>> # Define integration lines for modes + >>> int_lines = [ + ... [[0, 0, 0], [10, 0, 0]], # Mode 1 integration line + ... [[0, 0, 0], [0, 10, 0]], # Mode 2 integration line + ... ] + >>> wave_port.set_polarity_integration_line( + ... int_lines, "Global" + ... ) + True + + >>> # Disable integration lines for all modes + >>> wave_port.set_polarity_integration_line() + True + """ + try: + # Set UseLineModeAlignment to False for polarity mode + self.props["UseLineModeAlignment"] = False + self.props["UseAnalyticAlignment"] = False + + if integration_lines is None: + return self.update() + + # Normalize integration_lines to handle single mode case + if not isinstance(integration_lines, list): + raise ValueError( + "integration_lines must be a list of integration line definitions." + ) + + # If not a list of lists, assume it's a single integration + # line for first mode + if ( + len(integration_lines) > 0 + and not isinstance(integration_lines[0], list) + ): + integration_lines = [integration_lines] + elif ( + len(integration_lines) > 0 + and len(integration_lines[0]) == 2 + and isinstance(integration_lines[0][0], (int, float)) + ): + integration_lines = [integration_lines] + + # Validate each integration line format + for i, line in enumerate(integration_lines): + if not ( + isinstance(line, list) + and len(line) == 2 + and all( + isinstance(pt, list) and len(pt) == 3 + for pt in line + ) + ): + raise ValueError( + f"Integration line {i + 1} must be a list of two 3-element lists [[x1,y1,z1], [x2,y2,z2]]." + ) + + # Validate coordinate_system + if not isinstance(coordinate_system, str): + raise ValueError( + "coordinate_system must be a string." + ) + + # Configure each mode + mode_keys = list(self.props["Modes"].keys()) + for i, mode_key in enumerate(mode_keys): + # Set AlignmentGroup to 0 for polarity mode + mode_props = self.props["Modes"][mode_key] + mode_props["AlignmentGroup"] = 0 + + if i < len(integration_lines): + # Mode has an integration line + start = [ + ( + str(coord) + self._app.modeler.model_units + if isinstance(coord, (int, float)) + else coord + ) + for coord in integration_lines[i][0] + ] + stop = [ + ( + str(coord) + self._app.modeler.model_units + if isinstance(coord, (int, float)) + else coord + ) + for coord in integration_lines[i][1] + ] + + # Create IntLine structure + int_line = { + "Coordinate System": coordinate_system, + "Start": start, + "End": stop, + } + mode_props["IntLine"] = int_line + mode_props["UseIntLine"] = True + else: + # Mode does not have an integration line + mode_props["UseIntLine"] = False + if "IntLine" in mode_props: + del mode_props["IntLine"] + + return self.update() + except Exception as e: + self._app.logger.error( + f"Failed to set polarity integration lines: {str(e)}" + ) + return False From 836fb27d276f0f79d7bb01e7c16f7617a8fa33de Mon Sep 17 00:00:00 2001 From: Eduardo Blanco Date: Tue, 2 Sep 2025 13:23:41 +0200 Subject: [PATCH 06/22] Add filter mode reporter property + specify wave direction --- .../core/modules/boundary/hfss_boundary.py | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/src/ansys/aedt/core/modules/boundary/hfss_boundary.py b/src/ansys/aedt/core/modules/boundary/hfss_boundary.py index c18aa805e45..aed2032ffe6 100644 --- a/src/ansys/aedt/core/modules/boundary/hfss_boundary.py +++ b/src/ansys/aedt/core/modules/boundary/hfss_boundary.py @@ -905,3 +905,95 @@ def set_polarity_integration_line( f"Failed to set polarity integration lines: {str(e)}" ) return False + + @property + def filter_modes_reporter(self): + """Get the reporter filter setting for each mode. + + Returns + ------- + list of bool + List of boolean values indicating whether each mode is + filtered in the reporter. + """ + return self.props["ReporterFilter"] + + @filter_modes_reporter.setter + def filter_modes_reporter(self, value): + """Set the reporter filter setting for wave port modes. + + Parameters + ---------- + value : bool or list of bool + Boolean value(s) to set for the reporter filter. If a + single boolean is provided, it will be applied to all + modes. If a list is provided, it must match the number + of modes. + + Examples + -------- + >>> # Set all modes to be filtered + >>> wave_port.filter_modes_reporter = True + + >>> # Set specific filter values for each mode + >>> wave_port.filter_modes_reporter = [True, False, True] + """ + try: + num_modes = len(self.props["Modes"]) + show_reporter_filter = True + if isinstance(value, bool): + # Single boolean value - apply to all modes + filter_values = [value] * num_modes + # In case all values are the same, we hide the Reporter Filter + show_reporter_filter = False + elif isinstance(value, list): + # List of boolean values + if not all(isinstance(v, bool) for v in value): + raise ValueError( + "All values in the list must be boolean." + ) + if len(value) != num_modes: + raise ValueError( + f"List length ({len(value)}) must match the " + f"number of modes ({num_modes})." + ) + filter_values = value + else: + raise ValueError( + "Value must be a boolean or a list of booleans." + ) + self.props["ShowReporterFilter"] = show_reporter_filter + # Apply the filter values to each mode + self.props["ReporterFilter"] = filter_values + + self.update() + except Exception as e: + self._app.logger.error( + f"Failed to set filter modes reporter: {str(e)}" + ) + raise + + @property + def specify_wave_direction(self): + """Get the 'Specify Wave Direction' property. + + Returns + ------- + bool + Whether the wave direction is specified. + """ + return self.properties["Specify Wave Direction"] + + @specify_wave_direction.setter + def specify_wave_direction(self, value): + """Set the 'Specify Wave Direction' property. + + Parameters + ---------- + value : bool + Whether to specify the wave direction. + """ + if value == self.properties["Specify Wave Direction"]: + return value + self.properties["Specify Wave Direction"] = value + self.update() From 92c12428ab6e094625599a3e032566bd56db2e01 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 2 Sep 2025 11:24:34 +0000 Subject: [PATCH 07/22] CHORE: Auto fixes from pre-commit hooks --- .../core/modules/boundary/hfss_boundary.py | 59 ++++--------------- 1 file changed, 13 insertions(+), 46 deletions(-) diff --git a/src/ansys/aedt/core/modules/boundary/hfss_boundary.py b/src/ansys/aedt/core/modules/boundary/hfss_boundary.py index aed2032ffe6..3063b693547 100644 --- a/src/ansys/aedt/core/modules/boundary/hfss_boundary.py +++ b/src/ansys/aedt/core/modules/boundary/hfss_boundary.py @@ -767,9 +767,7 @@ def set_alignment_integration_line(self, integration_lines=None, coordinate_syst return False @pyaedt_function_handler() - def set_polarity_integration_line( - self, integration_lines=None, coordinate_system="Global" - ): + def set_polarity_integration_line(self, integration_lines=None, coordinate_system="Global"): """Set polarity integration lines for the wave port modes. This method configures integration lines for wave port modes @@ -802,9 +800,7 @@ def set_polarity_integration_line( ... [[0, 0, 0], [10, 0, 0]], # Mode 1 integration line ... [[0, 0, 0], [0, 10, 0]], # Mode 2 integration line ... ] - >>> wave_port.set_polarity_integration_line( - ... int_lines, "Global" - ... ) + >>> wave_port.set_polarity_integration_line(int_lines, "Global") True >>> # Disable integration lines for all modes @@ -821,16 +817,11 @@ def set_polarity_integration_line( # Normalize integration_lines to handle single mode case if not isinstance(integration_lines, list): - raise ValueError( - "integration_lines must be a list of integration line definitions." - ) + raise ValueError("integration_lines must be a list of integration line definitions.") # If not a list of lists, assume it's a single integration # line for first mode - if ( - len(integration_lines) > 0 - and not isinstance(integration_lines[0], list) - ): + if len(integration_lines) > 0 and not isinstance(integration_lines[0], list): integration_lines = [integration_lines] elif ( len(integration_lines) > 0 @@ -844,10 +835,7 @@ def set_polarity_integration_line( if not ( isinstance(line, list) and len(line) == 2 - and all( - isinstance(pt, list) and len(pt) == 3 - for pt in line - ) + and all(isinstance(pt, list) and len(pt) == 3 for pt in line) ): raise ValueError( f"Integration line {i + 1} must be a list of two 3-element lists [[x1,y1,z1], [x2,y2,z2]]." @@ -855,9 +843,7 @@ def set_polarity_integration_line( # Validate coordinate_system if not isinstance(coordinate_system, str): - raise ValueError( - "coordinate_system must be a string." - ) + raise ValueError("coordinate_system must be a string.") # Configure each mode mode_keys = list(self.props["Modes"].keys()) @@ -869,19 +855,11 @@ def set_polarity_integration_line( if i < len(integration_lines): # Mode has an integration line start = [ - ( - str(coord) + self._app.modeler.model_units - if isinstance(coord, (int, float)) - else coord - ) + (str(coord) + self._app.modeler.model_units if isinstance(coord, (int, float)) else coord) for coord in integration_lines[i][0] ] stop = [ - ( - str(coord) + self._app.modeler.model_units - if isinstance(coord, (int, float)) - else coord - ) + (str(coord) + self._app.modeler.model_units if isinstance(coord, (int, float)) else coord) for coord in integration_lines[i][1] ] @@ -901,9 +879,7 @@ def set_polarity_integration_line( return self.update() except Exception as e: - self._app.logger.error( - f"Failed to set polarity integration lines: {str(e)}" - ) + self._app.logger.error(f"Failed to set polarity integration lines: {str(e)}") return False @property @@ -949,28 +925,19 @@ def filter_modes_reporter(self, value): elif isinstance(value, list): # List of boolean values if not all(isinstance(v, bool) for v in value): - raise ValueError( - "All values in the list must be boolean." - ) + raise ValueError("All values in the list must be boolean.") if len(value) != num_modes: - raise ValueError( - f"List length ({len(value)}) must match the " - f"number of modes ({num_modes})." - ) + raise ValueError(f"List length ({len(value)}) must match the number of modes ({num_modes}).") filter_values = value else: - raise ValueError( - "Value must be a boolean or a list of booleans." - ) + raise ValueError("Value must be a boolean or a list of booleans.") self.props["ShowReporterFilter"] = show_reporter_filter # Apply the filter values to each mode self.props["ReporterFilter"] = filter_values self.update() except Exception as e: - self._app.logger.error( - f"Failed to set filter modes reporter: {str(e)}" - ) + self._app.logger.error(f"Failed to set filter modes reporter: {str(e)}") raise @property From c31e54fed822ffb4debe19beb7244d92bf624300 Mon Sep 17 00:00:00 2001 From: Eduardo Blanco Date: Wed, 3 Sep 2025 11:10:52 +0200 Subject: [PATCH 08/22] Added properties --- .../core/modules/boundary/hfss_boundary.py | 288 +++++++++++++++++- 1 file changed, 284 insertions(+), 4 deletions(-) diff --git a/src/ansys/aedt/core/modules/boundary/hfss_boundary.py b/src/ansys/aedt/core/modules/boundary/hfss_boundary.py index aed2032ffe6..d9d9b488700 100644 --- a/src/ansys/aedt/core/modules/boundary/hfss_boundary.py +++ b/src/ansys/aedt/core/modules/boundary/hfss_boundary.py @@ -550,7 +550,7 @@ class WavePortObject(BoundaryObject): def __init__(self, app, name, props, btype): """Initialize a wave port boundary object. - + Parameters ---------- app : :class:`ansys.aedt.core.application.analysis_3d.FieldAnalysis3D` @@ -677,7 +677,7 @@ def set_alignment_integration_line(self, integration_lines=None, coordinate_syst True """ try: - num_modes = len(self.props["Modes"]) + num_modes = self.properties["Num Modes"] if integration_lines is None: # Disable integration lines for all modes @@ -939,7 +939,7 @@ def filter_modes_reporter(self, value): >>> wave_port.filter_modes_reporter = [True, False, True] """ try: - num_modes = len(self.props["Modes"]) + num_modes = self.properties["Num Modes"] show_reporter_filter = True if isinstance(value, bool): # Single boolean value - apply to all modes @@ -996,4 +996,284 @@ def specify_wave_direction(self, value): if value == self.properties["Specify Wave Direction"]: return value self.properties["Specify Wave Direction"] = value - self.update() + + @property + def deembed(self): + """Get the de-embedding property of the wave port. + Returns + ------- + bool + Whether de-embedding is enabled. + """ + return self.properties["Deembed"] + + @deembed.setter + def deembed(self, value): + """Set the de-embedding property of the wave port. + Parameters + ---------- + value : bool + Whether to enable de-embedding. + """ + if value == self.properties["Deembed"]: + return value + self.properties["Deembed"] = value + + @property + def renorm_all_modes(self): + """Renormalize all modes property + Returns + ------- + bool + Whether renormalization of all modes is enabled. + """ + return self.properties["Renorm All Modes"] + @renorm_all_modes.setter + def renorm_all_modes(self, value): + """Set the renormalization property for all modes. + Parameters + ---------- + value : bool + Whether to enable renormalization for all modes. + """ + if value == self.properties["Renorm All Modes"]: + return value + self.properties["Renorm All Modes"] = value + + @property + def renorm_impedance_type(self): + """Get the renormalization impedance type + Returns + ------- + str + The type of renormalization impedance. + """ + return self.properties["Renorm Impedance Type"] + + @renorm_impedance_type.setter + def renorm_impedance_type(self, value): + """Set the renormalization impedance type. + Parameters + ---------- + value : str + The type of renormalization impedance. It can be a type contained in the list of choices. + """ + if value not in self.properties["Renorm Impedance Type/Choices"]: + raise ValueError(f"Renorm Impedance Type must be one of {self.properties['Renorm Impedance Type/Choices']}.") + if value == self.properties["Renorm Impedance Type"]: + return value + self.properties["Renorm Impedance Type"] = value + @property + def renorm_impedance(self): + """Get the renormalization impedance value. + Returns + ------- + str + The renormalization impedance value. + """ + return self.properties["Renorm Imped"] + @renorm_impedance.setter + def renorm_impedance(self, value): + """Set the renormalization impedance value. + Parameters + ---------- + value : str or int + The renormalization impedance value. Must be a string with units (e.g., "50ohm") or a int (defaults to "ohm"). + """ + allowed_units = ["uOhm", "mOhm", "ohm", "kOhm", "megohm", "GOhm"] + if self.renorm_impedance_type != "Impedance": + raise ValueError("Renorm Impedance can be set only if Renorm Impedance Type is 'Impedance'.") + + if isinstance(value, int): + value_str = f"{value}ohm" + elif isinstance(value, float): + raise ValueError("Renorm Impedance must be a string with units or an int.") + elif isinstance(value, str): + value_str = value.replace(" ", "") + if not any(value_str.endswith(unit) for unit in allowed_units): + raise ValueError(f"Renorm Impedance must end with one of {allowed_units}.") + else: + raise ValueError("Renorm Impedance must be a string with units or a float.") + + if isinstance(value, str): + value_str = value.replace(" ", "") + else: + value_str = f"{value}ohm" + + if not any(value_str.endswith(unit) for unit in allowed_units): + raise ValueError(f"Renorm Impedance must end with one of {allowed_units}.") + + if value_str == self.properties["Renorm Imped"]: + return value_str + self.properties["Renorm Imped"] = value_str + + # NOTE: The following properties are write-only as HFSS does not return their values. + # Attempting to read them will raise NotImplementedError. + # This is a workaround until the API provides a way to read these properties. + # Also, these properties reset to default + @property + def rlc_type(self): + """Get the RLC type property.""" + raise NotImplementedError("Getter for 'rlc_type' is not implemented. Use the setter only.") + + @rlc_type.setter + def rlc_type(self, value): + """Set the RLC type property. + Parameters + ---------- + value : str + The type of RLC circuit. + """ + if self.renorm_impedance_type != "RLC": + raise ValueError("RLC Type can be set only if Renorm Impedance Type is 'RLC'.") + allowed_types = ["Serial", "Parallel"] + if value not in allowed_types: + raise ValueError(f"RLC Type must be one of {allowed_types}.") + self.properties["RLC Type"] = value + + @property + def use_resistance(self): + """Get the 'Use Resistance' property.""" + raise NotImplementedError("Getter for 'use_resistance' is not implemented. Use the setter only.") + + @use_resistance.setter + def use_resistance(self, value): + """Set the 'Use Resistance' property. + Parameters + ---------- + value : bool + Whether to use resistance in the RLC circuit. + """ + if not isinstance(value, bool): + raise ValueError("Use Resistance must be a boolean value.") + if self.renorm_impedance_type != "RLC": + raise ValueError("Use Resistance can be set only if Renorm Impedance Type is 'RLC'.") + self.properties["Use Resistance"] = value + + @property + def resistance_value(self): + """Get the resistance value.""" + raise NotImplementedError("Getter for 'resistance_value' is not implemented. Use the setter only.") + + @resistance_value.setter + def resistance_value(self, value): + """Set the resistance value. + Parameters + ---------- + value : str or float + The resistance value. Must be a string with units (e.g., "50ohm") or a float (defaults to "ohm"). + """ + allowed_units = ["uOhm", "mOhm", "ohm", "kOhm", "megohm", "GOhm"] + if self.renorm_impedance_type != "RLC": + raise ValueError("Resistance can be set only if Renorm Impedance Type is 'RLC'.") + if isinstance(value, (int, float)): + value_str = f"{value}ohm" + elif isinstance(value, str): + value_str = value.replace(" ", "") + if not any(value_str.endswith(unit) for unit in allowed_units): + raise ValueError(f"Resistance must end with one of {allowed_units}.") + else: + raise ValueError("Resistance must be a string with units or a float.") + if isinstance(value, str): + value_str = value.replace(" ", "") + if not any(value_str.endswith(unit) for unit in allowed_units): + raise ValueError(f"Resistance must end with one of {allowed_units}.") + self.properties["Resistance Value"] = value_str + + @property + def use_inductance(self): + """Get the 'Use Inductance' property.""" + raise NotImplementedError("Getter for 'use_inductance' is not implemented. Use the setter only.") + + @use_inductance.setter + def use_inductance(self, value): + """Set the 'Use Inductance' property. + Parameters + ---------- + value : bool + Whether to use inductance in the RLC circuit. + """ + if self.renorm_impedance_type != "RLC": + raise ValueError("Use Inductance can be set only if Renorm Impedance Type is 'RLC'.") + if not isinstance(value, bool): + raise ValueError("Use Inductance must be a boolean value.") + self.properties["Use Inductance"] = value + + @property + def inductance_value(self): + """Get the inductance value.""" + raise NotImplementedError("Getter for 'inductance_value' is not implemented. Use the setter only.") + + @inductance_value.setter + def inductance_value(self, value): + """Set the inductance value. + Parameters + ---------- + value : str or float + The inductance value. Must be a string with units (e.g., "10nH") or a float (defaults to "H"). + """ + allowed_units = ["fH", "pH", "nH", "uH", "mH", "H"] + if self.renorm_impedance_type != "RLC": + raise ValueError("Inductance can be set only if Renorm Impedance Type is 'RLC'.") + if isinstance(value, (int, float)): + value_str = f"{value}H" + elif isinstance(value, str): + value_str = value.replace(" ", "") + if not any(value_str.endswith(unit) for unit in allowed_units): + raise ValueError(f"Inductance must end with one of {allowed_units}.") + else: + raise ValueError("Inductance must be a string with units or a float.") + if isinstance(value, str): + value_str = value.replace(" ", "") + if not any(value_str.endswith(unit) for unit in allowed_units): + raise ValueError(f"Inductance must end with one of {allowed_units}.") + self.properties["Inductance Value"] = value_str + + @property + def use_capacitance(self): + """Get the 'Use Capacitance' property.""" + raise NotImplementedError("Getter for 'use_capacitance' is not implemented. Use the setter only.") + + @use_capacitance.setter + def use_capacitance(self, value): + """Set the 'Use Capacitance' property. + Parameters + ---------- + value : bool + Whether to use capacitance in the RLC circuit. + """ + if self.renorm_impedance_type != "RLC": + raise ValueError("Use Capacitance can be set only if Renorm Impedance Type is 'RLC'.") + if not isinstance(value, bool): + raise ValueError("Use Capacitance must be a boolean value.") + self.properties["Use Capacitance"] = value + + @property + def capacitance_value(self): + """Get the capacitance value.""" + raise NotImplementedError("Getter for 'capacitance_value' is not implemented. Use the setter only.") + + @capacitance_value.setter + def capacitance_value(self, value): + """Set the capacitance value. + Parameters + ---------- + value : str or float + The capacitance value. Must be a string with units (e.g., "1pF") or a float (defaults to "F"). + """ + allowed_units = ["fF", "pF", "nF", "uF", "mF", "farad"] + if self.renorm_impedance_type != "RLC": + raise ValueError("Capacitance can be set only if Renorm Impedance Type is 'RLC'.") + if isinstance(value, (int, float)): + value_str = f"{value}F" + elif isinstance(value, str): + value_str = value.replace(" ", "") + if not any(value_str.endswith(unit) for unit in allowed_units): + raise ValueError(f"Capacitance must end with one of {allowed_units}.") + else: + raise ValueError("Capacitance must be a string with units or a float.") + if isinstance(value, str): + value_str = value.replace(" ", "") + if not any(value_str.endswith(unit) for unit in allowed_units): + raise ValueError(f"Capacitance must end with one of {allowed_units}.") + self.properties["Capacitance Value"] = value_str From 0327f472c2f1d70ee80c15b0e6c54de31f9d1368 Mon Sep 17 00:00:00 2001 From: Eduardo Blanco Date: Wed, 3 Sep 2025 11:11:01 +0200 Subject: [PATCH 09/22] Added tests --- tests/system/general/test_hfss_excitations.py | 743 ++++++++++++++++++ 1 file changed, 743 insertions(+) create mode 100644 tests/system/general/test_hfss_excitations.py diff --git a/tests/system/general/test_hfss_excitations.py b/tests/system/general/test_hfss_excitations.py new file mode 100644 index 00000000000..bb3b4973fcd --- /dev/null +++ b/tests/system/general/test_hfss_excitations.py @@ -0,0 +1,743 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2021 - 2025 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. + +"""Tests for HFSS excitations, particularly WavePortObject functionality.""" + +import pytest + +from ansys.aedt.core import Hfss +from ansys.aedt.core.generic.constants import Plane + +class TestHfssWavePortExcitations: + """Test cases for HFSS Wave Port excitations.""" + + @pytest.fixture(autouse=True) + def setup(self, add_app): + """Setup the HFSS application for testing.""" + self.aedtapp = add_app( + application=Hfss, solution_type="Modal" + ) + # Create a simple waveguide structure for testing + self._create_test_waveguide() + + def _create_test_waveguide(self): + """Create a simple rectangular waveguide structure for testing.""" + # Create rectangular waveguide + box = self.aedtapp.modeler.create_box( + [0, 0, 0], [10, 5, 50], name="waveguide" + ) + box.material_name = "vacuum" + + # Create input face for wave port + self.input_face = self.aedtapp.modeler.create_rectangle( + Plane.YZ, [0, 0, 0], [5, 10], name="input_face" + ) + + # Create output face for wave port + self.output_face = self.aedtapp.modeler.create_rectangle( + Plane.YZ, [50, 0, 0], [5, 10], name="output_face" + ) + + # Create PEC boundaries for waveguide walls + self.aedtapp.assign_perfecte_to_sheets(box.faces) + + def test_01_create_wave_port_basic(self): + """Test basic wave port creation.""" + port = self.aedtapp.wave_port( + assignment=self.input_face.name, name="test_port_basic" + ) + assert port is not None + assert port.name == "test_port_basic" + assert hasattr(port, "specify_wave_direction") + assert hasattr(port, "deembed") + assert hasattr(port, "renorm_all_modes") + + def test_02_specify_wave_direction_property(self): + """Test specify_wave_direction property setter and getter.""" + port = self.aedtapp.wave_port( + assignment=self.input_face.name, + name="test_port_wave_direction", + ) + + # Test getter + initial_value = port.specify_wave_direction + assert isinstance(initial_value, bool) + + # Test setter - True + port.specify_wave_direction = True + assert port.specify_wave_direction is True + + # Test setter - False + port.specify_wave_direction = False + assert port.specify_wave_direction is False + + # Test no change when setting same value + result = port.specify_wave_direction = False + assert result is False + + def test_03_deembed_property(self): + """Test deembed property setter and getter.""" + port = self.aedtapp.wave_port( + assignment=self.input_face.name, name="test_port_deembed" + ) + + # Test getter + initial_value = port.deembed + assert isinstance(initial_value, bool) + + # Test setter - True + port.deembed = True + assert port.deembed is True + + # Test setter - False + port.deembed = False + assert port.deembed is False + + # Test no change when setting same value + result = port.deembed = False + assert result is False + + def test_04_renorm_all_modes_property(self): + """Test renorm_all_modes property setter and getter.""" + port = self.aedtapp.wave_port( + assignment=self.input_face.name, name="test_port_renorm" + ) + + # Test getter + initial_value = port.renorm_all_modes + assert isinstance(initial_value, bool) + + # Test setter - True + port.renorm_all_modes = True + assert port.renorm_all_modes is True + + # Test setter - False + port.renorm_all_modes = False + assert port.renorm_all_modes is False + + # Test no change when setting same value + result = port.renorm_all_modes = False + assert result is False + + def test_05_renorm_impedance_type_property(self): + """Test renorm_impedance_type property setter and getter.""" + port = self.aedtapp.wave_port( + assignment=self.input_face.name, + name="test_port_impedance_type", + ) + + # Test getter + initial_type = port.renorm_impedance_type + assert isinstance(initial_type, str) + + # Test valid values from choices + choices = port.properties["Renorm Impedance Type/Choices"] + for choice in choices: + port.renorm_impedance_type = choice + assert port.renorm_impedance_type == choice + + # Test invalid value + with pytest.raises( + ValueError, match="Renorm Impedance Type must be one of" + ): + port.renorm_impedance_type = "InvalidType" + + # Test no change when setting same value + port.renorm_impedance_type = "Impedance" + result = port.renorm_impedance_type = "Impedance" + assert result == "Impedance" + + def test_06_renorm_impedance_property(self): + """Test renorm_impedance property setter and getter.""" + port = self.aedtapp.wave_port( + assignment=self.input_face.name, + name="test_port_impedance", + ) + + # Set renorm type to Impedance first + port.renorm_impedance_type = "Impedance" + + # Test setter with int + port.renorm_impedance = 75 + assert port.renorm_impedance == "75ohm" + + # Test setter with string with units + port.renorm_impedance = "100ohm" + assert port.renorm_impedance == "100ohm" + + port.renorm_impedance = "1kOhm" + assert port.renorm_impedance == "1kOhm" + + # Test invalid units + with pytest.raises(ValueError, match="must end with one of"): + port.renorm_impedance = "50invalid" + + # Test invalid type + with pytest.raises( + ValueError, match="must be a string with units or a float" + ): + port.renorm_impedance = [] + + # Test error when renorm type is not Impedance + port.renorm_impedance_type = "RLC" + with pytest.raises( + ValueError, + match="can be set only if Renorm Impedance Type is 'Impedance'", + ): + port.renorm_impedance = 50 + + def test_07_rlc_type_property(self): + """Test rlc_type property setter.""" + port = self.aedtapp.wave_port( + assignment=self.input_face.name, name="test_port_rlc_type" + ) + + # Set renorm type to RLC first + port.renorm_impedance_type = "RLC" + + # Test valid values + port.rlc_type = "Serial" + port.rlc_type = "Parallel" + + # Test invalid value + with pytest.raises( + ValueError, match="RLC Type must be one of" + ): + port.rlc_type = "Invalid" + + # Test error when renorm type is not RLC + port.renorm_impedance_type = "Impedance" + with pytest.raises( + ValueError, + match="can be set only if Renorm Impedance Type is 'RLC'", + ): + port.rlc_type = "Serial" + + # Test getter raises NotImplementedError + port.renorm_impedance_type = "RLC" + with pytest.raises(NotImplementedError): + _ = port.rlc_type + + def test_08_use_resistance_property(self): + """Test use_resistance property setter.""" + port = self.aedtapp.wave_port( + assignment=self.input_face.name, + name="test_port_use_resistance", + modes=8, + characteristic_impedance="Zwave", + renormalize=True + ) + + # Set renorm type to RLC first + port.renorm_all_modes = True + port.renorm_impedance_type = "RLC" + + + # Test error when renorm type is not RLC + port.renorm_impedance_type = "Impedance" + with pytest.raises( + ValueError, + match="can be set only if Renorm Impedance Type is 'RLC'", + ): + port.use_resistance = True + + # Test valid boolean values + port.renorm_impedance_type = "RLC" + port.use_resistance = True + port.use_resistance = False + + # Test invalid value + with pytest.raises( + ValueError, match="must be a boolean value" + ): + port.use_resistance = "True" + # Test getter raises NotImplementedError + port.renorm_all_modes = True + port.renorm_impedance_type = "RLC" + with pytest.raises(NotImplementedError): + _ = port.use_resistance + + def test_09_resistance_value_property(self): + """Test resistance_value property setter.""" + port = self.aedtapp.wave_port( + assignment=self.input_face.name, + name="test_port_resistance_value", + ) + + # Set renorm type to RLC first + port.renorm_impedance_type = "RLC" + + # Test setter with float + port.resistance_value = 50.0 + + # Test setter with int + port.resistance_value = 75 + + # Test setter with string with units + port.resistance_value = "100ohm" + port.resistance_value = "1kOhm" + + # Test invalid units + with pytest.raises(ValueError, match="must end with one of"): + port.resistance_value = "50invalid" + + # Test invalid type + with pytest.raises( + ValueError, match="must be a string with units or a float" + ): + port.resistance_value = [] + + # Test error when renorm type is not RLC + port.renorm_impedance_type = "Impedance" + with pytest.raises( + ValueError, + match="can be set only if Renorm Impedance Type is 'RLC'", + ): + port.resistance_value = 50 + + # Test getter raises NotImplementedError + port.renorm_impedance_type = "RLC" + with pytest.raises(NotImplementedError): + _ = port.resistance_value + + def test_10_use_inductance_property(self): + """Test use_inductance property setter.""" + port = self.aedtapp.wave_port( + assignment=self.input_face.name, + name="test_port_use_inductance", + ) + + # Set renorm type to RLC first + port.renorm_impedance_type = "RLC" + + # Test valid boolean values + port.use_inductance = True + port.use_inductance = False + + # Test invalid value + with pytest.raises( + ValueError, match="must be a boolean value" + ): + port.use_inductance = "True" + + # Test error when renorm type is not RLC + port.renorm_impedance_type = "Impedance" + with pytest.raises( + ValueError, + match="can be set only if Renorm Impedance Type is 'RLC'", + ): + port.use_inductance = True + + # Test getter raises NotImplementedError + port.renorm_impedance_type = "RLC" + with pytest.raises(NotImplementedError): + _ = port.use_inductance + + def test_11_inductance_value_property(self): + """Test inductance_value property setter.""" + port = self.aedtapp.wave_port( + assignment=self.input_face.name, + name="test_port_inductance_value", + ) + + # Set renorm type to RLC first + port.renorm_impedance_type = "RLC" + + # Test setter with float + port.inductance_value = 1e-9 + + # Test setter with int + port.inductance_value = 1 + + # Test setter with string with units + port.inductance_value = "10nH" + port.inductance_value = "1uH" + + # Test invalid units + with pytest.raises(ValueError, match="must end with one of"): + port.inductance_value = "10invalid" + + # Test invalid type + with pytest.raises( + ValueError, match="must be a string with units or a float" + ): + port.inductance_value = [] + + # Test error when renorm type is not RLC + port.renorm_impedance_type = "Impedance" + with pytest.raises( + ValueError, + match="can be set only if Renorm Impedance Type is 'RLC'", + ): + port.inductance_value = 1e-9 + + # Test getter raises NotImplementedError + port.renorm_impedance_type = "RLC" + with pytest.raises(NotImplementedError): + _ = port.inductance_value + + def test_12_use_capacitance_property(self): + """Test use_capacitance property setter.""" + port = self.aedtapp.wave_port( + assignment=self.input_face.name, + name="test_port_use_capacitance", + ) + + # Set renorm type to RLC first + port.renorm_impedance_type = "RLC" + + # Test valid boolean values + port.use_capacitance = True + port.use_capacitance = False + + # Test invalid value + with pytest.raises( + ValueError, match="must be a boolean value" + ): + port.use_capacitance = "True" + + # Test error when renorm type is not RLC + port.renorm_impedance_type = "Impedance" + with pytest.raises( + ValueError, + match="can be set only if Renorm Impedance Type is 'RLC'", + ): + port.use_capacitance = True + + # Test getter raises NotImplementedError + port.renorm_impedance_type = "RLC" + with pytest.raises(NotImplementedError): + _ = port.use_capacitance + + def test_13_capacitance_value_property(self): + """Test capacitance_value property setter.""" + port = self.aedtapp.wave_port( + assignment=self.input_face.name, + name="test_port_capacitance_value", + ) + + # Set renorm type to RLC first + port.renorm_impedance_type = "RLC" + + # Test setter with float + port.capacitance_value = 1e-12 + + # Test setter with int + port.capacitance_value = 1 + + # Test setter with string with units + port.capacitance_value = "1pF" + port.capacitance_value = "10nF" + + # Test invalid units + with pytest.raises(ValueError, match="must end with one of"): + port.capacitance_value = "1invalid" + + # Test invalid type + with pytest.raises( + ValueError, match="must be a string with units or a float" + ): + port.capacitance_value = [] + + # Test error when renorm type is not RLC + port.renorm_impedance_type = "Impedance" + with pytest.raises( + ValueError, + match="can be set only if Renorm Impedance Type is 'RLC'", + ): + port.capacitance_value = 1e-12 + + # Test getter raises NotImplementedError + port.renorm_impedance_type = "RLC" + with pytest.raises(NotImplementedError): + _ = port.capacitance_value + + def test_14_filter_modes_reporter_property(self): + """Test filter_modes_reporter property setter and getter.""" + port = self.aedtapp.wave_port( + assignment=self.input_face.name, + modes=3, + name="test_port_filter_modes", + ) + + # Test getter + filter_values = port.filter_modes_reporter + assert isinstance(filter_values, list) + assert len(filter_values) == 3 + + # Test setter with single boolean + port.filter_modes_reporter = True + assert all(port.filter_modes_reporter) + + port.filter_modes_reporter = False + assert not any(port.filter_modes_reporter) + + # Test setter with list + port.filter_modes_reporter = [True, False, True] + expected = [True, False, True] + assert port.filter_modes_reporter == expected + + # Test invalid list length + with pytest.raises( + ValueError, match="must match the number of modes" + ): + port.filter_modes_reporter = [True, False] + + # Test invalid type + with pytest.raises( + ValueError, + match="must be a boolean or a list of booleans", + ): + port.filter_modes_reporter = "True" + + def test_15_set_analytical_alignment(self): + """Test set_analytical_alignment method.""" + port = self.aedtapp.wave_port( + assignment=self.input_face.name, + name="test_port_analytical", + ) + + # Test with u_axis_line + u_line = [[0, 2.5, 0], [0, 2.5, 10]] + result = port.set_analytical_alignment(u_axis_line=u_line) + assert result is True + + # Test with all parameters + result = port.set_analytical_alignment( + u_axis_line=u_line, + analytic_reverse_v=True, + coordinate_system="Global", + alignment_group=1, + ) + assert result is True + + # Test with invalid u_axis_line format + result = port.set_analytical_alignment( + u_axis_line=[[0, 0], [1, 0]] + ) + assert result is False + + # Test with invalid u_axis_line type + result = port.set_analytical_alignment(u_axis_line="invalid") + assert result is False + + def test_16_set_alignment_integration_line(self): + """Test set_alignment_integration_line method.""" + port = self.aedtapp.wave_port( + assignment=self.input_face.name, + modes=3, + name="test_port_alignment_integration", + ) + + # Test disabling integration lines + result = port.set_alignment_integration_line() + assert result is True + + # Test with valid integration lines + integration_lines = [ + [[0, 0, 0], [0, 5, 0]], + [[0, 0, 0], [0, 1, 0]], + ] + result = port.set_alignment_integration_line( + integration_lines + ) + assert result is True + + # Test with alignment groups + alignment_groups = [1, 2, 0] + result = port.set_alignment_integration_line( + integration_lines, alignment_groups=alignment_groups + ) + assert result is True + + # Test with single alignment group + result = port.set_alignment_integration_line( + integration_lines, alignment_groups=1 + ) + assert result is True + + # Test with custom coordinate system + result = port.set_alignment_integration_line( + integration_lines, coordinate_system="Local" + ) + assert result is True + + # Test error cases + # Not enough integration lines + result = port.set_alignment_integration_line( + [[[0, 0, 0], [1, 0, 0]]] + ) + assert result is False + + # Invalid integration line format + result = port.set_alignment_integration_line( + [[[0, 0], [1, 0]]] + ) + assert result is False + + # Invalid coordinate system + result = port.set_alignment_integration_line( + integration_lines, coordinate_system=123 + ) + assert result is False + + def test_17_set_polarity_integration_line(self): + """Test set_polarity_integration_line method.""" + port = self.aedtapp.wave_port( + assignment=self.input_face.name, + modes=2, + name="test_port_polarity", + ) + + # Test disabling integration lines + result = port.set_polarity_integration_line() + assert result is True + + # Test with valid integration lines + integration_lines = [ + [[0, 0, 0], [0, 5, 0]], + [[0, 0, 0], [0, 1, 0]], + ] + result = port.set_polarity_integration_line(integration_lines) + assert result is True + + # Test with custom coordinate system + result = port.set_polarity_integration_line( + integration_lines, coordinate_system="Local" + ) + assert result is True + + # Test single integration line handling + single_line = [[0, 0, 0], [0, 5, 0]] + result = port.set_polarity_integration_line([single_line]) + assert result is True + + # Test error cases + # Invalid integration line format + result = port.set_polarity_integration_line( + [[[0, 0], [1, 0]]] + ) + assert result is False + + # Invalid coordinate system + result = port.set_polarity_integration_line( + integration_lines, coordinate_system=123 + ) + assert result is False + + # Invalid integration_lines type + result = port.set_polarity_integration_line("invalid") + assert result is False + + def test_18_multiple_ports_properties(self): + """Test properties with multiple ports to ensure independence.""" + # Create two ports + port1 = self.aedtapp.wave_port( + assignment=self.input_face.name, name="test_port1" + ) + port2 = self.aedtapp.wave_port( + assignment=self.output_face.name, name="test_port2" + ) + + # Set different properties for each port + port1.specify_wave_direction = True + port2.specify_wave_direction = False + + port1.deembed = True + port2.deembed = False + + port1.renorm_all_modes = True + port2.renorm_all_modes = False + + # Verify properties are independent + assert port1.specify_wave_direction is True + assert port2.specify_wave_direction is False + + assert port1.deembed is True + assert port2.deembed is False + + assert port1.renorm_all_modes is True + assert port2.renorm_all_modes is False + + def test_19_impedance_renormalization_workflow(self): + """Test complete impedance renormalization workflow.""" + port = self.aedtapp.wave_port( + assignment=self.input_face.name, name="test_port_workflow" + ) + + # Test Impedance renormalization + port.renorm_impedance_type = "Impedance" + port.renorm_impedance = 75 + assert port.renorm_impedance == "75ohm" + + # Test RLC renormalization workflow + port.renorm_impedance_type = "RLC" + port.rlc_type = "Serial" + port.use_resistance = True + port.resistance_value = "50ohm" + port.use_inductance = True + port.inductance_value = "10nH" + port.use_capacitance = True + port.capacitance_value = "1pF" + + # Verify all settings were applied (no exceptions thrown) + assert port.renorm_impedance_type == "RLC" + + def test_20_integration_lines_complex_scenario(self): + """Test complex integration line scenarios.""" + port = self.aedtapp.wave_port( + assignment=self.input_face.name, + modes=4, + name="test_port_complex", + ) + + # Test alignment integration lines with all modes + integration_lines = [ + [[0, 0, 0], [0, 0, 1]], + [[0, 0, 0], [0, 1, 0]], + [[0, 0, 0], [0, 3, 0]], + [[0, 0, 0], [0, 5, 0]], + ] + alignment_groups = [1, 1, 2, 2] + + result = port.set_alignment_integration_line( + integration_lines, + coordinate_system="Global", + alignment_groups=alignment_groups, + ) + assert result is True + + # Switch to polarity mode + result = port.set_polarity_integration_line( + integration_lines[:2] + ) + assert result is True + + # Verify mode alignment was disabled + # (This would need to be verified through properties access) + + # Test filter modes reporter with this port + port.filter_modes_reporter = [True, False, True, False] + expected = [True, False, True, False] + assert port.filter_modes_reporter == expected From 0291e36f39776e6bbe78c0b3434bb9108a86e4eb Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 3 Sep 2025 09:13:09 +0000 Subject: [PATCH 10/22] CHORE: Auto fixes from pre-commit hooks --- .../core/modules/boundary/hfss_boundary.py | 30 +++- tests/system/general/test_hfss_excitations.py | 136 +++++------------- 2 files changed, 60 insertions(+), 106 deletions(-) diff --git a/src/ansys/aedt/core/modules/boundary/hfss_boundary.py b/src/ansys/aedt/core/modules/boundary/hfss_boundary.py index 7039758036f..c4479ec6179 100644 --- a/src/ansys/aedt/core/modules/boundary/hfss_boundary.py +++ b/src/ansys/aedt/core/modules/boundary/hfss_boundary.py @@ -550,7 +550,7 @@ class WavePortObject(BoundaryObject): def __init__(self, app, name, props, btype): """Initialize a wave port boundary object. - + Parameters ---------- app : :class:`ansys.aedt.core.application.analysis_3d.FieldAnalysis3D` @@ -967,6 +967,7 @@ def specify_wave_direction(self, value): @property def deembed(self): """Get the de-embedding property of the wave port. + Returns ------- bool @@ -977,6 +978,7 @@ def deembed(self): @deembed.setter def deembed(self, value): """Set the de-embedding property of the wave port. + Parameters ---------- value : bool @@ -989,15 +991,18 @@ def deembed(self, value): @property def renorm_all_modes(self): """Renormalize all modes property + Returns ------- bool Whether renormalization of all modes is enabled. """ return self.properties["Renorm All Modes"] + @renorm_all_modes.setter def renorm_all_modes(self, value): """Set the renormalization property for all modes. + Parameters ---------- value : bool @@ -1010,38 +1015,46 @@ def renorm_all_modes(self, value): @property def renorm_impedance_type(self): """Get the renormalization impedance type + Returns ------- str - The type of renormalization impedance. + The type of renormalization impedance. """ return self.properties["Renorm Impedance Type"] - + @renorm_impedance_type.setter def renorm_impedance_type(self, value): """Set the renormalization impedance type. + Parameters ---------- value : str The type of renormalization impedance. It can be a type contained in the list of choices. """ if value not in self.properties["Renorm Impedance Type/Choices"]: - raise ValueError(f"Renorm Impedance Type must be one of {self.properties['Renorm Impedance Type/Choices']}.") + raise ValueError( + f"Renorm Impedance Type must be one of {self.properties['Renorm Impedance Type/Choices']}." + ) if value == self.properties["Renorm Impedance Type"]: return value self.properties["Renorm Impedance Type"] = value + @property def renorm_impedance(self): """Get the renormalization impedance value. + Returns ------- str The renormalization impedance value. """ return self.properties["Renorm Imped"] + @renorm_impedance.setter def renorm_impedance(self, value): """Set the renormalization impedance value. + Parameters ---------- value : str or int @@ -1086,6 +1099,7 @@ def rlc_type(self): @rlc_type.setter def rlc_type(self, value): """Set the RLC type property. + Parameters ---------- value : str @@ -1097,7 +1111,7 @@ def rlc_type(self, value): if value not in allowed_types: raise ValueError(f"RLC Type must be one of {allowed_types}.") self.properties["RLC Type"] = value - + @property def use_resistance(self): """Get the 'Use Resistance' property.""" @@ -1106,6 +1120,7 @@ def use_resistance(self): @use_resistance.setter def use_resistance(self, value): """Set the 'Use Resistance' property. + Parameters ---------- value : bool @@ -1125,6 +1140,7 @@ def resistance_value(self): @resistance_value.setter def resistance_value(self, value): """Set the resistance value. + Parameters ---------- value : str or float @@ -1155,6 +1171,7 @@ def use_inductance(self): @use_inductance.setter def use_inductance(self, value): """Set the 'Use Inductance' property. + Parameters ---------- value : bool @@ -1174,6 +1191,7 @@ def inductance_value(self): @inductance_value.setter def inductance_value(self, value): """Set the inductance value. + Parameters ---------- value : str or float @@ -1204,6 +1222,7 @@ def use_capacitance(self): @use_capacitance.setter def use_capacitance(self, value): """Set the 'Use Capacitance' property. + Parameters ---------- value : bool @@ -1223,6 +1242,7 @@ def capacitance_value(self): @capacitance_value.setter def capacitance_value(self, value): """Set the capacitance value. + Parameters ---------- value : str or float diff --git a/tests/system/general/test_hfss_excitations.py b/tests/system/general/test_hfss_excitations.py index bb3b4973fcd..b78b034071c 100644 --- a/tests/system/general/test_hfss_excitations.py +++ b/tests/system/general/test_hfss_excitations.py @@ -29,44 +29,35 @@ from ansys.aedt.core import Hfss from ansys.aedt.core.generic.constants import Plane + class TestHfssWavePortExcitations: """Test cases for HFSS Wave Port excitations.""" @pytest.fixture(autouse=True) def setup(self, add_app): """Setup the HFSS application for testing.""" - self.aedtapp = add_app( - application=Hfss, solution_type="Modal" - ) + self.aedtapp = add_app(application=Hfss, solution_type="Modal") # Create a simple waveguide structure for testing self._create_test_waveguide() def _create_test_waveguide(self): """Create a simple rectangular waveguide structure for testing.""" # Create rectangular waveguide - box = self.aedtapp.modeler.create_box( - [0, 0, 0], [10, 5, 50], name="waveguide" - ) + box = self.aedtapp.modeler.create_box([0, 0, 0], [10, 5, 50], name="waveguide") box.material_name = "vacuum" # Create input face for wave port - self.input_face = self.aedtapp.modeler.create_rectangle( - Plane.YZ, [0, 0, 0], [5, 10], name="input_face" - ) + self.input_face = self.aedtapp.modeler.create_rectangle(Plane.YZ, [0, 0, 0], [5, 10], name="input_face") # Create output face for wave port - self.output_face = self.aedtapp.modeler.create_rectangle( - Plane.YZ, [50, 0, 0], [5, 10], name="output_face" - ) + self.output_face = self.aedtapp.modeler.create_rectangle(Plane.YZ, [50, 0, 0], [5, 10], name="output_face") # Create PEC boundaries for waveguide walls self.aedtapp.assign_perfecte_to_sheets(box.faces) def test_01_create_wave_port_basic(self): """Test basic wave port creation.""" - port = self.aedtapp.wave_port( - assignment=self.input_face.name, name="test_port_basic" - ) + port = self.aedtapp.wave_port(assignment=self.input_face.name, name="test_port_basic") assert port is not None assert port.name == "test_port_basic" assert hasattr(port, "specify_wave_direction") @@ -98,9 +89,7 @@ def test_02_specify_wave_direction_property(self): def test_03_deembed_property(self): """Test deembed property setter and getter.""" - port = self.aedtapp.wave_port( - assignment=self.input_face.name, name="test_port_deembed" - ) + port = self.aedtapp.wave_port(assignment=self.input_face.name, name="test_port_deembed") # Test getter initial_value = port.deembed @@ -120,9 +109,7 @@ def test_03_deembed_property(self): def test_04_renorm_all_modes_property(self): """Test renorm_all_modes property setter and getter.""" - port = self.aedtapp.wave_port( - assignment=self.input_face.name, name="test_port_renorm" - ) + port = self.aedtapp.wave_port(assignment=self.input_face.name, name="test_port_renorm") # Test getter initial_value = port.renorm_all_modes @@ -158,9 +145,7 @@ def test_05_renorm_impedance_type_property(self): assert port.renorm_impedance_type == choice # Test invalid value - with pytest.raises( - ValueError, match="Renorm Impedance Type must be one of" - ): + with pytest.raises(ValueError, match="Renorm Impedance Type must be one of"): port.renorm_impedance_type = "InvalidType" # Test no change when setting same value @@ -194,9 +179,7 @@ def test_06_renorm_impedance_property(self): port.renorm_impedance = "50invalid" # Test invalid type - with pytest.raises( - ValueError, match="must be a string with units or a float" - ): + with pytest.raises(ValueError, match="must be a string with units or a float"): port.renorm_impedance = [] # Test error when renorm type is not Impedance @@ -209,9 +192,7 @@ def test_06_renorm_impedance_property(self): def test_07_rlc_type_property(self): """Test rlc_type property setter.""" - port = self.aedtapp.wave_port( - assignment=self.input_face.name, name="test_port_rlc_type" - ) + port = self.aedtapp.wave_port(assignment=self.input_face.name, name="test_port_rlc_type") # Set renorm type to RLC first port.renorm_impedance_type = "RLC" @@ -221,9 +202,7 @@ def test_07_rlc_type_property(self): port.rlc_type = "Parallel" # Test invalid value - with pytest.raises( - ValueError, match="RLC Type must be one of" - ): + with pytest.raises(ValueError, match="RLC Type must be one of"): port.rlc_type = "Invalid" # Test error when renorm type is not RLC @@ -246,14 +225,13 @@ def test_08_use_resistance_property(self): name="test_port_use_resistance", modes=8, characteristic_impedance="Zwave", - renormalize=True + renormalize=True, ) # Set renorm type to RLC first port.renorm_all_modes = True port.renorm_impedance_type = "RLC" - # Test error when renorm type is not RLC port.renorm_impedance_type = "Impedance" with pytest.raises( @@ -268,9 +246,7 @@ def test_08_use_resistance_property(self): port.use_resistance = False # Test invalid value - with pytest.raises( - ValueError, match="must be a boolean value" - ): + with pytest.raises(ValueError, match="must be a boolean value"): port.use_resistance = "True" # Test getter raises NotImplementedError port.renorm_all_modes = True @@ -303,9 +279,7 @@ def test_09_resistance_value_property(self): port.resistance_value = "50invalid" # Test invalid type - with pytest.raises( - ValueError, match="must be a string with units or a float" - ): + with pytest.raises(ValueError, match="must be a string with units or a float"): port.resistance_value = [] # Test error when renorm type is not RLC @@ -336,9 +310,7 @@ def test_10_use_inductance_property(self): port.use_inductance = False # Test invalid value - with pytest.raises( - ValueError, match="must be a boolean value" - ): + with pytest.raises(ValueError, match="must be a boolean value"): port.use_inductance = "True" # Test error when renorm type is not RLC @@ -379,9 +351,7 @@ def test_11_inductance_value_property(self): port.inductance_value = "10invalid" # Test invalid type - with pytest.raises( - ValueError, match="must be a string with units or a float" - ): + with pytest.raises(ValueError, match="must be a string with units or a float"): port.inductance_value = [] # Test error when renorm type is not RLC @@ -412,9 +382,7 @@ def test_12_use_capacitance_property(self): port.use_capacitance = False # Test invalid value - with pytest.raises( - ValueError, match="must be a boolean value" - ): + with pytest.raises(ValueError, match="must be a boolean value"): port.use_capacitance = "True" # Test error when renorm type is not RLC @@ -455,9 +423,7 @@ def test_13_capacitance_value_property(self): port.capacitance_value = "1invalid" # Test invalid type - with pytest.raises( - ValueError, match="must be a string with units or a float" - ): + with pytest.raises(ValueError, match="must be a string with units or a float"): port.capacitance_value = [] # Test error when renorm type is not RLC @@ -499,9 +465,7 @@ def test_14_filter_modes_reporter_property(self): assert port.filter_modes_reporter == expected # Test invalid list length - with pytest.raises( - ValueError, match="must match the number of modes" - ): + with pytest.raises(ValueError, match="must match the number of modes"): port.filter_modes_reporter = [True, False] # Test invalid type @@ -533,9 +497,7 @@ def test_15_set_analytical_alignment(self): assert result is True # Test with invalid u_axis_line format - result = port.set_analytical_alignment( - u_axis_line=[[0, 0], [1, 0]] - ) + result = port.set_analytical_alignment(u_axis_line=[[0, 0], [1, 0]]) assert result is False # Test with invalid u_axis_line type @@ -559,47 +521,33 @@ def test_16_set_alignment_integration_line(self): [[0, 0, 0], [0, 5, 0]], [[0, 0, 0], [0, 1, 0]], ] - result = port.set_alignment_integration_line( - integration_lines - ) + result = port.set_alignment_integration_line(integration_lines) assert result is True # Test with alignment groups alignment_groups = [1, 2, 0] - result = port.set_alignment_integration_line( - integration_lines, alignment_groups=alignment_groups - ) + result = port.set_alignment_integration_line(integration_lines, alignment_groups=alignment_groups) assert result is True # Test with single alignment group - result = port.set_alignment_integration_line( - integration_lines, alignment_groups=1 - ) + result = port.set_alignment_integration_line(integration_lines, alignment_groups=1) assert result is True # Test with custom coordinate system - result = port.set_alignment_integration_line( - integration_lines, coordinate_system="Local" - ) + result = port.set_alignment_integration_line(integration_lines, coordinate_system="Local") assert result is True # Test error cases # Not enough integration lines - result = port.set_alignment_integration_line( - [[[0, 0, 0], [1, 0, 0]]] - ) + result = port.set_alignment_integration_line([[[0, 0, 0], [1, 0, 0]]]) assert result is False # Invalid integration line format - result = port.set_alignment_integration_line( - [[[0, 0], [1, 0]]] - ) + result = port.set_alignment_integration_line([[[0, 0], [1, 0]]]) assert result is False # Invalid coordinate system - result = port.set_alignment_integration_line( - integration_lines, coordinate_system=123 - ) + result = port.set_alignment_integration_line(integration_lines, coordinate_system=123) assert result is False def test_17_set_polarity_integration_line(self): @@ -623,9 +571,7 @@ def test_17_set_polarity_integration_line(self): assert result is True # Test with custom coordinate system - result = port.set_polarity_integration_line( - integration_lines, coordinate_system="Local" - ) + result = port.set_polarity_integration_line(integration_lines, coordinate_system="Local") assert result is True # Test single integration line handling @@ -635,15 +581,11 @@ def test_17_set_polarity_integration_line(self): # Test error cases # Invalid integration line format - result = port.set_polarity_integration_line( - [[[0, 0], [1, 0]]] - ) + result = port.set_polarity_integration_line([[[0, 0], [1, 0]]]) assert result is False # Invalid coordinate system - result = port.set_polarity_integration_line( - integration_lines, coordinate_system=123 - ) + result = port.set_polarity_integration_line(integration_lines, coordinate_system=123) assert result is False # Invalid integration_lines type @@ -653,12 +595,8 @@ def test_17_set_polarity_integration_line(self): def test_18_multiple_ports_properties(self): """Test properties with multiple ports to ensure independence.""" # Create two ports - port1 = self.aedtapp.wave_port( - assignment=self.input_face.name, name="test_port1" - ) - port2 = self.aedtapp.wave_port( - assignment=self.output_face.name, name="test_port2" - ) + port1 = self.aedtapp.wave_port(assignment=self.input_face.name, name="test_port1") + port2 = self.aedtapp.wave_port(assignment=self.output_face.name, name="test_port2") # Set different properties for each port port1.specify_wave_direction = True @@ -682,9 +620,7 @@ def test_18_multiple_ports_properties(self): def test_19_impedance_renormalization_workflow(self): """Test complete impedance renormalization workflow.""" - port = self.aedtapp.wave_port( - assignment=self.input_face.name, name="test_port_workflow" - ) + port = self.aedtapp.wave_port(assignment=self.input_face.name, name="test_port_workflow") # Test Impedance renormalization port.renorm_impedance_type = "Impedance" @@ -729,9 +665,7 @@ def test_20_integration_lines_complex_scenario(self): assert result is True # Switch to polarity mode - result = port.set_polarity_integration_line( - integration_lines[:2] - ) + result = port.set_polarity_integration_line(integration_lines[:2]) assert result is True # Verify mode alignment was disabled From dc9160b669e6ecdf285e477c21e9fe93bd38d990 Mon Sep 17 00:00:00 2001 From: Eduardo Blanco Date: Wed, 3 Sep 2025 16:16:19 +0200 Subject: [PATCH 11/22] Fixed line issue --- src/ansys/aedt/core/modules/boundary/hfss_boundary.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ansys/aedt/core/modules/boundary/hfss_boundary.py b/src/ansys/aedt/core/modules/boundary/hfss_boundary.py index c4479ec6179..a7d6ada8339 100644 --- a/src/ansys/aedt/core/modules/boundary/hfss_boundary.py +++ b/src/ansys/aedt/core/modules/boundary/hfss_boundary.py @@ -1058,7 +1058,8 @@ def renorm_impedance(self, value): Parameters ---------- value : str or int - The renormalization impedance value. Must be a string with units (e.g., "50ohm") or a int (defaults to "ohm"). + The renormalization impedance value. Must be a string with units + (e.g., "50ohm") or a int (defaults to "ohm"). """ allowed_units = ["uOhm", "mOhm", "ohm", "kOhm", "megohm", "GOhm"] if self.renorm_impedance_type != "Impedance": From 78a2e1761f0c21b27ae7c450eada96bcb6a5c04b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 3 Sep 2025 14:17:08 +0000 Subject: [PATCH 12/22] CHORE: Auto fixes from pre-commit hooks --- src/ansys/aedt/core/modules/boundary/hfss_boundary.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ansys/aedt/core/modules/boundary/hfss_boundary.py b/src/ansys/aedt/core/modules/boundary/hfss_boundary.py index a7d6ada8339..29d219730c6 100644 --- a/src/ansys/aedt/core/modules/boundary/hfss_boundary.py +++ b/src/ansys/aedt/core/modules/boundary/hfss_boundary.py @@ -1058,7 +1058,7 @@ def renorm_impedance(self, value): Parameters ---------- value : str or int - The renormalization impedance value. Must be a string with units + The renormalization impedance value. Must be a string with units (e.g., "50ohm") or a int (defaults to "ohm"). """ allowed_units = ["uOhm", "mOhm", "ohm", "kOhm", "megohm", "GOhm"] From 19bdf2e82aebc4a4535fc4b4c56be77716d2b92c Mon Sep 17 00:00:00 2001 From: pyansys-ci-bot <92810346+pyansys-ci-bot@users.noreply.github.com> Date: Wed, 3 Sep 2025 14:19:52 +0000 Subject: [PATCH 13/22] chore: adding changelog file 6595.added.md [dependabot-skip] --- doc/changelog.d/6595.added.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/changelog.d/6595.added.md b/doc/changelog.d/6595.added.md index 5d25fbe24d0..c086228dc88 100644 --- a/doc/changelog.d/6595.added.md +++ b/doc/changelog.d/6595.added.md @@ -1 +1 @@ -Waveport object alignment +Waveport object advanced config From c50212eae6e7111d7160663cfc079fcbd40a9026 Mon Sep 17 00:00:00 2001 From: Eduardo Blanco Date: Fri, 5 Sep 2025 12:11:03 +0200 Subject: [PATCH 14/22] Tests run in one single HFSS design --- tests/system/general/test_hfss_excitations.py | 43 +++++++++++-------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/tests/system/general/test_hfss_excitations.py b/tests/system/general/test_hfss_excitations.py index b78b034071c..b6fc0594490 100644 --- a/tests/system/general/test_hfss_excitations.py +++ b/tests/system/general/test_hfss_excitations.py @@ -33,27 +33,32 @@ class TestHfssWavePortExcitations: """Test cases for HFSS Wave Port excitations.""" + @classmethod + def setup_class(cls): + """Setup the HFSS application and waveguide once for all tests.""" + # Use pytest's request fixture to get add_app + # This will be set in the first test via setup method + cls.aedtapp = None + cls.input_face = None + cls.output_face = None + @pytest.fixture(autouse=True) def setup(self, add_app): - """Setup the HFSS application for testing.""" - self.aedtapp = add_app(application=Hfss, solution_type="Modal") - # Create a simple waveguide structure for testing - self._create_test_waveguide() - - def _create_test_waveguide(self): - """Create a simple rectangular waveguide structure for testing.""" - # Create rectangular waveguide - box = self.aedtapp.modeler.create_box([0, 0, 0], [10, 5, 50], name="waveguide") - box.material_name = "vacuum" - - # Create input face for wave port - self.input_face = self.aedtapp.modeler.create_rectangle(Plane.YZ, [0, 0, 0], [5, 10], name="input_face") - - # Create output face for wave port - self.output_face = self.aedtapp.modeler.create_rectangle(Plane.YZ, [50, 0, 0], [5, 10], name="output_face") - - # Create PEC boundaries for waveguide walls - self.aedtapp.assign_perfecte_to_sheets(box.faces) + """Setup the HFSS application for testing, only once.""" + if TestHfssWavePortExcitations.aedtapp is None: + TestHfssWavePortExcitations.aedtapp = add_app(application=Hfss, solution_type="Modal") + # Create a simple waveguide structure for testing + box = TestHfssWavePortExcitations.aedtapp.modeler.create_box([0, 0, 0], [10, 5, 50], name="waveguide") + box.material_name = "vacuum" + TestHfssWavePortExcitations.input_face = TestHfssWavePortExcitations.aedtapp.modeler.create_rectangle( + Plane.YZ, [0, 0, 0], [5, 10], name="input_face" + ) + TestHfssWavePortExcitations.output_face = TestHfssWavePortExcitations.aedtapp.modeler.create_rectangle( + Plane.YZ, [50, 0, 0], [5, 10], name="output_face" + ) + self.aedtapp = TestHfssWavePortExcitations.aedtapp + self.input_face = TestHfssWavePortExcitations.input_face + self.output_face = TestHfssWavePortExcitations.output_face def test_01_create_wave_port_basic(self): """Test basic wave port creation.""" From eb8098fa76c4073fc775374a16c0d3fe67fff3d2 Mon Sep 17 00:00:00 2001 From: Eduardo Blanco Date: Fri, 5 Sep 2025 12:11:20 +0200 Subject: [PATCH 15/22] Fixed waveguide orientation --- tests/system/general/test_hfss_excitations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/system/general/test_hfss_excitations.py b/tests/system/general/test_hfss_excitations.py index b6fc0594490..f6a8fcef7cf 100644 --- a/tests/system/general/test_hfss_excitations.py +++ b/tests/system/general/test_hfss_excitations.py @@ -48,7 +48,7 @@ def setup(self, add_app): if TestHfssWavePortExcitations.aedtapp is None: TestHfssWavePortExcitations.aedtapp = add_app(application=Hfss, solution_type="Modal") # Create a simple waveguide structure for testing - box = TestHfssWavePortExcitations.aedtapp.modeler.create_box([0, 0, 0], [10, 5, 50], name="waveguide") + box = TestHfssWavePortExcitations.aedtapp.modeler.create_box([0, 0, 0], [50, 5, 10], name="waveguide") box.material_name = "vacuum" TestHfssWavePortExcitations.input_face = TestHfssWavePortExcitations.aedtapp.modeler.create_rectangle( Plane.YZ, [0, 0, 0], [5, 10], name="input_face" From 8231a9ffb83a7ebeda711b9382f54f40d4d058c9 Mon Sep 17 00:00:00 2001 From: Samuelopez-ansys Date: Tue, 16 Sep 2025 16:29:55 +0200 Subject: [PATCH 16/22] Add Enum --- src/ansys/aedt/core/hfss.py | 59 +++++++++++-------- .../core/modules/boundary/hfss_boundary.py | 2 +- tests/system/general/test_hfss_excitations.py | 2 +- 3 files changed, 36 insertions(+), 27 deletions(-) diff --git a/src/ansys/aedt/core/hfss.py b/src/ansys/aedt/core/hfss.py index 0d91b9ebdac..2bfc48bddc6 100644 --- a/src/ansys/aedt/core/hfss.py +++ b/src/ansys/aedt/core/hfss.py @@ -24,6 +24,7 @@ """This module contains the ``Hfss`` class.""" +from enum import Enum import math from pathlib import Path import tempfile @@ -57,11 +58,19 @@ from ansys.aedt.core.modules.boundary.common import BoundaryObject from ansys.aedt.core.modules.boundary.hfss_boundary import FarFieldSetup from ansys.aedt.core.modules.boundary.hfss_boundary import NearFieldSetup -from ansys.aedt.core.modules.boundary.hfss_boundary import WavePortObject +from ansys.aedt.core.modules.boundary.hfss_boundary import WavePort from ansys.aedt.core.modules.boundary.layout_boundary import NativeComponentObject from ansys.aedt.core.modules.setup_templates import SetupKeys +class NearFieldType(str, Enum): + Sphere = "NearFieldSphere" + Box = "NearFieldBox" + Rectangle = "NearFieldRectangle" + Line = "NearFieldLine" + Points = "NearFieldPoints" + + class Hfss(FieldAnalysis3D, ScatteringMethods, CreateBoundaryMixin): """Provides the HFSS application interface. @@ -240,12 +249,12 @@ def _init_from_design(self, *args, **kwargs): self.__init__(*args, **kwargs) @pyaedt_function_handler - # NOTE: Extend Mixin behaviour to handle near field setups + # NOTE: Extend Mixin behaviour to handle HFSS excitations def _create_boundary(self, name, props, boundary_type): - # Wave Port cases - return specialized WavePortObject + # Wave Port cases - return WavePort if boundary_type == "Wave Port": try: - bound = WavePortObject(self, name, props, boundary_type) + bound = WavePort(self, name, props, boundary_type) if not bound.create(): raise AEDTRuntimeError(f"Failed to create boundary {boundary_type} {name}") @@ -255,25 +264,25 @@ def _create_boundary(self, name, props, boundary_type): except GrpcApiError as e: raise AEDTRuntimeError(f"Failed to create boundary {boundary_type} {name}") from e - # No-near field cases - if boundary_type not in ( - "NearFieldSphere", - "NearFieldBox", - "NearFieldRectangle", - "NearFieldLine", - "NearFieldPoints", + # Near field cases + if boundary_type in ( + NearFieldType.Sphere, + NearFieldType.Box, + NearFieldType.Rectangle, + NearFieldType.Line, + NearFieldType.Points, ): + # Near field setup + bound = NearFieldSetup(self, name, props, boundary_type) + result = bound.create() + if result: + self.field_setups.append(bound) + self.logger.info(f"Field setup {boundary_type} {name} has been created.") + return bound + raise AEDTRuntimeError(f"Failed to create near field setup {boundary_type} {name}") + else: return super()._create_boundary(name, props, boundary_type) - # Near field setup - bound = NearFieldSetup(self, name, props, boundary_type) - result = bound.create() - if result: - self.field_setups.append(bound) - self.logger.info(f"Field setup {boundary_type} {name} has been created.") - return bound - raise AEDTRuntimeError(f"Failed to create near field setup {boundary_type} {name}") - @property def field_setups(self): """List of AEDT radiation fields. @@ -5639,7 +5648,7 @@ def insert_near_field_sphere( props["CoordSystem"] = custom_coordinate_system else: props["CoordSystem"] = "" - return self._create_boundary(name, props, "NearFieldSphere") + return self._create_boundary(name, props, NearFieldType.Sphere) @pyaedt_function_handler() def insert_near_field_box( @@ -5710,7 +5719,7 @@ def insert_near_field_box( props["CoordSystem"] = custom_coordinate_system else: props["CoordSystem"] = "Global" - return self._create_boundary(name, props, "NearFieldBox") + return self._create_boundary(name, props, NearFieldType.Box) @pyaedt_function_handler() def insert_near_field_rectangle( @@ -5774,7 +5783,7 @@ def insert_near_field_rectangle( else: props["CoordSystem"] = "Global" - return self._create_boundary(name, props, "NearFieldRectangle") + return self._create_boundary(name, props, NearFieldType.Rectangle) @pyaedt_function_handler(line="assignment") def insert_near_field_line( @@ -5819,7 +5828,7 @@ def insert_near_field_line( props["NumPts"] = points props["Line"] = assignment - return self._create_boundary(name, props, "NearFieldLine") + return self._create_boundary(name, props, NearFieldType.Line) @pyaedt_function_handler() def insert_near_field_points( @@ -5857,7 +5866,7 @@ def insert_near_field_points( props["CoordSystem"] = coordinate_system props["PointListFile"] = str(point_file) - return self._create_boundary(name, props, "NearFieldPoints") + return self._create_boundary(name, props, NearFieldType.Points) @pyaedt_function_handler() def set_sbr_current_sources_options(self, conformance=False, thin_sources=False, power_fraction=0.95): diff --git a/src/ansys/aedt/core/modules/boundary/hfss_boundary.py b/src/ansys/aedt/core/modules/boundary/hfss_boundary.py index 29d219730c6..fe93fd98f70 100644 --- a/src/ansys/aedt/core/modules/boundary/hfss_boundary.py +++ b/src/ansys/aedt/core/modules/boundary/hfss_boundary.py @@ -532,7 +532,7 @@ def __init__(self, app, component_name, props, component_type): FieldSetup.__init__(self, app, component_name, props, component_type) -class WavePortObject(BoundaryObject): +class WavePort(BoundaryObject): """Manages HFSS Wave Port boundary objects. This class provides specialized functionality for wave port diff --git a/tests/system/general/test_hfss_excitations.py b/tests/system/general/test_hfss_excitations.py index f6a8fcef7cf..d2b2c0788ac 100644 --- a/tests/system/general/test_hfss_excitations.py +++ b/tests/system/general/test_hfss_excitations.py @@ -22,7 +22,7 @@ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. -"""Tests for HFSS excitations, particularly WavePortObject functionality.""" +"""Tests for HFSS excitations, particularly WavePort functionality.""" import pytest From 94f5969bd475eccdb0f971a045391c14266bfaf0 Mon Sep 17 00:00:00 2001 From: Samuelopez-ansys Date: Tue, 16 Sep 2025 16:54:06 +0200 Subject: [PATCH 17/22] Fix Waveport --- src/ansys/aedt/core/application/analysis.py | 4 ++-- src/ansys/aedt/core/application/design.py | 3 +++ src/ansys/aedt/core/hfss.py | 2 +- src/ansys/aedt/core/modules/boundary/hfss_boundary.py | 8 +++----- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/ansys/aedt/core/application/analysis.py b/src/ansys/aedt/core/application/analysis.py index d14c1e011eb..37a8d81eed0 100644 --- a/src/ansys/aedt/core/application/analysis.py +++ b/src/ansys/aedt/core/application/analysis.py @@ -666,7 +666,7 @@ def design_excitations(self): >>> oModule.GetExcitations """ exc_names = self.excitation_names[::] - + exc_names = list(dict.fromkeys(s.split(":", 1)[0] for s in exc_names)) for el in self.boundaries: if el.name in exc_names: self._excitation_objects[el.name] = el @@ -675,7 +675,7 @@ def design_excitations(self): keys_to_remove = [ internal_excitation for internal_excitation in self._excitation_objects - if internal_excitation not in self.excitation_names + if internal_excitation not in self.excitation_names and internal_excitation not in exc_names ] for key in keys_to_remove: diff --git a/src/ansys/aedt/core/application/design.py b/src/ansys/aedt/core/application/design.py index 81796b8e911..13054b2e89e 100644 --- a/src/ansys/aedt/core/application/design.py +++ b/src/ansys/aedt/core/application/design.py @@ -85,6 +85,7 @@ from ansys.aedt.core.internal.errors import GrpcApiError from ansys.aedt.core.internal.load_aedt_file import load_entire_aedt_file from ansys.aedt.core.modules.boundary.common import BoundaryObject +from ansys.aedt.core.modules.boundary.hfss_boundary import WavePort from ansys.aedt.core.modules.boundary.icepak_boundary import NetworkObject from ansys.aedt.core.modules.boundary.layout_boundary import BoundaryObject3dLayout from ansys.aedt.core.modules.boundary.maxwell_boundary import MaxwellParameters @@ -511,6 +512,8 @@ def boundaries(self) -> List[BoundaryObject]: self._boundaries[boundary] = BoundaryObject(self, boundary, boundarytype=maxwell_motion_type) elif boundarytype == "Network": self._boundaries[boundary] = NetworkObject(self, boundary) + elif boundarytype == "Wave Port": + self._boundaries[boundary] = WavePort(self, boundary) else: self._boundaries[boundary] = BoundaryObject(self, boundary, boundarytype=boundarytype) diff --git a/src/ansys/aedt/core/hfss.py b/src/ansys/aedt/core/hfss.py index 2bfc48bddc6..4ca8b83ecb8 100644 --- a/src/ansys/aedt/core/hfss.py +++ b/src/ansys/aedt/core/hfss.py @@ -254,7 +254,7 @@ def _create_boundary(self, name, props, boundary_type): # Wave Port cases - return WavePort if boundary_type == "Wave Port": try: - bound = WavePort(self, name, props, boundary_type) + bound = WavePort(self, name, props) if not bound.create(): raise AEDTRuntimeError(f"Failed to create boundary {boundary_type} {name}") diff --git a/src/ansys/aedt/core/modules/boundary/hfss_boundary.py b/src/ansys/aedt/core/modules/boundary/hfss_boundary.py index fe93fd98f70..84e3a783a20 100644 --- a/src/ansys/aedt/core/modules/boundary/hfss_boundary.py +++ b/src/ansys/aedt/core/modules/boundary/hfss_boundary.py @@ -548,7 +548,7 @@ class WavePort(BoundaryObject): >>> wave_port.set_analytical_alignment(True) """ - def __init__(self, app, name, props, btype): + def __init__(self, app, name, props=None): """Initialize a wave port boundary object. Parameters @@ -557,12 +557,10 @@ def __init__(self, app, name, props, btype): The AEDT application instance. name : str Name of the boundary. - props : dict + props : dict, optional Dictionary of boundary properties. - btype : str - Type of the boundary. """ - super().__init__(app, name, props, btype) + super().__init__(app, name, props, "Wave Port") @pyaedt_function_handler() def set_analytical_alignment( From ba441abdc5994b769a6a53f48e28741fe3fd6fc0 Mon Sep 17 00:00:00 2001 From: Samuelopez-ansys Date: Wed, 17 Sep 2025 11:57:40 +0200 Subject: [PATCH 18/22] Fix some properties --- .../core/modules/boundary/hfss_boundary.py | 344 ++++++++++-------- 1 file changed, 196 insertions(+), 148 deletions(-) diff --git a/src/ansys/aedt/core/modules/boundary/hfss_boundary.py b/src/ansys/aedt/core/modules/boundary/hfss_boundary.py index 84e3a783a20..da887c19866 100644 --- a/src/ansys/aedt/core/modules/boundary/hfss_boundary.py +++ b/src/ansys/aedt/core/modules/boundary/hfss_boundary.py @@ -22,8 +22,14 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +from typing import Optional +from typing import Union + +from ansys.aedt.core.generic.constants import AEDT_UNITS from ansys.aedt.core.generic.data_handlers import _dict2arg from ansys.aedt.core.generic.general_methods import pyaedt_function_handler +from ansys.aedt.core.generic.numbers_utils import Quantity +from ansys.aedt.core.internal.errors import AEDTRuntimeError from ansys.aedt.core.modeler.cad.elements_3d import BinaryTreeNode from ansys.aedt.core.modules.boundary.common import BoundaryCommon from ansys.aedt.core.modules.boundary.common import BoundaryObject @@ -562,6 +568,196 @@ def __init__(self, app, name, props=None): """ super().__init__(app, name, props, "Wave Port") + @property + def specify_wave_direction(self) -> bool: + """Enable specify wave direction. + + Returns + ------- + bool + Whether the wave direction is specified. + """ + value = None + if "Specify Wave Direction" in self.properties: + value = self.properties["Specify Wave Direction"] + return value + + @specify_wave_direction.setter + def specify_wave_direction(self, value: bool): + if not isinstance(value, bool) or self.specify_wave_direction is None: + raise AEDTRuntimeError("Wave direction must be a boolean.") + self.properties["Specify Wave Direction"] = value + + @property + def wave_direction(self) -> list: + """Wave direction. + + Returns + ------- + list + Wave direction. + """ + value = None + if "Wave Direction" in self.properties: + value = list(self.properties["Wave Direction"]) + return value + + @property + def deembed(self) -> Union[Quantity, None]: + """Dembedding distance. + + Returns + ------- + Quantity or None + Whether de-embedding is enabled. + """ + value = None + if "Deembed" in self.properties: + value_deemb = self.properties["Deembed"] + if value_deemb: + value = Quantity(self.properties["Deembed Dist"]) + return value + + @deembed.setter + def deembed(self, value: Optional[Union[Quantity, float, int, str, bool]]): + if value is None or value is False: + self.properties["Deembed"] = False + elif value is True: + self.properties["Deembed"] = True + self.properties["Deembed Dist"] = str("0.0mm") + else: + if not self.properties["Deembed"]: + self.properties["Deembed"] = True + if not isinstance(value, Quantity): + value = Quantity(self._app.value_with_units(value)) + self.properties["Deembed Dist"] = str(value) + + @property + def renorm_all_modes(self) -> bool: + """Renormalize all modes. + + Returns + ------- + bool + Whether renormalization of all modes is enabled. + """ + value = None + if "Renorm All Modes" in self.properties: + value = self.properties["Renorm All Modes"] + return value + + @renorm_all_modes.setter + def renorm_all_modes(self, value: bool): + if not isinstance(value, bool) or self.renorm_all_modes is None: + raise AEDTRuntimeError("Renorm all modes must be a boolean.") + self.properties["Renorm All Modes"] = value + + @property + def renorm_impedance_type(self) -> str: + """Get the renormalization impedance type + + Returns + ------- + str + The type of renormalization impedance. + """ + value = None + if "Renorm Impedance Type" in self.properties: + value = self.properties["Renorm Impedance Type"] + return value + + @renorm_impedance_type.setter + def renorm_impedance_type(self, value: str): + # Activate renorm all modes + if not self.renorm_all_modes: + self.renorm_all_modes = True + + if self.renorm_impedance_type and ( + "Renorm Impedance Type/Choices" in self.properties + and value not in self.properties["Renorm Impedance Type/Choices"] + ): + raise ValueError( + f"Renorm Impedance Type must be one of {self.properties['Renorm Impedance Type/Choices']}." + ) + self.properties["Renorm Impedance Type"] = value + + @property + def renorm_impedance(self) -> Union[Quantity, None]: + """Get the renormalization impedance value. + + Returns + ------- + Quantity + The renormalization impedance value. + """ + value = None + if "Renorm Imped" in self.properties: + value = Quantity(self.properties["Renorm Imped"]) + return value + + @renorm_impedance.setter + def renorm_impedance(self, value: Optional[Union[Quantity, float, int, str]]): + if self.renorm_impedance_type != "Impedance": + raise ValueError("Renorm Impedance can be set only if Renorm Impedance Type is 'Impedance'.") + + if not isinstance(value, Quantity): + value = Quantity(self._app.value_with_units(value, units_system="Resistance")) + + if value.unit not in AEDT_UNITS["Resistance"]: + raise ValueError("Renorm Impedance must have resistance units.") + self.properties["Renorm Imped"] = str(value) + + # @pyaedt_function_handler() + # def deembed_distance( + # self, distance: Optional[Union[Quantity, str, float]] = 0.0, + # ): + # """Set the deembeding distance. + # + # Parameters + # ---------- + # distance + # Deembeding distance. + # + # Returns + # ------- + # bool + # True if the operation was successful, False otherwise. + # + # Examples + # -------- + # >>> u_line = [[0, 0, 0], [1, 0, 0]] + # >>> wave_port.set_analytical_alignment(u_line, analytic_reverse_v=True) + # True + # """ + # self.properties["Deembed"] = True + # props = [ + # "NAME:ChangedProps", + # [ + # "NAME:Deembed Dist", + # "Value:=", str(distance), + # ] + # ] + # return self._change_property(props) + # + # @pyaedt_function_handler() + # def _change_property(self, arg): + # """Update properties of the mesh operation. + # + # Parameters + # ---------- + # arg : list + # List of the properties to update. For example, + # ``["NAME:ChangedProps", ["NAME:Max Length", "Value:=", "2mm"]]``. + # + # Returns + # ------- + # list + # List of changed properties of the mesh operation. + # + # """ + # arguments = ["NAME:AllTabs", ["NAME:HfssTab", ["NAME:PropServers", f"BoundarySetup:{self.name}"], arg]] + # self._app.odesign.ChangeProperty(arguments) + @pyaedt_function_handler() def set_analytical_alignment( self, u_axis_line=None, analytic_reverse_v=False, coordinate_system="Global", alignment_group=None @@ -938,154 +1134,6 @@ def filter_modes_reporter(self, value): self._app.logger.error(f"Failed to set filter modes reporter: {str(e)}") raise - @property - def specify_wave_direction(self): - """Get the 'Specify Wave Direction' property. - - Returns - ------- - bool - Whether the wave direction is specified. - """ - return self.properties["Specify Wave Direction"] - - @specify_wave_direction.setter - def specify_wave_direction(self, value): - """Set the 'Specify Wave Direction' property. - - Parameters - ---------- - value : bool - Whether to specify the wave direction. - """ - if value == self.properties["Specify Wave Direction"]: - return value - self.properties["Specify Wave Direction"] = value - - @property - def deembed(self): - """Get the de-embedding property of the wave port. - - Returns - ------- - bool - Whether de-embedding is enabled. - """ - return self.properties["Deembed"] - - @deembed.setter - def deembed(self, value): - """Set the de-embedding property of the wave port. - - Parameters - ---------- - value : bool - Whether to enable de-embedding. - """ - if value == self.properties["Deembed"]: - return value - self.properties["Deembed"] = value - - @property - def renorm_all_modes(self): - """Renormalize all modes property - - Returns - ------- - bool - Whether renormalization of all modes is enabled. - """ - return self.properties["Renorm All Modes"] - - @renorm_all_modes.setter - def renorm_all_modes(self, value): - """Set the renormalization property for all modes. - - Parameters - ---------- - value : bool - Whether to enable renormalization for all modes. - """ - if value == self.properties["Renorm All Modes"]: - return value - self.properties["Renorm All Modes"] = value - - @property - def renorm_impedance_type(self): - """Get the renormalization impedance type - - Returns - ------- - str - The type of renormalization impedance. - """ - return self.properties["Renorm Impedance Type"] - - @renorm_impedance_type.setter - def renorm_impedance_type(self, value): - """Set the renormalization impedance type. - - Parameters - ---------- - value : str - The type of renormalization impedance. It can be a type contained in the list of choices. - """ - if value not in self.properties["Renorm Impedance Type/Choices"]: - raise ValueError( - f"Renorm Impedance Type must be one of {self.properties['Renorm Impedance Type/Choices']}." - ) - if value == self.properties["Renorm Impedance Type"]: - return value - self.properties["Renorm Impedance Type"] = value - - @property - def renorm_impedance(self): - """Get the renormalization impedance value. - - Returns - ------- - str - The renormalization impedance value. - """ - return self.properties["Renorm Imped"] - - @renorm_impedance.setter - def renorm_impedance(self, value): - """Set the renormalization impedance value. - - Parameters - ---------- - value : str or int - The renormalization impedance value. Must be a string with units - (e.g., "50ohm") or a int (defaults to "ohm"). - """ - allowed_units = ["uOhm", "mOhm", "ohm", "kOhm", "megohm", "GOhm"] - if self.renorm_impedance_type != "Impedance": - raise ValueError("Renorm Impedance can be set only if Renorm Impedance Type is 'Impedance'.") - - if isinstance(value, int): - value_str = f"{value}ohm" - elif isinstance(value, float): - raise ValueError("Renorm Impedance must be a string with units or an int.") - elif isinstance(value, str): - value_str = value.replace(" ", "") - if not any(value_str.endswith(unit) for unit in allowed_units): - raise ValueError(f"Renorm Impedance must end with one of {allowed_units}.") - else: - raise ValueError("Renorm Impedance must be a string with units or a float.") - - if isinstance(value, str): - value_str = value.replace(" ", "") - else: - value_str = f"{value}ohm" - - if not any(value_str.endswith(unit) for unit in allowed_units): - raise ValueError(f"Renorm Impedance must end with one of {allowed_units}.") - - if value_str == self.properties["Renorm Imped"]: - return value_str - self.properties["Renorm Imped"] = value_str - # NOTE: The following properties are write-only as HFSS does not return their values. # Attempting to read them will raise NotImplementedError. # This is a workaround until the API provides a way to read these properties. From 78ca59baf2418dd3cf02e5f3a4fadb1be841bcd0 Mon Sep 17 00:00:00 2001 From: Samuelopez-ansys Date: Wed, 17 Sep 2025 15:32:05 +0200 Subject: [PATCH 19/22] Fix some properties --- .../core/modules/boundary/hfss_boundary.py | 571 +++++++++--------- 1 file changed, 277 insertions(+), 294 deletions(-) diff --git a/src/ansys/aedt/core/modules/boundary/hfss_boundary.py b/src/ansys/aedt/core/modules/boundary/hfss_boundary.py index da887c19866..e95f70a2e9e 100644 --- a/src/ansys/aedt/core/modules/boundary/hfss_boundary.py +++ b/src/ansys/aedt/core/modules/boundary/hfss_boundary.py @@ -568,6 +568,35 @@ def __init__(self, app, name, props=None): """ super().__init__(app, name, props, "Wave Port") + @property + def assignment(self) -> Union[str, int]: + """Wave port object assignment. + + Returns + ------- + str or int + Object assignment. + """ + value = None + if "Assignment" in self.properties: + value = self.properties["Assignment"] + if "Face_" in value: + value = [int(i.replace("Face_", "")) for i in value.split("(")[1].split(")")[0].split(",")][0] + return value + + @property + def modes(self) -> int: + """Number of modes. + + Returns + ------- + int + """ + value = None + if "Num Modes" in self.properties: + value = self.properties["Num Modes"] + return value + @property def specify_wave_direction(self) -> bool: """Enable specify wave direction. @@ -602,6 +631,26 @@ def wave_direction(self) -> list: value = list(self.properties["Wave Direction"]) return value + @property + def use_deembed(self) -> bool: + """Use dembedding. + + Returns + ------- + bool + Use dembedding. + """ + value = None + if "Deembed" in self.properties: + value = self.properties["Deembed"] + return value + + @use_deembed.setter + def use_deembed(self, value: bool): + if not isinstance(value, bool) or self.use_deembed is None: + raise AEDTRuntimeError("Use dembedding must be a boolean.") + self.properties["Deembed"] = value + @property def deembed(self) -> Union[Quantity, None]: """Dembedding distance. @@ -612,22 +661,20 @@ def deembed(self) -> Union[Quantity, None]: Whether de-embedding is enabled. """ value = None - if "Deembed" in self.properties: - value_deemb = self.properties["Deembed"] - if value_deemb: - value = Quantity(self.properties["Deembed Dist"]) + if self.use_deembed: + value = Quantity(self.properties["Deembed Dist"]) return value @deembed.setter def deembed(self, value: Optional[Union[Quantity, float, int, str, bool]]): if value is None or value is False: - self.properties["Deembed"] = False + self.use_deembed = False elif value is True: - self.properties["Deembed"] = True + self.use_deembed = True self.properties["Deembed Dist"] = str("0.0mm") else: - if not self.properties["Deembed"]: - self.properties["Deembed"] = True + if not self.use_deembed: + self.use_deembed = True if not isinstance(value, Quantity): value = Quantity(self._app.value_with_units(value)) self.properties["Deembed Dist"] = str(value) @@ -707,56 +754,228 @@ def renorm_impedance(self, value: Optional[Union[Quantity, float, int, str]]): raise ValueError("Renorm Impedance must have resistance units.") self.properties["Renorm Imped"] = str(value) - # @pyaedt_function_handler() - # def deembed_distance( - # self, distance: Optional[Union[Quantity, str, float]] = 0.0, - # ): - # """Set the deembeding distance. - # - # Parameters - # ---------- - # distance - # Deembeding distance. - # - # Returns - # ------- - # bool - # True if the operation was successful, False otherwise. - # - # Examples - # -------- - # >>> u_line = [[0, 0, 0], [1, 0, 0]] - # >>> wave_port.set_analytical_alignment(u_line, analytic_reverse_v=True) - # True - # """ - # self.properties["Deembed"] = True - # props = [ - # "NAME:ChangedProps", - # [ - # "NAME:Deembed Dist", - # "Value:=", str(distance), - # ] - # ] - # return self._change_property(props) - # - # @pyaedt_function_handler() - # def _change_property(self, arg): - # """Update properties of the mesh operation. - # - # Parameters - # ---------- - # arg : list - # List of the properties to update. For example, - # ``["NAME:ChangedProps", ["NAME:Max Length", "Value:=", "2mm"]]``. - # - # Returns - # ------- - # list - # List of changed properties of the mesh operation. - # - # """ - # arguments = ["NAME:AllTabs", ["NAME:HfssTab", ["NAME:PropServers", f"BoundarySetup:{self.name}"], arg]] - # self._app.odesign.ChangeProperty(arguments) + @property + def rlc_type(self) -> str: + """Get the RLC type property. + + Returns + ------- + str + RLC type. + """ + value = None + if "RLC Type" in self.properties: + value = self.properties["RLC Type"] + return value + + @rlc_type.setter + def rlc_type(self, value): + if self.renorm_impedance_type != "RLC": + raise ValueError("RLC Type can be set only if Renorm Impedance Type is 'RLC'.") + allowed_types = ["Serial", "Parallel"] + if value not in allowed_types: + raise ValueError(f"RLC Type must be one of {allowed_types}.") + self.properties["RLC Type"] = value + + @property + def use_resistance(self) -> bool: + """Use resistance. + + Returns + ------- + bool + Use resistance. + """ + # This property can not be disabled + value = None + if "Use Resistance" in self.properties: + value = self.properties["Use Resistance"] + return value + + @property + def resistance(self) -> Quantity: + """Resistance value. + + Returns + ------- + Quantity or None + Resistance value. + """ + value = None + if self.use_resistance: + value = Quantity(self.properties["Resistance Value"]) + return value + + @resistance.setter + def resistance(self, value: Optional[Union[Quantity, float, int, str]]): + if self.renorm_impedance_type == "RLC": + if not isinstance(value, Quantity): + value = Quantity(self._app.value_with_units(value, units_system="Resistance")) + if value.unit not in AEDT_UNITS["Resistance"]: + raise ValueError("Renorm Impedance must have resistance units.") + self.properties["Resistance Value"] = str(value) + + @property + def use_inductance(self) -> bool: + """Use inductance. + + Returns + ------- + bool + Use inductance. + """ + value = None + if "Use Inductance" in self.properties: + value = self.properties["Use Inductance"] + return value + + @use_inductance.setter + def use_inductance(self, value: bool): + if not isinstance(value, bool) or self.use_inductance is None: + raise AEDTRuntimeError("Use inductance must be a boolean.") + self.properties["Use Inductance"] = value + + @property + def inductance(self) -> Quantity: + """Inductance value. + + Returns + ------- + Quantity or None + Inductance value. + """ + value = None + if self.use_inductance: + value = Quantity(self.properties["Use Inductance"]) + return value + + @inductance.setter + def inductance(self, value: Optional[Union[Quantity, float, int, str, bool]]): + if value is None or value is False: + self.use_inductance = False + elif value is True: + self.use_inductance = True + self.properties["Inductance value"] = str("0.0nH") + else: + if not self.use_inductance: + self.use_inductance = True + + if not isinstance(value, Quantity): + value = Quantity(self._app.value_with_units(value, units_system="Inductance")) + if value.unit not in AEDT_UNITS["Inductance"]: + raise ValueError("Inductance must have inductance units.") + + self.properties["Inductance value"] = str(value) + + @property + def use_capacitance(self) -> bool: + """Use capacitance. + + Returns + ------- + bool + Use capacitance. + """ + value = None + if "Use Capacitance" in self.properties: + value = self.properties["Use Capacitance"] + return value + + @use_capacitance.setter + def use_capacitance(self, value: bool): + if not isinstance(value, bool) or self.use_capacitance is None: + raise AEDTRuntimeError("Use capacitance must be a boolean.") + self.properties["Use Capacitance"] = value + + @property + def capacitance(self) -> Quantity: + """Capacitance value. + + Returns + ------- + Quantity or None + Capacitance value. + """ + value = None + if self.use_capacitance: + value = Quantity(self.properties["Use Capacitance"]) + return value + + @capacitance.setter + def capacitance(self, value: Optional[Union[Quantity, float, int, str, bool]]): + if value is None or value is False: + self.use_capacitance = False + elif value is True: + self.use_capacitance = True + self.properties["Capactiance value"] = str("0.0nF") + else: + if not self.use_capacitance: + self.use_capacitance = True + + if not isinstance(value, Quantity): + value = Quantity(self._app.value_with_units(value, units_system="Capacitance")) + if value.unit not in AEDT_UNITS["Capacitance"]: + raise ValueError("Capacitance must have inductance units.") + + self.properties["Capacitance value"] = str(value) + + @property + def filter_modes_reporter(self): + """Get the reporter filter setting for each mode. + + Returns + ------- + list of bool + List of boolean values indicating whether each mode is + filtered in the reporter. + """ + return self.props["ReporterFilter"] + + @filter_modes_reporter.setter + def filter_modes_reporter(self, value): + """Set the reporter filter setting for wave port modes. + + Parameters + ---------- + value : bool or list of bool + Boolean value(s) to set for the reporter filter. If a + single boolean is provided, it will be applied to all + modes. If a list is provided, it must match the number + of modes. + + Examples + -------- + >>> # Set all modes to be filtered + >>> wave_port.filter_modes_reporter = True + + >>> # Set specific filter values for each mode + >>> wave_port.filter_modes_reporter = [True, False, True] + """ + try: + num_modes = self.properties["Num Modes"] + show_reporter_filter = True + if isinstance(value, bool): + # Single boolean value - apply to all modes + filter_values = [value] * num_modes + # In case all values are the same, we hide the Reporter Filter + show_reporter_filter = False + elif isinstance(value, list): + # List of boolean values + if not all(isinstance(v, bool) for v in value): + raise ValueError("All values in the list must be boolean.") + if len(value) != num_modes: + raise ValueError(f"List length ({len(value)}) must match the number of modes ({num_modes}).") + filter_values = value + else: + raise ValueError("Value must be a boolean or a list of booleans.") + self.props["ShowReporterFilter"] = show_reporter_filter + # Apply the filter values to each mode + self.props["ReporterFilter"] = filter_values + + self.update() + except Exception as e: + self._app.logger.error(f"Failed to set filter modes reporter: {str(e)}") + raise @pyaedt_function_handler() def set_analytical_alignment( @@ -1075,239 +1294,3 @@ def set_polarity_integration_line(self, integration_lines=None, coordinate_syste except Exception as e: self._app.logger.error(f"Failed to set polarity integration lines: {str(e)}") return False - - @property - def filter_modes_reporter(self): - """Get the reporter filter setting for each mode. - - Returns - ------- - list of bool - List of boolean values indicating whether each mode is - filtered in the reporter. - """ - return self.props["ReporterFilter"] - - @filter_modes_reporter.setter - def filter_modes_reporter(self, value): - """Set the reporter filter setting for wave port modes. - - Parameters - ---------- - value : bool or list of bool - Boolean value(s) to set for the reporter filter. If a - single boolean is provided, it will be applied to all - modes. If a list is provided, it must match the number - of modes. - - Examples - -------- - >>> # Set all modes to be filtered - >>> wave_port.filter_modes_reporter = True - - >>> # Set specific filter values for each mode - >>> wave_port.filter_modes_reporter = [True, False, True] - """ - try: - num_modes = self.properties["Num Modes"] - show_reporter_filter = True - if isinstance(value, bool): - # Single boolean value - apply to all modes - filter_values = [value] * num_modes - # In case all values are the same, we hide the Reporter Filter - show_reporter_filter = False - elif isinstance(value, list): - # List of boolean values - if not all(isinstance(v, bool) for v in value): - raise ValueError("All values in the list must be boolean.") - if len(value) != num_modes: - raise ValueError(f"List length ({len(value)}) must match the number of modes ({num_modes}).") - filter_values = value - else: - raise ValueError("Value must be a boolean or a list of booleans.") - self.props["ShowReporterFilter"] = show_reporter_filter - # Apply the filter values to each mode - self.props["ReporterFilter"] = filter_values - - self.update() - except Exception as e: - self._app.logger.error(f"Failed to set filter modes reporter: {str(e)}") - raise - - # NOTE: The following properties are write-only as HFSS does not return their values. - # Attempting to read them will raise NotImplementedError. - # This is a workaround until the API provides a way to read these properties. - # Also, these properties reset to default - @property - def rlc_type(self): - """Get the RLC type property.""" - raise NotImplementedError("Getter for 'rlc_type' is not implemented. Use the setter only.") - - @rlc_type.setter - def rlc_type(self, value): - """Set the RLC type property. - - Parameters - ---------- - value : str - The type of RLC circuit. - """ - if self.renorm_impedance_type != "RLC": - raise ValueError("RLC Type can be set only if Renorm Impedance Type is 'RLC'.") - allowed_types = ["Serial", "Parallel"] - if value not in allowed_types: - raise ValueError(f"RLC Type must be one of {allowed_types}.") - self.properties["RLC Type"] = value - - @property - def use_resistance(self): - """Get the 'Use Resistance' property.""" - raise NotImplementedError("Getter for 'use_resistance' is not implemented. Use the setter only.") - - @use_resistance.setter - def use_resistance(self, value): - """Set the 'Use Resistance' property. - - Parameters - ---------- - value : bool - Whether to use resistance in the RLC circuit. - """ - if not isinstance(value, bool): - raise ValueError("Use Resistance must be a boolean value.") - if self.renorm_impedance_type != "RLC": - raise ValueError("Use Resistance can be set only if Renorm Impedance Type is 'RLC'.") - self.properties["Use Resistance"] = value - - @property - def resistance_value(self): - """Get the resistance value.""" - raise NotImplementedError("Getter for 'resistance_value' is not implemented. Use the setter only.") - - @resistance_value.setter - def resistance_value(self, value): - """Set the resistance value. - - Parameters - ---------- - value : str or float - The resistance value. Must be a string with units (e.g., "50ohm") or a float (defaults to "ohm"). - """ - allowed_units = ["uOhm", "mOhm", "ohm", "kOhm", "megohm", "GOhm"] - if self.renorm_impedance_type != "RLC": - raise ValueError("Resistance can be set only if Renorm Impedance Type is 'RLC'.") - if isinstance(value, (int, float)): - value_str = f"{value}ohm" - elif isinstance(value, str): - value_str = value.replace(" ", "") - if not any(value_str.endswith(unit) for unit in allowed_units): - raise ValueError(f"Resistance must end with one of {allowed_units}.") - else: - raise ValueError("Resistance must be a string with units or a float.") - if isinstance(value, str): - value_str = value.replace(" ", "") - if not any(value_str.endswith(unit) for unit in allowed_units): - raise ValueError(f"Resistance must end with one of {allowed_units}.") - self.properties["Resistance Value"] = value_str - - @property - def use_inductance(self): - """Get the 'Use Inductance' property.""" - raise NotImplementedError("Getter for 'use_inductance' is not implemented. Use the setter only.") - - @use_inductance.setter - def use_inductance(self, value): - """Set the 'Use Inductance' property. - - Parameters - ---------- - value : bool - Whether to use inductance in the RLC circuit. - """ - if self.renorm_impedance_type != "RLC": - raise ValueError("Use Inductance can be set only if Renorm Impedance Type is 'RLC'.") - if not isinstance(value, bool): - raise ValueError("Use Inductance must be a boolean value.") - self.properties["Use Inductance"] = value - - @property - def inductance_value(self): - """Get the inductance value.""" - raise NotImplementedError("Getter for 'inductance_value' is not implemented. Use the setter only.") - - @inductance_value.setter - def inductance_value(self, value): - """Set the inductance value. - - Parameters - ---------- - value : str or float - The inductance value. Must be a string with units (e.g., "10nH") or a float (defaults to "H"). - """ - allowed_units = ["fH", "pH", "nH", "uH", "mH", "H"] - if self.renorm_impedance_type != "RLC": - raise ValueError("Inductance can be set only if Renorm Impedance Type is 'RLC'.") - if isinstance(value, (int, float)): - value_str = f"{value}H" - elif isinstance(value, str): - value_str = value.replace(" ", "") - if not any(value_str.endswith(unit) for unit in allowed_units): - raise ValueError(f"Inductance must end with one of {allowed_units}.") - else: - raise ValueError("Inductance must be a string with units or a float.") - if isinstance(value, str): - value_str = value.replace(" ", "") - if not any(value_str.endswith(unit) for unit in allowed_units): - raise ValueError(f"Inductance must end with one of {allowed_units}.") - self.properties["Inductance Value"] = value_str - - @property - def use_capacitance(self): - """Get the 'Use Capacitance' property.""" - raise NotImplementedError("Getter for 'use_capacitance' is not implemented. Use the setter only.") - - @use_capacitance.setter - def use_capacitance(self, value): - """Set the 'Use Capacitance' property. - - Parameters - ---------- - value : bool - Whether to use capacitance in the RLC circuit. - """ - if self.renorm_impedance_type != "RLC": - raise ValueError("Use Capacitance can be set only if Renorm Impedance Type is 'RLC'.") - if not isinstance(value, bool): - raise ValueError("Use Capacitance must be a boolean value.") - self.properties["Use Capacitance"] = value - - @property - def capacitance_value(self): - """Get the capacitance value.""" - raise NotImplementedError("Getter for 'capacitance_value' is not implemented. Use the setter only.") - - @capacitance_value.setter - def capacitance_value(self, value): - """Set the capacitance value. - - Parameters - ---------- - value : str or float - The capacitance value. Must be a string with units (e.g., "1pF") or a float (defaults to "F"). - """ - allowed_units = ["fF", "pF", "nF", "uF", "mF", "farad"] - if self.renorm_impedance_type != "RLC": - raise ValueError("Capacitance can be set only if Renorm Impedance Type is 'RLC'.") - if isinstance(value, (int, float)): - value_str = f"{value}F" - elif isinstance(value, str): - value_str = value.replace(" ", "") - if not any(value_str.endswith(unit) for unit in allowed_units): - raise ValueError(f"Capacitance must end with one of {allowed_units}.") - else: - raise ValueError("Capacitance must be a string with units or a float.") - if isinstance(value, str): - value_str = value.replace(" ", "") - if not any(value_str.endswith(unit) for unit in allowed_units): - raise ValueError(f"Capacitance must end with one of {allowed_units}.") - self.properties["Capacitance Value"] = value_str From bb72ad27cbef8e9ae98e730fa85d3b8efa5f1e9b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 28 Oct 2025 09:26:43 +0000 Subject: [PATCH 20/22] CHORE: Auto fixes from pre-commit hooks --- src/ansys/aedt/core/modules/boundary/hfss_boundary.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ansys/aedt/core/modules/boundary/hfss_boundary.py b/src/ansys/aedt/core/modules/boundary/hfss_boundary.py index 48ddcae8e0a..5e2b6225e4d 100644 --- a/src/ansys/aedt/core/modules/boundary/hfss_boundary.py +++ b/src/ansys/aedt/core/modules/boundary/hfss_boundary.py @@ -25,8 +25,8 @@ from typing import Optional from typing import Union -from ansys.aedt.core.generic.constants import AEDT_UNITS from ansys.aedt.core.base import PyAedtBase +from ansys.aedt.core.generic.constants import AEDT_UNITS from ansys.aedt.core.generic.data_handlers import _dict2arg from ansys.aedt.core.generic.general_methods import pyaedt_function_handler from ansys.aedt.core.generic.numbers_utils import Quantity From ad5cdcc45a1cf9346ddce48751339582f2491c04 Mon Sep 17 00:00:00 2001 From: Eduardo Blanco Date: Tue, 28 Oct 2025 14:42:24 +0100 Subject: [PATCH 21/22] Minor fixes --- .../core/modules/boundary/hfss_boundary.py | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/ansys/aedt/core/modules/boundary/hfss_boundary.py b/src/ansys/aedt/core/modules/boundary/hfss_boundary.py index 5e2b6225e4d..5d28964fe97 100644 --- a/src/ansys/aedt/core/modules/boundary/hfss_boundary.py +++ b/src/ansys/aedt/core/modules/boundary/hfss_boundary.py @@ -832,7 +832,7 @@ def use_inductance(self) -> bool: @use_inductance.setter def use_inductance(self, value: bool): - if not isinstance(value, bool) or self.use_inductance is None: + if not isinstance(value, bool): raise AEDTRuntimeError("Use inductance must be a boolean.") self.properties["Use Inductance"] = value @@ -884,7 +884,7 @@ def use_capacitance(self) -> bool: @use_capacitance.setter def use_capacitance(self, value: bool): - if not isinstance(value, bool) or self.use_capacitance is None: + if not isinstance(value, bool): raise AEDTRuntimeError("Use capacitance must be a boolean.") self.properties["Use Capacitance"] = value @@ -908,7 +908,7 @@ def capacitance(self, value: Optional[Union[Quantity, float, int, str, bool]]): self.use_capacitance = False elif value is True: self.use_capacitance = True - self.properties["Capactiance value"] = str("0.0nF") + self.properties["Capacitance value"] = str("0.0nF") else: if not self.use_capacitance: self.use_capacitance = True @@ -933,7 +933,7 @@ def filter_modes_reporter(self): return self.props["ReporterFilter"] @filter_modes_reporter.setter - def filter_modes_reporter(self, value): + def filter_modes_reporter(self, value: Union[bool, list]): """Set the reporter filter setting for wave port modes. Parameters @@ -980,7 +980,7 @@ def filter_modes_reporter(self, value): @pyaedt_function_handler() def set_analytical_alignment( - self, u_axis_line=None, analytic_reverse_v=False, coordinate_system="Global", alignment_group=None + self, u_axis_line: list, analytic_reverse_v: Optional[bool] = None, coordinate_system: str = "Global", alignment_group: Optional[Union[int, list]] = None ): """Set the analytical alignment property for the wave port. @@ -990,7 +990,7 @@ def set_analytical_alignment( List containing start and end points for the U-axis line. Format: [[x1, y1, z1], [x2, y2, z2]] analytic_reverse_v : bool, optional - Whether to reverse the V direction. Default is False. + Whether to reverse the V direction. Default is None. coordinate_system : str, optional Coordinate system to use. Default is "Global". alignment_group : int, list or None, optional @@ -1044,7 +1044,12 @@ def set_analytical_alignment( return False @pyaedt_function_handler() - def set_alignment_integration_line(self, integration_lines=None, coordinate_system="Global", alignment_groups=None): + def set_alignment_integration_line( + self, + integration_lines: Optional[list] = None, + coordinate_system: str = "Global", + alignment_groups: Optional[Union[int, list]] = None, + ): """Set the integration line alignment property for the wave port modes. This method configures integration lines for wave port modes, @@ -1181,7 +1186,9 @@ def set_alignment_integration_line(self, integration_lines=None, coordinate_syst return False @pyaedt_function_handler() - def set_polarity_integration_line(self, integration_lines=None, coordinate_system="Global"): + def set_polarity_integration_line( + self, integration_lines: Optional[list] = None, coordinate_system: str = "Global" + ): """Set polarity integration lines for the wave port modes. This method configures integration lines for wave port modes From e7a7c5aacb7d97041f0e6ff7ddc3bbb6c054ecff Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 28 Oct 2025 18:35:36 +0000 Subject: [PATCH 22/22] CHORE: Auto fixes from pre-commit hooks --- src/ansys/aedt/core/modules/boundary/hfss_boundary.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/ansys/aedt/core/modules/boundary/hfss_boundary.py b/src/ansys/aedt/core/modules/boundary/hfss_boundary.py index 5d28964fe97..e36dbe4ef54 100644 --- a/src/ansys/aedt/core/modules/boundary/hfss_boundary.py +++ b/src/ansys/aedt/core/modules/boundary/hfss_boundary.py @@ -980,7 +980,11 @@ def filter_modes_reporter(self, value: Union[bool, list]): @pyaedt_function_handler() def set_analytical_alignment( - self, u_axis_line: list, analytic_reverse_v: Optional[bool] = None, coordinate_system: str = "Global", alignment_group: Optional[Union[int, list]] = None + self, + u_axis_line: list, + analytic_reverse_v: Optional[bool] = None, + coordinate_system: str = "Global", + alignment_group: Optional[Union[int, list]] = None, ): """Set the analytical alignment property for the wave port.