Skip to content

Commit 5a28b0a

Browse files
jsalant22pre-commit-ci[bot]pyansys-ci-botgmalinveAlberto-DM
authored
FIX: EMIT Pyaedt fixes (#6768)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: pyansys-ci-bot <[email protected]> Co-authored-by: Giulia Malinverno <[email protected]> Co-authored-by: Alberto Di Maria <[email protected]>
1 parent 3056555 commit 5a28b0a

32 files changed

+646
-289
lines changed

.github/workflows/ci_cd.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -991,7 +991,7 @@ jobs:
991991
with:
992992
max_attempts: 2
993993
retry_on: error
994-
timeout_minutes: 20
994+
timeout_minutes: 30
995995
command: |
996996
.venv\Scripts\Activate.ps1
997997
pytest ${{ env.PYTEST_ARGUMENTS }} --timeout=600 -v -rA --color=yes -m emit

doc/changelog.d/6768.fixed.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
EMIT Pyaedt fixes

src/ansys/aedt/core/emit_core/emit_constants.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -168,20 +168,20 @@ def data_rate_conv(value: float, units: str, to_internal: bool = True):
168168
if units == "bps":
169169
mult = 1.0
170170
elif units == "kbps":
171-
mult = 1e-3
171+
mult = 1e3
172172
elif units == "Mbps":
173-
mult = 1e-6
173+
mult = 1e6
174174
elif units == "Gbps":
175-
mult = 1e-9
175+
mult = 1e9
176176
else:
177177
if units == "bps":
178178
mult = 1.0
179179
elif units == "kbps":
180-
mult = 1e3
180+
mult = 1e-3
181181
elif units == "Mbps":
182-
mult = 1e6
182+
mult = 1e-6
183183
elif units == "Gbps":
184-
mult = 1e9
184+
mult = 1e-9
185185
return value * mult
186186

187187

src/ansys/aedt/core/emit_core/emit_schematic.py

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
from ansys.aedt.core.emit_core.nodes.emit_node import EmitNode
2727
from ansys.aedt.core.generic.general_methods import pyaedt_function_handler
28+
from ansys.aedt.core.internal.errors import AEDTRuntimeError
2829

2930

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

9596
try:
9697
# Retrieve matching components from the catalog
98+
matching_components = []
9799
matching_components = self.emit_instance.modeler.components.components_catalog[component_type]
98100

99101
if not matching_components:
100-
self.emit_instance.logger.error(f"No component found for type '{component_type}'.")
101-
raise ValueError(f"No component found for type '{component_type}'.")
102+
# couldn't find a component match, try looking at all component names
103+
catalog_comps = self.emit_instance.modeler.components.components_catalog.components
104+
for value in catalog_comps.values():
105+
if value.name == component_type:
106+
matching_components.append(value)
107+
108+
if not matching_components:
109+
self.emit_instance.logger.error(f"No component found for type '{component_type}'.")
110+
raise ValueError(f"No component found for type '{component_type}'.")
102111

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

125134
# Create the component using the EmitCom module
135+
component.name = component.name.strip("'")
126136
new_component_id = self._emit_com_module.CreateEmitComponent(
127137
name, component.name, component.component_library
128138
)
@@ -203,3 +213,24 @@ def connect_components(self, component_name_1: str, component_name_2: str) -> No
203213
f"Failed to connect components '{component_name_1}' and '{component_name_2}': {e}"
204214
)
205215
raise RuntimeError(f"Failed to connect components '{component_name_1}' and '{component_name_2}': {e}")
216+
217+
@pyaedt_function_handler
218+
def delete_component(self, name: str):
219+
"""Delete a component from the schematic.
220+
221+
Parameters
222+
----------
223+
name : str
224+
Name of the component.
225+
226+
Raises
227+
------
228+
RuntimeError
229+
If the deletion fails.
230+
"""
231+
try:
232+
self._emit_com_module.DeleteEmitComponent(name)
233+
self.emit_instance.logger.info(f"Successfully deleted component '{name}'.")
234+
except Exception as e:
235+
self.emit_instance.logger.error(f"Failed to delete component '{name}': {e}")
236+
raise AEDTRuntimeError(f"Failed to delete component '{name}': {e}")

