From ca577424546317b4cdde0509b1da86398915e762 Mon Sep 17 00:00:00 2001 From: Eduardo Blanco Date: Mon, 1 Sep 2025 19:10:56 +0200 Subject: [PATCH 01/29] 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 7379a957e095..e0a9ceb73a36 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/29] 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 998058efd983..ef668c9c3cc8 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/29] 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 ef668c9c3cc8..0d91b9ebdaca 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 e0a9ceb73a36..89c96a47f0d2 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/29] 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 000000000000..5d25fbe24d0b --- /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/29] 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 89c96a47f0d2..c18aa805e45a 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/29] 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 c18aa805e45a..aed2032ffe63 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/29] 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 aed2032ffe63..3063b693547a 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/29] 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 aed2032ffe63..d9d9b488700b 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/29] 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 000000000000..bb3b4973fcdd --- /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/29] 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 7039758036fe..c4479ec6179e 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 bb3b4973fcdd..b78b034071c2 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/29] 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 c4479ec6179e..a7d6ada83393 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/29] 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 a7d6ada83393..29d219730c68 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/29] 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 5d25fbe24d0b..c086228dc88e 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/29] 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 b78b034071c2..b6fc05944902 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/29] 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 b6fc05944902..f6a8fcef7cff 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/29] 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 0d91b9ebdaca..2bfc48bddc6c 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 29d219730c68..fe93fd98f703 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 f6a8fcef7cff..d2b2c0788ac4 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/29] 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 d14c1e011ebf..37a8d81eed04 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 81796b8e911e..13054b2e89e1 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 2bfc48bddc6c..4ca8b83ecb8b 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 fe93fd98f703..84e3a783a207 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/29] 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 84e3a783a207..da887c19866a 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/29] 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 da887c19866a..e95f70a2e9eb 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/29] 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 48ddcae8e0a0..5e2b6225e4dd 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/29] 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 5e2b6225e4dd..5d28964fe979 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/29] 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 5d28964fe979..e36dbe4ef546 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. From 2f32d1c79f9bed0cda9f1ab06e7cb3a702bf42c4 Mon Sep 17 00:00:00 2001 From: gkorompi Date: Fri, 22 May 2026 16:47:09 +0300 Subject: [PATCH 23/29] wwport --- src/ansys/aedt/core/application/design.py | 2 +- src/ansys/aedt/core/mixins.py | 3 +- .../core/modules/boundary/hfss_boundary.py | 136 ++++++++++++------ 3 files changed, 93 insertions(+), 48 deletions(-) diff --git a/src/ansys/aedt/core/application/design.py b/src/ansys/aedt/core/application/design.py index de1ff547fa64..fe12d63a320e 100644 --- a/src/ansys/aedt/core/application/design.py +++ b/src/ansys/aedt/core/application/design.py @@ -82,7 +82,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.hfss_boundary import WavePortCommon 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 diff --git a/src/ansys/aedt/core/mixins.py b/src/ansys/aedt/core/mixins.py index 31dd0c6deecc..3854447bc384 100644 --- a/src/ansys/aedt/core/mixins.py +++ b/src/ansys/aedt/core/mixins.py @@ -26,6 +26,7 @@ from ansys.aedt.core.internal.errors import AEDTRuntimeError from ansys.aedt.core.internal.errors import GrpcApiError from ansys.aedt.core.modules.boundary.common import BoundaryObject +from ansys.aedt.core.modules.boundary.hfss_boundary import WavePortCommon, WavePortModal, WavePortTerminal class CreateBoundaryMixin: @@ -69,7 +70,7 @@ def _create_boundary(self, name: str, props, boundary_type) -> BoundaryObject: """ try: - bound = BoundaryObject(self, name, props, boundary_type) + bound = WavePortTerminal(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 63a5b8fb1ae1..f4bf8a5b068e 100644 --- a/src/ansys/aedt/core/modules/boundary/hfss_boundary.py +++ b/src/ansys/aedt/core/modules/boundary/hfss_boundary.py @@ -539,7 +539,7 @@ def __init__(self, app, component_name, props, component_type) -> None: FieldSetup.__init__(self, app, component_name, props, component_type) -class WavePort(BoundaryObject): +class WavePortCommon(BoundaryObject): """Manages HFSS Wave Port boundary objects. This class provides specialized functionality for wave port @@ -570,32 +570,34 @@ 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. + def wave_port_type(self) -> str: + """Wave port type. Returns ------- - str or int - Object assignment. + str + Either 'Terminal' or 'Modal". It cannot be changed. """ 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] + if "Wave Port Type" in self.properties: + value = self.properties["Wave Port Type"] return value + @property - def modes(self) -> int: - """Number of modes. + def assignment(self) -> Union[str, int]: + """Wave port object assignment. Returns ------- - int + str or int + Object assignment. """ value = None - if "Num Modes" in self.properties: - value = self.properties["Num Modes"] + 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 @@ -633,7 +635,7 @@ def wave_direction(self) -> list: return value @property - def use_deembed(self) -> bool: + def deembed(self) -> bool: """Use dembedding. Returns @@ -646,14 +648,14 @@ def use_deembed(self) -> bool: value = self.properties["Deembed"] return value - @use_deembed.setter - def use_deembed(self, value: bool): + @deembed.setter + def 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]: + def deembed_distance(self) -> Union[Quantity, None]: """Dembedding distance. Returns @@ -666,8 +668,8 @@ def deembed(self) -> Union[Quantity, None]: value = Quantity(self.properties["Deembed Dist"]) return value - @deembed.setter - def deembed(self, value: Optional[Union[Quantity, float, int, str, bool]]): + @deembed_distance.setter + def deembed_distance(self, value: Optional[Union[Quantity, float, int, str, bool]]): if value is None or value is False: self.use_deembed = False elif value is True: @@ -680,6 +682,22 @@ def deembed(self, value: Optional[Union[Quantity, float, int, str, bool]]): value = Quantity(self._app.value_with_units(value)) self.properties["Deembed Dist"] = str(value) + +class WavePortModal(WavePortCommon): + + @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 renorm_all_modes(self) -> bool: """Renormalize all modes. @@ -721,8 +739,8 @@ def renorm_impedance_type(self, value: str): 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"] + "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']}." @@ -932,6 +950,7 @@ def filter_modes_reporter(self): """ return self.props["ReporterFilter"] + @filter_modes_reporter.setter def filter_modes_reporter(self, value: Union[bool, list]): """Set the reporter filter setting for wave port modes. @@ -980,11 +999,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. @@ -1018,14 +1037,15 @@ def set_analytical_alignment( 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)): + 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) + 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): @@ -1049,10 +1069,10 @@ def set_analytical_alignment( @pyaedt_function_handler() def set_alignment_integration_line( - self, - integration_lines: Optional[list] = None, - coordinate_system: str = "Global", - alignment_groups: Optional[Union[int, list]] = None, + 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. @@ -1121,9 +1141,9 @@ def set_alignment_integration_line( # 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) + 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]]." @@ -1140,7 +1160,8 @@ def set_alignment_integration_line( 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): @@ -1191,7 +1212,7 @@ def set_alignment_integration_line( @pyaedt_function_handler() def set_polarity_integration_line( - self, integration_lines: Optional[list] = None, coordinate_system: str = "Global" + self, integration_lines: Optional[list] = None, coordinate_system: str = "Global" ): """Set polarity integration lines for the wave port modes. @@ -1249,18 +1270,18 @@ def set_polarity_integration_line( 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)) + 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) + 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]]." @@ -1306,3 +1327,26 @@ def set_polarity_integration_line( except Exception as e: self._app.logger.error(f"Failed to set polarity integration lines: {str(e)}") return False + + +class WavePortTerminal(WavePortCommon): + + @property + def renorm_all_terminals(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_terminals.setter + def renorm_all_terminals(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 From 8d82636f55897037f6f1deee119347819a9f6fb0 Mon Sep 17 00:00:00 2001 From: gkorompi Date: Sat, 23 May 2026 12:11:18 +0300 Subject: [PATCH 24/29] wwport --- src/ansys/aedt/core/application/design.py | 2 +- src/ansys/aedt/core/hfss.py | 93 ++++++++++--------- .../core/modules/boundary/hfss_boundary.py | 38 +++++++- 3 files changed, 85 insertions(+), 48 deletions(-) diff --git a/src/ansys/aedt/core/application/design.py b/src/ansys/aedt/core/application/design.py index fe12d63a320e..56e766058e0f 100644 --- a/src/ansys/aedt/core/application/design.py +++ b/src/ansys/aedt/core/application/design.py @@ -528,7 +528,7 @@ def boundaries(self) -> list[BoundaryObject]: elif boundarytype == "Network": self._boundaries[boundary] = NetworkObject(self, boundary) elif boundarytype == "Wave Port": - self._boundaries[boundary] = WavePort(self, boundary) + self._boundaries[boundary] = WavePortCommon(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 809e01fd12a5..cfda14d68435 100644 --- a/src/ansys/aedt/core/hfss.py +++ b/src/ansys/aedt/core/hfss.py @@ -624,53 +624,54 @@ def _create_waveport_driven( modes = {} i = 1 report_filter = [] - while i <= nummodes: - start = None - stop = None - line_index = i - 1 - if ( - len(int_line_start) > line_index - and len(int_line_stop) > line_index - and int_line_start[line_index] - and int_line_stop[line_index] - ): - useintline = True - start = [ - str(i) + self.modeler.model_units if type(i) in (int, float) else i - for i in int_line_start[line_index] - ] - stop = [ - str(i) + self.modeler.model_units if type(i) in (int, float) else i - for i in int_line_stop[line_index] - ] - else: - useintline = False - if useintline: - mode = {"ModeNum": i, "UseIntLine": useintline} + if nummodes > 0: + while i <= nummodes: + start = None + stop = None + line_index = i - 1 + if ( + len(int_line_start) > line_index + and len(int_line_stop) > line_index + and int_line_start[line_index] + and int_line_stop[line_index] + ): + useintline = True + start = [ + str(i) + self.modeler.model_units if type(i) in (int, float) else i + for i in int_line_start[line_index] + ] + stop = [ + str(i) + self.modeler.model_units if type(i) in (int, float) else i + for i in int_line_stop[line_index] + ] + else: + useintline = False if useintline: - mode["IntLine"] = dict({"Start": start, "End": stop}) - mode["AlignmentGroup"] = 0 - mode["CharImp"] = characteristic_impedance[line_index] - if renorm: - mode["RenormImp"] = str(impedance) + "ohm" - modes["Mode" + str(i)] = mode - else: - mode = { - "ModeNum": i, - "UseIntLine": False, - "AlignmentGroup": 0, - "CharImp": characteristic_impedance[line_index], - } - - if renorm: - mode["RenormImp"] = str(impedance) + "ohm" - modes["Mode" + str(i)] = mode - report_filter.append(True) - i += 1 - props["Modes"] = modes - props["ShowReporterFilter"] = False - props["ReporterFilter"] = report_filter - props["UseAnalyticAlignment"] = False + mode = {"ModeNum": i, "UseIntLine": useintline} + if useintline: + mode["IntLine"] = dict({"Start": start, "End": stop}) + mode["AlignmentGroup"] = 0 + mode["CharImp"] = characteristic_impedance[line_index] + if renorm: + mode["RenormImp"] = str(impedance) + "ohm" + modes["Mode" + str(i)] = mode + else: + mode = { + "ModeNum": i, + "UseIntLine": False, + "AlignmentGroup": 0, + "CharImp": characteristic_impedance[line_index], + } + + if renorm: + mode["RenormImp"] = str(impedance) + "ohm" + modes["Mode" + str(i)] = mode + report_filter.append(True) + i += 1 + props["Modes"] = modes + props["ShowReporterFilter"] = False + props["ReporterFilter"] = report_filter + props["UseAnalyticAlignment"] = False return self._create_boundary(port_name, props, "Wave Port") # Boundaries diff --git a/src/ansys/aedt/core/modules/boundary/hfss_boundary.py b/src/ansys/aedt/core/modules/boundary/hfss_boundary.py index f4bf8a5b068e..2c4ef03c67c3 100644 --- a/src/ansys/aedt/core/modules/boundary/hfss_boundary.py +++ b/src/ansys/aedt/core/modules/boundary/hfss_boundary.py @@ -31,7 +31,9 @@ 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.modeler import cad +from ansys.aedt.core.modeler.cad.elements_3d import BinaryTreeNode, FacePrimitive +from ansys.aedt.core.modeler.cad.object_3d import Object3d 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 @@ -1350,3 +1352,37 @@ def renorm_all_terminals(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 + + @pyaedt_function_handler() + def assign_terminal( + self, + faces: list | int | FacePrimitive, + name: str = None, + impedance: float = 50): + + props_terminal = {} + if isinstance(faces, int): + faces = [faces] + elif isinstance(faces, FacePrimitive): + faces = [faces.id] + props_terminal["TerminalResistance"] = str(impedance) + "ohm" + props_terminal["ParentBndID"] = self.name + props_terminal["ImpedanceType"] = "Impedance" + + if not name: + name = self.name + "_T" + for boundary in self._app.boundaries: + if boundary.name == name: + name = name + "_1" + props = [] + props.append("Name:" + name) + props.append("Faces:=") + props.append(faces) + props.append("ParentBndID:=") + props.append(self.name) + props.append("ImpedanceType:=") + props.append("Impedance") + props.append("TerminalResistance:=") + props.append(str(impedance) + "ohm") + self._app.oboundary.AssignTerminal(props) + From 451095032059ecf6318d07bab93bde0cfee5d1e4 Mon Sep 17 00:00:00 2001 From: gkorompi Date: Sat, 23 May 2026 13:49:55 +0300 Subject: [PATCH 25/29] wwport --- src/ansys/aedt/core/application/design.py | 11 +++++-- .../core/modules/boundary/hfss_boundary.py | 29 +++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/ansys/aedt/core/application/design.py b/src/ansys/aedt/core/application/design.py index 56e766058e0f..c3f422826798 100644 --- a/src/ansys/aedt/core/application/design.py +++ b/src/ansys/aedt/core/application/design.py @@ -82,7 +82,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 WavePortCommon +from ansys.aedt.core.modules.boundary.hfss_boundary import WavePortCommon, WavePortModal, WavePortTerminal, Terminal 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 @@ -528,7 +528,14 @@ def boundaries(self) -> list[BoundaryObject]: elif boundarytype == "Network": self._boundaries[boundary] = NetworkObject(self, boundary) elif boundarytype == "Wave Port": - self._boundaries[boundary] = WavePortCommon(self, boundary) + temp_bound = WavePortCommon(self, boundary) + if "Wave Port Type" in temp_bound.properties: + if temp_bound.properties["Wave Port Type"] == "Modal": + self._boundaries[boundary] = WavePortModal(self, boundary) + else: + self._boundaries[boundary] = WavePortTerminal(self, boundary) + else: + self._boundaries[boundary] = Terminal(self, boundary) else: self._boundaries[boundary] = BoundaryObject(self, boundary, boundarytype=boundarytype) diff --git a/src/ansys/aedt/core/modules/boundary/hfss_boundary.py b/src/ansys/aedt/core/modules/boundary/hfss_boundary.py index 2c4ef03c67c3..e99d82c24033 100644 --- a/src/ansys/aedt/core/modules/boundary/hfss_boundary.py +++ b/src/ansys/aedt/core/modules/boundary/hfss_boundary.py @@ -1386,3 +1386,32 @@ def assign_terminal( props.append(str(impedance) + "ohm") self._app.oboundary.AssignTerminal(props) + #bound = BoundaryObject(self, name, props_terminal) + #self._app._boundaries[name] = bound + #return bound + + @property + def terminals(self): + terminals = [] + for boundary in self._app.boundaries: + for terminal in list(self.children.keys()): + if boundary.name == terminal: + terminals.append(boundary) + return terminals + + +class Terminal(BoundaryObject): + def __init__(self, app, name, props=None): + """Initialize a terminal boundary object. + + Parameters + ---------- + app : :class:`ansys.aedt.core.application.analysis_3d.FieldAnalysis3D` + The AEDT application instance. + name : str + Name of the boundary. + props : dict, optional + Dictionary of boundary properties. + """ + super().__init__(app, name, props, "Terminal") + From 871d058fc6448042cb8503224379092310629e59 Mon Sep 17 00:00:00 2001 From: gkorompi Date: Mon, 25 May 2026 18:51:11 +0300 Subject: [PATCH 26/29] wwport --- src/ansys/aedt/core/hfss.py | 6 +- src/ansys/aedt/core/mixins.py | 23 +- .../aedt/core/modules/boundary/common.py | 2 + .../core/modules/boundary/hfss_boundary.py | 247 ++++++++++++++++-- 4 files changed, 255 insertions(+), 23 deletions(-) diff --git a/src/ansys/aedt/core/hfss.py b/src/ansys/aedt/core/hfss.py index cfda14d68435..8e814e3fa362 100644 --- a/src/ansys/aedt/core/hfss.py +++ b/src/ansys/aedt/core/hfss.py @@ -75,7 +75,7 @@ from ansys.aedt.core.modeler.cad.components_3d import UserDefinedComponent from ansys.aedt.core.modeler.geometry_operators import GeometryOperators 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 FarFieldSetup, WavePortTerminal, WavePortModal, Terminal from ansys.aedt.core.modules.boundary.hfss_boundary import NearFieldSetup from ansys.aedt.core.modules.boundary.layout_boundary import NativeComponentObject from ansys.aedt.core.modules.setup_templates import SetupKeys @@ -255,6 +255,8 @@ def _init_from_design(self, *args, **kwargs) -> None: # NOTE: Extend Mixin behaviour to handle near field setups def _create_boundary(self, name: str, props, boundary_type) -> "NearFieldSetup | BoundaryObject": # No-near field cases + if boundary_type == "Wave Port": + return super()._create_wave_port_boundary(name, props) if boundary_type not in ( "NearFieldSphere", "NearFieldBox", @@ -6774,7 +6776,7 @@ def wave_port( else: deembed = deembed - # Draw terminal lumped port between two objects. + # Draw terminal wave port between two objects. return self._create_port_terminal( faces[0], reference, diff --git a/src/ansys/aedt/core/mixins.py b/src/ansys/aedt/core/mixins.py index 3854447bc384..dd1998573a49 100644 --- a/src/ansys/aedt/core/mixins.py +++ b/src/ansys/aedt/core/mixins.py @@ -26,7 +26,7 @@ from ansys.aedt.core.internal.errors import AEDTRuntimeError from ansys.aedt.core.internal.errors import GrpcApiError from ansys.aedt.core.modules.boundary.common import BoundaryObject -from ansys.aedt.core.modules.boundary.hfss_boundary import WavePortCommon, WavePortModal, WavePortTerminal +from ansys.aedt.core.modules.boundary.hfss_boundary import WavePortModal, WavePortTerminal, Terminal class CreateBoundaryMixin: @@ -70,7 +70,7 @@ def _create_boundary(self, name: str, props, boundary_type) -> BoundaryObject: """ try: - bound = WavePortTerminal(self, name, props) + bound = BoundaryObject(self, name, props, boundary_type) if not bound.create(): raise AEDTRuntimeError(f"Failed to create boundary {boundary_type} {name}") @@ -79,3 +79,22 @@ def _create_boundary(self, name: str, props, boundary_type) -> BoundaryObject: return bound except GrpcApiError as e: raise AEDTRuntimeError(f"Failed to create boundary {boundary_type} {name}") from e + + @pyaedt_function_handler + def _create_wave_port_boundary(self, name: str, props) -> WavePortModal | WavePortTerminal | Terminal: + try: + if "NumModes" in props: + if props["NumModes"] == 0: + bound = WavePortTerminal(self, name, props) + else: + bound = WavePortModal(self, name, props) + else: + bound = Terminal(self, name, props) + if not bound.create(): + raise AEDTRuntimeError(f"Failed to create Wave Port Boundary {name}") + + self._boundaries[bound.name] = bound + self.logger.info(f"Wave Port Boundary {name} has been created.") + return bound + except GrpcApiError as e: + raise AEDTRuntimeError(f"Failed to create Wave Port Boundary {name}") from e \ No newline at end of file diff --git a/src/ansys/aedt/core/modules/boundary/common.py b/src/ansys/aedt/core/modules/boundary/common.py index aaaacf1bef0d..7433ad1d675c 100644 --- a/src/ansys/aedt/core/modules/boundary/common.py +++ b/src/ansys/aedt/core/modules/boundary/common.py @@ -529,6 +529,8 @@ def create(self) -> bool: self._app.oboundary.AssignLumpedPort(self._get_args()) elif bound_type == "Wave Port": self._app.oboundary.AssignWavePort(self._get_args()) + elif bound_type == "Terminal": + self._app.oboundary.AssignTerminal(self._get_args()) elif bound_type == "Floquet Port": self._app.oboundary.AssignFloquetPort(self._get_args()) elif bound_type == "AutoIdentify": diff --git a/src/ansys/aedt/core/modules/boundary/hfss_boundary.py b/src/ansys/aedt/core/modules/boundary/hfss_boundary.py index e99d82c24033..73af26b5f352 100644 --- a/src/ansys/aedt/core/modules/boundary/hfss_boundary.py +++ b/src/ansys/aedt/core/modules/boundary/hfss_boundary.py @@ -1343,15 +1343,15 @@ def renorm_all_terminals(self) -> bool: Whether renormalization of all modes is enabled. """ value = None - if "Renorm All Modes" in self.properties: - value = self.properties["Renorm All Modes"] + if "Renorm All Terminals" in self.properties: + value = self.properties["Renorm All Terminals"] return value @renorm_all_terminals.setter def renorm_all_terminals(self, value: bool): - if not isinstance(value, bool) or self.renorm_all_modes is None: + if not isinstance(value, bool) or self.renorm_all_terminals is None: raise AEDTRuntimeError("Renorm all modes must be a boolean.") - self.properties["Renorm All Modes"] = value + self.properties["Renorm All Terminals"] = value @pyaedt_function_handler() def assign_terminal( @@ -1368,27 +1368,14 @@ def assign_terminal( props_terminal["TerminalResistance"] = str(impedance) + "ohm" props_terminal["ParentBndID"] = self.name props_terminal["ImpedanceType"] = "Impedance" + props_terminal["Faces"] = faces if not name: name = self.name + "_T" for boundary in self._app.boundaries: if boundary.name == name: name = name + "_1" - props = [] - props.append("Name:" + name) - props.append("Faces:=") - props.append(faces) - props.append("ParentBndID:=") - props.append(self.name) - props.append("ImpedanceType:=") - props.append("Impedance") - props.append("TerminalResistance:=") - props.append(str(impedance) + "ohm") - self._app.oboundary.AssignTerminal(props) - - #bound = BoundaryObject(self, name, props_terminal) - #self._app._boundaries[name] = bound - #return bound + return self._app._create_wave_port_boundary(name, props_terminal) @property def terminals(self): @@ -1415,3 +1402,225 @@ def __init__(self, app, name, props=None): """ super().__init__(app, name, props, "Terminal") + @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 + for bound in self._app.boundaries: + if bound.name == self.props["ParentBndID"]: + parent_port = bound + if not parent_port.renorm_all_terminals: + parent_port.renorm_all_terminals = 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) + + @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): + 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): + 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["Capacitance 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) \ No newline at end of file From f8e3649a732b4c49ece06bc2384868f30c8421fb Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 25 May 2026 15:59:50 +0000 Subject: [PATCH 27/29] CHORE: Auto fixes from pre-commit hooks --- src/ansys/aedt/core/application/design.py | 5 +- src/ansys/aedt/core/hfss.py | 2 +- src/ansys/aedt/core/mixins.py | 6 +- .../core/modules/boundary/hfss_boundary.py | 107 ++++++++---------- tests/system/general/test_hfss_excitations.py | 2 +- 5 files changed, 56 insertions(+), 66 deletions(-) diff --git a/src/ansys/aedt/core/application/design.py b/src/ansys/aedt/core/application/design.py index c3f422826798..4aa84b2cb43c 100644 --- a/src/ansys/aedt/core/application/design.py +++ b/src/ansys/aedt/core/application/design.py @@ -82,7 +82,10 @@ 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 WavePortCommon, WavePortModal, WavePortTerminal, Terminal +from ansys.aedt.core.modules.boundary.hfss_boundary import Terminal +from ansys.aedt.core.modules.boundary.hfss_boundary import WavePortCommon +from ansys.aedt.core.modules.boundary.hfss_boundary import WavePortModal +from ansys.aedt.core.modules.boundary.hfss_boundary import WavePortTerminal 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 diff --git a/src/ansys/aedt/core/hfss.py b/src/ansys/aedt/core/hfss.py index 8e814e3fa362..4f0758d471b5 100644 --- a/src/ansys/aedt/core/hfss.py +++ b/src/ansys/aedt/core/hfss.py @@ -75,7 +75,7 @@ from ansys.aedt.core.modeler.cad.components_3d import UserDefinedComponent from ansys.aedt.core.modeler.geometry_operators import GeometryOperators from ansys.aedt.core.modules.boundary.common import BoundaryObject -from ansys.aedt.core.modules.boundary.hfss_boundary import FarFieldSetup, WavePortTerminal, WavePortModal, Terminal +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.layout_boundary import NativeComponentObject from ansys.aedt.core.modules.setup_templates import SetupKeys diff --git a/src/ansys/aedt/core/mixins.py b/src/ansys/aedt/core/mixins.py index dd1998573a49..df362a49343e 100644 --- a/src/ansys/aedt/core/mixins.py +++ b/src/ansys/aedt/core/mixins.py @@ -26,7 +26,9 @@ from ansys.aedt.core.internal.errors import AEDTRuntimeError from ansys.aedt.core.internal.errors import GrpcApiError from ansys.aedt.core.modules.boundary.common import BoundaryObject -from ansys.aedt.core.modules.boundary.hfss_boundary import WavePortModal, WavePortTerminal, Terminal +from ansys.aedt.core.modules.boundary.hfss_boundary import Terminal +from ansys.aedt.core.modules.boundary.hfss_boundary import WavePortModal +from ansys.aedt.core.modules.boundary.hfss_boundary import WavePortTerminal class CreateBoundaryMixin: @@ -97,4 +99,4 @@ def _create_wave_port_boundary(self, name: str, props) -> WavePortModal | WavePo self.logger.info(f"Wave Port Boundary {name} has been created.") return bound except GrpcApiError as e: - raise AEDTRuntimeError(f"Failed to create Wave Port Boundary {name}") from e \ No newline at end of file + raise AEDTRuntimeError(f"Failed to create Wave Port Boundary {name}") from e diff --git a/src/ansys/aedt/core/modules/boundary/hfss_boundary.py b/src/ansys/aedt/core/modules/boundary/hfss_boundary.py index 73af26b5f352..68f651a11a00 100644 --- a/src/ansys/aedt/core/modules/boundary/hfss_boundary.py +++ b/src/ansys/aedt/core/modules/boundary/hfss_boundary.py @@ -22,8 +22,6 @@ # 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.base import PyAedtBase from ansys.aedt.core.generic.constants import AEDT_UNITS @@ -31,9 +29,8 @@ 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 import cad -from ansys.aedt.core.modeler.cad.elements_3d import BinaryTreeNode, FacePrimitive -from ansys.aedt.core.modeler.cad.object_3d import Object3d +from ansys.aedt.core.modeler.cad.elements_3d import BinaryTreeNode +from ansys.aedt.core.modeler.cad.elements_3d import FacePrimitive 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 @@ -585,9 +582,8 @@ def wave_port_type(self) -> str: value = self.properties["Wave Port Type"] return value - @property - def assignment(self) -> Union[str, int]: + def assignment(self) -> str | int: """Wave port object assignment. Returns @@ -657,7 +653,7 @@ def deembed(self, value: bool): self.properties["Deembed"] = value @property - def deembed_distance(self) -> Union[Quantity, None]: + def deembed_distance(self) -> Quantity | None: """Dembedding distance. Returns @@ -671,7 +667,7 @@ def deembed_distance(self) -> Union[Quantity, None]: return value @deembed_distance.setter - def deembed_distance(self, value: Optional[Union[Quantity, float, int, str, bool]]): + def deembed_distance(self, value: Quantity | float | int | str | bool | None): if value is None or value is False: self.use_deembed = False elif value is True: @@ -686,7 +682,6 @@ def deembed_distance(self, value: Optional[Union[Quantity, float, int, str, bool class WavePortModal(WavePortCommon): - @property def modes(self) -> int: """Number of modes. @@ -741,8 +736,8 @@ def renorm_impedance_type(self, value: str): 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"] + "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']}." @@ -750,7 +745,7 @@ def renorm_impedance_type(self, value: str): self.properties["Renorm Impedance Type"] = value @property - def renorm_impedance(self) -> Union[Quantity, None]: + def renorm_impedance(self) -> Quantity | None: """Get the renormalization impedance value. Returns @@ -764,7 +759,7 @@ def renorm_impedance(self) -> Union[Quantity, None]: return value @renorm_impedance.setter - def renorm_impedance(self, value: Optional[Union[Quantity, float, int, str]]): + def renorm_impedance(self, value: Quantity | float | int | str | None): if self.renorm_impedance_type != "Impedance": raise ValueError("Renorm Impedance can be set only if Renorm Impedance Type is 'Impedance'.") @@ -828,7 +823,7 @@ def resistance(self) -> Quantity: return value @resistance.setter - def resistance(self, value: Optional[Union[Quantity, float, int, str]]): + def resistance(self, value: Quantity | float | int | str | None): if self.renorm_impedance_type == "RLC": if not isinstance(value, Quantity): value = Quantity(self._app.value_with_units(value, units_system="Resistance")) @@ -871,7 +866,7 @@ def inductance(self) -> Quantity: return value @inductance.setter - def inductance(self, value: Optional[Union[Quantity, float, int, str, bool]]): + def inductance(self, value: Quantity | float | int | str | bool | None): if value is None or value is False: self.use_inductance = False elif value is True: @@ -923,7 +918,7 @@ def capacitance(self) -> Quantity: return value @capacitance.setter - def capacitance(self, value: Optional[Union[Quantity, float, int, str, bool]]): + def capacitance(self, value: Quantity | float | int | str | bool | None): if value is None or value is False: self.use_capacitance = False elif value is True: @@ -952,9 +947,8 @@ def filter_modes_reporter(self): """ return self.props["ReporterFilter"] - @filter_modes_reporter.setter - def filter_modes_reporter(self, value: Union[bool, list]): + def filter_modes_reporter(self, value: bool | list): """Set the reporter filter setting for wave port modes. Parameters @@ -1001,11 +995,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: bool | None = None, + coordinate_system: str = "Global", + alignment_group: int | list | None = None, ): """Set the analytical alignment property for the wave port. @@ -1039,15 +1033,14 @@ def set_analytical_alignment( 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)): + 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) + 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): @@ -1071,10 +1064,10 @@ def set_analytical_alignment( @pyaedt_function_handler() def set_alignment_integration_line( - self, - integration_lines: Optional[list] = None, - coordinate_system: str = "Global", - alignment_groups: Optional[Union[int, list]] = None, + self, + integration_lines: list | None = None, + coordinate_system: str = "Global", + alignment_groups: int | list | None = None, ): """Set the integration line alignment property for the wave port modes. @@ -1143,9 +1136,9 @@ def set_alignment_integration_line( # 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) + 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]]." @@ -1162,8 +1155,7 @@ def set_alignment_integration_line( 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): @@ -1213,9 +1205,7 @@ def set_alignment_integration_line( return False @pyaedt_function_handler() - def set_polarity_integration_line( - self, integration_lines: Optional[list] = None, coordinate_system: str = "Global" - ): + def set_polarity_integration_line(self, integration_lines: list | None = None, coordinate_system: str = "Global"): """Set polarity integration lines for the wave port modes. This method configures integration lines for wave port modes @@ -1272,18 +1262,18 @@ def set_polarity_integration_line( 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)) + 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) + 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]]." @@ -1332,7 +1322,6 @@ def set_polarity_integration_line( class WavePortTerminal(WavePortCommon): - @property def renorm_all_terminals(self) -> bool: """Renormalize all modes. @@ -1354,11 +1343,7 @@ def renorm_all_terminals(self, value: bool): self.properties["Renorm All Terminals"] = value @pyaedt_function_handler() - def assign_terminal( - self, - faces: list | int | FacePrimitive, - name: str = None, - impedance: float = 50): + def assign_terminal(self, faces: list | int | FacePrimitive, name: str = None, impedance: float = 50): props_terminal = {} if isinstance(faces, int): @@ -1426,8 +1411,8 @@ def renorm_impedance_type(self, value: str): parent_port.renorm_all_terminals = True if self.renorm_impedance_type and ( - "Renorm Impedance Type/Choices" in self.properties - and value not in self.properties["Renorm Impedance Type/Choices"] + "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']}." @@ -1435,7 +1420,7 @@ def renorm_impedance_type(self, value: str): self.properties["Renorm Impedance Type"] = value @property - def renorm_impedance(self) -> Union[Quantity, None]: + def renorm_impedance(self) -> Quantity | None: """Get the renormalization impedance value. Returns @@ -1449,7 +1434,7 @@ def renorm_impedance(self) -> Union[Quantity, None]: return value @renorm_impedance.setter - def renorm_impedance(self, value: Optional[Union[Quantity, float, int, str]]): + def renorm_impedance(self, value: Quantity | float | int | str | None): if self.renorm_impedance_type != "Impedance": raise ValueError("Renorm Impedance can be set only if Renorm Impedance Type is 'Impedance'.") @@ -1513,7 +1498,7 @@ def resistance(self) -> Quantity: return value @resistance.setter - def resistance(self, value: Optional[Union[Quantity, float, int, str]]): + def resistance(self, value: Quantity | float | int | str | None): if self.renorm_impedance_type == "RLC": if not isinstance(value, Quantity): value = Quantity(self._app.value_with_units(value, units_system="Resistance")) @@ -1556,7 +1541,7 @@ def inductance(self) -> Quantity: return value @inductance.setter - def inductance(self, value: Optional[Union[Quantity, float, int, str, bool]]): + def inductance(self, value: Quantity | float | int | str | bool | None): if value is None or value is False: self.use_inductance = False elif value is True: @@ -1608,7 +1593,7 @@ def capacitance(self) -> Quantity: return value @capacitance.setter - def capacitance(self, value: Optional[Union[Quantity, float, int, str, bool]]): + def capacitance(self, value: Quantity | float | int | str | bool | None): if value is None or value is False: self.use_capacitance = False elif value is True: @@ -1623,4 +1608,4 @@ def capacitance(self, value: Optional[Union[Quantity, float, int, str, bool]]): if value.unit not in AEDT_UNITS["Capacitance"]: raise ValueError("Capacitance must have inductance units.") - self.properties["Capacitance value"] = str(value) \ No newline at end of file + self.properties["Capacitance value"] = str(value) diff --git a/tests/system/general/test_hfss_excitations.py b/tests/system/general/test_hfss_excitations.py index d2b2c0788ac4..229566e234fc 100644 --- a/tests/system/general/test_hfss_excitations.py +++ b/tests/system/general/test_hfss_excitations.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2021 - 2025 ANSYS, Inc. and/or its affiliates. +# Copyright (C) 2021 - 2026 ANSYS, Inc. and/or its affiliates. # SPDX-License-Identifier: MIT # # From b855a7b8b7fce1ebb58149166c44e902a22de015 Mon Sep 17 00:00:00 2001 From: gkorompi Date: Thu, 28 May 2026 12:24:00 +0300 Subject: [PATCH 28/29] wwport --- .../core/modules/boundary/hfss_boundary.py | 18 ++--- .../example_models/T51/port_example.aedtz | Bin 0 -> 28593 bytes tests/system/visualization/test_waveports.py | 75 ++++++++++++++++++ 3 files changed, 84 insertions(+), 9 deletions(-) create mode 100644 tests/system/visualization/example_models/T51/port_example.aedtz create mode 100644 tests/system/visualization/test_waveports.py diff --git a/src/ansys/aedt/core/modules/boundary/hfss_boundary.py b/src/ansys/aedt/core/modules/boundary/hfss_boundary.py index 73af26b5f352..64cb8da528ed 100644 --- a/src/ansys/aedt/core/modules/boundary/hfss_boundary.py +++ b/src/ansys/aedt/core/modules/boundary/hfss_boundary.py @@ -652,7 +652,7 @@ def deembed(self) -> bool: @deembed.setter def deembed(self, value: bool): - if not isinstance(value, bool) or self.use_deembed is None: + if not isinstance(value, bool) or self.deembed is None: raise AEDTRuntimeError("Use dembedding must be a boolean.") self.properties["Deembed"] = value @@ -666,20 +666,20 @@ def deembed_distance(self) -> Union[Quantity, None]: Whether de-embedding is enabled. """ value = None - if self.use_deembed: + if self.deembed: value = Quantity(self.properties["Deembed Dist"]) return value @deembed_distance.setter def deembed_distance(self, value: Optional[Union[Quantity, float, int, str, bool]]): if value is None or value is False: - self.use_deembed = False + self.deembed = False elif value is True: - self.use_deembed = True + self.deembed = True self.properties["Deembed Dist"] = str("0.0mm") else: - if not self.use_deembed: - self.use_deembed = True + if not self.deembed: + self.deembed = True if not isinstance(value, Quantity): value = Quantity(self._app.value_with_units(value)) self.properties["Deembed Dist"] = str(value) @@ -1420,7 +1420,7 @@ def renorm_impedance_type(self) -> str: def renorm_impedance_type(self, value: str): # Activate renorm all modes for bound in self._app.boundaries: - if bound.name == self.props["ParentBndID"]: + if bound.name == self.props["ParentBndID"] or bound.props["ID"] == self.props["ParentBndID"]: parent_port = bound if not parent_port.renorm_all_terminals: parent_port.renorm_all_terminals = True @@ -1444,8 +1444,8 @@ def renorm_impedance(self) -> Union[Quantity, None]: The renormalization impedance value. """ value = None - if "Renorm Imped" in self.properties: - value = Quantity(self.properties["Renorm Imped"]) + if "Terminal Renormalizing Impedance" in self.properties: + value = Quantity(self.properties["Terminal Renormalizing Impedance"]) return value @renorm_impedance.setter diff --git a/tests/system/visualization/example_models/T51/port_example.aedtz b/tests/system/visualization/example_models/T51/port_example.aedtz new file mode 100644 index 0000000000000000000000000000000000000000..efac43513df9545b4c304d88e2814dbe65d1795f GIT binary patch literal 28593 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 aQ;#QLx0g%D;E!V z1>+w=3o3)hO23U6wvPB)NiboYf|3!1s1CJh8gdVQ=wb=^#_>frW89A*^lVTmA?3So zU5H9U+M(&uIkJj4HycT)89XqOVXa*wI4oPV3BM_pO0;b1Hk{-RS~oubqoJi4_f z(_eSK5}+2i&8jO{^vd~@?m<>z`t(aNWeLD}BjTHRlH5vEwTO+>iv>-di!b%(=F#!4 zvo3UXpE-w|+C(Qf{i>^Ra{X0pl}z}8_tgkn6*!=gp?1JdiDdfzY#r2S_haQyVQafO zU3o!l{kGO_R zMxMaFq8BPtviXG)FF~4~i&op$T#bsetGo2yKTP2+83veoYf-b!ea=$*|0aaJH5@K4 zZGhABQfRGg5{wa>H}GYKbw=aYa+3JBaba5cib!gTd<|}lT*}gG&U^0{Wo25#-KznM z{Xt>nHcpJs#zf1wb>^_(?B1gYu9U}x^a%yYq50t{9(*N+S+w0m6jS%Y;(C#-+!HW|m`zFs(>GQ|#lhoTb2a+=`6T<{nz;VPi<7sF{tUGA zEX$j#o*q1+QTw?Ny`mQW_2i7~9{!E;(SiH~`=^|)5 z%gT!Tm0cf9I*+ybrLIk&du6Cl_Mx;$HCUtHLwY<=)?q||(>lFq_H0PlfXcAp4|>!2=-b@Bc%sUqQW9{`aM4u z&q?)(P*eS(y7BW*S~9P6%q&vB3Wn3dl$@qg{cB^+HSiOCvO%Twfv&@K$hF-iLxS;m z{bwmMlf}t@xciL)s~2RVew}$-*m}ti_;YEAsNjP8iREk zMqUfo(Dbq=`gUp!rsgCD-!NYq;Rb2*pVcpgA|R;X{qCBFHOk|`Sl{9^qEVG{uZHh@ zG;&)TCXuz5+b2*(=Wtg5jN<7dZPNVlC=F)?XJMVYS4PRYbzOENK?`(^;l#N(bKgGC zX!$0$B=P5n>R89o$yF;mQy7JYI#w-kiuogyL>WP!T?<^g_YhtmdfjN?ukgcP;*XP2|Drel1Onr?9V>xNG>$#B>QVf-z*lDFQs5T>VI{eGZKZEed#K|8o5jtTi;Avj0Bu|Mrsl zPvdDZPoSfKk^$W7z4=vi-d>tkN)=GA_i#={LlX1Ts$mjco2ItcLO6Q6H6;M pv;Q6Fe?#Q{d;Wg{A^wBx{r`}1iZYN;|J?=qx0C-_m|y>${s$Qs0D1rb literal 0 HcmV?d00001 diff --git a/tests/system/visualization/test_waveports.py b/tests/system/visualization/test_waveports.py new file mode 100644 index 000000000000..47f94da8a34f --- /dev/null +++ b/tests/system/visualization/test_waveports.py @@ -0,0 +1,75 @@ +from ansys.aedt.core import Hfss +from ansys.aedt.core.modules.boundary.common import BoundaryObject +from ansys.aedt.core.modules.boundary.hfss_boundary import WavePortTerminal, WavePortModal, Terminal +import pytest +from tests import TESTS_VISUALIZATION_PATH + +PORT_EXAMPLE = "port_example" +TEST_SUBFOLDER = "T51" + + +@pytest.fixture +def port_example(add_app_example): + app = add_app_example(project=PORT_EXAMPLE, subfolder=TEST_SUBFOLDER, solution_type="Terminal") + yield app + app.close_project(app.project_name, save=False) + +@pytest.mark.avoid_ansys_load +def test_wave_port_terminal(port_example) -> None: + app = port_example + for bound in app.boundaries: + if bound.type == "Wave Port" and bound.wave_port_type == "Terminal": + wport_term = bound + + assert wport_term.name == "WavePortTerminal" + assert wport_term.wave_port_type == "Terminal" + assert wport_term.type == "Wave Port" + assert wport_term.renorm_all_terminals + assert wport_term.specify_wave_direction + wport_term.specify_wave_direction = False + assert not wport_term.specify_wave_direction + assert not wport_term.deembed + assert wport_term.terminals + assert wport_term.assignment + terminal = wport_term.assign_terminal(faces=9818,impedance=25,name="Terminal2") + assert terminal.type == "Terminal" + + +@pytest.mark.avoid_ansys_load +def test_wave_port_modal(port_example) -> None: + app = port_example + for bound in app.boundaries: + if bound.type == "Wave Port" and bound.wave_port_type == "Modal": + wport_modal = bound + + assert wport_modal.name == "WavePortModal" + assert wport_modal.wave_port_type == "Modal" + assert wport_modal.type == "Wave Port" + assert wport_modal.renorm_all_modes + assert wport_modal.renorm_impedance_type == "RLC" + assert wport_modal.rlc_type == "Parallel" + assert not wport_modal.use_capacitance + assert wport_modal.use_resistance + assert float(wport_modal.resistance) == 50 + assert wport_modal.use_inductance + assert not wport_modal.specify_wave_direction + assert wport_modal.deembed + assert float(wport_modal.deembed_distance) == 1 + assert wport_modal.assignment + +@pytest.mark.avoid_ansys_load +def test_terminal(port_example) -> None: + app = port_example + for bound in app.boundaries: + if bound.type == "Terminal" and bound.name == "Terminal1": + terminal = bound + + assert terminal.type == "Terminal" + assert terminal.renorm_impedance_type == "Impedance" + assert float(terminal.renorm_impedance) == 50 + terminal.renorm_impedance_type = "RLC" + terminal.rlc_type = "Serial" + assert terminal.rlc_type == "Serial" + terminal.rlc_type = "Parallel" + assert terminal.rlc_type == "Parallel" + From a3a1d1a28f7364fcaca10bda9a5262024e6c9a81 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 28 May 2026 09:26:35 +0000 Subject: [PATCH 29/29] CHORE: Auto fixes from pre-commit hooks --- tests/system/visualization/test_waveports.py | 33 ++++++++++++++++---- 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/tests/system/visualization/test_waveports.py b/tests/system/visualization/test_waveports.py index 47f94da8a34f..a303319b2e8e 100644 --- a/tests/system/visualization/test_waveports.py +++ b/tests/system/visualization/test_waveports.py @@ -1,8 +1,28 @@ -from ansys.aedt.core import Hfss -from ansys.aedt.core.modules.boundary.common import BoundaryObject -from ansys.aedt.core.modules.boundary.hfss_boundary import WavePortTerminal, WavePortModal, Terminal +# -*- coding: utf-8 -*- +# +# Copyright (C) 2021 - 2026 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + import pytest -from tests import TESTS_VISUALIZATION_PATH PORT_EXAMPLE = "port_example" TEST_SUBFOLDER = "T51" @@ -14,6 +34,7 @@ def port_example(add_app_example): yield app app.close_project(app.project_name, save=False) + @pytest.mark.avoid_ansys_load def test_wave_port_terminal(port_example) -> None: app = port_example @@ -31,7 +52,7 @@ def test_wave_port_terminal(port_example) -> None: assert not wport_term.deembed assert wport_term.terminals assert wport_term.assignment - terminal = wport_term.assign_terminal(faces=9818,impedance=25,name="Terminal2") + terminal = wport_term.assign_terminal(faces=9818, impedance=25, name="Terminal2") assert terminal.type == "Terminal" @@ -57,6 +78,7 @@ def test_wave_port_modal(port_example) -> None: assert float(wport_modal.deembed_distance) == 1 assert wport_modal.assignment + @pytest.mark.avoid_ansys_load def test_terminal(port_example) -> None: app = port_example @@ -72,4 +94,3 @@ def test_terminal(port_example) -> None: assert terminal.rlc_type == "Serial" terminal.rlc_type = "Parallel" assert terminal.rlc_type == "Parallel" -