diff --git a/doc/changelog.d/6249.added.md b/doc/changelog.d/6249.added.md new file mode 100644 index 00000000000..8a6130ddc65 --- /dev/null +++ b/doc/changelog.d/6249.added.md @@ -0,0 +1 @@ +emit_schematic_feature \ No newline at end of file diff --git a/doc/changelog.d/6300.added.md b/doc/changelog.d/6300.added.md new file mode 100644 index 00000000000..6a6d635b287 --- /dev/null +++ b/doc/changelog.d/6300.added.md @@ -0,0 +1 @@ +Ability to connect components \ No newline at end of file diff --git a/src/ansys/aedt/core/emit.py b/src/ansys/aedt/core/emit.py index 4d6de06de52..fd44ecc68bc 100644 --- a/src/ansys/aedt/core/emit.py +++ b/src/ansys/aedt/core/emit.py @@ -29,6 +29,7 @@ from ansys.aedt.core.emit_core.couplings import CouplingsEmit from ansys.aedt.core.emit_core.emit_constants import EMIT_VALID_UNITS from ansys.aedt.core.emit_core.emit_constants import emit_unit_type_string_to_enum +from ansys.aedt.core.emit_core.emit_schematic import EmitSchematic from ansys.aedt.core.emit_core.results.results import Results from ansys.aedt.core.generic.general_methods import pyaedt_function_handler from ansys.aedt.core.modeler.schematic import ModelerEmit @@ -172,6 +173,7 @@ def __init__( ) self._modeler = ModelerEmit(self) self._couplings = CouplingsEmit(self) + self._schematic = EmitSchematic(self) if self._aedt_version > "2023.1": # the next 2 lines of code are needed to point # the EMIT object to the correct EmiApiPython @@ -210,6 +212,17 @@ def couplings(self): """ return self._couplings + @property + def schematic(self): + """EMIT Schematic. + + Returns + ------- + :class:`ansys.aedt.core.emit_core.emit_schematic.EmitSchematic` + EMIT schematic. + """ + return self._schematic + @pyaedt_function_handler() def version(self, detailed=False): """ diff --git a/src/ansys/aedt/core/emit_core/emit_schematic.py b/src/ansys/aedt/core/emit_core/emit_schematic.py new file mode 100644 index 00000000000..85e4a19d323 --- /dev/null +++ b/src/ansys/aedt/core/emit_core/emit_schematic.py @@ -0,0 +1,292 @@ +# -*- 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. + + +from ansys.aedt.core.emit_core.nodes.emit_node import EmitNode +from ansys.aedt.core.generic.general_methods import pyaedt_function_handler + + +class EmitSchematic: + """Represents the EMIT schematic and provides methods to interact with it.""" + + def __init__(self, emit_instance): + """Initialize the EmitSchematic class. + + Parameters + ---------- + emit_instance : Emit + Instance of the Emit class. + """ + self.emit_instance = emit_instance + + @property + def _emit_com_module(self): + """Retrieve the EmitCom module from the Emit instance. + + Returns + ------- + object + The EmitCom module. + + Raises + ------ + RuntimeError + If the EmitCom module cannot be retrieved. + """ + if not hasattr(self.emit_instance, "_odesign"): + raise RuntimeError("Emit instance does not have a valid '_odesign' attribute.") + try: + return self.emit_instance._odesign.GetModule("EmitCom") + except Exception as e: + raise RuntimeError(f"Failed to retrieve EmitCom module: {e}") + + @pyaedt_function_handler + def create_component(self, component_type: str, name: str = None, library: str = None) -> EmitNode: + """Create a component. + + Parameters + ---------- + component_type : str + Type of the component to create. + name : str, optional + Name of the component to create. AEDT defaults used if not provided. + library : str, optional + Name of the component library. Defaults to an empty string if not provided. + + Returns + ------- + EmitNode + The EmitNode of the created component. + + Raises + ------ + ValueError + If the component type is empty or no matching component is found. + RuntimeError + If the component creation fails. + """ + if not component_type: + raise ValueError("The 'component_type' argument is required.") + + name = name or "" + library = library or "" + + try: + # Retrieve matching components from the catalog + 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}'.") + + if len(matching_components) == 1: + # Use the single matching component + component = matching_components[0] + self.emit_instance.logger.info( + f"Using component '{component.name}' from library '{component.component_library}" + f"' for type '{component_type}'." + ) + else: + # Attempt to find an exact match + component = next((comp for comp in matching_components if comp.name == component_type), None) + if not component: + self.emit_instance.logger.error( + f"Multiple components found for type '{component_type}', but no exact match." + " Please specify a unique component." + ) + raise ValueError(f"Multiple components found for type '{component_type}', but no exact match.") + self.emit_instance.logger.info( + f"Using exact match component '{component.name}' from library '{component.component_library}" + f"' for type '{component_type}'." + ) + stripped_component_name = component.name.strip("'") + revision = self.emit_instance.results.get_revision() + # Create the component using the EmitCom module + new_component_id = self._emit_com_module.CreateEmitComponent( + name, stripped_component_name, component.component_library + ) + + component_node = revision._get_node(node_id=new_component_id) + return component_node + except Exception as e: + self.emit_instance.logger.error(f"Failed to create component '{name}' of type '{component_type}': {e}") + raise RuntimeError(f"Failed to create component of type '{component_type}': {e}") + + @pyaedt_function_handler + def create_radio_antenna( + self, radio_type: str, radio_name: str = None, antenna_name: str = None, library: str = None + ) -> tuple[EmitNode, EmitNode]: + """Create a new radio and antenna and connect them. + + Parameters + ---------- + radio_type : str + Type of radio to create. For example, "Bluetooth". Must match + a radio name in the specified library. + radio_name : str, optional + Name to assign to the new radio. If ``None``, then an instance + name is assigned automatically. The default is ``None``. + antenna_name : str, optional + Name to assign to the new antenna. If ``None``, then an instance + name is assigned automatically. The default is ``None``. + library : str, optional + Name of the component library. If ``None``, then the default + library is used. The default is ``None``. + + Returns + ------- + tuple[EmitNode, EmitNode] + A tuple containing the EmitNode of the created radio and antenna. + + Raises + ------ + RuntimeError + If the radio or antenna creation fails. + """ + radio_name = radio_name or "" + antenna_name = antenna_name or "" + library = library or "" + + try: + new_radio = self.create_component(radio_type, radio_name, library) + new_antenna = self.create_component("Antenna", antenna_name, "Antennas") + if new_radio and new_antenna: + self.connect_components(new_antenna, new_radio, "in", "n1") # Connect antenna to radio + return new_radio, new_antenna + except Exception as e: + self.emit_instance.logger.error(f"Failed to create radio of type '{radio_type}' or antenna: {e}") + raise RuntimeError(f"Failed to create radio of type '{radio_type}' or antenna: {e}") + + @pyaedt_function_handler + def connect_components( + self, component_1: EmitNode, component_2: EmitNode, component_port_1: str = None, component_port_2: str = None + ) -> None: + """Connect two components in the schematic. + + Parameters + ---------- + component_1 : EmitNode + First component to connect. + component_2 : EmitNode + Second component to connect. + component_port_1 : str, optional + Port of the first component to connect. If ``None``, the default port is used. + component_port_2 : str, optional + Port of the second component to connect. If ``None``, the default port is used. + + Raises + ------ + RuntimeError + If the connection fails. + """ + try: + component_port_1 = component_port_1 or "n1" + component_port_2 = component_port_2 or "n2" + # Update the component ports to match the emit definition + component_port_1 = self._component_port_update(component_port_1) + component_port_2 = self._component_port_update(component_port_2) + # Get the ports and their locations for both components + ports_1 = self.emit_instance._oeditor.GetComponentPorts(component_1.name) + port_locs_1 = { + port: self.emit_instance._oeditor.GetComponentPortLocation(component_1.name, port) for port in ports_1 + } + if len(ports_1) == 1: + component_port_1 = ports_1[0] + if component_1.properties["Type"] == "Multiplexer": + component_port_1 = component_port_1.strip("n") + + ports_2 = self.emit_instance._oeditor.GetComponentPorts(component_2.name) + port_locs_2 = { + port: self.emit_instance._oeditor.GetComponentPortLocation(component_2.name, port) for port in ports_2 + } + if len(ports_2) == 1: + component_port_2 = ports_2[0] + if component_2.properties["Type"] == "Multiplexer": + component_port_2 = component_port_2.strip("n") + + # Validate that the specified ports exist in their respective components + if component_port_1 not in port_locs_1: + raise ValueError(f"Port '{component_port_1}' does not exist in component '{component_1.name}'.") + if component_port_2 not in port_locs_2: + raise ValueError(f"Port '{component_port_2}' does not exist in component '{component_2.name}'.") + # Check if the ports are on the same side of the components + component_1_antenna_pors_list = [x for x in component_1.properties["AntennaSidePorts"].split("|")] + component_1_radio_ports_list = [x for x in component_1.properties["RadioSidePorts"].split("|")] + if component_1.properties["Type"] == "AntennaNode": + component_1_radio_ports_list = ["n"] + if component_1.properties["Type"] == "RadioNode": + component_1_antenna_pors_list = ["1"] + component_2_antenna_ports_list = [x for x in component_2.properties["AntennaSidePorts"].split("|")] + component_2_radio_ports_list = [x for x in component_2.properties["RadioSidePorts"].split("|")] + if component_2.properties["Type"] == "AntennaNode": + component_2_radio_ports_list = ["n"] + if component_2.properties["Type"] == "RadioNode": + component_2_antenna_ports_list = ["1"] + + if ( + component_port_1[-1] in component_1_antenna_pors_list + and component_port_2[-1] in component_2_antenna_ports_list + ) or ( + component_port_1[-1] in component_1_radio_ports_list + and component_port_2[-1] in component_2_radio_ports_list + ): + raise RuntimeError("Both ports are on the same side. Connection cannot be established.") + # Move the first component to align with the second component's port + delta_x = port_locs_2[component_port_2][0] - port_locs_1[component_port_1][0] + delta_y = port_locs_2[component_port_2][1] - port_locs_1[component_port_1][1] + self.emit_instance._oeditor.Move(component_1.name, delta_x, delta_y) + self.emit_instance.logger.info( + f"Successfully connected components '{component_1.name}' and '{component_2.name}'." + ) + except Exception as e: + self.emit_instance.logger.error( + f"Failed to connect components '{component_1.name}' and '{component_2.name}' with the given ports: {e}" + ) + raise RuntimeError( + f"Failed to connect components '{component_1.name}' and '{component_2.name}' with the given ports: {e}" + ) + + @pyaedt_function_handler + def _component_port_update(self, input_port: str) -> str: + """Update the component port properties as emit definition. + + Parameters + ---------- + input_port : str + Name of the input port to update. + + Returns + ------- + updated_port: int + The updated port number after the update. + """ + + port_number = input_port[-1] + if port_number in ["2", "3", "4", "5", "6"]: + updated_port = "n" + port_number + elif port_number in ["1", "n"]: + updated_port = "n1" + else: + raise ValueError(f"Invalid port format: '{input_port}'") + return updated_port diff --git a/src/ansys/aedt/core/emit_core/nodes/emit_node.py b/src/ansys/aedt/core/emit_core/nodes/emit_node.py index 11bd6e214a3..6512f22914b 100644 --- a/src/ansys/aedt/core/emit_core/nodes/emit_node.py +++ b/src/ansys/aedt/core/emit_core/nodes/emit_node.py @@ -471,3 +471,39 @@ def _add_child_node(self, child_type, child_name=None): except Exception as e: print(f"Failed to add child node of type {child_type} to node {self.name}. Error: {e}") return new_id + + @property + def orientation(self) -> int: + """Returns the orientation of the component. + + Returns: + int: The orientation of the component, where valid values range from 0 to 3. + """ + return self._emit_obj.oeditor.GetComponentOrientation(self.name) + + @orientation.setter + def orientation(self, value: int) -> None: + if self.properties["Type"] != "Multiplexer" and value not in [0, 1]: + raise ValueError(f"Orientation must be either 0 or 1 for component '{self.name}'.") + elif self.properties["Type"] == "Multiplexer" and value not in [0, 1, 2, 3]: + raise ValueError("Orientation must be one of the following: 0, 1, 2, or 3 for Multiplexer components.") + if self.orintation_count > 1: + self._emit_obj.oeditor.ReorientComponent(self.name, value) + self._emit_obj.logger.info(f"Successfully set orientation to {value} for component '{self.name}'.") + else: + error_message = ( + f"Orientation adjustment is not supported for component '{self.name}'. " + "This component cannot be reoriented." + ) + self._emit_obj.logger.error(error_message) + raise ValueError(error_message) + + @property + def orintation_count(self) -> int: + """Returns the number of orientations available for the component. + + Returns: + int: The number of valid orientations. A value of 1 indicates the component cannot be reoriented, + 2 indicates support for basic reorientation, and 4 is used for multiplexer components. + """ + return self._emit_obj.oeditor.GetNumComponentOrientations(self.name) diff --git a/src/ansys/aedt/core/emit_core/results/revision.py b/src/ansys/aedt/core/emit_core/results/revision.py index b01a93bda69..5b1ddd1a8b1 100644 --- a/src/ansys/aedt/core/emit_core/results/revision.py +++ b/src/ansys/aedt/core/emit_core/results/revision.py @@ -113,7 +113,6 @@ def __init__(self, parent_results, emit_obj, name=None): self.name = name """Name of the revision.""" - else: if not name: name = emit_obj.odesign.GetCurrentResult() diff --git a/src/ansys/aedt/core/modeler/circuits/primitives_circuit.py b/src/ansys/aedt/core/modeler/circuits/primitives_circuit.py index 5fd444b36b2..c25f24c9e27 100644 --- a/src/ansys/aedt/core/modeler/circuits/primitives_circuit.py +++ b/src/ansys/aedt/core/modeler/circuits/primitives_circuit.py @@ -293,10 +293,10 @@ def create_unique_id(self): except (IndexError, ValueError): pass secure_random = secrets.SystemRandom() - id = secure_random.randint(1, 65535) - while id in element_ids: - id = secure_random.randint(1, 65535) - return id + comp_id = secure_random.randint(1, 65535) + while comp_id in element_ids: + comp_id = secure_random.randint(1, 65535) + return comp_id @pyaedt_function_handler() def add_pin_iports(self, name, id_num): @@ -413,7 +413,7 @@ def create_page_port(self, name, location=None, angle=0, label_position="Auto"): ], ) - id = int(comp_name.split(";")[1]) + comp_id = int(comp_name.split(";")[1]) # self.refresh_all_ids() self.add_id_to_component(id, comp_name) if label_position == "Auto": @@ -432,7 +432,7 @@ def create_page_port(self, name, location=None, angle=0, label_position="Auto"): "NAME:PropDisplayPropTab", [ "NAME:PropServers", - self.components[id].composed_name, + self.components[comp_id].composed_name, ], [ "NAME:ChangedProps", @@ -441,7 +441,7 @@ def create_page_port(self, name, location=None, angle=0, label_position="Auto"): ], ] ) - return self.components[id] + return self.components[comp_id] @pyaedt_function_handler() def create_gnd(self, location=None, angle=0, page=1): @@ -475,8 +475,8 @@ def create_gnd(self, location=None, angle=0, page=1): ["NAME:GroundProps"], ["NAME:Attributes", "Page:=", page, "X:=", xpos, "Y:=", ypos, "Angle:=", angle, "Flip:=", False], ) - id = int(name.split(";")[1]) - self.add_id_to_component(id, name) + comp_id = int(name.split(";")[1]) + self.add_id_to_component(comp_id) # return id, self.components[id].composed_name for el in self.components: if name in self.components[el].composed_name: @@ -1150,16 +1150,16 @@ def create_component( angle = math.pi * angle / 180 arg2 = ["NAME:Attributes", "Page:=", page, "X:=", xpos, "Y:=", ypos, "Angle:=", angle, "Flip:=", False] comp_name = self.oeditor.CreateComponent(arg1, arg2) - id = int(comp_name.split(";")[1]) + comp_id = int(comp_name.split(";")[1]) # self.refresh_all_ids() - self.add_id_to_component(id, comp_name) + self.add_id_to_component(comp_id, comp_name) if name: - self.components[id].set_property("InstanceName", name) + self.components[comp_id].set_property("InstanceName", name) if use_instance_id_netlist: self.enable_use_instance_name(component_library, component_name) elif global_netlist_list: self.enable_global_netlist(component_name, global_netlist_list) - return self.components[id] + return self.components[comp_id] @pyaedt_function_handler(component_name="assignment") def disable_data_netlist(self, assignment): @@ -1284,9 +1284,9 @@ def create_symbol(self, name, pins): ] self.osymbol_manager.Add(arg) - id = 2 + comp_id = 2 i = 1 - id += 2 + comp_id += 2 r = numpins - (h * 2) for pin in pins: arg.append( @@ -1302,7 +1302,7 @@ def create_symbol(self, name, pins): angle = math.pi else: yp -= 0.00254 - id += 2 + comp_id += 2 i += 1 arg.append( @@ -1742,10 +1742,10 @@ def _index_components(self, library_path=None): for compname, comp_value in comps.items(): root_name = str(Path(file).with_suffix("")) full_path = list(Path(root_name).parts) - id = full_path.index(root) + 1 - if self._component_manager.design_libray in full_path[id:]: - id += 1 - comp_lib = "\\".join(full_path[id:]) + ":" + compname + comp_id = full_path.index(root) + 1 + if self._component_manager.design_libray in full_path[comp_id:]: + comp_id += 1 + comp_lib = "\\".join(full_path[comp_id:]) + ":" + compname self.components[comp_lib] = ComponentInfo( compname, self._component_manager, file, comp_lib.split(":")[0] ) diff --git a/tests/system/filter_solutions/resources/__init__.py b/tests/system/filter_solutions/resources/__init__.py index 0077ee82d31..72ef2a16e69 100644 --- a/tests/system/filter_solutions/resources/__init__.py +++ b/tests/system/filter_solutions/resources/__init__.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # # Copyright (C) 2021 - 2025 ANSYS, Inc. and/or its affiliates. +# SPDX-FileCopyrightText: 2021 - 2025 ANSYS, Inc. and /or its affiliates. # SPDX-License-Identifier: MIT # # @@ -26,3 +27,16 @@ from .resources import resource_path __all__ = ["read_resource_file", "resource_path"] + +from ansys.aedt.core.emit_core.nodes.emit_node import EmitNode + + +class ReadOnlyRxSaturationNode(EmitNode): + def __init__(self, emit_obj, result_id, node_id): + self._is_component = False + EmitNode.__init__(self, emit_obj, result_id, node_id) + + @property + def parent(self): + """The parent of this emit node.""" + return self._parent diff --git a/tests/system/solvers/sequential/test_flatten_3d_component.py b/tests/system/solvers/sequential/test_flatten_3d_component.py index 13e0b492433..05fac86a956 100644 --- a/tests/system/solvers/sequential/test_flatten_3d_component.py +++ b/tests/system/solvers/sequential/test_flatten_3d_component.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # # Copyright (C) 2021 - 2025 ANSYS, Inc. and/or its affiliates. +# SPDX-FileCopyrightText: 2021 - 2025 ANSYS, Inc. and /or its affiliates. # SPDX-License-Identifier: MIT # # diff --git a/tests/system/solvers/test_26_emit.py b/tests/system/solvers/test_26_emit.py index 7062cf004d7..8172f22bb90 100644 --- a/tests/system/solvers/test_26_emit.py +++ b/tests/system/solvers/test_26_emit.py @@ -30,6 +30,7 @@ import sys import tempfile import types +from unittest.mock import MagicMock import pytest @@ -49,6 +50,7 @@ from ansys.aedt.core.emit_core.emit_constants import ResultType from ansys.aedt.core.emit_core.emit_constants import TxRxMode from ansys.aedt.core.emit_core.nodes import generated + from ansys.aedt.core.emit_core.nodes.emit_node import EmitNode from ansys.aedt.core.modeler.circuits.primitives_emit import EmitAntennaComponent from ansys.aedt.core.modeler.circuits.primitives_emit import EmitComponent from ansys.aedt.core.modeler.circuits.primitives_emit import EmitComponents @@ -1681,3 +1683,79 @@ def test_27_components_catalog(self, add_app): assert len(comp_list) == 14 assert comp_list[12].name == "LTE BTS" assert comp_list[13].name == "LTE Mobile Station" + + @pytest.mark.skipif(config["desktopVersion"] < "2025.2", reason="Skipped on versions earlier than 2025 R2.") + def test_28_create_component(self, add_app): + self.aedtapp = add_app(project_name="create_component", application=Emit) + self.aedtapp.logger.info = MagicMock() + new_radio = self.aedtapp.schematic.create_component("MICS") + assert isinstance(new_radio, EmitNode) + self.aedtapp.logger.info.assert_called_with( + r"Using component 'MICS' from library 'Radios\Commercial Unlicensed Systems\Medical' for type 'MICS'." + ) + with pytest.raises(TypeError) as e: + self.aedtapp.schematic.create_component() + assert "EmitSchematic.create_component() missing 1 required positional argument: 'component_type'" in str( + e.value + ) + with pytest.raises(RuntimeError) as e: + self.aedtapp.schematic.create_component("WrongComponent") + assert ( + "Failed to create component of type 'WrongComponent': No component found for type 'WrongComponent'." + ) in str(e.value) + with pytest.raises(RuntimeError) as e: + self.aedtapp.schematic.create_component("lte") + assert ( + "Failed to create component of type 'lte': Multiple components found for type 'lte', but no exact match." + ) in str(e.value) + + @pytest.mark.skipif(config["desktopVersion"] < "2025.2", reason="Skipped on versions earlier than 2025 R2.") + def test_29_create_radio_antenna(self, add_app): + self.aedtapp = add_app(project_name="radio_antenna", application=Emit) + new_radio, new_antenna = self.aedtapp.schematic.create_radio_antenna("MICS", "Radio", "Antenna") + assert isinstance(new_radio, EmitNode) + assert isinstance(new_antenna, EmitNode) + with pytest.raises(RuntimeError) as e: + self.aedtapp.schematic.create_radio_antenna("WrongComponent", "Radio", "Antenna") + assert "Failed to create radio of type 'WrongComponent'" in str(e.value) + + @pytest.mark.skipif(config["desktopVersion"] < "2025.2", reason="Skipped on versions earlier than 2025 R2.") + def test_30_connect_components(self, add_app): + self.aedtapp = add_app(project_name="connect_components", application=Emit) + self.aedtapp.logger.info = MagicMock() + new_radio = self.aedtapp.schematic.create_component("MICS") + new_antenna = self.aedtapp.schematic.create_component("Antenna") + self.aedtapp.schematic.connect_components(new_radio, new_antenna) + self.aedtapp.logger.info.assert_called_with("Successfully connected components 'MICS' and 'Antenna'.") + with pytest.raises(RuntimeError) as e: + self.aedtapp.schematic.connect_components(new_radio, new_antenna, "wrongport", "n1") + assert ( + "Failed to connect components 'MICS' and 'Antenna' with the given ports: Invalid port format: 'wrongport'" + ) in str(e.value) + + @pytest.mark.skipif(config["desktopVersion"] < "2025.2", reason="Skipped on versions earlier than 2025 R2.") + def test_31_orientation(self, add_app): + self.aedtapp = add_app(project_name="reorient_component", application=Emit) + new_circulator = self.aedtapp.schematic.create_component("Circulator") + assert 2 == new_circulator.orintation_count + assert ["2", "3"] == [x for x in new_circulator.properties["AntennaSidePorts"].split("|")] + assert ["1"] == [x for x in new_circulator.properties["RadioSidePorts"].split("|")] + new_circulator.orientation = 1 + assert ["1"] == [x for x in new_circulator.properties["AntennaSidePorts"].split("|")] + assert ["3", "2"] == [x for x in new_circulator.properties["RadioSidePorts"].split("|")] + with pytest.raises(ValueError) as e: + new_circulator.orientation = 3 + assert "Orientation must be either 0 or 1 for component 'Circulator'." in str(e.value) + new_multiplexer = self.aedtapp.schematic.create_component("5 Port") + assert 4 == new_multiplexer.orintation_count + assert ["2", "3", "4", "5"] == [x for x in new_multiplexer.properties["AntennaSidePorts"].split("|")] + assert ["1"] == [x for x in new_multiplexer.properties["RadioSidePorts"].split("|")] + new_multiplexer.orientation = 2 + assert ["1"] == [x for x in new_multiplexer.properties["AntennaSidePorts"].split("|")] + assert ["5", "4", "3", "2"] == [x for x in new_multiplexer.properties["RadioSidePorts"].split("|")] + with pytest.raises(ValueError) as e: + new_radio = self.aedtapp.schematic.create_component("New Radio") + new_radio.orientation = 1 + assert ( + "Orientation adjustment is not supported for component 'Radio'. This component cannot be reoriented." + ) in str(e.value)