src/ansys/aedt/core/emit_core/nodes/emit_node.py

Lines changed: 144 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -109,9 +109,12 @@ def _parent(self):
109109
Returns
110110
-------
111111
EmitNode
112-
Parent node name.
112+
Parent node.
113113
"""
114-
return self._get_property("Parent", True)
114+
parent_name = self._get_property("Parent", True)
115+
parent_name = parent_name.replace("NODE-*-", "")
116+
node_id = self._oRevisionData.GetTopLevelNodeID(0, parent_name)
117+
return self._get_node(node_id)
115118

116119
@property
117120
def properties(self) -> dict:
@@ -130,6 +133,22 @@ def properties(self) -> dict:
130133
def node_warnings(self) -> str:
131134
"""Warnings for the node, if any.
132135
136+
.. deprecated: 0.21.3
137+
Use warnings property instead
138+
139+
Returns
140+
-------
141+
str
142+
Warning message(s).
143+
"""
144+
warnings.warn("This property is deprecated in 0.21.3. Use the warnings property instead.", DeprecationWarning)
145+
146+
return self.warnings
147+
148+
@property
149+
def warnings(self) -> str:
150+
"""Warnings for the node, if any.
151+
133152
Returns
134153
-------
135154
str
@@ -174,6 +193,7 @@ def _get_node(self, node_id: int):
174193
>>> new_node = node._get_node(node_id)
175194
"""
176195
from ansys.aedt.core.emit_core.nodes import generated
196+
from ansys.aedt.core.emit_core.nodes.emitter_node import EmitterNode
177197

178198
props = self._oRevisionData.GetEmitNodeProperties(self._result_id, node_id, True)
179199
props = self.props_to_dict(props)
@@ -183,7 +203,23 @@ def _get_node(self, node_id: int):
183203

184204
node = None
185205
try:
186-
type_class = getattr(generated, f"{prefix}{node_type}")
206+
type_class = EmitNode
207+
if node_type == "RadioNode" and props["IsEmitter"] == "true":
208+
type_class = EmitterNode
209+
# TODO: enable when we add ReadOnlyNodes
210+
# if prefix == "":
211+
# type_class = EmitterNode
212+
# else:
213+
# type_class = ReadOnlyEmitterNode
214+
elif node_type == "Band" and props["IsEmitterBand"] == "true":
215+
type_class = getattr(generated, f"{prefix}Waveform")
216+
elif node_type == "TxSpectralProfNode":
217+
if self.properties["IsEmitterBand"] == "true":
218+
type_class = getattr(generated, f"{prefix}TxSpectralProfEmitterNode")
219+
else:
220+
type_class = getattr(generated, f"{prefix}TxSpectralProfNode")
221+
else:
222+
type_class = getattr(generated, f"{prefix}{node_type}")
187223
node = type_class(self._emit_obj, self._result_id, node_id)
188224
except AttributeError:
189225
node = EmitNode(self._emit_obj, self._result_id, node_id)
@@ -203,7 +239,7 @@ def children(self):
203239
child_nodes = [self._get_node(child_id) for child_id in child_ids]
204240
return child_nodes
205241

206-
def _get_property(self, prop, skipChecks=False) -> Union[str, List[str]]:
242+
def _get_property(self, prop, skipChecks=False, isTable=False) -> Union[str, List[str]]:
207243
"""Fetch the value of a given property.
208244
209245
Parameters
@@ -226,7 +262,14 @@ def _get_property(self, prop, skipChecks=False) -> Union[str, List[str]]:
226262
selected_kv_pair = selected_kv_pairs[0]
227263
val = selected_kv_pair[1]
228264

