Skip to content
Merged
Show file tree
Hide file tree
Changes from 39 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
030889a
EMIT Pyaedt fixes
jsalant22 Oct 14, 2025
3401c18
Merge branch 'main' into B1342786
jsalant22 Oct 14, 2025
66c53a5
CHORE: Auto fixes from pre-commit hooks
pre-commit-ci[bot] Oct 14, 2025
a44496e
chore: adding changelog file 6768.fixed.md [dependabot-skip]
pyansys-ci-bot Oct 14, 2025
f4dbd17
Fix codacy issues
jsalant22 Oct 15, 2025
6b717ae
Merge branch 'B1342786' of https://github.com/ansys/pyaedt into B1342786
jsalant22 Oct 15, 2025
6841dac
CHORE: Auto fixes from pre-commit hooks
pre-commit-ci[bot] Oct 15, 2025
4f0d8d4
Update test_emit.py
jsalant22 Oct 15, 2025
8a2a401
Merge branch 'main' into B1342786
jsalant22 Oct 15, 2025
7a59ba8
Merge branch 'main' into B1342786
jsalant22 Oct 16, 2025
63ad4e7
fix some unit conversions
jsalant22 Oct 16, 2025
f02bfee
Merge branch 'B1342786' of https://github.com/ansys/pyaedt into B1342786
jsalant22 Oct 16, 2025
fb087ea
CHORE: Auto fixes from pre-commit hooks
pre-commit-ci[bot] Oct 16, 2025
9d515a9
Merge branch 'main' into B1342786
jsalant22 Oct 20, 2025
e579586
Merge branch 'main' into B1342786
gmalinve Oct 23, 2025
fd28fdf
Merge branch 'main' into B1342786
Alberto-DM Oct 23, 2025
1673c4e
add testing
jsalant22 Oct 24, 2025
65c09c2
CHORE: Auto fixes from pre-commit hooks
pre-commit-ci[bot] Oct 24, 2025
5cff9b1
fix test
jsalant22 Oct 24, 2025
21c859a
Merge branch 'B1342786' of https://github.com/ansys/pyaedt into B1342786
jsalant22 Oct 24, 2025
8670771
fix test
jsalant22 Oct 24, 2025
a1fc145
fix test
jsalant22 Oct 24, 2025
a6aee83
test fix
jsalant22 Oct 24, 2025
c800dcf
add some more test coverage
jsalant22 Oct 25, 2025
c548bd6
CHORE: Auto fixes from pre-commit hooks
pre-commit-ci[bot] Oct 25, 2025
cbae46e
data rate test
jsalant22 Oct 25, 2025
5fd4dab
Merge branch 'B1342786' of https://github.com/ansys/pyaedt into B1342786
jsalant22 Oct 25, 2025
9fcd3c0
switch error to AEDTRuntimeError
jsalant22 Oct 25, 2025
4f85247
CHORE: Auto fixes from pre-commit hooks
pre-commit-ci[bot] Oct 25, 2025
1b59566
Merge branch 'main' into B1342786
jsalant22 Oct 26, 2025
81f42a2
fix test
jsalant22 Oct 26, 2025
7fb7589
fix test
jsalant22 Oct 26, 2025
008386c
CHORE: Auto fixes from pre-commit hooks
pre-commit-ci[bot] Oct 26, 2025
415f70b
fix test
jsalant22 Oct 26, 2025
e35112f
Merge branch 'B1342786' of https://github.com/ansys/pyaedt into B1342786
jsalant22 Oct 26, 2025
ee89ba6
cleanup a comment and add some more test coverage
jsalant22 Oct 27, 2025
1a25063
CHORE: Auto fixes from pre-commit hooks
pre-commit-ci[bot] Oct 27, 2025
cde2fb0
add some exception testing
jsalant22 Oct 27, 2025
9781764
Merge branch 'B1342786' of https://github.com/ansys/pyaedt into B1342786
jsalant22 Oct 27, 2025
0261f13
deprecate a prop instead of removing it
jsalant22 Oct 27, 2025
6e7e202
CHORE: Auto fixes from pre-commit hooks
pre-commit-ci[bot] Oct 27, 2025
a764290
Merge branch 'main' into B1342786
jsalant22 Oct 27, 2025
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
2 changes: 1 addition & 1 deletion .github/workflows/ci_cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -990,7 +990,7 @@ jobs:
with:
max_attempts: 2
retry_on: error
timeout_minutes: 20
timeout_minutes: 30
command: |
.venv\Scripts\Activate.ps1
pytest ${{ env.PYTEST_ARGUMENTS }} --timeout=600 -v -rA --color=yes -m emit
Expand Down
1 change: 1 addition & 0 deletions doc/changelog.d/6768.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
EMIT Pyaedt fixes
12 changes: 6 additions & 6 deletions src/ansys/aedt/core/emit_core/emit_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,20 +168,20 @@ def data_rate_conv(value: float, units: str, to_internal: bool = True):
if units == "bps":
mult = 1.0
elif units == "kbps":
mult = 1e-3
mult = 1e3
elif units == "Mbps":
mult = 1e-6
mult = 1e6
elif units == "Gbps":
mult = 1e-9
mult = 1e9
else:
if units == "bps":
mult = 1.0
elif units == "kbps":
mult = 1e3
mult = 1e-3
elif units == "Mbps":
mult = 1e6
mult = 1e-6
elif units == "Gbps":
mult = 1e9
mult = 1e-9
return value * mult


