Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/changelog.d/1945.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Wave port terminal fixed issue #1909
9 changes: 9 additions & 0 deletions src/pyedb/grpc/database/inner/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
73 changes: 70 additions & 3 deletions src/pyedb/grpc/database/inner/layout_obj.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""

Expand All @@ -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())
6 changes: 0 additions & 6 deletions src/pyedb/grpc/database/primitive/path.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
7 changes: 3 additions & 4 deletions src/pyedb/grpc/database/source_excitations.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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}'",
Expand All @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion src/pyedb/grpc/database/terminal/edge_terminal.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
75 changes: 36 additions & 39 deletions src/pyedb/grpc/database/terminal/terminal.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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:
Expand All @@ -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
Expand All @@ -148,29 +124,29 @@ 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
-------
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
Expand All @@ -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
Expand All @@ -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.
Expand Down
28 changes: 25 additions & 3 deletions tests/system/test_edb.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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
Expand All @@ -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()
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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
Expand All @@ -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")
Expand Down
Loading
Loading