diff --git a/doc/changelog.d/6255.added.md b/doc/changelog.d/6255.added.md new file mode 100644 index 00000000000..0a0ad96076f --- /dev/null +++ b/doc/changelog.d/6255.added.md @@ -0,0 +1 @@ +Coordinate system in hfss 3d layout \ No newline at end of file diff --git a/src/ansys/aedt/core/modeler/pcb/object_3d_layout.py b/src/ansys/aedt/core/modeler/pcb/object_3d_layout.py index 05c295a8946..c48a7ab7be0 100644 --- a/src/ansys/aedt/core/modeler/pcb/object_3d_layout.py +++ b/src/ansys/aedt/core/modeler/pcb/object_3d_layout.py @@ -32,6 +32,7 @@ from typing import Tuple from ansys.aedt.core.generic.constants import unit_converter +from ansys.aedt.core.generic.file_utils import generate_unique_name from ansys.aedt.core.generic.general_methods import pyaedt_function_handler from ansys.aedt.core.generic.numbers import Quantity from ansys.aedt.core.modeler.geometry_operators import GeometryOperators @@ -2361,3 +2362,198 @@ def remove(self): """ self.padstackmgr.Remove(self.name, True, "", "Project") + + +class CoordinateSystems3DLayout(object): + """Coordinate systems in HFSS 3D Layout.""" + + def __init__(self, primitives): + self._primitives = primitives + self._oeditor = self._primitives.oeditor + self.logger = self._primitives.logger + + # Private properties + self.__name = None + self.__origin = None + + def __getitem__(self, key): + return self.get_property_value(key) + + @property + def valid_properties(self): + """Valid properties. + + References + ---------- + >>> oEditor.GetProperties + """ + all_props = [] + if self.name: + all_props = self._oeditor.GetProperties("BaseElementTab", self.name) + return all_props + + @property + def name(self): + """Name of the coordinate system as a string value. + + Returns + ------- + str + Name of the coordinate system as a string value. + + References + ---------- + >>> oEditor.GetPropertyValue + >>> oEditor.ChangeProperty + + """ + return self.__name + + @name.setter + def name(self, obj_name): + if self.__name is None: + self.__name = obj_name + + elif obj_name != self.name and obj_name not in self._primitives.coordinate_systems: + v_name = ["NAME:Name", "Value:=", obj_name] + v_changed_props = ["NAME:ChangedProps", v_name] + v_prop_servers = ["NAME:PropServers", self.__name] + v_element_tab = ["NAME:BaseElementTab", v_prop_servers, v_changed_props] + v_out = ["NAME:AllTabs", v_element_tab] + self._primitives.oeditor.ChangeProperty(v_out) + self.__name = obj_name + else: + self.logger.warning(f"{obj_name} is already used in current design.") + + @property + def origin(self): + """Location of the coordinate system. + + Returns + ------- + list + Location of the coordinate system. + + References + ---------- + >>> oEditor.GetPropertyValue + >>> oEditor.ChangeProperty + + """ + if self.__origin is None: + location = self.get_property_value("Location") + self.__origin = [float(x.strip()) for x in location.split(",")] + return self.__origin + + @origin.setter + def origin(self, value): + if not isinstance(value, list) or len(value) != 2: + self.logger.warning(f"Origin is not a list of two elements.") + vName = ["NAME:Location", "X:=", value[0], "Y:=", value[1]] + self.change_property(vName) + self.__origin = value + + @pyaedt_function_handler() + def create(self) -> bool: + """Create coordinate systems in HFSS 3D Layout. + + Returns + ------- + bool + ``True`` when successful, ``False`` when failed. + + References + ---------- + >>> oEditor.CreateCS + """ + if self.name is None: + self.name = generate_unique_name("CS") + + # Set Global CS + self._oeditor.SetCS("") + + args = [ + "NAME:Contents", + ["NAME:full_definition", "type:=", "CS", "N:=", self.name], + "placement:=", + ["x:=", self._origin[0], "y:=", self._origin[1]], + "layer:=", + "Postprocessing", + "Clf:=", + False, + "Col:=", + 8421504, + ] + self._oeditor.CreateCS(args) + return True + + @pyaedt_function_handler() + def get_property_value(self, property_name: str): + """Retrieve a property value. + + Parameters + ---------- + property_name : str + Name of the property. + + Returns + ------- + type + Value of the property. + + References + ---------- + >>> oDesign.GetPropertyValue + """ + if property_name not in self.valid_properties: + raise AttributeError(f"{property_name} is not a valid property.") + val = self._oeditor.GetPropertyValue("BaseElementTab", self.name, property_name) + return val + + @pyaedt_function_handler() + def change_property(self, value: list) -> bool: + """Modify a property. + + Parameters + ---------- + value : list + + Returns + ------- + bool + ``True`` when successful, ``False`` when failed. + + References + ---------- + >>> oEditor.ChangeProperty + + Examples + -------- + >>> app = Hfss3dLayout() + + >>> cs = app.modeler.create_coordinate_system() + >>> property = ["NAME:Flipped", "Value:=", True] + >>> cs.change_property(property) + """ + vChangedProps = ["NAME:ChangedProps", value] + vPropServers = ["NAME:PropServers", self.name] + vGeo3dlayout = ["NAME:BaseElementTab", vPropServers, vChangedProps] + vOut = ["NAME:AllTabs", vGeo3dlayout] + self._oeditor.ChangeProperty(vOut) + return True + + @pyaedt_function_handler() + def delete(self): + """Delete the coordinate system. + + Returns + ------- + bool + ``True`` when successful, ``False`` when failed. + """ + try: + self._oeditor.Delete(self.name) + return True + except Exception: + self.logger.warning(f"{self.__name} can not be deleted.") + return False diff --git a/src/ansys/aedt/core/modeler/pcb/primitives_3d_layout.py b/src/ansys/aedt/core/modeler/pcb/primitives_3d_layout.py index 97d7fa10e96..3c32081e9fd 100644 --- a/src/ansys/aedt/core/modeler/pcb/primitives_3d_layout.py +++ b/src/ansys/aedt/core/modeler/pcb/primitives_3d_layout.py @@ -35,6 +35,7 @@ from ansys.aedt.core.modeler.pcb.object_3d_layout import Circle3dLayout from ansys.aedt.core.modeler.pcb.object_3d_layout import Components3DLayout from ansys.aedt.core.modeler.pcb.object_3d_layout import ComponentsSubCircuit3DLayout +from ansys.aedt.core.modeler.pcb.object_3d_layout import CoordinateSystems3DLayout from ansys.aedt.core.modeler.pcb.object_3d_layout import Line3dLayout from ansys.aedt.core.modeler.pcb.object_3d_layout import Nets3DLayout from ansys.aedt.core.modeler.pcb.object_3d_layout import Padstack @@ -161,6 +162,36 @@ def components(self): self._components[obj] = Components3DLayout(self, obj) return self._components + @property + def coordinate_systems(self): + """Coordinate systems. + + Returns + ------- + dict[str, :class:`ansys.aedt.core.modeler.cad.object_3dlayout.CoordinateSystems3DLayout`] + Coordinate system objects. + + """ + objs = self.modeler.oeditor.FindObjects("Type", "CS") + coordinate_systems = {} + for obj_name in objs: + cs_obj = CoordinateSystems3DLayout(self) + cs_obj.name = obj_name + coordinate_systems[obj_name] = cs_obj + return coordinate_systems + + @property + def coordinate_system_names(self): + """Coordinate system names. + + Returns + ------- + list + Coordinate system names. + + """ + return list(self.coordinate_systems.keys()) + @property def geometries(self): """All Geometries including voids. @@ -1593,3 +1624,37 @@ def create_text(self, text, position, layer="PostProcessing", angle=0, font_size ], ] return self.modeler.oeditor.CreateText(args) + + @pyaedt_function_handler() + def create_coordinate_system(self, origin=None, name=None): + """Create a coordinate system. + + Parameters + ---------- + origin : list + List of ``[x, y]`` coordinates for the origin of the + coordinate system. The default is ``None``, in which case + ``[0, 0]`` is used. + name : str, optional + Name of the coordinate system. The default is ``None``. + + Returns + ------- + :class:`ansys.aedt.core.modeler.cad.object_3dlayout.CoordinateSystems3DLayout` + Created coordinate system. + + References + ---------- + >>> oEditor.CreateCS + """ + if name and self.coordinate_systems: + cs_names = [i.name for i in self.coordinate_systems.values()] + if name in cs_names: + raise AttributeError("A coordinate system with the specified name already exists.") + if origin is None: + origin = [0, 0] + cs = CoordinateSystems3DLayout(self) + cs.name = name + cs._origin = origin + cs.create() + return cs diff --git a/tests/system/general/test_41_3dlayout_modeler.py b/tests/system/general/test_41_3dlayout_modeler.py index a8f2f1f0449..6c56ea4dea6 100644 --- a/tests/system/general/test_41_3dlayout_modeler.py +++ b/tests/system/general/test_41_3dlayout_modeler.py @@ -970,3 +970,30 @@ def test_99_export_on_completion(self, add_app, local_scratch): aedtapp = add_app(project_name="test_export_on_completion", application=Hfss3dLayout) assert aedtapp.export_touchstone_on_completion() assert aedtapp.export_touchstone_on_completion(export=True, output_dir=local_scratch.path) + + def test_create_coordinate_system(self, add_app): + aedtapp = add_app(project_name="test_coordinate_system", application=Hfss3dLayout) + cs1 = aedtapp.modeler.create_coordinate_system() + + assert len(cs1.origin) == 2 + assert len(aedtapp.modeler.coordinate_systems) == 1 + assert cs1.name in aedtapp.modeler.coordinate_system_names + assert cs1["Location"] == "0 ,0" + assert cs1.delete() + + cs2 = aedtapp.modeler.create_coordinate_system(name="new", origin=["1mm", "2mm"]) + assert len(aedtapp.modeler.coordinate_systems) == 1 + cs_location = cs2.get_property_value("Location") + assert cs_location == "1 ,2" + cs2.origin = ["2mm", "2mm"] + cs_location = cs2.get_property_value("Location") + assert cs_location == "2 ,2" + + cs2.name = "new2" + assert cs2.name in aedtapp.modeler.coordinate_system_names + + with pytest.raises(AttributeError): + aedtapp.modeler.create_coordinate_system(name=cs2.name) + + # If CS is renamed, it can not be deleted + assert not cs2.delete()