diff --git a/doc/changelog.d/1945.fixed.md b/doc/changelog.d/1945.fixed.md new file mode 100644 index 0000000000..6741ff214d --- /dev/null +++ b/doc/changelog.d/1945.fixed.md @@ -0,0 +1 @@ +Wave port terminal fixed issue #1909 diff --git a/src/pyedb/grpc/database/inner/base.py b/src/pyedb/grpc/database/inner/base.py index 0b1c8c8cd1..7ae3d2a0a8 100644 --- a/src/pyedb/grpc/database/inner/base.py +++ b/src/pyedb/grpc/database/inner/base.py @@ -7,6 +7,15 @@ # 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 +# Copyright (C) 2023 - 2026 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all diff --git a/src/pyedb/grpc/database/inner/layout_obj.py b/src/pyedb/grpc/database/inner/layout_obj.py index 3e1697a87c..9dbab2ac09 100644 --- a/src/pyedb/grpc/database/inner/layout_obj.py +++ b/src/pyedb/grpc/database/inner/layout_obj.py @@ -20,11 +20,69 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -from ansys.edb.core.database import ProductIdType as GrpcProductIdType +from dataclasses import dataclass +import re + +from ansys.edb.core.database import ProductIdType as CoreProductIdType from pyedb.grpc.database.inner.base import ObjBase +@dataclass +class HFSSProductProperty: + hfss_type: str = "Gap" + orientation: str = "" + layer_alignment: str = "" + horizontal_extent_factor: float | int = 0.0 + vertical_extent_factor: float | int = 0.0 + pec_launch_width: str = "10um" + + def to_hfss_string(self) -> str: + def _fmt_num(val: float) -> str: + try: + v = float(val) + except Exception: + return str(val) + # Render as integer when the float is integral + if v.is_integer(): + return str(int(v)) + return str(v) + + h_val = _fmt_num(self.horizontal_extent_factor) + v_val = _fmt_num(self.vertical_extent_factor) + + return ( + "HFSS(" + f"'HFSS Type'='{self.hfss_type}', " + f"Orientation='{self.orientation}', " + f"'Layer Alignment'='{self.layer_alignment}', " + f"'Horizontal Extent Factor'='{h_val}', " + f"'Vertical Extent Factor'='{v_val}', " + f"'PEC Launch Width'='{self.pec_launch_width}'" + ")" + ) + + +def parse_hfss_string(s: str | None) -> HFSSProductProperty: + if s is None: + return HFSSProductProperty() + + def get(key: str) -> str | None: + match = re.search(rf"'{re.escape(key)}'='([^']*)'", s) + return match.group(1) if match else None + + defaults = HFSSProductProperty() + + return HFSSProductProperty( + hfss_type=get("HFSS Type") or defaults.hfss_type, + orientation=get("Orientation") or defaults.orientation, + layer_alignment=get("Layer Alignment") or defaults.layer_alignment, + horizontal_extent_factor=float(get("Horizontal Extent Factor") or defaults.horizontal_extent_factor), + vertical_extent_factor=float(get("Vertical Extent Factor") or defaults.vertical_extent_factor), + pec_launch_width=get("PEC Launch Width") or defaults.pec_launch_width, + ) + + class LayoutObj(ObjBase): """Represents a layout object.""" @@ -33,9 +91,18 @@ def __init__(self, pedb, core): @property def _edb_properties(self): - p = self.core.get_product_property(GrpcProductIdType.DESIGNER, 18) + p = self.core.get_product_property(CoreProductIdType.DESIGNER, 18) return p @_edb_properties.setter def _edb_properties(self, value): - self.core.set_product_property(GrpcProductIdType.DESIGNER, 18, value) + self.core.set_product_property(CoreProductIdType.DESIGNER, 18, value) + + @property + def _hfss_properties(self) -> HFSSProductProperty: + return parse_hfss_string(self.core.product_solver_option(CoreProductIdType.DESIGNER, "HFSS")) + + @_hfss_properties.setter + def _hfss_properties(self, value): + if isinstance(value, HFSSProductProperty): + self.core.set_product_solver_option(CoreProductIdType.DESIGNER, "HFSS", value.to_hfss_string()) diff --git a/src/pyedb/grpc/database/primitive/path.py b/src/pyedb/grpc/database/primitive/path.py index cfb5ecd910..6234a0396d 100644 --- a/src/pyedb/grpc/database/primitive/path.py +++ b/src/pyedb/grpc/database/primitive/path.py @@ -280,12 +280,6 @@ def create_edge_port( """ center_line = self.get_center_line() pos = center_line[-1] if position.lower() == "end" else center_line[0] - - # if port_type.lower() == "wave": - # return self._pedb.excitation_manager.create_wave_port( - # self.id, pos, name, 50, horizontal_extent_factor, vertical_extent_factor, pec_launch_width - # ) - # else: return self._pedb.excitation_manager.create_edge_port_vertical( self.edb_uid, pos, diff --git a/src/pyedb/grpc/database/source_excitations.py b/src/pyedb/grpc/database/source_excitations.py index 93b4be28aa..2cf421eb13 100644 --- a/src/pyedb/grpc/database/source_excitations.py +++ b/src/pyedb/grpc/database/source_excitations.py @@ -2014,7 +2014,7 @@ def create_edge_port_vertical( horizontal_extent_factor: Union[int, float] = 5, vertical_extent_factor: Union[int, float] = 3, pec_launch_width: str = "0.01mm", - ) -> str: + ) -> str | None: """Create a vertical edge port. Parameters @@ -2060,7 +2060,6 @@ def create_edge_port_vertical( if reference_layer: reference_layer = self._pedb.stackup.signal_layers[reference_layer] pos_edge_term.reference_layer = reference_layer - prop = ", ".join( [ f"HFSS('HFSS Type'='{hfss_type}'", @@ -2077,9 +2076,9 @@ def create_edge_port_vertical( prop, ) if not pos_edge_term.is_null: - return pos_edge_term + return pos_edge_term.name else: - return False + return None def create_edge_port_horizontal( self, diff --git a/src/pyedb/grpc/database/terminal/edge_terminal.py b/src/pyedb/grpc/database/terminal/edge_terminal.py index b14e72c753..bd0d752612 100644 --- a/src/pyedb/grpc/database/terminal/edge_terminal.py +++ b/src/pyedb/grpc/database/terminal/edge_terminal.py @@ -107,7 +107,7 @@ def port_post_processing_prop(self, value): @property def is_wave_port(self) -> bool: - if self._hfss_port_property: + if self.hfss_type == "Wave": return True return False diff --git a/src/pyedb/grpc/database/terminal/terminal.py b/src/pyedb/grpc/database/terminal/terminal.py index 2c14bd90ff..8f1783b67c 100644 --- a/src/pyedb/grpc/database/terminal/terminal.py +++ b/src/pyedb/grpc/database/terminal/terminal.py @@ -29,7 +29,6 @@ if TYPE_CHECKING: from pyedb.grpc.database.primitive.padstack_instance import PadstackInstance -import re from ansys.edb.core.terminal.edge_terminal import EdgeType as CoreEdgeType from ansys.edb.core.terminal.terminal import ( @@ -96,30 +95,7 @@ def port_post_processing_prop(self): @property def _hfss_port_property(self): """HFSS port property.""" - hfss_prop = re.search(r"HFSS\(.*?\)", self._edb_properties) - p = {} - if hfss_prop: - hfss_type = re.search(r"'HFSS Type'='([^']+)'", hfss_prop.group()) - orientation = re.search(r"'Orientation'='([^']+)'", hfss_prop.group()) - horizontal_ef = re.search(r"'Horizontal Extent Factor'='([^']+)'", hfss_prop.group()) - vertical_ef = re.search(r"'Vertical Extent Factor'='([^']+)'", hfss_prop.group()) - radial_ef = re.search(r"'Radial Extent Factor'='([^']+)'", hfss_prop.group()) - pec_w = re.search(r"'PEC Launch Width'='([^']+)'", hfss_prop.group()) - - p["HFSS Type"] = hfss_type.group(1) if hfss_type else "" - p["Orientation"] = orientation.group(1) if orientation else "" - p["Horizontal Extent Factor"] = float(horizontal_ef.group(1)) if horizontal_ef else "" - p["Vertical Extent Factor"] = float(vertical_ef.group(1)) if vertical_ef else "" - p["Radial Extent Factor"] = float(radial_ef.group(1)) if radial_ef else "" - p["PEC Launch Width"] = pec_w.group(1) if pec_w else "" - else: - p["HFSS Type"] = "" - p["Orientation"] = "" - p["Horizontal Extent Factor"] = "" - p["Vertical Extent Factor"] = "" - p["Radial Extent Factor"] = "" - p["PEC Launch Width"] = "" - return p + return self._hfss_properties @property def horizontal_extent_factor(self) -> float: @@ -130,12 +106,12 @@ def horizontal_extent_factor(self) -> float: float Extent value. """ - return self._hfss_port_property["Horizontal Extent Factor"] + return self._hfss_port_property.horizontal_extent_factor @horizontal_extent_factor.setter def horizontal_extent_factor(self, value): p = self._hfss_port_property - p["Horizontal Extent Factor"] = value + p.horizontal_extent_factor = value self._hfss_port_property = p @property @@ -148,16 +124,16 @@ def vertical_extent_factor(self) -> float: Vertical extent value. """ - return self._hfss_port_property["Vertical Extent Factor"] + return self._hfss_port_property.vertical_extent_factor @vertical_extent_factor.setter def vertical_extent_factor(self, value): p = self._hfss_port_property - p["Vertical Extent Factor"] = value + p.vertical_extent_factor = value self._hfss_port_property = p @property - def pec_launch_width(self) -> float: + def pec_launch_width(self) -> str: """Launch width for the printed electronic component (PEC). Returns @@ -165,12 +141,12 @@ def pec_launch_width(self) -> float: float Pec launch width value. """ - return self._hfss_port_property["PEC Launch Width"] + return self._hfss_port_property.pec_launch_width @pec_launch_width.setter def pec_launch_width(self, value): p = self._hfss_port_property - p["PEC Launch Width"] = value + p.pec_launch_width = value self._hfss_port_property = p @property @@ -181,21 +157,17 @@ def reference_layer(self): @_hfss_port_property.setter def _hfss_port_property(self, value): - txt = [] - for k, v in value.items(): - txt.append("'{}'='{}'".format(k, v)) - txt = ",".join(txt) - self._edb_properties = "HFSS({})".format(txt) + self._hfss_properties = value @property def hfss_type(self) -> str: """HFSS port type.""" - return self._hfss_port_property["HFSS Type"] + return self._hfss_port_property.hfss_type @hfss_type.setter def hfss_type(self, value): p = self._hfss_port_property - p["HFSS Type"] = value + p.hfss_type = value self._hfss_port_property = p @property @@ -213,6 +185,31 @@ def do_renormalize(self) -> bool: def do_renormalize(self, value): self.port_post_processing_prop.do_renormalize = value + @property + def do_deembed(self) -> bool: + """Determine whether port deembed is enabled. + Returns + """ + return self.port_post_processing_prop.do_deembed + + @do_deembed.setter + def do_deembed(self, value): + self.port_post_processing_prop.do_deembed = value + + @property + @deprecated_property("use do_deembed property instead") + def deembed(self) -> bool: + """Determine whether port deembed is enabled. + + .. deprecated:: 0.71.0 + The `deembed` property is deprecated. Please use `do_deembed` instead. + """ + return self.port_post_processing_prop.do_deembed + + @deembed.setter + def deembed(self, value): + self.port_post_processing_prop.do_deembed = value + @property def renormalization_impedance(self) -> float: """Get the renormalization impedance value. diff --git a/tests/system/test_edb.py b/tests/system/test_edb.py index 8490a7411c..4a7205f261 100644 --- a/tests/system/test_edb.py +++ b/tests/system/test_edb.py @@ -47,6 +47,9 @@ def test_hfss_create_coax_port_on_component_from_hfss(self): assert edbapp.excitation_manager.create_coax_port_on_component("U1", ["DDR4_DQS0_P", "DDR4_DQS0_N"], True) edbapp.close(terminate_rpc_session=False) + @pytest.mark.skipif( + config["use_grpc"] and config["desktopVersion"] < "2026.1", reason="working with latest release" + ) def test_layout_bounding_box(self): """Evaluate layout bounding box""" edbapp = self.edb_examples.get_si_verse() @@ -278,6 +281,9 @@ def test_export_3d(self): edb.close(terminate_rpc_session=False) + @pytest.mark.skipif( + config["use_grpc"] and config["desktopVersion"] < "2026.1", reason="working with latest release" + ) def test_create_edge_port_on_polygon(self): """Create lumped and vertical port.""" target_path = self.edb_examples.copy_test_files_into_local_folder("TEDB/edge_ports.aedb")[0] @@ -334,10 +340,13 @@ def test_create_edge_port_on_polygon(self): ) sig = edb.modeler.create_trace([[0, 0], ["9mm", 0]], "sig2", "1mm", "SIG", "Flat", "Flat") assert sig.create_edge_port("pcb_port_1", "end", "Wave", None, 8, 8) - assert sig.create_edge_port("pcb_port_2", "start", "gap") + assert sig.create_edge_port("pcb_port_2", "start", "Gap") gap_port = edb.ports["pcb_port_2"] if edb.grpc: + assert edb.ports["pcb_port_1"].is_wave_port + assert not edb.ports["pcb_port_2"].is_wave_port assert gap_port.component.is_null + assert not gap_port.is_circuit_port else: assert not gap_port.component assert gap_port.source_amplitude == 0.0 @@ -351,6 +360,9 @@ def test_create_edge_port_on_polygon(self): assert gap_port.is_circuit_port edb.close(terminate_rpc_session=False) + @pytest.mark.skipif( + config["use_grpc"] and config["desktopVersion"] < "2026.1", reason="working with latest release" + ) def test_edb_statistics(self): """Get statistics.""" edb = self.edb_examples.get_si_verse_sfp() @@ -413,6 +425,9 @@ def test_configure_hfss_analysis_setup_enforce_causality(self): assert setup.sweep_data[0].enforce_causality edb.close() + @pytest.mark.skipif( + config["use_grpc"] and config["desktopVersion"] < "2026.1", reason="working with latest release" + ) def test_create_various_ports_0(self): """Create various ports.""" target_path = self.edb_examples.copy_test_files_into_local_folder("edb_edge_ports.aedb")[0] @@ -527,6 +542,9 @@ def test_create_various_ports_0(self): assert df_port.deembed_length == 1e-3 edb.close(terminate_rpc_session=False) + @pytest.mark.skipif( + config["use_grpc"] and config["desktopVersion"] < "2026.1", reason="working with latest release" + ) def test_create_various_ports_1(self): """Create various ports.""" target_path = self.edb_examples.copy_test_files_into_local_folder("edb_edge_ports.aedb")[0] @@ -1280,7 +1298,9 @@ def test_siwave_simulation_setup_dotnet_compatibility(self): assert "pi_slider_position", "si_slider_position" in setup2.get_configurations().items() edbapp.close() - @pytest.mark.skipif(config["use_grpc"], reason="only dotnet") + @pytest.mark.skipif( + config["use_grpc"] and config["desktopVersion"] < "2026.1", reason="working with latest release" + ) def test_edb_settings(self): edbapp = self.edb_examples.get_si_verse() assert type(edbapp.logger) == EdbLogger @@ -1303,7 +1323,9 @@ def test_edb_settings(self): assert edbapp.are_port_reference_terminals_connected() edbapp.close() - @pytest.mark.skipif(config["use_grpc"], reason="only dotnet") + @pytest.mark.skipif( + config["use_grpc"] and config["desktopVersion"] < "2026.1", reason="working with latest release" + ) def test_ports_and_sources_creation(self): edbapp = self.edb_examples.get_si_verse() p1 = edbapp.padstacks.instances_by_name["Via1"].create_terminal("p1") diff --git a/tests/system/test_extensions.py b/tests/system/test_extensions.py index 26e916e4ee..4fda7b46c8 100644 --- a/tests/system/test_extensions.py +++ b/tests/system/test_extensions.py @@ -104,6 +104,7 @@ @pytest.mark.usefixtures("close_rpc_session") +@pytest.mark.skipif(config["use_grpc"] and config["desktopVersion"] < "2026.1", reason="working with latest release") class TestClass(BaseTestClass): def test_backend_single(self): cfg = { diff --git a/tests/system/test_rf_libraries.py b/tests/system/test_rf_libraries.py index 971c698ee1..da18f90020 100644 --- a/tests/system/test_rf_libraries.py +++ b/tests/system/test_rf_libraries.py @@ -217,6 +217,9 @@ def test_ustrip(self): assert ustrip.width == 300e-6 assert ustrip.impedance == 37.52 + @pytest.mark.skipif( + config["use_grpc"] and config["desktopVersion"] < "2026.1", reason="working with latest release" + ) def test_patch_antenna(self): edb = self.edb_examples.create_empty_edb() stackup = MicroStripTechnologyStackup(pedb=edb) @@ -234,6 +237,9 @@ def test_patch_antenna(self): assert patch_antenna.length == 0.03337 edb.close(terminate_rpc_session=False) + @pytest.mark.skipif( + config["use_grpc"] and config["desktopVersion"] < "2026.1", reason="working with latest release" + ) def test_circular_patch_antenna(self): edb = self.edb_examples.create_empty_edb() stackup = MicroStripTechnologyStackup(pedb=edb) @@ -245,6 +251,9 @@ def test_circular_patch_antenna(self): assert patch_antenna.radius == 0.0174 edb.close(terminate_rpc_session=False) + @pytest.mark.skipif( + config["use_grpc"] and config["desktopVersion"] < "2026.1", reason="working with latest release" + ) def test_triangular_antenna(self): edb = self.edb_examples.create_empty_edb() stackup = MicroStripTechnologyStackup(pedb=edb)