229-
if val.find("|") != -1:
265+
if isTable:
266+
# Node Prop tables
267+
# Data formatted using compact string serialization
268+
# with ';' separating rows and '|' separating columns
269+
rows = val.split(";")
270+
table = [tuple(row.split("|")) for row in rows if row]
271+
return table
272+
elif val.find("|") != -1:
230273
return val.split("|")
231274
else:
232275
return val
@@ -271,6 +314,7 @@ def _string_to_value_units(value) -> tuple[float, str]:
271314
# see if we can split it based on a space between number
272315
# and units
273316
vals = value.split(" ")
317+
units = ""
274318
if len(vals) == 2:
275319
dec_val = float(vals[0])
276320
units = vals[1].strip()
@@ -281,7 +325,11 @@ def _string_to_value_units(value) -> tuple[float, str]:
281325
dec_val = float(value[:i])
282326
units = value[i:]
283327
return dec_val, units
284-
raise ValueError(f"{value} is not valid for this property.")
328+
# maybe it's a string but with no units
329+
try:
330+
return float(value), units
331+
except ValueError:
332+
raise ValueError(f"{value} is not valid for this property.")
285333

286334
def _convert_to_internal_units(self, value: float | str, unit_system: str) -> float:
287335
"""Takes a value and converts to internal EMIT units used for storing values.
@@ -301,9 +349,20 @@ def _convert_to_internal_units(self, value: float | str, unit_system: str) -> fl
301349
"""
302350
if isinstance(value, float) or isinstance(value, int):
303351
# unitless, so assume SI Units
304-
units = consts.SI_UNITS[unit_system]
352+
if unit_system == "Data Rate":
353+
# Data rate isn't included as part of PyAedt's unit class
354+
units = "bps"
355+
else:
356+
units = consts.SI_UNITS[unit_system]
305357
else:
306358
value, units = self._string_to_value_units(value)
359+
# make sure units were specified, if not use SI Units
360+
if units == "":
361+
if unit_system == "Data Rate":
362+
# Data rate isn't included as part of PyAedt unit class
363+
units = "bps"
364+
else:
365+
units = consts.SI_UNITS[unit_system]
307366
# verify the units are valid for the specified type
308367
if units not in EMIT_VALID_UNITS[unit_system]:
309368
raise ValueError(f"{units} are not valid units for this property.")
@@ -331,10 +390,11 @@ def _convert_from_internal_units(value: float, unit_system: str) -> float:
331390
Value in SI units.
332391
"""
333392
# get the SI units
334-
units = consts.SI_UNITS[unit_system]
335393
if unit_system == "Data Rate":
394+
units = "bps"
336395
converted_value = data_rate_conv(value, units, False)
337396
else:
397+
units = consts.SI_UNITS[unit_system]
338398
converted_value = consts.unit_converter(value, unit_system, EMIT_INTERNAL_UNITS[unit_system], units)
339399
return converted_value
340400

@@ -424,28 +484,88 @@ def _get_child_node_id(self, child_name: str) -> int:
424484
"""
425485
return self._oRevisionData.GetChildNodeID(self._result_id, self._node_id, child_name)
426486

