Skip to content

Commit

Permalink
New methods added (#1728)
Browse files Browse the repository at this point in the history
* New methods added:
- Maxwell 3d will have a get_conduction_paths method starting from AEDT 23R1
- hfss3dlayout components has multiple methods to enable die and solderballs in IC, IO and Other components type
- hfss3dlayout has now a method to get the x_t generated during mesh phase (23R1)
- hfss3dlayout.mesh has a generate_mesh command

* New Methods added:
 - Maxwell 3d Transient now has the possibility to setup and export Harmonic Forces and

* Update pyaedt/maxwell.py

* Update pyaedt/maxwell.py

* New Methods added:
 - Maxwell 3d Transient now has the possibility to setup and export Harmonic Forces and

* New Methods added:
 - Maxwell 3d Transient now has the possibility to setup and export Harmonic Forces and

* Apply suggestions from code review

Co-authored-by: Kathy Pippert <[email protected]>
Co-authored-by: Maxime Rey <[email protected]>

* New Methods added:
 - Maxwell 3d Transient now has the possibility to setup and export Harmonic Forces and

* New Methods added:
 - Maxwell 3d Transient now has the possibility to setup and export Harmonic Forces and

* Fixed UT

Co-authored-by: maxcapodi78 <Shark78>
Co-authored-by: Maxime Rey <[email protected]>
Co-authored-by: Kathy Pippert <[email protected]>
  • Loading branch information
3 people authored Sep 23, 2022
1 parent bde5272 commit 155e02e
Show file tree
Hide file tree
Showing 12 changed files with 19,547 additions and 30 deletions.
19,058 changes: 19,058 additions & 0 deletions _unittest/example_models/TMaxwell/Transient_StrandedWindings.aedt

Large diffs are not rendered by default.

27 changes: 27 additions & 0 deletions _unittest/test_28_Maxwell3D.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
core_loss_file = "PlanarTransformer_231"
else:
core_loss_file = "PlanarTransformer"
transient = "Transient_StrandedWindings"


class TestClass(BasisTest, object):
Expand All @@ -29,6 +30,9 @@ def setup_class(self):
self.aedtapp = BasisTest.add_app(self, application=Maxwell3d, solution_type="EddyCurrent")
example_project = os.path.join(local_path, "example_models", test_subfolder, core_loss_file + ".aedt")
self.file_path = self.local_scratch.copyfile(example_project)
self.m3dtransient = BasisTest.add_app(
self, application=Maxwell3d, project_name=transient, subfolder=test_subfolder
)

def teardown_class(self):
BasisTest.my_teardown(self)
Expand Down Expand Up @@ -637,3 +641,26 @@ def test_40_assign_impedance(self):
assert impedance_assignment_copper.name == "ImpedanceExampleCopperNonLinear"
impedance_assignment_copper.name = "ImpedanceExampleCopperNonLinearModified"
assert impedance_assignment_copper.update()

@pytest.mark.skipif(desktop_version < "2023.1", reason="Method implemented in AEDT 2023R1")
def test_41_conduction_paths(self):
self.aedtapp.insert_design("conduction")
box1 = self.aedtapp.modeler.create_box([0, 0, 0], [10, 10, 1], matname="copper")
box1 = self.aedtapp.modeler.create_box([0, 0, 0], [-10, 10, 1], matname="copper")
box3 = self.aedtapp.modeler.create_box([-50, -50, -50], [1, 1, 1], matname="copper")
assert len(self.aedtapp.get_conduction_paths()) == 2

def test_42_harmonic_forces(self):
assert self.m3dtransient.enable_harmonic_force(
["Stator"],
force_type=2,
window_function="Rectangular",
use_number_of_last_cycles=True,
last_cycles_number=3,
calculate_force="Harmonic",
)
self.m3dtransient.analyze_nominal()
assert self.m3dtransient.export_element_based_harmonic_force(
start_frequency=1, stop_frequency=100, number_of_frequency=None
)
assert self.m3dtransient.export_element_based_harmonic_force(number_of_frequency=5)
11 changes: 11 additions & 0 deletions _unittest/test_40_3dlayout_edb.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,3 +254,14 @@ def test_13_objects_by_layer(self):
lines_on_top = self.aedtapp.modeler.objects_by_layer("TOP", "line")
assert len(lines_on_top) > 0
assert self.aedtapp.modeler.geometries[lines_on_top[0]].placement_layer == "TOP"

def test_14_set_solderball(self):
assert not self.aedtapp.modeler.components["U3B2"].die_enabled
assert not self.aedtapp.modeler.components["U3B2"].die_type
assert self.aedtapp.modeler.components["U3B2"].set_die_type()
assert self.aedtapp.modeler.components["U3B2"].set_solderball("Cyl")
assert self.aedtapp.modeler.components["U3B2"].solderball_enabled
assert self.aedtapp.modeler.components["U3B2"].set_solderball(None)
assert not self.aedtapp.modeler.components["U3B2"].solderball_enabled
assert not self.aedtapp.modeler.components["L3A1"].set_solderball(None)
assert self.aedtapp.modeler.components["J1"].set_solderball("Sph")
2 changes: 1 addition & 1 deletion _unittest/test_41_3dlayout_modeler.py
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,7 @@ def test_19A_validate(self):

def test_19B_analyze_setup(self):
self.aedtapp.save_project()
assert self.aedtapp.mesh.generate_mesh("RFBoardSetup3")
assert self.aedtapp.analyze_setup("RFBoardSetup3")
self.aedtapp.save_project()
assert os.path.exists(self.aedtapp.export_profile("RFBoardSetup3"))
Expand Down Expand Up @@ -593,7 +594,6 @@ def test_36_import_gds(self):
control_file = ""
aedb_file = os.path.join(self.local_scratch.path, "gds_out.aedb")
assert self.aedtapp.import_gds(gds_file, aedb_path=aedb_file, control_file=control_file)
assert self.aedtapp.import_gds(gds_file, aedb_path=aedb_file, control_file=control_file)

@pytest.mark.skipif(os.name == "posix", reason="Failing on linux")
def test_37_import_gerber(self):
Expand Down
1 change: 1 addition & 0 deletions pyaedt/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import warnings
from distutils.sysconfig import get_python_lib

os.environ["ANS_MESHER_PROC_DUMP_PREPOST_BEND_SM3"] = "1"
os.environ["ANSYSEM_FEATURE_SF6694_NON_GRAPHICAL_COMMAND_EXECUTION_ENABLE"] = "1"
os.environ["ANSYSEM_FEATURE_SF159726_SCRIPTOBJECT_ENABLE"] = "1"
os.environ["ANSYSEM_FEATURE_SF222134_CABLE_MODELING_ENHANCEMENTS_ENABLE"] = "1"
Expand Down
49 changes: 41 additions & 8 deletions pyaedt/hfss3dlayout.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from __future__ import absolute_import # noreorder

import fnmatch
import io
import os
import warnings
Expand Down Expand Up @@ -346,7 +347,7 @@ def create_wave_port_from_two_conductors(self, primivitivenames=[""], edgenumber
return False

@pyaedt_function_handler()
def create_coax_port(self, vianame, radial_extent, layer, alignment="lower"):
def create_coax_port(self, vianame, radial_extent=0.1, layer=None, alignment="lower"):
"""Create a new coax port.
Parameters
Expand All @@ -356,9 +357,9 @@ def create_coax_port(self, vianame, radial_extent, layer, alignment="lower"):
radial_extent : float
Radial coax extension.
layer : str
Name of the layer.
Name of the layer to apply the reference to.
alignment : str, optional
Port alignment on Layer.
Port alignment on the layer.
Returns
-------
Expand All @@ -383,11 +384,12 @@ def create_coax_port(self, vianame, radial_extent, layer, alignment="lower"):
"Excitations:{}".format(a[0]), "Radial Extent Factor", str(radial_extent), "EM Design"
)
self.modeler.change_property("Excitations:{}".format(a[0]), "Layer Alignment", alignment, "EM Design")
self.modeler.change_property(
a[0],
"Pad Port Layer",
layer,
)
if layer:
self.modeler.change_property(
a[0],
"Pad Port Layer",
layer,
)
bound = self._update_port_info(a[0])
if bound:
self.boundaries.append(bound)
Expand Down Expand Up @@ -1726,3 +1728,34 @@ def _update_port_info(self, port):
for prop in propnames:
props[prop] = self.oeditor.GetPropertyValue("EM Design", "Excitations:{}".format(port), prop)
return BoundaryObject3dLayout(self, port, props, "Port")

@pyaedt_function_handler()
def get_model_from_mesh_results(self, binary=True):
"""Get the path for the parasolid file in the results folder.
The parasolid file is generated after the mesh is created in 3D Layout.
Parameters
----------
binary : str, optional
Either if retrieve binary format of parasoli or not.
Returns
-------
str
Path for the parasolid file in the results folder.
"""
startpath = os.path.join(self.results_directory, self.design_name)
if not binary:
model_name = "model_sm3.x_t"
else:
model_name = "model.x_b"

out_files = [
os.path.join(dirpath, filename)
for dirpath, _, filenames in os.walk(startpath)
for filename in filenames
if fnmatch.fnmatch(filename, model_name)
]
if out_files:
out_files.sort(key=lambda x: os.path.getmtime(x))
return out_files[0]
return ""
136 changes: 134 additions & 2 deletions pyaedt/maxwell.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@


class Maxwell(object):
def __enter__(self):
return self

def __init__(self):
pass

Expand Down Expand Up @@ -1434,8 +1437,117 @@ def assign_current_density(
self.logger.error("Current density can only be applied to Eddy current or magnetostatic solution types.")
return False

def __enter__(self):
return self
@pyaedt_function_handler()
def enable_harmonic_force(
self,
objects,
force_type=0,
window_function="Rectangular",
use_number_of_last_cycles=True,
last_cycles_number=1,
calculate_force="Harmonic",
):
"""Set the Harmonic Force for Transient Analysis.
Parameters
----------
objects : list
Object list to enable force computation.
force_type : int, optional
Force Type. `0` for Objects, `1` for Surface, `2` for volumetric.
window_function : str, optional
Windowing function. Default is `"Rectangular"`.
use_number_of_last_cycles : bool, optional
Either to use or not the last cycle. Default is `True`.
last_cycles_number : int, optional
Defines the number of cycles to compute if `use_number_of_last_cycle` is `True`.
calculate_force : sr, optional
Either `"Harmonic"` or `"Transient"`. Default is `"Harmonic"`.
Returns
-------
"""
if self.solution_type != "Transient":
self.logger.error("This methods work only with Maxwell Transient Analysis.")
return False
objects = self.modeler.convert_to_selections(objects, True)
self.odesign.EnableHarmonicForceCalculation(
[
"EnabledObjects:=",
objects,
"ForceType:=",
force_type,
"WindowFunctionType:=",
window_function,
"UseNumberOfLastCycles:=",
use_number_of_last_cycles,
"NumberOfLastCycles:=",
last_cycles_number,
"StartTime:=",
"0s",
"UseNumberOfCyclesForStopTime:=",
True,
"NumberOfCyclesForStopTime:=",
1,
"StopTime:=",
"0.01s",
"OutputFreqRangeType:=",
"Use All",
"CaculateForceType:=",
calculate_force + " Force",
]
)
return True

@pyaedt_function_handler()
def export_element_based_harmonic_force(
self,
output_directory=None,
setup_name=None,
start_frequency=None,
stop_frequency=None,
number_of_frequency=None,
):
"""Export Element Based Harmonic Forces csv to file.
Parameters
----------
output_directory : str, optional
Path to export. If ``None`` pyaedt working dir will be used.
setup_name : str, optional
Setup name. If ``None`` pyaedt will use nominal setup.
start_frequency : float, optional
When a float is entered the Start-Stop Frequency approach is used.
stop_frequency : float, optional
A float must be entered when the Start-Stop Frequency approach is used.
number_of_frequency : int, optional
When a number is entered, the number of frequencies approach is used.
Returns
-------
str
Path to the export directory.
"""
if self.solution_type != "Transient":
self.logger.error("This methods work only with Maxwell Transient Analysis.")
return False
if not output_directory:
output_directory = self.working_directory
if not setup_name:
setup_name = self.setups[0].name
freq_option = 1
f1 = -1
f2 = -1
if start_frequency and stop_frequency:
freq_option = 2
f1 = start_frequency
f2 = stop_frequency
elif number_of_frequency:
freq_option = 3
f1 = number_of_frequency
self.odesign.ExportElementBasedHarmonicForce(output_directory, setup_name, freq_option, f1, f2)
return output_directory


class Maxwell3d(Maxwell, FieldAnalysis3D, object):
Expand Down Expand Up @@ -1765,6 +1877,26 @@ def _create_boundary(self, name, props, boundary_type):
self.logger.error("Error in boundary creation for %s %s.", boundary_type, name)
return result

@pyaedt_function_handler()
def get_conduction_paths(self):
"""Get a dictionary of all conduction paths with relative objects. It works from AEDT 23R1.
Returns
-------
dict
Dictionary of all conduction paths with relative objects.
"""
conduction_paths = {}

try:
paths = list(self.oboundary.GetConductionPaths())
for path in paths:
conduction_paths[path] = list(self.oboundary.GetConductionPathObjects(path))
return conduction_paths
except:
return conduction_paths


class Maxwell2d(Maxwell, FieldAnalysis3D, object):
"""Provides the Maxwell 2D application interface.
Expand Down
8 changes: 5 additions & 3 deletions pyaedt/modeler/Object3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -625,16 +625,18 @@ def touching_objects(self):
"""
list_names = []
for vertex in self.vertices:
body_names = self._object3d._primitives.get_bodynames_from_position(vertex.position)
body_names = self._object3d._primitives.get_bodynames_from_position(
vertex.position, include_non_model=False
)
a = [i for i in body_names if i != self._object3d.name and i not in list_names]
if a:
list_names.extend(a)
for edge in self.edges:
body_names = self._object3d._primitives.get_bodynames_from_position(edge.midpoint)
body_names = self._object3d._primitives.get_bodynames_from_position(edge.midpoint, include_non_model=False)
a = [i for i in body_names if i != self._object3d.name and i not in list_names]
if a:
list_names.extend(a)
body_names = self._object3d._primitives.get_bodynames_from_position(self.center)
body_names = self._object3d._primitives.get_bodynames_from_position(self.center, include_non_model=False)
a = [i for i in body_names if i != self._object3d.name and i not in list_names]
if a:
list_names.extend(a)
Expand Down
14 changes: 12 additions & 2 deletions pyaedt/modeler/Primitives.py
Original file line number Diff line number Diff line change
Expand Up @@ -1083,9 +1083,14 @@ def model_objects(self):

@property
def non_model_objects(self):
"""List of names of all non-model objects."""
"""List of objects of all non-model objects."""
return self._get_model_objects(model=False)

@property
def non_model_names(self):
"""List of names of all non-model objects."""
return self.oeditor.GetObjectsInGroup("Non Model")

@property
def model_consistency_report(self):
"""Summary of detected inconsistencies between the AEDT modeler and PyAEDT structures.
Expand Down Expand Up @@ -2698,7 +2703,7 @@ def get_edge_midpoint(self, partID):
return

@pyaedt_function_handler()
def get_bodynames_from_position(self, position, units=None):
def get_bodynames_from_position(self, position, units=None, include_non_model=True):
"""Retrieve the names of the objects that are in contact with a given point.
Parameters
Expand All @@ -2708,6 +2713,8 @@ def get_bodynames_from_position(self, position, units=None):
units : str, optional
Units, such as ``"m"``. The default is ``None``, in which case the
model units are used.
include_non_model : bool, optional
Either if include or not non model objects.
Returns
-------
Expand All @@ -2726,6 +2733,9 @@ def get_bodynames_from_position(self, position, units=None):
vArg1.append("YPosition:="), vArg1.append(YCenter)
vArg1.append("ZPosition:="), vArg1.append(ZCenter)
list_of_bodies = list(self.oeditor.GetBodyNamesByPosition(vArg1))
if not include_non_model:
non_models = [i for i in self.non_model_names]
list_of_bodies = [i for i in list_of_bodies if i not in non_models]
return list_of_bodies

@pyaedt_function_handler()
Expand Down
Loading

0 comments on commit 155e02e

Please sign in to comment.