Skip to content

FEAT: Coordinate system in HFSS 3D Layout #6255

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Jun 13, 2025
Merged
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/6255.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Coordinate system in hfss 3d layout
196 changes: 196 additions & 0 deletions src/ansys/aedt/core/modeler/pcb/object_3d_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -2361,3 +2362,198 @@

"""
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.")

Check warning on line 2426 in src/ansys/aedt/core/modeler/pcb/object_3d_layout.py

View check run for this annotation

Codecov / codecov/patch

src/ansys/aedt/core/modeler/pcb/object_3d_layout.py#L2426

Added line #L2426 was not covered by tests

@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.")

Check warning on line 2451 in src/ansys/aedt/core/modeler/pcb/object_3d_layout.py

View check run for this annotation

Codecov / codecov/patch

src/ansys/aedt/core/modeler/pcb/object_3d_layout.py#L2451

Added line #L2451 was not covered by tests
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.")

Check warning on line 2509 in src/ansys/aedt/core/modeler/pcb/object_3d_layout.py

View check run for this annotation

Codecov / codecov/patch

src/ansys/aedt/core/modeler/pcb/object_3d_layout.py#L2509

Added line #L2509 was not covered by tests
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
65 changes: 65 additions & 0 deletions src/ansys/aedt/core/modeler/pcb/primitives_3d_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
27 changes: 27 additions & 0 deletions tests/system/general/test_41_3dlayout_modeler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()