487+
def _is_column_data_table(self):
488+
"""Returns true if the node uses column data tables.
489+
490+
Returns
491+
-------
492+
bool
493+
True if the table is ColumnData, False otherwise.
494+
"""
495+
# BB Emission Nodes can have ColumnData or NodeProp tables
496+
# so handle them first
497+
if self._node_type == "TxBbEmissionNode":
498+
if self._get_property("Noise Behavior") == "BroadbandEquation":
499+
return False
500+
return True
501+
502+
table_title = self._get_property("CDTableTitle", True)
503+
if table_title == "":
504+
# No ColumnData Table Title, so it's a NodePropTable
505+
return False
506+
return True
507+
427508
def _get_table_data(self):
428509
"""Returns the node's table data.
429510
430511
Returns
431512
-------
432-
list
433-
The node's table data.
513+
list of tuples
514+
The node's table data as a list of tuples.
515+
[(x1, y1, z1), (x2, y2, z2)]
434516
"""
435-
rows = self._oRevisionData.GetTableData(self._result_id, self._node_id)
436-
nested_list = [col.split(" ") for col in rows]
437-
return nested_list
517+
try:
518+
if self._is_column_data_table():
519+
# Column Data tables
520+
# Data formatted using compact string serialization
521+
# with '|' separating rows and ';' separating columns
522+
data = self._oRevisionData.GetTableData(self._result_id, self._node_id)
523+
rows = data.split("|")
524+
string_table = [tuple(row.split(";")) for row in rows if row]
525+
table = [tuple(float(x) for x in t) for t in string_table]
526+
else:
527+
# Node Prop tables
528+
# Data formatted using compact string serialization
529+
# with ';' separating rows and '|' separating columns
530+
table_key = self._get_property("TableKey", True)
531+
string_table = self._get_property(table_key, True, True)
532+
533+
def try_float(val):
534+
try:
535+
return float(val)
536+
except ValueError:
537+
return val # keep as string for non-numeric (e.g. equations)
538+
539+
table = [tuple(try_float(x) for x in t) for t in string_table]
540+
except Exception as e:
541+
print(f"Failed to get table data for node {self.name}. Error: {e}")
542+
return table
438543

439-
def _set_table_data(self, nested_list):
544+
def _set_table_data(self, table):
440545
"""Sets the table data for the node.
441546
442547
Parameters
443548
----------
444-
nested_list : list
549+
list of tuples
445550
Data to populate the table with.
551+
[(x1, y1, z1), (x2, y2, z2)]
446552
"""
447-
rows = [col.join(" ") for col in nested_list]
448-
self._oRevisionData.SetTableData(self._result_id, self._node_id, rows)
553+
try:
554+
if self._is_column_data_table():
555+
# Column Data tables
556+
# Data formatted using compact string serialization
557+
# with '|' separating rows and ';' separating columns
558+
data = "|".join(";".join(map(str, row)) for row in table)
559+
self._oRevisionData.SetTableData(self._result_id, self._node_id, data)
560+
else:
561+
# Node Prop tables
562+
# Data formatted using compact string serialization
563+
# with ';' separating rows and '|' separating columns
564+
table_key = self._get_property("TableKey", True)
565+
data = ";".join("|".join(map(str, row)) for row in table)
566+
self._set_property(table_key, data)
567+
except Exception as e:
568+
print(f"Failed to set table data for node {self.name}. Error: {e}")
449569

450570
def _add_child_node(self, child_type, child_name=None):
451571
"""Creates a child node of the given type and name.
@@ -455,28 +575,29 @@ def _add_child_node(self, child_type, child_name=None):
455575
child_type : EmitNode
456576
Type of child node to create.
457577
child_name : str, optional
458-
Optional name to use for the child node. If None, a default name is used.
578+
Name to use for the child node. If None, a default name is used.
459579
460580
Returns
461581
-------
462-
int
463-
Unique node ID assigned to the created child node.
582+
node: EmitNode
583+
The node.
464584
465585
Raises
466586
------
467587
ValueError
468588
If the specified child type is not allowed.
469589
"""
470590
if not child_name:
471-
child_name = f"New {child_type}"
591+
child_name = f"{child_type}"
472592

473-
new_id = None
593+
new_node = None
474594
if child_type not in self.allowed_child_types:
475595
raise ValueError(
476596
f"Child type {child_type} is not allowed for this node. Allowed types are: {self.allowed_child_types}"
477597
)
478598
try:
479599
new_id = self._oRevisionData.CreateEmitNode(self._result_id, self._node_id, child_name, child_type)
600+
new_node = self._get_node(new_id)
480601
except Exception as e:
481602
print(f"Failed to add child node of type {child_type} to node {self.name}. Error: {e}")
482-
return new_id
603+
return new_node

0 commit comments

Comments
 (0)