Expand Down
35 changes: 33 additions & 2 deletions src/ansys/aedt/core/emit_core/emit_schematic.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

from ansys.aedt.core.emit_core.nodes.emit_node import EmitNode
from ansys.aedt.core.generic.general_methods import pyaedt_function_handler
from ansys.aedt.core.internal.errors import AEDTRuntimeError


class EmitSchematic:
Expand Down Expand Up @@ -94,11 +95,19 @@ def create_component(self, component_type: str, name: str = None, library: str =

try:
# Retrieve matching components from the catalog
matching_components = []
matching_components = self.emit_instance.modeler.components.components_catalog[component_type]

if not matching_components:
self.emit_instance.logger.error(f"No component found for type '{component_type}'.")
raise ValueError(f"No component found for type '{component_type}'.")
# couldn't find a component match, try looking at all component names
catalog_comps = self.emit_instance.modeler.components.components_catalog.components
for value in catalog_comps.values():
if value.name == component_type:
matching_components.append(value)

if not matching_components:
self.emit_instance.logger.error(f"No component found for type '{component_type}'.")
raise ValueError(f"No component found for type '{component_type}'.")

if len(matching_components) == 1:
# Use the single matching component
Expand All @@ -123,6 +132,7 @@ def create_component(self, component_type: str, name: str = None, library: str =
revision = self.emit_instance.results.get_revision()

# Create the component using the EmitCom module
component.name = component.name.strip("'")
new_component_id = self._emit_com_module.CreateEmitComponent(
name, component.name, component.component_library
)
Expand Down Expand Up @@ -203,3 +213,24 @@ def connect_components(self, component_name_1: str, component_name_2: str) -> No
f"Failed to connect components '{component_name_1}' and '{component_name_2}': {e}"
)
raise RuntimeError(f"Failed to connect components '{component_name_1}' and '{component_name_2}': {e}")

@pyaedt_function_handler
def delete_component(self, name: str):
"""Delete a component from the schematic.

Parameters
----------
name : str
Name of the component.

Raises
------
RuntimeError
If the deletion fails.
"""
try:
self._emit_com_module.DeleteEmitComponent(name)
self.emit_instance.logger.info(f"Successfully deleted component '{name}'.")
except Exception as e:
self.emit_instance.logger.error(f"Failed to delete component '{name}': {e}")
raise AEDTRuntimeError(f"Failed to delete component '{name}': {e}")
153 changes: 129 additions & 24 deletions src/ansys/aedt/core/emit_core/nodes/emit_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,12 @@ def _parent(self):
Returns
-------
EmitNode
Parent node name.
Parent node.
"""
return self._get_property("Parent", True)
parent_name = self._get_property("Parent", True)
parent_name = parent_name.replace("NODE-*-", "")
node_id = self._oRevisionData.GetTopLevelNodeID(0, parent_name)
return self._get_node(node_id)

@property
def properties(self) -> dict:
Expand All @@ -127,7 +130,7 @@ def properties(self) -> dict:
return props

@property
def node_warnings(self) -> str:
def warnings(self) -> str:
"""Warnings for the node, if any.

Returns
Expand Down Expand Up @@ -174,6 +177,7 @@ def _get_node(self, node_id: int):
>>> new_node = node._get_node(node_id)
"""
from ansys.aedt.core.emit_core.nodes import generated
from ansys.aedt.core.emit_core.nodes.emitter_node import EmitterNode

props = self._oRevisionData.GetEmitNodeProperties(self._result_id, node_id, True)
props = self.props_to_dict(props)
Expand All @@ -183,7 +187,23 @@ def _get_node(self, node_id: int):

node = None
try:
type_class = getattr(generated, f"{prefix}{node_type}")
type_class = EmitNode
if node_type == "RadioNode" and props["IsEmitter"] == "true":
type_class = EmitterNode
# TODO: enable when we add ReadOnlyNodes
# if prefix == "":
# type_class = EmitterNode
# else:
# type_class = ReadOnlyEmitterNode
elif node_type == "Band" and props["IsEmitterBand"] == "true":
type_class = getattr(generated, f"{prefix}Waveform")
elif node_type == "TxSpectralProfNode":
if self.properties["IsEmitterBand"] == "true":
type_class = getattr(generated, f"{prefix}TxSpectralProfEmitterNode")
else:
type_class = getattr(generated, f"{prefix}TxSpectralProfNode")
else:
type_class = getattr(generated, f"{prefix}{node_type}")
node = type_class(self._emit_obj, self._result_id, node_id)
except AttributeError:
node = EmitNode(self._emit_obj, self._result_id, node_id)
Expand All @@ -203,7 +223,7 @@ def children(self):
child_nodes = [self._get_node(child_id) for child_id in child_ids]
return child_nodes

def _get_property(self, prop, skipChecks=False) -> Union[str, List[str]]:
def _get_property(self, prop, skipChecks=False, isTable=False) -> Union[str, List[str]]:
"""Fetch the value of a given property.

Parameters
Expand All @@ -226,7 +246,14 @@ def _get_property(self, prop, skipChecks=False) -> Union[str, List[str]]:
selected_kv_pair = selected_kv_pairs[0]
val = selected_kv_pair[1]

if val.find("|") != -1:
if isTable:
# Node Prop tables
# Data formatted using compact string serialization
# with ';' separating rows and '|' separating columns
rows = val.split(";")
table = [tuple(row.split("|")) for row in rows if row]
return table
elif val.find("|") != -1:
return val.split("|")
else:
return val
Expand Down Expand Up @@ -271,6 +298,7 @@ def _string_to_value_units(value) -> tuple[float, str]:
# see if we can split it based on a space between number
# and units
vals = value.split(" ")
units = ""
if len(vals) == 2:
dec_val = float(vals[0])
units = vals[1].strip()
Expand All @@ -281,7 +309,11 @@ def _string_to_value_units(value) -> tuple[float, str]:
dec_val = float(value[:i])
units = value[i:]
return dec_val, units
raise ValueError(f"{value} is not valid for this property.")
# maybe it's a string but with no units
try:
return float(value), units
except ValueError:
raise ValueError(f"{value} is not valid for this property.")

def _convert_to_internal_units(self, value: float | str, unit_system: str) -> float:
"""Takes a value and converts to internal EMIT units used for storing values.
Expand All @@ -301,9 +333,20 @@ def _convert_to_internal_units(self, value: float | str, unit_system: str) -> fl
"""
if isinstance(value, float) or isinstance(value, int):
# unitless, so assume SI Units
units = consts.SI_UNITS[unit_system]
if unit_system == "Data Rate":
# Data rate isn't included as part of PyAedt's unit class
units = "bps"
else:
units = consts.SI_UNITS[unit_system]
else:
value, units = self._string_to_value_units(value)
# make sure units were specified, if not use SI Units
if units == "":
if unit_system == "Data Rate":
# Data rate isn't included as part of PyAedt unit class
units = "bps"
else:
units = consts.SI_UNITS[unit_system]
# verify the units are valid for the specified type
if units not in EMIT_VALID_UNITS[unit_system]:
raise ValueError(f"{units} are not valid units for this property.")
Expand Down Expand Up @@ -331,10 +374,11 @@ def _convert_from_internal_units(value: float, unit_system: str) -> float:
Value in SI units.
"""
# get the SI units
units = consts.SI_UNITS[unit_system]
if unit_system == "Data Rate":
units = "bps"
converted_value = data_rate_conv(value, units, False)
else:
units = consts.SI_UNITS[unit_system]
converted_value = consts.unit_converter(value, unit_system, EMIT_INTERNAL_UNITS[unit_system], units)
return converted_value

Expand Down Expand Up @@ -424,28 +468,88 @@ def _get_child_node_id(self, child_name: str) -> int:
"""
return self._oRevisionData.GetChildNodeID(self._result_id, self._node_id, child_name)

def _is_column_data_table(self):
"""Returns true if the node uses column data tables.

Returns
-------
bool
True if the table is ColumnData, False otherwise.
"""
# BB Emission Nodes can have ColumnData or NodeProp tables
# so handle them first
if self._node_type == "TxBbEmissionNode":
if self._get_property("Noise Behavior") == "BroadbandEquation":
return False
return True

table_title = self._get_property("CDTableTitle", True)
if table_title == "":
# No ColumnData Table Title, so it's a NodePropTable
return False
return True

def _get_table_data(self):
"""Returns the node's table data.

Returns
-------
list
The node's table data.
list of tuples
The node's table data as a list of tuples.
[(x1, y1, z1), (x2, y2, z2)]
"""
rows = self._oRevisionData.GetTableData(self._result_id, self._node_id)
nested_list = [col.split(" ") for col in rows]
return nested_list
try:
if self._is_column_data_table():
# Column Data tables
# Data formatted using compact string serialization
# with '|' separating rows and ';' separating columns
data = self._oRevisionData.GetTableData(self._result_id, self._node_id)
rows = data.split("|")
string_table = [tuple(row.split(";")) for row in rows if row]
table = [tuple(float(x) for x in t) for t in string_table]
else:
# Node Prop tables
# Data formatted using compact string serialization
# with ';' separating rows and '|' separating columns
table_key = self._get_property("TableKey", True)
string_table = self._get_property(table_key, True, True)

def try_float(val):
try:
return float(val)
except ValueError:
return val # keep as string for non-numeric (e.g. equations)

table = [tuple(try_float(x) for x in t) for t in string_table]
except Exception as e:
print(f"Failed to get table data for node {self.name}. Error: {e}")
return table

def _set_table_data(self, nested_list):
def _set_table_data(self, table):
"""Sets the table data for the node.

Parameters
----------
nested_list : list
list of tuples
Data to populate the table with.
[(x1, y1, z1), (x2, y2, z2)]
"""
rows = [col.join(" ") for col in nested_list]
self._oRevisionData.SetTableData(self._result_id, self._node_id, rows)
try:
if self._is_column_data_table():
# Column Data tables
# Data formatted using compact string serialization
# with '|' separating rows and ';' separating columns
data = "|".join(";".join(map(str, row)) for row in table)
self._oRevisionData.SetTableData(self._result_id, self._node_id, data)
else:
# Node Prop tables
# Data formatted using compact string serialization
# with ';' separating rows and '|' separating columns
table_key = self._get_property("TableKey", True)
data = ";".join("|".join(map(str, row)) for row in table)
self._set_property(table_key, data)
except Exception as e:
print(f"Failed to set table data for node {self.name}. Error: {e}")

def _add_child_node(self, child_type, child_name=None):
"""Creates a child node of the given type and name.
Expand All @@ -455,28 +559,29 @@ def _add_child_node(self, child_type, child_name=None):
child_type : EmitNode
Type of child node to create.
child_name : str, optional
Optional name to use for the child node. If None, a default name is used.
Name to use for the child node. If None, a default name is used.

Returns
-------
int
Unique node ID assigned to the created child node.
node: EmitNode
The node.

Raises
------
ValueError
If the specified child type is not allowed.
"""
if not child_name:
child_name = f"New {child_type}"
child_name = f"{child_type}"

new_id = None
new_node = None
if child_type not in self.allowed_child_types:
raise ValueError(
f"Child type {child_type} is not allowed for this node. Allowed types are: {self.allowed_child_types}"
)
try:
new_id = self._oRevisionData.CreateEmitNode(self._result_id, self._node_id, child_name, child_type)
new_node = self._get_node(new_id)
except Exception as e:
print(f"Failed to add child node of type {child_type} to node {self.name}. Error: {e}")
return new_id
return new_node
Loading
Loading