diff --git a/.github/labeler.yml b/.github/labeler.yml
index 954fa3db203..f0ccc72fd79 100644
--- a/.github/labeler.yml
+++ b/.github/labeler.yml
@@ -13,3 +13,10 @@ testing:
- _unittest/conftest.py
- _unittest_ironpython/run_unittests.py
- _unittest_ironpython/run_unittests_batchmode.cmd
+# TODO : Remove once EDB is extracted from PyAEDT
+edb:
+- examples/00-EDB/**
+- examples/01-HFSS3DLayout/EDB_in_3DLayout.py
+- examples/05-Q3D/Q3D_from_EDB.py
+- pyaedt/edb_core/**
+- pyaedt/edb.py
diff --git a/.github/workflows/full_documentation.yml b/.github/workflows/full_documentation.yml
index 6fe075780fd..9803b95be47 100644
--- a/.github/workflows/full_documentation.yml
+++ b/.github/workflows/full_documentation.yml
@@ -34,7 +34,7 @@ jobs:
# The type of runner that the job will run on
name: full_documentation
runs-on: [windows-latest, pyaedt]
- timeout-minutes: 600
+ timeout-minutes: 480
strategy:
matrix:
python-version: ['3.10']
@@ -73,10 +73,10 @@ jobs:
testenv\Scripts\Activate.ps1
sphinx-build -j auto --color -b html -a doc/source doc/_build/html
-# - name: Create PDF Documentations
-# run: |
-# testenv\Scripts\Activate.ps1
-# .\doc\make.bat pdf
+ - name: Create PDF Documentations
+ run: |
+ testenv\Scripts\Activate.ps1
+ .\doc\make.bat pdf
- name: Upload HTML documentation artifact
uses: actions/upload-artifact@v3
@@ -92,20 +92,20 @@ jobs:
path: doc/_build/html/EDBAPI
retention-days: 7
-# - name: Upload PDF documentation artifact
-# uses: actions/upload-artifact@v3
-# with:
-# name: documentation-pdf
-# path: doc/_build/pdf
-# retention-days: 7
-
-# - name: Release
-# uses: softprops/action-gh-release@v1
-# if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
-# with:
-# generate_release_notes: true
-# files: |
-# doc/_build/pdf
+ - name: Upload PDF documentation artifact
+ uses: actions/upload-artifact@v3
+ with:
+ name: documentation-pdf
+ path: doc/_build/pdf
+ retention-days: 7
+
+ - name: Release
+ uses: softprops/action-gh-release@v1
+ if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
+ with:
+ generate_release_notes: true
+ files: |
+ doc/_build/pdf
doc-deploy-stable:
name: Deploy stable documentation
@@ -169,4 +169,4 @@ jobs:
host-url: ${{ vars.MEILISEARCH_HOST_URL }}
api-key: ${{ env.MEILISEARCH_API_KEY }}
doc-artifact-name: documentation-html-edb # Add only EDB API as page in this index.
- pymeilisearchopts: --port 8001 #serve in another port
\ No newline at end of file
+ pymeilisearchopts: --port 8001 #serve in another port
diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml
index a853245d5f8..17f0b6580c6 100644
--- a/.github/workflows/unit_tests.yml
+++ b/.github/workflows/unit_tests.yml
@@ -66,11 +66,15 @@ jobs:
# uses: pyansys/pydpf-actions/check-licenses@v2.0
- name: 'Unit testing'
- timeout-minutes: 40
- run: |
- testenv_s\Scripts\Activate.ps1
- Set-Item -Path env:PYTHONMALLOC -Value "malloc"
- pytest --durations=50 -v --cov=pyaedt --cov-report=xml --cov-report=html --junitxml=junit/test-results.xml _unittest_solvers
+ uses: nick-fields/retry@v2
+ with:
+ max_attempts: 3
+ retry_on: error
+ timeout_minutes: 40
+ command: |
+ testenv_s\Scripts\Activate.ps1
+ Set-Item -Path env:PYTHONMALLOC -Value "malloc"
+ pytest --durations=50 -v --cov=pyaedt --cov-report=xml --cov-report=html --junitxml=junit/test-results.xml _unittest_solvers
- uses: codecov/codecov-action@v3
env:
@@ -125,11 +129,15 @@ jobs:
# uses: pyansys/pydpf-actions/check-licenses@v2.0
- name: 'Unit testing'
- timeout-minutes: 40
- run: |
- testenv\Scripts\Activate.ps1
- Set-Item -Path env:PYTHONMALLOC -Value "malloc"
- pytest -n 6 --dist loadfile --durations=50 -v --cov=pyaedt --cov-report=xml --cov-report=html --junitxml=junit/test-results.xml _unittest
+ uses: nick-fields/retry@v2
+ with:
+ max_attempts: 3
+ retry_on: error
+ timeout_minutes: 40
+ command: |
+ testenv\Scripts\Activate.ps1
+ Set-Item -Path env:PYTHONMALLOC -Value "malloc"
+ pytest -n 6 --dist loadfile --durations=50 -v --cov=pyaedt --cov-report=xml --cov-report=html --junitxml=junit/test-results.xml _unittest
- uses: codecov/codecov-action@v3
env:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 148db396a9a..36e15bcaf00 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -38,14 +38,14 @@ repos:
- --max-line-length=120
- repo: https://github.com/codespell-project/codespell
- rev: v2.2.5
+ rev: v2.2.6
hooks:
- id: codespell
additional_dependencies:
- tomli
- repo: https://github.com/pre-commit/pre-commit-hooks
- rev: v4.4.0
+ rev: v4.5.0
hooks:
- id: requirements-txt-fixer
- id: debug-statements
@@ -53,7 +53,7 @@ repos:
# validate GitHub workflow files
- repo: https://github.com/python-jsonschema/check-jsonschema
- rev: 0.26.3
+ rev: 0.27.0
hooks:
- id: check-github-workflows
@@ -63,6 +63,12 @@ repos:
- id: blacken-docs
additional_dependencies: [black==23.9.1]
+
+# - repo: https://github.com/numpy/numpydoc
+# rev: v1.6.0
+# hooks:
+# - id: numpydoc-validation
+
# - repo: https://github.com/pycqa/pydocstyle
# rev: 6.1.1
# hooks:
diff --git a/_unittest/example_models/TEDB/stackup.json b/_unittest/example_models/TEDB/stackup.json
new file mode 100644
index 00000000000..0fc405a8373
--- /dev/null
+++ b/_unittest/example_models/TEDB/stackup.json
@@ -0,0 +1,92 @@
+{
+ "materials": {
+ "copper": {
+ "name": "copper",
+ "conductivity": 58000000.0
+ },
+ "fr4_epoxy": {
+ "name": "fr4_epoxy",
+ "loss_tangent": 0.02,
+ "permittivity": 4.4
+ },
+ "solder_mask": {
+ "name": "solder_mask",
+ "loss_tangent": 0.035,
+ "permittivity": 3.1
+ }
+ },
+ "layers": {
+ "TOP": {
+ "name": "TOP",
+ "type": "signal",
+ "material": "copper",
+ "dielectric_fill": "copper",
+ "thickness": 5.000000000000004e-05
+ },
+ "D1": {
+ "name": "D1",
+ "type": "dielectric",
+ "material": "fr4_epoxy",
+ "thickness": 0.0001
+ },
+ "L2": {
+ "name": "L2",
+ "type": "signal",
+ "material": "copper",
+ "dielectric_fill": "copper",
+ "thickness": 3.5000000000000004e-05
+ },
+ "D2": {
+ "name": "D2",
+ "type": "dielectric",
+ "material": "fr4_epoxy",
+ "thickness": 0.0001
+ },
+ "L3": {
+ "name": "L3",
+ "type": "signal",
+ "material": "copper",
+ "dielectric_fill": "copper",
+ "thickness": 3.5000000000000004e-05
+ },
+ "D3": {
+ "name": "D3",
+ "type": "dielectric",
+ "material": "fr4_epoxy",
+ "thickness": 0.0001
+ },
+ "L4": {
+ "name": "L4",
+ "type": "signal",
+ "material": "copper",
+ "dielectric_fill": "copper",
+ "thickness": 3.5000000000000004e-05
+ },
+ "D4": {
+ "name": "D4",
+ "type": "dielectric",
+ "material": "fr4_epoxy",
+ "thickness": 0.0001
+ },
+ "L5": {
+ "name": "L5",
+ "type": "signal",
+ "material": "copper",
+ "dielectric_fill": "copper",
+ "thickness": 3.5000000000000004e-05
+ },
+ "D5": {
+ "name": "D5",
+ "type": "dielectric",
+ "material": "fr4_epoxy",
+ "thickness": 0.0001
+ },
+ "BOT": {
+ "name": "BOT",
+ "type": "signal",
+ "material": "copper",
+ "dielectric_fill": "copper",
+ "thickness": 5.000000000000004e-05
+ }
+ }
+}
\ No newline at end of file
diff --git a/_unittest/test_00_EDB.py b/_unittest/test_00_EDB.py
index f7c687af0b6..66fd114c5ad 100644
--- a/_unittest/test_00_EDB.py
+++ b/_unittest/test_00_EDB.py
@@ -1,3 +1,4 @@
+import json
import os
# Setup paths for module imports
@@ -117,6 +118,7 @@ def test_003_create_coax_port_on_component(self):
assert self.edbapp.components["U6"].pins["R3"].id
assert self.edbapp.terminals
assert self.edbapp.ports
+ assert self.edbapp.components["U6"].pins["R3"].get_connected_objects()
def test_004_get_properties(self):
assert len(self.edbapp.components.components) > 0
@@ -884,6 +886,11 @@ def test_069_create_path(self):
assert trace
assert isinstance(trace.get_center_line(), list)
assert isinstance(trace.get_center_line(True), list)
+ self.edbapp["delta_x"] = "1mm"
+ assert trace.add_point("delta_x", "1mm", True)
+ assert trace.get_center_line(True)[-1][0] == "(delta_x)+(0.025)"
+ assert trace.add_point(0.001, 0.002)
+ assert trace.get_center_line()[-1] == [0.001, 0.002]
def test_070_create_outline(self):
edbapp = Edb(
@@ -1840,6 +1847,18 @@ def test_125c_layer(self):
assert layer.material == "copper"
edbapp.close()
+ def test_125d_stackup(self):
+ fpath = os.path.join(local_path, "example_models", test_subfolder, "stackup.json")
+ stackup_json = json.load(open(fpath, "r"))
+
+ edbapp = Edb(edbversion=desktop_version)
+ edbapp.stackup.load(fpath)
+ edbapp.close()
+
+ edbapp = Edb(edbversion=desktop_version)
+ edbapp.stackup.load(stackup_json)
+ edbapp.close()
+
def test_126_comp_def(self):
source_path = os.path.join(local_path, "example_models", test_subfolder, "ANSYS-HSD_V1.aedb")
target_path = os.path.join(self.local_scratch.path, "test_0126.aedb")
@@ -2461,6 +2480,11 @@ def test_130_create_padstack_instance(self):
assert pad_instance3.dcir_equipotential_region
pad_instance3.dcir_equipotential_region = False
assert not pad_instance3.dcir_equipotential_region
+
+ trace = edb.modeler.create_trace([[0, 0], [0, 10e-3]], "1_Top", "0.1mm", "trace_with_via_fence")
+ edb.padstacks.create_padstack("via_0")
+ trace.create_via_fence("1mm", "1mm", "via_0")
+
edb.close()
def test_131_assign_hfss_extent_non_multiple_with_simconfig(self):
@@ -2841,6 +2865,7 @@ def test_145_arc_data(self):
assert self.edbapp.nets["1.2V_DVDDL"].primitives[0].arcs[0].height
def test_145_via_volume(self):
+ #
vias = [
via
for via in list(self.edbapp.padstacks.padstack_instances.values())
@@ -2879,10 +2904,29 @@ def test_147_find_dc_shorts(self):
target_path = os.path.join(self.local_scratch.path, "test_dc_shorts", "ANSYS-HSD_V1_dc_shorts.aedb")
self.local_scratch.copyfolder(source_path, target_path)
edbapp = Edb(target_path, edbversion=desktop_version)
- dc_shorts = edbapp.nets.find_dc_shorts()
+ dc_shorts = edbapp.layout_validation.dc_shorts()
assert dc_shorts
+ edbapp.nets.nets["DDR4_A0"].name = "DDR4$A0"
+ edbapp.layout_validation.illegal_net_names(True)
+ edbapp.layout_validation.illegal_rlc_values(True)
+
# assert len(dc_shorts) == 20
assert ["LVDS_CH09_N", "GND"] in dc_shorts
assert ["LVDS_CH09_N", "DDR4_DM3"] in dc_shorts
assert ["DDR4_DM3", "LVDS_CH07_N"] in dc_shorts
+ assert len(edbapp.nets["DDR4_DM3"].find_dc_short()) > 0
+ edbapp.nets["DDR4_DM3"].find_dc_short(True)
+ assert len(edbapp.nets["DDR4_DM3"].find_dc_short()) == 0
edbapp.close()
+
+ def test_148_load_amat(self):
+ assert "Rogers RO3003 (tm)" in self.edbapp.materials.materials_in_aedt
+ material_file = os.path.join(self.edbapp.materials.syslib, "Materials.amat")
+ assert self.edbapp.materials.add_material_from_aedt("Arnold_Magnetics_N28AH_-40C")
+ assert "Arnold_Magnetics_N28AH_-40C" in self.edbapp.materials.materials.keys()
+ assert self.edbapp.materials.load_amat(material_file)
+ material_list = list(self.edbapp.materials.materials.keys())
+ assert material_list
+ assert len(material_list) > 0
+ assert self.edbapp.materials.materials["Rogers RO3003 (tm)"].loss_tangent == 0.0013
+ assert self.edbapp.materials.materials["Rogers RO3003 (tm)"].permittivity == 3.0
diff --git a/_unittest/test_01_downloads.py b/_unittest/test_01_downloads.py
index 21fb893f42a..f8d1b71a447 100644
--- a/_unittest/test_01_downloads.py
+++ b/_unittest/test_01_downloads.py
@@ -88,6 +88,7 @@ def test_13_download_specific_folder(self):
def test_14_download_icepak_3d_component(self):
assert self.examples.download_icepak_3d_component()
+ @pytest.mark.skipif(is_linux, reason="Failing download files")
def test_15_download_fss_file(self):
example_folder = self.examples.download_FSS_3dcomponent()
assert os.path.exists(example_folder)
diff --git a/_unittest/test_03_Materials.py b/_unittest/test_03_Materials.py
index 2adcae5d68a..7fc62a651e3 100644
--- a/_unittest/test_03_Materials.py
+++ b/_unittest/test_03_Materials.py
@@ -69,8 +69,17 @@ def test_02_create_material(self):
assert self.aedtapp.change_validation_settings()
assert self.aedtapp.change_validation_settings(ignore_unclassified=True, skip_intersections=True)
- assert mat1.set_magnetic_coercitivity(1, 2, 3, 4)
- assert mat1.get_magnetic_coercitivity() == ("1A_per_meter", "2", "3", "4")
+ assert mat1.set_magnetic_coercivity(1, 2, 3, 4)
+ assert mat1.get_magnetic_coercivity() == ("1A_per_meter", "2", "3", "4")
+ mat1.coordinate_system = "Cylindrical"
+ assert mat1.coordinate_system == "Cylindrical"
+ mat1.magnetic_coercivity = [2, 1, 0, 1]
+
+ assert mat1.get_magnetic_coercivity() == ("2A_per_meter", "1", "0", "1")
+ mat1.magnetic_coercivity.value = ["1", "2", "3", "4"]
+ assert mat1.get_magnetic_coercivity() == ("1A_per_meter", "2", "3", "4")
+ assert mat1.magnetic_coercivity.evaluated_value == [1.0, 2.0, 3.0, 4.0]
+
assert mat1.set_electrical_steel_coreloss(1, 2, 3, 4, 0.002)
assert mat1.get_curve_coreloss_type() == "Electrical Steel"
assert mat1.get_curve_coreloss_values()["core_loss_equiv_cut_depth"] == "0.002meter"
diff --git a/_unittest/test_08_Primitives3D.py b/_unittest/test_08_Primitives3D.py
index d7302f2e8f5..d23a7fe61ec 100644
--- a/_unittest/test_08_Primitives3D.py
+++ b/_unittest/test_08_Primitives3D.py
@@ -170,17 +170,10 @@ def test_02_create_box(self):
assert o.material_name == "copper"
assert "MyCreatedBox_11" in self.aedtapp.modeler.solid_names
assert len(self.aedtapp.modeler.object_names) == len(self.aedtapp.modeler.objects)
+ assert not self.aedtapp.modeler.create_box([0, 0], [10, 10, 10], "MyCreatedBox_12", "Copper")
+ assert not self.aedtapp.modeler.create_box([0, 0, 0], [10, 10], "MyCreatedBox_12", "Copper")
- def test_03_create_box_assertions(self):
- try:
- invalid_entry = "Frank"
- self.aedtapp.modeler.create_box([0, 0, 0], invalid_entry, "MyCreatedBox", "Copper")
- except ValueError:
- pass
- else:
- assert False
-
- def test_04_create_polyhedron(self):
+ def test_03_create_polyhedron(self):
o1 = self.aedtapp.modeler.create_polyhedron()
assert o1.id > 0
assert o1.name.startswith("New")
@@ -207,6 +200,37 @@ def test_04_create_polyhedron(self):
assert o1.name in self.aedtapp.modeler.solid_names
assert o2.name in self.aedtapp.modeler.solid_names
assert len(self.aedtapp.modeler.object_names) == len(self.aedtapp.modeler.objects)
+
+ assert not self.aedtapp.modeler.create_polyhedron(
+ cs_axis=AXIS.Z,
+ center_position=[0, 0],
+ start_position=[0, 1, 0],
+ height=2.0,
+ num_sides=5,
+ name="MyPolyhedron",
+ matname="Aluminum",
+ )
+
+ assert not self.aedtapp.modeler.create_polyhedron(
+ cs_axis=AXIS.Z,
+ center_position=[0, 0, 0],
+ start_position=[0, 1],
+ height=2.0,
+ num_sides=5,
+ name="MyPolyhedron",
+ matname="Aluminum",
+ )
+
+ assert not self.aedtapp.modeler.create_polyhedron(
+ cs_axis=AXIS.Z,
+ center_position=[0, 0, 0],
+ start_position=[0, 0, 0],
+ height=2.0,
+ num_sides=5,
+ name="MyPolyhedron",
+ matname="Aluminum",
+ )
+
pass
def test_05_center_and_centroid(self):
@@ -276,6 +300,8 @@ def test_14_create_sphere(self):
assert o.name.startswith("MySphere")
assert o.object_type == "Solid"
assert o.is3d is True
+ assert not self.aedtapp.modeler.create_sphere([10, 10], radius, "MySphere", "Copper")
+ assert not self.aedtapp.modeler.create_sphere(udp, -5, "MySphere", "Copper")
def test_15_create_cylinder(self):
udp = self.aedtapp.modeler.Position(20, 20, 0)
@@ -287,6 +313,8 @@ def test_15_create_cylinder(self):
assert o.name.startswith("MyCyl")
assert o.object_type == "Solid"
assert o.is3d is True
+ assert not self.aedtapp.modeler.create_cylinder(axis, [2, 2], radius, height, 8, "MyCyl", "Copper")
+ assert not self.aedtapp.modeler.create_cylinder(axis, udp, -0.1, height, 8, "MyCyl", "Copper")
pass
def test_16_create_ellipse(self):
@@ -414,6 +442,7 @@ def test_23_create_rectangle(self):
assert o.name.startswith("MyRectangle")
assert o.object_type == "Sheet"
assert o.is3d is False
+ assert not self.aedtapp.modeler.create_rectangle(plane, udp, [4, 5, 10], name="MyRectangle", matname="Copper")
def test_24_create_cone(self):
udp = self.aedtapp.modeler.Position(5, 3, 8)
@@ -423,6 +452,11 @@ def test_24_create_cone(self):
assert o.name.startswith("MyCone")
assert o.object_type == "Solid"
assert o.is3d is True
+ assert not self.aedtapp.modeler.create_cone(axis, [1, 1], 20, 10, 5, name="MyCone", matname="Copper")
+ assert not self.aedtapp.modeler.create_cone(axis, udp, 20, 20, 5, name="MyCone", matname="Copper")
+ assert not self.aedtapp.modeler.create_cone(axis, udp, -20, 20, 5, name="MyCone", matname="Copper")
+ assert not self.aedtapp.modeler.create_cone(axis, udp, 20, -20, 5, name="MyCone", matname="Copper")
+ assert not self.aedtapp.modeler.create_cone(axis, udp, 20, 20, -5, name="MyCone", matname="Copper")
def test_25_get_object_id(self):
udp = self.aedtapp.modeler.Position(5, 3, 8)
@@ -647,6 +681,8 @@ def test_43_fillet_and_undo(self):
assert o.edges[0].fillet()
self.aedtapp._odesign.Undo()
assert o.edges[0].fillet()
+ r = self.create_rectangle(name="MyRect")
+ assert not r.edges[0].fillet()
def test_44_create_polyline_basic_segments(self):
prim3D = self.aedtapp.modeler
@@ -1071,6 +1107,12 @@ def test_55_create_bond_wires(self):
name="low",
)
assert b6
+ assert not self.aedtapp.modeler.create_bondwire(
+ [0, 0], [10, 10, 2], h1=0.15, h2=0, diameter=0.034, facets=8, matname="copper", name="jedec51"
+ )
+ assert not self.aedtapp.modeler.create_bondwire(
+ [0, 0, 0], [10, 10], h1=0.15, h2=0, diameter=0.034, facets=8, matname="copper", name="jedec51"
+ )
def test_56_create_group(self):
assert self.aedtapp.modeler.create_group(["jedec51", "jedec41"], "mygroup")
@@ -1269,11 +1311,12 @@ def test_69_create_torus(self):
assert torus.is3d is True
def test_70_create_torus_exceptions(self):
- with pytest.raises(ValueError) as excinfo:
- self.aedtapp.modeler.create_torus(
- [30, 30], major_radius=-0.3, minor_radius=0.5, axis="Z", name="torus", material_name="Copper"
- )
- assert "Center argument must be a valid 3 element sequence." in str(excinfo.value)
+ assert self.aedtapp.modeler.create_torus(
+ [30, 30, 0], major_radius=1.3, minor_radius=0.5, axis="Z", name="torus", material_name="Copper"
+ )
+ assert not self.aedtapp.modeler.create_torus(
+ [30, 30], major_radius=1.3, minor_radius=0.5, axis="Z", name="torus", material_name="Copper"
+ )
def test_71_create_point(self):
name = "mypoint"
@@ -1517,20 +1560,22 @@ def test_77_create_helix(self):
right_hand=False,
)
- # Test that an exception is raised if the name of the polyline is not provided.
- # We can't use with.pytest.raises pattern below because IronPython does not support pytest.
- try:
- self.aedtapp.modeler.create_helix(
- polyline_name="",
- position=[0, 0, 0],
- x_start_dir=1.0,
- y_start_dir=1.0,
- z_start_dir=1.0,
- )
- except ValueError as exc_info:
- assert "The name of the polyline cannot be an empty string." in str(exc_info.args[0])
- else:
- assert False
+ assert not self.aedtapp.modeler.create_helix(
+ polyline_name="",
+ position=[0, 0, 0],
+ x_start_dir=1.0,
+ y_start_dir=1.0,
+ z_start_dir=1.0,
+ )
+
+ assert not self.aedtapp.modeler.create_helix(
+ polyline_name=polyline_left.name,
+ position=[0, 0],
+ x_start_dir=1.0,
+ y_start_dir=1.0,
+ z_start_dir=1.0,
+ right_hand=False,
+ )
def test_78_get_touching_objects(self):
box1 = self.aedtapp.modeler.create_box([-20, -20, -20], [1, 1, 1], matname="copper")
@@ -1746,3 +1791,43 @@ def test_85_insert_layoutcomponent(self):
assert comp2.layout_component.display_mode == 1
comp2.layout_component.layers["Trace"] = [True, True, 90]
assert comp2.layout_component.update_visibility()
+
+ def test_87_set_mesh_fusion_settings(self):
+ self.aedtapp.insert_design("MeshFusionSettings")
+ box1 = self.aedtapp.modeler.create_box([0, 0, 0], [10, 20, 30])
+ obj_3dcomp = self.aedtapp.modeler.replace_3dcomponent(
+ object_list=[box1.name],
+ )
+ box2 = self.aedtapp.modeler.create_box([0, 0, 0], [100, 20, 30])
+ obj2_3dcomp = self.aedtapp.modeler.replace_3dcomponent(
+ object_list=[box2.name],
+ )
+ assert self.aedtapp.set_mesh_fusion_settings(component=obj2_3dcomp.name, volume_padding=None, priority=None)
+
+ assert self.aedtapp.set_mesh_fusion_settings(
+ component=[obj_3dcomp.name, obj2_3dcomp.name, "Dummy"], volume_padding=None, priority=None
+ )
+
+ assert self.aedtapp.set_mesh_fusion_settings(
+ component=[obj_3dcomp.name, obj2_3dcomp.name],
+ volume_padding=[[0, 5, 0, 0, 0, 1], [0, 0, 0, 2, 0, 0]],
+ priority=None,
+ )
+ assert not self.aedtapp.set_mesh_fusion_settings(
+ component=[obj_3dcomp.name, obj2_3dcomp.name], volume_padding=[[0, 0, 0, 2, 0, 0]], priority=None
+ )
+
+ assert self.aedtapp.set_mesh_fusion_settings(
+ component=[obj_3dcomp.name, obj2_3dcomp.name], volume_padding=None, priority=[obj2_3dcomp.name, "Dummy"]
+ )
+
+ assert self.aedtapp.set_mesh_fusion_settings(
+ component=[obj_3dcomp.name, obj2_3dcomp.name],
+ volume_padding=[[0, 5, 0, 0, 0, 1], [10, 0, 0, 2, 0, 0]],
+ priority=[obj_3dcomp.name],
+ )
+ assert self.aedtapp.set_mesh_fusion_settings(
+ component=None,
+ volume_padding=None,
+ priority=None,
+ )
diff --git a/_unittest/test_12_1_PostProcessing.py b/_unittest/test_12_1_PostProcessing.py
index 8815f2e26f2..120b5e46be3 100644
--- a/_unittest/test_12_1_PostProcessing.py
+++ b/_unittest/test_12_1_PostProcessing.py
@@ -199,7 +199,15 @@ def test_05_export_report_to_jpg(self):
assert os.path.exists(os.path.join(self.local_scratch.path, "MyTestScattering.jpg"))
def test_06_export_report_to_csv(self):
- self.aedtapp.post.export_report_to_csv(self.local_scratch.path, "MyTestScattering")
+ self.aedtapp.post.export_report_to_csv(
+ self.local_scratch.path,
+ "MyTestScattering",
+ start="3GHz",
+ end="6GHz",
+ step="0.12GHz",
+ uniform=True,
+ use_trace_number_format=False,
+ )
assert os.path.exists(os.path.join(self.local_scratch.path, "MyTestScattering.csv"))
def test_06_export_report_to_rdat(self):
diff --git a/_unittest/test_14_AedtLogger.py b/_unittest/test_14_AedtLogger.py
index 48c9c636afa..a9c8436f3aa 100644
--- a/_unittest/test_14_AedtLogger.py
+++ b/_unittest/test_14_AedtLogger.py
@@ -73,7 +73,7 @@ def test_02_output_file_with_app_filter(self):
# file handler on every logger has been released properly.
# Otherwise, we can't read the content of the log file.
- # delete the global file handler but not the log hadler because
+ # delete the global file handler but not the log handler because
# it is used to write some info messages when closing AEDT.
logger.disable_log_on_file()
diff --git a/_unittest/test_41_3dlayout_modeler.py b/_unittest/test_41_3dlayout_modeler.py
index eb26e281f63..20947315811 100644
--- a/_unittest/test_41_3dlayout_modeler.py
+++ b/_unittest/test_41_3dlayout_modeler.py
@@ -1,4 +1,5 @@
import os
+import tempfile
import time
from _unittest.conftest import config
@@ -678,12 +679,14 @@ def test_42_post_processing_3d_layout(self, add_app):
test = add_app(
project_name="test_post_3d_layout_solved_23R2", application=Hfss3dLayout, subfolder=test_subfolder
)
- assert test.post.create_fieldplot_layers_nets(
+ pl1 = test.post.create_fieldplot_layers_nets(
[["TOP", "GND", "V3P3_S5"], ["PWR", "V3P3_S5"]],
- "Mag_Volume_Force_Density",
- intrinsics={"Time": "1ms"},
+ "Mag_E",
+ intrinsics={"Freq": "1GHz"},
plot_name="Test_Layers",
)
+ assert pl1
+ assert pl1.export_image_from_aedtplt(tempfile.gettempdir())
self.aedtapp.close_project(test.project_name)
@pytest.mark.skipif(is_linux, reason="Bug on linux")
diff --git a/doc/source/Getting_started/Installation.rst b/doc/source/Getting_started/Installation.rst
index 6371f08d164..e0569085fb0 100644
--- a/doc/source/Getting_started/Installation.rst
+++ b/doc/source/Getting_started/Installation.rst
@@ -45,8 +45,8 @@ Starting from 2023R2, a Ribbon button is available in Automation Tab as in the e
Build Toolkits with PyAEDT
~~~~~~~~~~~~~~~~~~~~~~~~~~
You can create and install external toolkits.
-The template provides a framework to create your own toolkits using PyAEDT.
-The template can be found at `Template `_.
+The Antenna Wizard toolkit provides an example for modeling antennas using Ansys Electronics Desktop (AEDT).
+The Antenna Wizard can be found at `Antenna Wizard `_.
.. image:: ../Resources/template_ribbon.png
:width: 800
@@ -97,67 +97,6 @@ For example, on Windows with Python 3.7, install PyAEDT and all its dependencies
pip install --no-cache-dir --no-index --find-links=file:////PyAEDT-v-wheelhouse-Windows-3.7 pyaedt
-Install from a batch file
-~~~~~~~~~~~~~~~~~~~~~~~~~
-If you are running on Windows, you can download
-:download:`PyAEDT Environment with IDE bat file <../Resources/pyaedt_with_IDE.bat>`
-and run this batch file on your local machine. Using this approach
-provides you with a complete integrated development environment (IDE)
-for writing PyAEDT scripts in Windows with a simple batch file.
-
-This batch file executes these steps:
-
-1. Creates a Python virtual environment in your ``%APPDATA%`` folder. To accomplish
- this, it uses CPython in the selected version of AEDT available on your machine.
-2. Installs PyAEDT.
-3. Optionally installs `Spyder `_ with -s flag.
-4. Installs `Jupyter Lab `_.
-5. Creates a symbolic link from your PyAEDT installation to AEDT ``PersonalLib`` so
- that scripts can also be run within AEDT.
-6. Updates PyAEDT.
-7. Install PyAEDT toolkit in AEDT to enable PyAEDT Console and PyAEDT Run Script.
-8. Runs the tool that you choose (Spyder, Jupyter Lab, or a simple console).
-
-.. image:: ../Resources/toolkits.png
- :width: 800
- :alt: PyAEDT toolkit installed after batch run
-
-Steps 1 through 5 are executed only the first time that you run the batch file or when ``-f`` is used:
-
-.. code::
-
- pyaedt_with_IDE.bat --force-install
-
- pyaedt_with_IDE.bat -f
-
-Step 6 is executed only when running the command with the ``-update`` option:
-
-.. code::
-
- pyaedt_with_IDE.bat --update
-
- pyaedt_with_IDE.bat -u
-
-Optionally, you can decide to pass a Python path. This path is then used to create a virtual environment:
-
-.. code::
-
- pyaedt_with_IDE.bat -f -p
-
-
-In addition, it is possible to install the PyAEDT package and all its dependencies provided in the wheelhouse by
-executing the batch file mentioned earlier. You must use the Wheelhouse 3.7 package if no Python path is provided.
-Otherwise, you must download and use the correct wheelhouse:
-
-.. code::
-
- pyaedt_with_IDE.bat-w PyAEDT-v-wheelhouse-Windows-3.7
-
- pyaedt_with_IDE.bat -p -w PyAEDT-v-wheelhouse-Windows-3.8
- pyaedt_with_IDE.bat -p -w PyAEDT-v-wheelhouse-Windows-3.7
- pyaedt_with_IDE.bat -p -w PyAEDT-v-wheelhouse-Windows-3.9
-
-
Use IronPython in AEDT
~~~~~~~~~~~~~~~~~~~~~~
PyAEDT is designed to work in CPython 3.7+ and supports many advanced processing packages like
diff --git a/doc/source/Getting_started/Quickcode.rst b/doc/source/Getting_started/Quickcode.rst
index 529eb42963d..7c484435191 100644
--- a/doc/source/Getting_started/Quickcode.rst
+++ b/doc/source/Getting_started/Quickcode.rst
@@ -31,7 +31,7 @@ page on the Ansys Developer portal, you can post questions, share ideas, and get
To reach the project support team, email `pyansys.core@ansys.com `_.
-Example Workflow
+Example workflow
----------------
Here’s a brief example of how PyAEDT works:
diff --git a/doc/source/Resources/pyaedt_with_IDE.bat b/doc/source/Resources/pyaedt_with_IDE.bat
deleted file mode 100644
index 15841b86f4f..00000000000
--- a/doc/source/Resources/pyaedt_with_IDE.bat
+++ /dev/null
@@ -1,152 +0,0 @@
-@echo off
-set current_dir=%~dp0
-setlocal enabledelayedexpansion
-set argCount=0
-for %%x in (%*) do (
- set /a argCount+=1
- set "argVec[!argCount!]=%%~x"
-)
-
-set args=%1 %2 %3 %4 %5 %6
-set update_pyaedt=n
-set install_pyaedt=n
-set install_spyder=n
-
-for /L %%i in (1,1,%argCount%) do (
- if [!argVec[%%i]!]==[-f] set install_pyaedt=y
- if [!argVec[%%i]!]==[--force-install] set install_pyaedt=y
- if [!argVec[%%i]!]==[-u] set update_pyaedt=y
- if [!argVec[%%i]!]==[--update] set update_pyaedt=y
- if [!argVec[%%i]!]==[-p] (
- set /A usepython=%%i+1
- )
- if [!argVec[%%i]!]==[-w] (
- set /A usewheel=%%i+1
- )
- if [!argVec[%%i]!]==[-s] set install_spyder=y
-
-)
-if NOT [%usepython%]==[] (
- set pythonpyaedt="!argVec[%usepython%]!"
- echo Python Path has been specified.
-)
-if NOT [%usewheel%]==[] (
- set wheelpyaedt="!argVec[%usewheel%]!"
- if [%usepython%]==[] (
- echo ----------------------------------------------------------------------
- echo WheelHouse has been specified. Make sure you are using version 3_7.
- echo ----------------------------------------------------------------------
-
- ) ELSE (
- echo ----------------------------------------------------------------------------------------------
- echo WheelHouse has been spefified. Make sure you are using the same version of Python interpreter.
- echo ----------------------------------------------------------------------------------------------
-
- )
-)
-
-
-set env_vars=ANSYSEM_ROOT232 ANSYSEM_ROOT231 ANSYSEM_ROOT222 ANSYSEM_ROOT221 ANSYSEM_ROOT212
-set /A choice_index=1
-for %%c in (%env_vars%) do (
- set env_var_name=%%c
- if defined !env_var_name! (
- set root_var[!choice_index!]=!env_var_name!
- set version=!env_var_name:ANSYSEM_ROOT=!
- set versions[!choice_index!]=!version!
- set version_pretty=20!version:~0,2! R!version:~2,1!
- echo [!choice_index!] !version_pretty!
- set /A choice_index=!choice_index!+1
- )
-)
-REM If choice_index wasn't incremented then it means none of the variables are installed
-if [%choice_index%]==1 (
- echo AEDT 2021 R2 or later must be installed.
- pause
- EXIT /B
-)
-
-set /p chosen_index="Select Version to Install PyAEDT for (number in bracket): "
-if [%chosen_index%] == [] set chosen_index=1
-
-set chosen_root=!root_var[%chosen_index%]!
-set version=!versions[%chosen_index%]!
-echo Selected %version% at !%chosen_root%!.
-
-set aedt_path=potato
-if [%specified_python%]==[y] (
- aedt_path=!argVec[%python_path_index%]!
-) else (
- set aedt_path=!%chosen_root%!\commonfiles\CPython\3_7\winx64\Release\python
- echo Built-in Python is !aedt_path!.
-)
-set aedt_path=!aedt_path:"=!
-
-echo %aedt_path%
-
-
-
-set pyaedt_install_dir=%APPDATA%\pyaedt_env_ide\v%version%
-echo %pyaedt_install_dir%
-if NOT exist "%pyaedt_install_dir%" (
- set install_pyaedt=y
-)
-setlocal enableDelayedExpansion
-
-if [%install_pyaedt%]==[y] (
- if exist "%pyaedt_install_dir%" (
- echo Removing existing PyAEDT environment.
- @RD /S /Q "%pyaedt_install_dir%"
- )
- echo Installing PyAEDT environment in "%pyaedt_install_dir%".
-
- cd "%APPDATA%"
-
- if [%pythonpyaedt%] == [] (
- "%aedt_path%\python.exe" -m venv "%pyaedt_install_dir%" --system-site-packages
- ) ELSE (
- "%pythonpyaedt%\python.exe" -m venv "%pyaedt_install_dir%"
- )
- call "%pyaedt_install_dir%\Scripts\activate.bat"
- if NOT [%wheelpyaedt%]==[] (
- echo Installing PyAEDT from local wheels %arg1%.
- pip install --no-cache-dir --no-index --find-links=%wheelpyaedt% pyaedt
- ) ELSE (
- IF EXIST %current_dir%.git (
- echo Installing PyAEDT from local clone "%current_dir%".
- ) ELSE (
- echo Installing PyAEDT from pip.
- )
-
- python -m pip install --upgrade pip
- pip --default-timeout=1000 install wheel
-
- IF EXIST %current_dir%.git (
- pushd %current_dir%
- pip install .
- popd
- ) ELSE (
- pip --default-timeout=1000 install pyaedt
- )
-
- pip --default-timeout=1000 install jupyterlab -I
- if [%install_spyder%]==[y] pip --default-timeout=1000 install spyder
- pip --default-timeout=1000 install ipython -U
- pip --default-timeout=1000 install ipyvtklink
- )
- if [%pythonpyaedt%]==[] (
- if %version% geq 231 pip uninstall -y pywin32
- )
-
- call python "%pyaedt_install_dir%\Lib\site-packages\pyaedt\misc\aedtlib_personalib_install.py" --version=%version%
-)
-if [%update_pyaedt%]==[y] (
- echo Updating PyAEDT.
- "%pyaedt_install_dir%\Scripts\pip" install pythonnet -U
- "%pyaedt_install_dir%\Scripts\pip" install pyaedt --no-deps -U
- call "%pyaedt_install_dir%\Scripts\python" "%pyaedt_install_dir%\Lib\site-packages\pyaedt\misc\aedtlib_personalib_install.py" --version=%version%
-
-)
-
-
-cmd /k "%pyaedt_install_dir%\Scripts\activate.bat"
diff --git a/doc/source/conf.py b/doc/source/conf.py
index dfa5cf51105..f4e03a5fb1f 100644
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -158,7 +158,7 @@ def setup(app):
"GL10", # reST directives {directives} must be followed by two colons
# Return
"RT04", # Return value description should start with a capital letter"
- "RT05", # Return value description should finish with "."'
+ "RT05", # Return value description should finish with "."
# Summary
"SS01", # No summary found
"SS02", # Summary does not start with a capital letter
@@ -166,7 +166,7 @@ def setup(app):
"SS04", # Summary contains heading whitespaces
"SS05", # Summary must start with infinitive verb, not third person
# Parameters
- "PR10", # Parameter "{param_name}" requires a space before the colon '
+ "PR10", # Parameter "{param_name}" requires a space before the colon
# separating the parameter name and type",
}
diff --git a/doc/styles/Vocab/ANSYS/accept.txt b/doc/styles/Vocab/ANSYS/accept.txt
index 9110f363fe4..16e7708252b 100644
--- a/doc/styles/Vocab/ANSYS/accept.txt
+++ b/doc/styles/Vocab/ANSYS/accept.txt
@@ -81,5 +81,6 @@ EDT
pyansys
Slurm
Python.NET
-
+Toolkits
+toolkits
diff --git a/examples/02-HFSS/Waveguide_Filter.py b/examples/02-HFSS/Waveguide_Filter.py
index 42ed095620e..edae3bcad21 100644
--- a/examples/02-HFSS/Waveguide_Filter.py
+++ b/examples/02-HFSS/Waveguide_Filter.py
@@ -199,8 +199,7 @@ def place_iris(zpos, dz, n):
###############################################################################
# Generate S-Parameter Plots
-# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-#################################################################################
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~
# The following commands fetch solution data from HFSS for plotting directly
# from the Python interpreter.
# Caution: The syntax for expressions must be identical to that used
@@ -209,7 +208,27 @@ def place_iris(zpos, dz, n):
traces_to_plot = hfss.get_traces_for_plot(second_element_filter="P1*")
report = hfss.post.create_report(traces_to_plot) # Creates a report in HFSS
solution = report.get_solution_data()
+
plt = solution.plot(solution.expressions) # Matplotlib axes object.
+###############################################################################
+# Generate E field plot
+# ~~~~~~~~~~~~~~~~~~~~~
+# The following command generates a field plot in HFSS and uses PyVista
+# to plot the field in Jupyter.
+
+plot=hfss.post.plot_field(quantity="Mag_E",
+ objects_list=["Global:XZ"],
+ plot_type="CutPlane",
+ setup_name=hfss.nominal_adaptive,
+ intrinsics={"Freq":"9.8GHz", "Phase":"0deg"},
+ export_path=hfss.working_directory,
+ show=False)
+
+###############################################################################
+# Save and close the desktop
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~
+# The following command saves the project to a file and closes the desktop.
+
hfss.save_project()
hfss.release_desktop()
diff --git a/examples/03-Maxwell/Maxwell2D_NissanLeaf.py b/examples/03-Maxwell/Maxwell2D_NissanLeaf.py
index 1dda2ff6558..60bccf51e97 100644
--- a/examples/03-Maxwell/Maxwell2D_NissanLeaf.py
+++ b/examples/03-Maxwell/Maxwell2D_NissanLeaf.py
@@ -183,7 +183,7 @@
mat_PM = M2D.materials.add_material("Arnold_Magnetics_N30UH_80C_new")
mat_PM.update()
mat_PM.conductivity = "555555.5556"
-mat_PM.set_magnetic_coercitivity(value=-800146.66287534, x=1, y=0, z=0)
+mat_PM.set_magnetic_coercivity(value=-800146.66287534, x=1, y=0, z=0)
mat_PM.mass_density = "7500"
BH_List_PM = []
with open(filename_PM) as f:
diff --git a/examples/06-Multiphysics/MRI.py b/examples/06-Multiphysics/MRI.py
index 01790e3641b..f943c1b749c 100644
--- a/examples/06-Multiphysics/MRI.py
+++ b/examples/06-Multiphysics/MRI.py
@@ -99,11 +99,20 @@
# Draw Point1 at origin of the implant coordinate system
hfss.sar_setup(-1, Average_SAR_method=1, TissueMass=1, MaterialDensity=1, )
-hfss.post.create_fieldplot_cutplane("implant:YZ", "Average_SAR", filter_objects=["implant_box"])
+hfss.post.create_fieldplot_cutplane(objlist="implant:YZ",
+ quantityName="Average_SAR",
+ filter_objects=["implant_box"])
hfss.modeler.set_working_coordinate_system("implant")
hfss.modeler.create_point([0, 0, 0], name="Point1")
+hfss.post.plot_field(quantity="Average_SAR",
+ object_list="implant:YZ",
+ plot_type="CutPlane",
+ show_legend=False,
+ filter_objects=["implant_box"],
+ )
+
###############################################################################
# Adjust Input Power to MRI Coil
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -113,7 +122,9 @@
# input_scale = 1/AverageSAR at Point1
-sol_data = hfss.post.get_solution_data("Average_SAR", primary_sweep_variable="Freq", context="Point1",
+sol_data = hfss.post.get_solution_data(expressions="Average_SAR",
+ primary_sweep_variable="Freq",
+ context="Point1",
report_category="Fields")
sol_data.data_real()
@@ -204,6 +215,15 @@
report_category="Fields")
data.plot()
+mech.post.plot_animated_field(quantity="Temperature",
+ object_list="implant:YZ",
+ plot_type="CutPlane",
+ intrinsics={"Time": "10s"},
+ variation_variable="Time",
+ variation_list=["10s", "20s", "30s", "40s", "50s", "60s"],
+ filter_objects=["implant_box"],
+ )
+
###############################################################################
# Thermal Simulation
# ~~~~~~~~~~~~~~~~~~
@@ -277,7 +297,7 @@
# Plot Temperature on cut plane.
# Plot Temperature on monitor point.
-ipk.analyze(num_cores=6)
+ipk.analyze(num_cores=4,num_tasks=4)
ipk.post.create_fieldplot_cutplane("implant:YZ", "Temperature", filter_objects=["implant_box"],
intrinsincDict={"Time": "0s"})
ipk.save_project()
diff --git a/examples/07-Circuit/Reports.py b/examples/07-Circuit/Reports.py
index 5c3a5d2fc6a..e09ee381453 100644
--- a/examples/07-Circuit/Reports.py
+++ b/examples/07-Circuit/Reports.py
@@ -9,9 +9,8 @@
# ~~~~~~~~~~~~~~~~~~~~~~~~
# Perform required imports and set the local path to the path for PyAEDT.
-# sphinx_gallery_thumbnail_path = 'Resources/spectrum_plot.png'
import os
-
+from IPython.display import Image
import pyaedt
# Set local path to path for PyAEDT
@@ -33,7 +32,7 @@
# The Boolean parameter ``new_thread`` defines whether to create a new instance
# of AEDT or try to connect to an existing instance of it.
-non_graphical = False
+non_graphical = True
NewThread = True
###############################################################################
@@ -47,6 +46,7 @@
specified_version=desktopVersion,
new_desktop_session=True
)
+cir.analyze()
###############################################################################
# Create spectrum report
@@ -58,10 +58,17 @@
# in non-graphical mode in AEDT 2023 R2 and later.
report1 = cir.post.create_report_from_configuration(os.path.join(project_path,'Spectrum_CISPR_Basic.json'))
+out = cir.post.export_report_to_jpg(cir.working_directory, report1.plot_name)
+Image(out)
-if not non_graphical:
- report1_full = cir.post.create_report_from_configuration(os.path.join(project_path,'Spectrum_CISPR_Custom.json'))
+###############################################################################
+# Create spectrum report
+# ~~~~~~~~~~~~~~~~~~~~~~
+# Every aspect of the report can be customized.
+report1_full = cir.post.create_report_from_configuration(os.path.join(project_path,'Spectrum_CISPR_Custom.json'))
+out = cir.post.export_report_to_jpg(cir.working_directory, report1_full.plot_name)
+Image(out)
###############################################################################
# Create transient report
# ~~~~~~~~~~~~~~~~~~~~~~~
@@ -70,31 +77,45 @@
# before generating the report. You can create custom reports in non-graphical
# mode in AEDT 2023 R2 and later.
-if non_graphical:
- props = pyaedt.data_handler.json_to_dict(os.path.join(project_path, 'Transient_CISPR_Basic.json'))
-else:
- props = pyaedt.data_handler.json_to_dict(os.path.join(project_path, 'Transient_CISPR_Custom.json'))
+
+props = pyaedt.data_handler.json_to_dict(os.path.join(project_path, 'Transient_CISPR_Custom.json'))
report2 = cir.post.create_report_from_configuration(input_dict=props, solution_name="NexximTransient")
+out = cir.post.export_report_to_jpg(cir.working_directory, report2.plot_name)
+Image(out)
+
+###############################################################################
+# Create transient report
+# ~~~~~~~~~~~~~~~~~~~~~~~
+# Property dictionary can be customized in any aspect and new report can be created easily.
+# In this example the curve name is customized.
+
props["expressions"] = {"V(Battery)": {}, "V(U1_VDD)": {}}
props["plot_name"] = "Battery Voltage"
report3 = cir.post.create_report_from_configuration(input_dict=props, solution_name="NexximTransient")
+out = cir.post.export_report_to_jpg(cir.working_directory, report3.plot_name)
+Image(out)
###############################################################################
# Create eye diagram
# ~~~~~~~~~~~~~~~~~~
# Create an eye diagram. If the JSON file contains an eye mask, you can create
-# an eye diagram and fully customize it. You can create custom reports in
-# non-graphical mode in AEDT 2023 R2 and later.
+# an eye diagram and fully customize it.
report4 = cir.post.create_report_from_configuration(os.path.join(project_path, 'EyeDiagram_CISPR_Basic.json'))
+out = cir.post.export_report_to_jpg(cir.working_directory, report4.plot_name)
+Image(out)
-if not non_graphical:
- report4_full = cir.post.create_report_from_configuration(os.path.join(project_path, 'EyeDiagram_CISPR_Custom.json'))
+###############################################################################
+# Create eye diagram
+# ~~~~~~~~~~~~~~~~~~
+# You can create custom reports in
+# non-graphical mode in AEDT 2023 R2 and later.
-if not non_graphical:
- cir.post.export_report_to_jpg(cir.working_directory, report4.plot_name)
+report4_full = cir.post.create_report_from_configuration(os.path.join(project_path, 'EyeDiagram_CISPR_Custom.json'))
+out = cir.post.export_report_to_jpg(cir.working_directory, report4_full.plot_name)
+Image(out)
################################################
# This is how the spectrum looks like
# .. image:: Resources/spectrum_plot.png
diff --git a/examples/07-EMIT/EMIT_HFSS_Example.py b/examples/07-EMIT/EMIT_HFSS_Example.py
new file mode 100644
index 00000000000..840d5b96f56
--- /dev/null
+++ b/examples/07-EMIT/EMIT_HFSS_Example.py
@@ -0,0 +1,139 @@
+"""
+EMIT: HFSS to EMIT coupling
+---------------------------
+This example shows how you can use PyAEDT to open an AEDT project with
+an HFSS design, create an EMIT design in the project, and link the HFSS design
+as a coupling link in the EMIT design.
+"""
+###############################################################################
+# Perform required imports
+# ~~~~~~~~~~~~~~~~~~~~~~~~
+# Perform required imports.
+#
+# sphinx_gallery_thumbnail_path = "Resources/emit_hfss.png"
+
+import os
+
+# Import required modules
+import pyaedt
+from pyaedt.generic.filesystem import Scratch
+from pyaedt.emit_core.emit_constants import TxRxMode, ResultType
+
+###############################################################################
+## Set non-graphical mode
+# ~~~~~~~~~~~~~~~~~~~~~~~
+# Set non-graphical mode.
+# You can set ``non_graphical`` either to ``True`` or ``False``.
+# The Boolean parameter ``new_thread`` defines whether to create a new instance
+# of AEDT or try to connect to an existing instance of it.
+#
+# The following code uses AEDT 2023 R2.
+
+non_graphical = False
+NewThread = True
+desktop_version = "2023.2"
+scratch_path = pyaedt.generate_unique_folder_name()
+
+###############################################################################
+# Launch AEDT with EMIT
+# ~~~~~~~~~~~~~~~~~~~~~
+# Launch AEDT with EMIT. The ``Desktop`` class initializes AEDT and starts it
+# on the specified version and in the specified graphical mode.
+
+d = pyaedt.launch_desktop(desktop_version, non_graphical, NewThread)
+
+temp_folder = os.path.join(scratch_path, ("EmitHFSSExample"))
+if not os.path.exists(temp_folder):
+ os.mkdir(temp_folder)
+
+example_name = "Cell Phone RFI Desense"
+example_aedt = example_name + ".aedt"
+example_results = example_name + ".aedtresults"
+example_lock = example_aedt + ".lock"
+example_pdf_file = example_name + " Example.pdf"
+
+example_dir = os.path.join(d.install_path, "Examples\\EMIT")
+example_project = os.path.join(example_dir, example_aedt)
+example_results_folder = os.path.join(example_dir, example_results)
+example_pdf = os.path.join(example_dir, example_pdf_file)
+
+########################################################################################################
+# If the ``Cell Phone RFT Defense`` example is not
+# in the installation directory, exit from this example.
+
+if not os.path.exists(example_project):
+ msg = """
+ Cell phone RFT Desense example file is not in the
+ Examples/EMIT directory under the EDT installation. You cannot run this example.
+ """
+ print(msg)
+ d.release_desktop(True, True)
+ exit()
+
+my_project = os.path.join(temp_folder, example_aedt)
+my_results_folder = os.path.join(temp_folder, example_results)
+my_project_lock = os.path.join(temp_folder, example_lock)
+my_project_pdf = os.path.join(temp_folder, example_pdf_file)
+
+if os.path.exists(my_project):
+ os.remove(my_project)
+
+if os.path.exists(my_project_lock):
+ os.remove(my_project_lock)
+
+with Scratch(scratch_path) as local_scratch:
+ local_scratch.copyfile(example_project, my_project)
+ local_scratch.copyfolder(example_results_folder, my_results_folder)
+ if os.path.exists(example_pdf):
+ local_scratch.copyfile(example_pdf, my_project_pdf)
+
+aedtapp = pyaedt.Emit(my_project)
+
+###############################################################################
+# Create and connect EMIT components
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+# Create two radios with antennas connected to each one.
+
+rad1, ant1 = aedtapp.modeler.components.create_radio_antenna("Bluetooth Low Energy (LE)")
+rad2, ant2 = aedtapp.modeler.components.create_radio_antenna("Bluetooth Low Energy (LE)")
+
+###############################################################################
+# Define coupling among RF systems
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+# Define coupling among the RF systems.
+
+for link in aedtapp.couplings.linkable_design_names:
+ aedtapp.couplings.add_link(link)
+
+for link in aedtapp.couplings.coupling_names:
+ aedtapp.couplings.update_link(link)
+
+###############################################################################
+# Run EMIT simulation
+# ~~~~~~~~~~~~~~~~~~~
+# Run the EMIT simulation. This portion of the EMIT API is not yet implemented.
+#
+# This part of the example requires Ansys AEDT 2023 R2.
+
+if desktop_version > "2023.1":
+ rev = aedtapp.results.analyze()
+ rx_bands = rev.get_band_names(rad1.name, TxRxMode.RX)
+ tx_bands = rev.get_band_names(rad2.name, TxRxMode.TX)
+ domain = aedtapp.results.interaction_domain()
+ domain.set_receiver(rad1.name, rx_bands[0], -1)
+ domain.set_interferer(rad2.name,tx_bands[0])
+ interaction = rev.run(domain)
+ worst = interaction.get_worst_instance(ResultType.EMI)
+ if worst.has_valid_values():
+ emi = worst.get_value(ResultType.EMI)
+ print("Worst case interference is: {} dB".format(emi))
+
+###############################################################################
+# Save project and close AEDT
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+# After the simulation completes, you can close AEDT or release it using the
+# :func:`pyaedt.Desktop.force_close_desktop` method.
+# All methods provide for saving the project before closing.
+
+aedtapp.save_project()
+aedtapp.release_desktop(close_projects=True, close_desktop=True)
diff --git a/examples/07-EMIT/interference_gui.py b/examples/07-EMIT/interference_gui.py
index 6dfce44cb1b..7a2c2a78904 100644
--- a/examples/07-EMIT/interference_gui.py
+++ b/examples/07-EMIT/interference_gui.py
@@ -57,7 +57,7 @@ def install(package):
# Add emitapi to system path
emit_path = os.path.join(desktop.install_path, "Delcross")
-sys.path.append(emit_path)
+sys.path.insert(0,emit_path)
import EmitApiPython
api = EmitApiPython.EmitApi()
@@ -244,8 +244,20 @@ def open_file_dialog(self):
# Close previous project and open specified one
if self.emitapp is not None:
self.emitapp.close_project()
+ self.emitapp = None
desktop_proj = desktop.load_project(self.file_path_box.text())
+ # check for an empty project (i.e. no designs)
+ if isinstance(desktop_proj, bool):
+ self.file_path_box.setText("")
+ msg = QtWidgets.QMessageBox()
+ msg.setWindowTitle("Error: Project missing designs.")
+ msg.setText(
+ "The selected project has no designs. Projects must have at least "
+ "one EMIT design. See AEDT log for more information.")
+ x = msg.exec()
+ return
+
# Check if project is already open
if desktop_proj.lock_file == None:
msg = QtWidgets.QMessageBox()
@@ -317,6 +329,9 @@ def design_dropdown_changed(self):
self.warning_label.setText("Warning: The selected design must contain at least two radios.")
self.warning_label.setHidden(False)
+ # clear the table if the design is changed
+ self.clear_table()
+
###############################################################################
# Enable radio specific protection levels
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -556,7 +571,26 @@ def populate_table(self):
button.setEnabled(True)
img_btn.setEnabled(True)
-
+
+ def clear_table(self):
+ # get the table/buttons based on current tab
+ if self.tab_widget.currentIndex() == 0:
+ table = self.protection_matrix
+ button = self.protection_export_btn
+ img_btn = self.protection_save_img_btn
+ else:
+ table = self.interference_matrix
+ button = self.interference_export_btn
+ img_btn = self.interference_save_img_btn
+
+ # disable export options
+ button.setEnabled(False)
+ img_btn.setEnabled(False)
+
+ # clear the table
+ table.setColumnCount(0)
+ table.setRowCount(0)
+
###############################################################################
# GUI closing event
# ~~~~~~~~~~~~~~~~~
@@ -564,10 +598,13 @@ def populate_table(self):
def closeEvent(self, event):
msg = QtWidgets.QMessageBox()
msg.setWindowTitle("Closing GUI")
- msg.setText("Closing AEDT, please wait for GUI to close on its own.")
+ msg.setText("Closing AEDT. Wait for the GUI to close on its own.")
x = msg.exec()
- self.emitapp.close_project()
- self.emitapp.close_desktop()
+ if self.emitapp:
+ self.emitapp.close_project()
+ self.emitapp.close_desktop()
+ else:
+ desktop.release_desktop(True, True)
###############################################################################
# Run GUI
diff --git a/ignore_words.txt b/ignore_words.txt
index 7e7db77bd34..c8fa792f47c 100644
--- a/ignore_words.txt
+++ b/ignore_words.txt
@@ -31,3 +31,4 @@ ro
aline
COM
gRPC
+Toolkits
diff --git a/pyaedt/__init__.py b/pyaedt/__init__.py
index 99d4411193d..99ceb167ade 100644
--- a/pyaedt/__init__.py
+++ b/pyaedt/__init__.py
@@ -6,7 +6,7 @@
pyaedt_path = os.path.dirname(__file__)
-__version__ = "0.7.0"
+__version__ = "0.7.1"
version = __version__
import pyaedt.downloads as downloads
diff --git a/pyaedt/aedt_logger.py b/pyaedt/aedt_logger.py
index f1c635f56a3..a3a307eb1f8 100644
--- a/pyaedt/aedt_logger.py
+++ b/pyaedt/aedt_logger.py
@@ -320,7 +320,7 @@ def get_messages(self, project_name=None, design_name=None, level=0, aedt_messag
"""
project_name = project_name or self._project_name
design_name = design_name or self._design_name
- if self._log_on_desktop or aedt_messages:
+ if aedt_messages and self._desktop.GetVersion() > "2022.2":
global_message_data = self._desktop.GetMessages("", "", level)
# if a 3d component is open, GetMessages without the project name argument returns messages with
# "(3D Component)" appended to project name
diff --git a/pyaedt/application/AEDT_File_Management.py b/pyaedt/application/AEDT_File_Management.py
index 2b20cd25013..6d641ba45a4 100644
--- a/pyaedt/application/AEDT_File_Management.py
+++ b/pyaedt/application/AEDT_File_Management.py
@@ -79,7 +79,7 @@ def create_output_folder(ProjectDir):
# set pathname for the files
ResultsPath = os.path.join(npath, os.path.basename(npath), "Results")
- # Add foldes for outputs
+ # Add folders for outputs
if not os.path.exists(OutputPath):
os.mkdir(OutputPath)
if not os.path.exists(PicturePath):
diff --git a/pyaedt/application/Analysis.py b/pyaedt/application/Analysis.py
index 12bf51149aa..23d37b3ebf0 100644
--- a/pyaedt/application/Analysis.py
+++ b/pyaedt/application/Analysis.py
@@ -1724,27 +1724,36 @@ def analyze_setup(
skip_files = True
if not skip_files:
if num_cores:
- skip_files = update_hpc_option(target_name, "NumCores", num_cores, False)
+ succeeded = update_hpc_option(target_name, "NumCores", num_cores, False)
+ skip_files = True if not succeeded else skip_files
if num_gpu:
- skip_files = update_hpc_option(target_name, "NumGPUs", num_gpu, False)
+ succeeded = update_hpc_option(target_name, "NumGPUs", num_gpu, False)
+ skip_files = True if not succeeded else skip_files
if num_tasks:
- skip_files = update_hpc_option(target_name, "NumEngines", num_tasks, False)
- skip_files = update_hpc_option(target_name, "ConfigName", config_name, True)
- skip_files = update_hpc_option(target_name, "DesignType", self.design_type, True)
+ succeeded = update_hpc_option(target_name, "NumEngines", num_tasks, False)
+ skip_files = True if not succeeded else skip_files
+ succeeded = update_hpc_option(target_name, "ConfigName", config_name, True)
+ skip_files = True if not succeeded else skip_files
+ succeeded = update_hpc_option(target_name, "DesignType", self.design_type, True)
+ skip_files = True if not succeeded else skip_files
if self.design_type == "Icepak":
use_auto_settings = False
- skip_files = update_hpc_option(target_name, "UseAutoSettings", use_auto_settings, False)
+ succeeded = update_hpc_option(target_name, "UseAutoSettings", use_auto_settings, False)
+ skip_files = True if not succeeded else skip_files
if num_variations_to_distribute:
- skip_files = update_hpc_option(
+ succeeded = update_hpc_option(
target_name, "NumVariationsToDistribute", num_variations_to_distribute, False
)
+ skip_files = True if not succeeded else skip_files
if isinstance(allowed_distribution_types, list):
num_adt = len(allowed_distribution_types)
adt_string = "', '".join(allowed_distribution_types)
adt_string = "[{}: '{}']".format(num_adt, adt_string)
- skip_files = update_hpc_option(
+
+ succeeded = update_hpc_option(
target_name, "AllowedDistributionTypes", adt_string, False, separator=""
)
+ skip_files = True if not succeeded else skip_files
if settings.remote_rpc_session:
remote_name = (
diff --git a/pyaedt/application/Design.py b/pyaedt/application/Design.py
index 7f57ec975c2..be6978c1e23 100644
--- a/pyaedt/application/Design.py
+++ b/pyaedt/application/Design.py
@@ -364,7 +364,7 @@ def boundaries_by_type(self):
@property
def excitations_by_type(self):
- """Design excitations by tupe.
+ """Design excitations by type.
Returns
-------
diff --git a/pyaedt/application/JobManager.py b/pyaedt/application/JobManager.py
index 76ca8823bed..1277424e1f5 100644
--- a/pyaedt/application/JobManager.py
+++ b/pyaedt/application/JobManager.py
@@ -185,7 +185,7 @@ def update_cluster_cores(file_name, param_name, param_val):
def update_hpc_template(file_name, param_name, param_val):
- """Update a paramerter in the HPC template file.
+ """Update a parameter in the HPC template file.
Parameters
----------
diff --git a/pyaedt/application/Variables.py b/pyaedt/application/Variables.py
index db9694a3947..06d91b48ab2 100644
--- a/pyaedt/application/Variables.py
+++ b/pyaedt/application/Variables.py
@@ -263,7 +263,7 @@ def decompose_variable_value(variable_value, full_variables={}):
Returns
-------
tuples
- tuples made of the float value of the variable and the units exposed as a string.
+ Tuples made of the float value of the variable and the units exposed as a string.
"""
# set default return values - then check for valid units
float_value = variable_value
@@ -323,7 +323,7 @@ def generate_validation_errors(property_names, expected_settings, actual_setting
List of property names.
expected_settings : List[str]
List of the expected settings.
- actual_settings: List[str]
+ actual_settings : List[str]
List of actual settings.
Returns
@@ -1761,7 +1761,7 @@ def __mul__(self, other):
"""Multiply the variable with a number or another variable and return a new object.
Parameters
- ---------
+ ----------
other : numbers.Number or variable
Object to be multiplied.
@@ -1820,7 +1820,7 @@ def __add__(self, other):
"""Add the variable to another variable to return a new object.
Parameters
- ---------
+ ----------
other : class:`pyaedt.application.Variables.Variable`
Object to be multiplied.
@@ -1861,7 +1861,7 @@ def __sub__(self, other):
"""Subtract another variable from the variable to return a new object.
Parameters
- ---------
+ ----------
other : class:`pyaedt.application.Variables.Variable`
Object to be subtracted.
@@ -1904,7 +1904,7 @@ def __truediv__(self, other):
"""Divide the variable by a number or another variable to return a new object.
Parameters
- ---------
+ ----------
other : numbers.Number or variable
Object by which to divide.
@@ -1948,7 +1948,7 @@ def __rtruediv__(self, other):
"""Divide another object by this object.
Parameters
- ---------
+ ----------
other : numbers.Number or variable
Object to divide by.
diff --git a/pyaedt/application/aedt_objects.py b/pyaedt/application/aedt_objects.py
index c88ef3508aa..eb76dd488c4 100644
--- a/pyaedt/application/aedt_objects.py
+++ b/pyaedt/application/aedt_objects.py
@@ -65,7 +65,7 @@ def get_module(self, module_name):
@property
def o_symbol_manager(self):
- """Aedt Simbol Manager.
+ """Aedt Symbol Manager.
References
----------
diff --git a/pyaedt/circuit.py b/pyaedt/circuit.py
index 4a99c6552c9..f641716cba9 100644
--- a/pyaedt/circuit.py
+++ b/pyaedt/circuit.py
@@ -1587,7 +1587,7 @@ def connect_circuit_models_from_multi_zone_cutout(
Returns
-------
bool
- ``True`` when succeessful, ``False`` when failed.
+ ``True`` when successful, ``False`` when failed.
Examples
--------
diff --git a/pyaedt/desktop.py b/pyaedt/desktop.py
index b33bd4f293a..93b9bd14c82 100644
--- a/pyaedt/desktop.py
+++ b/pyaedt/desktop.py
@@ -66,6 +66,8 @@ def launch_desktop_on_port():
command = [full_path, "-grpcsrv", str(port)]
if non_graphical:
command.append("-ng")
+ if settings.wait_for_license:
+ command.append("-waitforlicense")
my_env = os.environ.copy()
for env, val in settings.aedt_environment_variables.items():
my_env[env] = val
@@ -131,6 +133,8 @@ def launch_aedt_in_lsf(non_graphical, port): # pragma: no cover
]
if non_graphical:
command.append("-ng")
+ if settings.wait_for_license:
+ command.append("-waitforlicense")
print(command)
p = subprocess.Popen(command, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
timeout = settings.lsf_timeout
diff --git a/pyaedt/edb.py b/pyaedt/edb.py
index ec2cc3ddd01..ee9a5b13c4b 100644
--- a/pyaedt/edb.py
+++ b/pyaedt/edb.py
@@ -37,11 +37,14 @@
from pyaedt.edb_core.edb_data.terminals import PadstackInstanceTerminal
from pyaedt.edb_core.edb_data.terminals import Terminal
from pyaedt.edb_core.edb_data.variables import Variable
+from pyaedt.edb_core.general import LayoutObjType
+from pyaedt.edb_core.general import Primitives
from pyaedt.edb_core.general import TerminalType
from pyaedt.edb_core.general import convert_py_list_to_net_list
from pyaedt.edb_core.hfss import EdbHfss
from pyaedt.edb_core.ipc2581.ipc2581 import Ipc2581
from pyaedt.edb_core.layout import EdbLayout
+from pyaedt.edb_core.layout_validation import LayoutValidation
from pyaedt.edb_core.materials import Materials
from pyaedt.edb_core.net_class import EdbDifferentialPairs
from pyaedt.edb_core.net_class import EdbExtendedNets
@@ -328,6 +331,11 @@ def project_variables(self):
p_var[i] = Variable(self, i)
return p_var
+ @property
+ def layout_validation(self):
+ """:class:`pyaedt.edb_core.edb_data.layout_validation.LayoutValidation`."""
+ return LayoutValidation(self)
+
@property
def variables(self):
"""Get all Edb variables.
@@ -1032,9 +1040,53 @@ def layout_instance(self):
"""Edb Layout Instance."""
return self.layout.layout_instance
+ @pyaedt_function_handler
+ def get_connected_objects(self, layout_object_instance):
+ """Get connected objects.
+
+ Returns
+ -------
+ list
+ """
+ temp = []
+ for i in list(
+ [
+ loi.GetLayoutObj()
+ for loi in self.layout_instance.GetConnectedObjects(layout_object_instance._edb_object).Items
+ ]
+ ):
+ obj_type = i.GetObjType().ToString()
+ if obj_type == LayoutObjType.PadstackInstance.name:
+ from pyaedt.edb_core.edb_data.padstacks_data import EDBPadstackInstance
+
+ temp.append(EDBPadstackInstance(i, self))
+ elif obj_type == LayoutObjType.Primitive.name:
+ prim_type = i.GetPrimitiveType().ToString()
+ if prim_type == Primitives.Path.name:
+ from pyaedt.edb_core.edb_data.primitives_data import EdbPath
+
+ temp.append(EdbPath(i, self))
+ elif prim_type == Primitives.Rectangle.name:
+ from pyaedt.edb_core.edb_data.primitives_data import EdbRectangle
+
+ temp.append(EdbRectangle(i, self))
+ elif prim_type == Primitives.Circle.name:
+ from pyaedt.edb_core.edb_data.primitives_data import EdbCircle
+
+ temp.append(EdbCircle(i, self))
+ elif prim_type == Primitives.Polygon.name:
+ from pyaedt.edb_core.edb_data.primitives_data import EdbPolygon
+
+ temp.append(EdbPolygon(i, self))
+ else:
+ continue
+ else:
+ continue
+ return temp
+
@property
def pins(self):
- """EDBPadstackInstance of Component.
+ """EDB padstack instance of the component.
.. deprecated:: 0.6.62
Use new method :func:`edb.padstacks.pins` instead.
diff --git a/pyaedt/edb_core/components.py b/pyaedt/edb_core/components.py
index 20afa025359..d5d21b2f7ef 100644
--- a/pyaedt/edb_core/components.py
+++ b/pyaedt/edb_core/components.py
@@ -1240,7 +1240,7 @@ def _create_pin_group_terminal(self, pingroup, isref=False):
@pyaedt_function_handler()
def _is_top_component(self, cmp):
- """Test the component placment layer.
+ """Test the component placement layer.
Parameters
----------
diff --git a/pyaedt/edb_core/dotnet/database.py b/pyaedt/edb_core/dotnet/database.py
index f0527335342..35de57a6bc5 100644
--- a/pyaedt/edb_core/dotnet/database.py
+++ b/pyaedt/edb_core/dotnet/database.py
@@ -80,6 +80,43 @@ def arcs(self): # pragma: no cover
"""List of Edb.Geometry.ArcData."""
return list(self.edb_api.GetArcData())
+ def get_points(self):
+ """Get all points in polygon.
+
+ Returns
+ -------
+ list[list[edb_value]]
+ """
+
+ return [[self._pedb.edb_value(i.X), self._pedb.edb_value(i.Y)] for i in list(self.edb_api.Points)]
+
+ def add_point(self, x, y, incremental=False):
+ """Add a point at the end of the point list of the polygon.
+
+ Parameters
+ ----------
+ x: str, int, float
+ X coordinate.
+ y: str, in, float
+ Y coordinate.
+ incremental: bool
+ Whether to add the point incrementally. The default value is ``False``. When
+ ``True``, the coordinates of the added point are incremental to the last point.
+
+
+ Returns
+ -------
+ bool
+ ``True`` when successful, ``False`` when failed.
+ """
+ if incremental:
+ x = self._pedb.edb_value(x)
+ y = self._pedb.edb_value(y)
+ last_point = self.get_points()[-1]
+ x = "({})+({})".format(x, last_point[0].ToString())
+ y = "({})+({})".format(y, last_point[1].ToString())
+ return self.edb_api.AddPoint(GeometryDotNet(self._pedb).point_data(x, y))
+
def get_bbox_of_boxes(self, points):
"""Get the EDB .NET API ``Edb.Geometry.GetBBoxOfBoxes`` database.
diff --git a/pyaedt/edb_core/edb_data/connectable.py b/pyaedt/edb_core/edb_data/connectable.py
index 3eda3bd5dbb..bc55363f7d7 100644
--- a/pyaedt/edb_core/edb_data/connectable.py
+++ b/pyaedt/edb_core/edb_data/connectable.py
@@ -1,7 +1,16 @@
from pyaedt import pyaedt_function_handler
+from pyaedt.edb_core.edb_data.obj_base import ObjBase
-class LayoutObj(object):
+class LayoutObjInstance:
+ """Manages EDB functionalities for the layout object instance."""
+
+ def __init__(self, pedb, edb_object):
+ self._pedb = pedb
+ self._edb_object = edb_object
+
+
+class LayoutObj(ObjBase):
"""Manages EDB functionalities for the layout object."""
def __getattr__(self, key): # pragma: no cover
@@ -14,8 +23,7 @@ def __getattr__(self, key): # pragma: no cover
raise AttributeError("Attribute not present")
def __init__(self, pedb, edb_object):
- self._pedb = pedb
- self._edb_object = edb_object
+ super().__init__(pedb, edb_object)
@property
def _edb(self):
@@ -28,9 +36,10 @@ def _edb(self):
return self._pedb.edb_api
@property
- def _layout(self):
- """Return Ansys.Ansoft.Edb.Cell.Layout object."""
- return self._pedb.active_layout
+ def _layout_obj_instance(self):
+ """Returns :class:`pyaedt.edb_core.edb_data.connectable.LayoutObjInstance`."""
+ obj = self._pedb.layout_instance.GetLayoutObjInstance(self._edb_object, None)
+ return LayoutObjInstance(self._pedb, obj)
@property
def _edb_properties(self):
@@ -42,9 +51,9 @@ def _edb_properties(self, value):
self._edb_object.SetProductSolverOption(self._edb.edb_api.ProductId.Designer, "HFSS", value)
@property
- def is_null(self):
- """Determine if this object is null."""
- return self._edb_object.IsNull()
+ def _obj_type(self):
+ """Returns LayoutObjType."""
+ return self._edb_object.GetObjType().ToString()
@property
def id(self):
@@ -81,6 +90,12 @@ def net(self):
return EDBNetsData(self._edb_object.GetNet(), self._pedb)
+ @net.setter
+ def net(self, value):
+ """Set net."""
+ net = self._pedb.nets[value]
+ self._edb_object.SetNet(net.net_object)
+
@property
def component(self):
"""Component connected to this object.
diff --git a/pyaedt/edb_core/edb_data/hfss_simulation_setup_data.py b/pyaedt/edb_core/edb_data/hfss_simulation_setup_data.py
index 7c9e0e395ef..76e9cc8e6da 100644
--- a/pyaedt/edb_core/edb_data/hfss_simulation_setup_data.py
+++ b/pyaedt/edb_core/edb_data/hfss_simulation_setup_data.py
@@ -1031,7 +1031,7 @@ def basic(self):
Returns
-------
- ``True`` if bais adaptive is used, ``False`` otherwise.
+ ``True`` if basic adaptive is used, ``False`` otherwise.
"""
return self.adaptive_settings.Basic
@@ -1042,7 +1042,7 @@ def basic(self, value):
@property
def do_adaptive(self):
- """Whether if adpative mesh is on.
+ """Whether if adaptive mesh is on.
Returns
-------
diff --git a/pyaedt/edb_core/edb_data/nets_data.py b/pyaedt/edb_core/edb_data/nets_data.py
index b16c572c4fd..0574e17b7f8 100644
--- a/pyaedt/edb_core/edb_data/nets_data.py
+++ b/pyaedt/edb_core/edb_data/nets_data.py
@@ -74,6 +74,22 @@ def components(self):
comps[comp.refdes] = comp
return comps
+ @pyaedt_function_handler
+ def find_dc_short(self, fix=False):
+ """Find DC-shorted nets.
+
+ Parameters
+ ----------
+ fix : bool, optional
+ If `True`, rename all the nets. (default)
+ If `False`, only report dc shorts.
+ Returns
+ -------
+ List[List[str, str]]
+ [[net name, net name]].
+ """
+ return self._app.layout_validation.dc_shorts(self.name, fix)
+
@pyaedt_function_handler()
def plot(
self,
diff --git a/pyaedt/edb_core/edb_data/obj_base.py b/pyaedt/edb_core/edb_data/obj_base.py
new file mode 100644
index 00000000000..4d43360e47a
--- /dev/null
+++ b/pyaedt/edb_core/edb_data/obj_base.py
@@ -0,0 +1,16 @@
+class ObjBase(object):
+ """Manages EDB functionalities for a base object."""
+
+ def __init__(self, pedb, model):
+ self._pedb = pedb
+ self._edb_object = model
+
+ @property
+ def is_null(self):
+ """Flag indicating if this object is null."""
+ return self._edb_object.IsNull()
+
+ @property
+ def type(self):
+ """Get type."""
+ return self._edb_object.GetType()
diff --git a/pyaedt/edb_core/edb_data/padstacks_data.py b/pyaedt/edb_core/edb_data/padstacks_data.py
index 2c381ab0e3d..a37d2b2987a 100644
--- a/pyaedt/edb_core/edb_data/padstacks_data.py
+++ b/pyaedt/edb_core/edb_data/padstacks_data.py
@@ -1869,12 +1869,6 @@ def get_connected_object_id_set(self):
layoutObjInst = self.object_instance
return [loi.GetLayoutObj().GetId() for loi in layoutInst.GetConnectedObjects(layoutObjInst).Items]
- @pyaedt_function_handler()
- def _get_connected_object_obj_set(self):
- layoutInst = self._edb_padstackinstance.GetLayout().GetLayoutInstance()
- layoutObjInst = self.object_instance
- return list([loi.GetLayoutObj() for loi in layoutInst.GetConnectedObjects(layoutObjInst).Items])
-
@pyaedt_function_handler()
def get_reference_pins(self, reference_net="GND", search_radius=5e-3, max_limit=0, component_only=True):
"""Search for reference pins using given criteria.
diff --git a/pyaedt/edb_core/edb_data/primitives_data.py b/pyaedt/edb_core/edb_data/primitives_data.py
index 514b0d279ae..4b29f1c4dbc 100644
--- a/pyaedt/edb_core/edb_data/primitives_data.py
+++ b/pyaedt/edb_core/edb_data/primitives_data.py
@@ -3,6 +3,7 @@
from pyaedt.edb_core.dotnet.primitive import BondwireDotNet
from pyaedt.edb_core.dotnet.primitive import CircleDotNet
from pyaedt.edb_core.dotnet.primitive import PathDotNet
+from pyaedt.edb_core.dotnet.primitive import PolygonDataDotNet
from pyaedt.edb_core.dotnet.primitive import PolygonDotNet
from pyaedt.edb_core.dotnet.primitive import RectangleDotNet
from pyaedt.edb_core.dotnet.primitive import TextDotNet
@@ -81,11 +82,7 @@ def type(self):
-------
str
"""
- types = ["Circle", "Path", "Polygon", "Rectangle", "Bondwire"]
- str_type = self.primitive_type.ToString().split(".")
- if str_type[-1] in types:
- return str_type[-1]
- return None
+ return self._edb_object.GetPrimitiveType().ToString()
@property
def net_name(self):
@@ -149,6 +146,15 @@ def is_void(self):
"""
return self._edb_object.IsVoid()
+ def get_connected_objects(self):
+ """Get connected objects.
+
+ Returns
+ -------
+ list
+ """
+ return self._pedb.get_connected_objects(self._layout_obj_instance)
+
class EDBPrimitives(EDBPrimitivesMain):
"""Manages EDB functionalities for a primitives.
@@ -349,12 +355,6 @@ def get_connected_object_id_set(self):
layoutObjInst = layoutInst.GetLayoutObjInstance(self.primitive_object, None) # 2nd arg was []
return [loi.GetLayoutObj().GetId() for loi in layoutInst.GetConnectedObjects(layoutObjInst).Items]
- @pyaedt_function_handler()
- def _get_connected_object_obj_set(self):
- layoutInst = self.primitive_object.GetLayout().GetLayoutInstance()
- layoutObjInst = layoutInst.GetLayoutObjInstance(self.primitive_object, None)
- return list([loi.GetLayoutObj() for loi in layoutInst.GetConnectedObjects(layoutObjInst).Items])
-
@pyaedt_function_handler()
def convert_to_polygon(self):
"""Convert path to polygon.
@@ -716,6 +716,27 @@ def length(self):
length += GeometryOperators.points_distance(center_line[pt_ind], center_line[pt_ind + 1])
return length
+ @pyaedt_function_handler
+ def add_point(self, x, y, incremental=False):
+ """Add a point at the end of the path.
+
+ Parameters
+ ----------
+ x: str, int, float
+ X coordinate.
+ y: str, in, float
+ Y coordinate.
+ incremental: bool
+ Add point incrementally. If True, coordinates of the added point is incremental to the last point.
+ The default value is ``False``.
+ Returns
+ -------
+ bool
+ """
+ center_line = PolygonDataDotNet(self._pedb, self._edb_object.GetCenterLine())
+ center_line.add_point(x, y, incremental)
+ return self._edb_object.SetCenterLine(center_line.edb_api)
+
@pyaedt_function_handler
def get_center_line(self, to_string=False):
"""Get the center line of the trace.
@@ -814,6 +835,113 @@ def create_edge_port(
else:
return self._app.hfss.create_edge_port_vertical(self.id, pos, name, 50, reference_layer)
+ pyaedt_function_handler()
+
+ def create_via_fence(self, distance, gap, padstack_name):
+ """Create via fences on both sides of the trace.
+
+ Parameters
+ ----------
+ distance: str, float
+ Distance between via fence and trace center line.
+ gap: str, float
+ Gap between vias.
+ padstack_name: str
+ Name of the via padstack.
+
+ Returns
+ -------
+
+ """
+
+ def getAngle(v1, v2): # pragma: no cover
+ v1_mag = math.sqrt(v1[0] ** 2 + v1[1] ** 2)
+ v2_mag = math.sqrt(v2[0] ** 2 + v2[1] ** 2)
+ dotsum = v1[0] * v2[0] + v1[1] * v2[1]
+ if v1[0] * v2[1] - v1[1] * v2[0] > 0:
+ scale = 1
+ else:
+ scale = -1
+ dtheta = scale * math.acos(dotsum / (v1_mag * v2_mag))
+
+ return dtheta
+
+ def getLocations(line, gap): # pragma: no cover
+ location = [line[0]]
+ residual = 0
+
+ for n in range(len(line) - 1):
+ x0, y0 = line[n]
+ x1, y1 = line[n + 1]
+ length = math.sqrt((x1 - x0) ** 2 + (y1 - y0) ** 2)
+ dx, dy = (x1 - x0) / length, (y1 - y0) / length
+ x = x0 - dx * residual
+ y = y0 - dy * residual
+ length = length + residual
+ while length >= gap:
+ x += gap * dx
+ y += gap * dy
+ location.append((x, y))
+ length -= gap
+
+ residual = length
+ return location
+
+ def getParalletLines(pts, distance): # pragma: no cover
+ leftline = []
+ rightline = []
+
+ x0, y0 = pts[0]
+ x1, y1 = pts[1]
+ vector = (x1 - x0, y1 - y0)
+ orientation1 = getAngle((1, 0), vector)
+
+ leftturn = orientation1 + math.pi / 2
+ righrturn = orientation1 - math.pi / 2
+ leftPt = (x0 + distance * math.cos(leftturn), y0 + distance * math.sin(leftturn))
+ leftline.append(leftPt)
+ rightPt = (x0 + distance * math.cos(righrturn), y0 + distance * math.sin(righrturn))
+ rightline.append(rightPt)
+
+ for n in range(1, len(pts) - 1):
+ x0, y0 = pts[n - 1]
+ x1, y1 = pts[n]
+ x2, y2 = pts[n + 1]
+
+ v1 = (x1 - x0, y1 - y0)
+ v2 = (x2 - x1, y2 - y1)
+ dtheta = getAngle(v1, v2)
+ orientation1 = getAngle((1, 0), v1)
+
+ leftturn = orientation1 + dtheta / 2 + math.pi / 2
+ righrturn = orientation1 + dtheta / 2 - math.pi / 2
+
+ distance2 = distance / math.sin((math.pi - dtheta) / 2)
+ leftPt = (x1 + distance2 * math.cos(leftturn), y1 + distance2 * math.sin(leftturn))
+ leftline.append(leftPt)
+ rightPt = (x1 + distance2 * math.cos(righrturn), y1 + distance2 * math.sin(righrturn))
+ rightline.append(rightPt)
+
+ x0, y0 = pts[-2]
+ x1, y1 = pts[-1]
+
+ vector = (x1 - x0, y1 - y0)
+ orientation1 = getAngle((1, 0), vector)
+ leftturn = orientation1 + math.pi / 2
+ righrturn = orientation1 - math.pi / 2
+ leftPt = (x1 + distance * math.cos(leftturn), y1 + distance * math.sin(leftturn))
+ leftline.append(leftPt)
+ rightPt = (x1 + distance * math.cos(righrturn), y1 + distance * math.sin(righrturn))
+ rightline.append(rightPt)
+ return leftline, rightline
+
+ distance = self._pedb.edb_value(distance).ToDouble()
+ gap = self._pedb.edb_value(gap).ToDouble()
+ center_line = self.get_center_line()
+ leftline, rightline = getParalletLines(center_line, distance)
+ for x, y in getLocations(rightline, gap) + getLocations(leftline, gap):
+ self._pedb.padstacks.place([x, y], padstack_name)
+
class EdbRectangle(EDBPrimitives, RectangleDotNet):
def __init__(self, raw_primitive, core_app):
diff --git a/pyaedt/edb_core/edb_data/terminals.py b/pyaedt/edb_core/edb_data/terminals.py
index 48e0f349476..30e551e241e 100644
--- a/pyaedt/edb_core/edb_data/terminals.py
+++ b/pyaedt/edb_core/edb_data/terminals.py
@@ -442,7 +442,12 @@ def create(self, padstack_instance, name=None, layer=None, is_ref=False):
layer_obj = self._pedb.stackup.signal_layers[layer]
terminal = self._edb.cell.terminal.PadstackInstanceTerminal.Create(
- self._layout, self.net.net_object, name, padstack_instance._edb_object, layer_obj._edb_layer, isRef=is_ref
+ self._pedb.active_layout,
+ self.net.net_object,
+ name,
+ padstack_instance._edb_object,
+ layer_obj._edb_layer,
+ isRef=is_ref,
)
terminal = PadstackInstanceTerminal(self._pedb, terminal)
diff --git a/pyaedt/edb_core/general.py b/pyaedt/edb_core/general.py
index 951c252d1f1..6be4a4b417d 100644
--- a/pyaedt/edb_core/general.py
+++ b/pyaedt/edb_core/general.py
@@ -172,3 +172,36 @@ class TerminalType(Enum):
PadstackInstanceTerminal = 3
BundleTerminal = 4
PinGroupTerminal = 5
+
+
+class Primitives(Enum):
+ Rectangle = 0
+ Circle = 1
+ Polygon = 2
+ Path = 3
+ Bondwire = 4
+ PrimitivePlugin = 5
+ Text = 6
+ Path3D = 7
+ BoardBendDef = 8
+ InValidType = 9
+
+
+class LayoutObjType(Enum):
+ InvalidLayoutObj = -1
+ Primitive = 0
+ PadstackInstance = 1
+ Terminal = 2
+ TerminalInstance = 3
+ CellInstance = 4
+ Layer = 5
+ Net = 6
+ Padstack = 7
+ Group = 8
+ NetClass = 9
+ Cell = 10
+ DifferentialPair = 11
+ PinGroup = 12
+ VoltageRegulator = 13
+ ExtendedNet = 14
+ LayoutObjTypeCount = 15
diff --git a/pyaedt/edb_core/layout_validation.py b/pyaedt/edb_core/layout_validation.py
new file mode 100644
index 00000000000..1f5446ab8d8
--- /dev/null
+++ b/pyaedt/edb_core/layout_validation.py
@@ -0,0 +1,250 @@
+import re
+
+from pyaedt.edb_core.edb_data.padstacks_data import EDBPadstackInstance
+from pyaedt.edb_core.edb_data.primitives_data import EDBPrimitives
+from pyaedt.generic.general_methods import generate_unique_name
+from pyaedt.generic.general_methods import pyaedt_function_handler
+
+
+class LayoutValidation:
+ """Manages all layout validation capabilities"""
+
+ def __init__(self, pedb):
+ self._pedb = pedb
+
+ @pyaedt_function_handler()
+ def dc_shorts(self, net_list=None, fix=False):
+ """Find DC shorts on layout.
+
+ Parameters
+ ----------
+ net_list : str or list[str], optional
+ List of nets.
+ fix : bool, optional
+ If `True`, rename all the nets. (default)
+ If `False`, only report dc shorts.
+
+ Returns
+ -------
+ List[List[str, str]]
+ [[net name, net name]].
+
+ Examples
+ --------
+
+ >>> edb = Edb("edb_file")
+ >>> dc_shorts = edb.layout_validation.dc_shorts()
+
+ """
+ if not net_list:
+ net_list = list(self._pedb.nets.nets.keys())
+ elif isinstance(net_list, str):
+ net_list = [net_list]
+ _objects_list = {}
+ _padstacks_list = {}
+ for prim in self._pedb.modeler.primitives:
+ n_name = prim.net_name
+ if n_name in _objects_list:
+ _objects_list[n_name].append(prim)
+ else:
+ _objects_list[n_name] = [prim]
+ for pad in list(self._pedb.padstacks.instances.values()):
+ n_name = pad.net_name
+ if n_name in _padstacks_list:
+ _padstacks_list[n_name].append(pad)
+ else:
+ _padstacks_list[n_name] = [pad]
+ dc_shorts = []
+ for net in net_list:
+ objs = []
+ for i in _objects_list.get(net, []):
+ objs.append(i)
+ for i in _padstacks_list.get(net, []):
+ objs.append(i)
+ if not len(objs):
+ self._pedb.nets[net].delete()
+ continue
+
+ connected_objs = objs[0].get_connected_objects()
+ connected_objs.append(objs[0])
+ net_dc_shorts = [obj for obj in connected_objs]
+ if net_dc_shorts:
+ dc_nets = list(set([obj.net.name for obj in net_dc_shorts if not obj.net.name == net]))
+ for dc in dc_nets:
+ if dc:
+ dc_shorts.append([net, dc])
+ if fix:
+ temp = []
+ for i in net_dc_shorts:
+ temp.append(i.net.name)
+ temp_key = set(temp)
+ temp_count = {temp.count(i): i for i in temp_key}
+ temp_count = dict(sorted(temp_count.items()))
+ while True:
+ temp_name = list(temp_count.values()).pop()
+ if not temp_name.lower().startswith("unnamed"):
+ break
+ elif temp_name.lower():
+ break
+ elif len(temp) == 0:
+ break
+ for i in net_dc_shorts:
+ if not i.net.name == temp_name:
+ i.net = temp_name
+ return dc_shorts
+
+ @pyaedt_function_handler()
+ def disjoint_nets(
+ self, net_list=None, keep_only_main_net=False, clean_disjoints_less_than=0.0, order_by_area=False
+ ):
+ """Find and fix disjoint nets from a given netlist.
+
+ Parameters
+ ----------
+ net_list : str, list, optional
+ List of nets on which check disjoints. If `None` is provided then the algorithm will loop on all nets.
+ keep_only_main_net : bool, optional
+ Remove all secondary nets other than principal one (the one with more objects in it). Default is `False`.
+ clean_disjoints_less_than : bool, optional
+ Clean all disjoint nets with area less than specified area in square meters. Default is `0.0` to disable it.
+ order_by_area : bool, optional
+ Whether if the naming order has to be by number of objects (fastest) or area (slowest but more accurate).
+ Default is ``False``.
+ Returns
+ -------
+ List
+ New nets created.
+
+ Examples
+ --------
+
+ >>> renamed_nets = edb.layout_validation.disjoint_nets(["GND","Net2"])
+ """
+ timer_start = self._pedb._logger.reset_timer()
+
+ if not net_list:
+ net_list = list(self._pedb.nets.keys())
+ elif isinstance(net_list, str):
+ net_list = [net_list]
+ _objects_list = {}
+ _padstacks_list = {}
+ for prim in self._pedb.modeler.primitives:
+ n_name = prim.net_name
+ if n_name in _objects_list:
+ _objects_list[n_name].append(prim)
+ else:
+ _objects_list[n_name] = [prim]
+ for pad in list(self._pedb.padstacks.instances.values()):
+ n_name = pad.net_name
+ if n_name in _padstacks_list:
+ _padstacks_list[n_name].append(pad)
+ else:
+ _padstacks_list[n_name] = [pad]
+ new_nets = []
+ disjoints_objects = []
+ self._pedb._logger.reset_timer()
+ for net in net_list:
+ net_groups = []
+ obj_dict = {}
+ for i in _objects_list.get(net, []):
+ obj_dict[i.id] = i
+ for i in _padstacks_list.get(net, []):
+ obj_dict[i.id] = i
+ objs = list(obj_dict.values())
+ l = len(objs)
+ while l > 0:
+ l1 = objs[0].get_connected_object_id_set()
+ l1.append(objs[0].id)
+ repetition = False
+ for net_list in net_groups:
+ if set(l1).intersection(net_list):
+ net_groups.append([i for i in l1 if i not in net_list])
+ repetition = True
+ if not repetition:
+ net_groups.append(l1)
+ objs = [i for i in objs if i.id not in l1]
+ l = len(objs)
+ if len(net_groups) > 1:
+
+ def area_calc(elem):
+ sum = 0
+ for el in elem:
+ try:
+ if isinstance(obj_dict[el], EDBPrimitives):
+ if not obj_dict[el].is_void:
+ sum += obj_dict[el].area()
+ except:
+ pass
+ return sum
+
+ if order_by_area:
+ areas = [area_calc(i) for i in net_groups]
+ sorted_list = [x for _, x in sorted(zip(areas, net_groups), reverse=True)]
+ else:
+ sorted_list = sorted(net_groups, key=len, reverse=True)
+ for disjoints in sorted_list[1:]:
+ if keep_only_main_net:
+ for geo in disjoints:
+ try:
+ obj_dict[geo].delete()
+ except KeyError:
+ pass
+ elif len(disjoints) == 1 and (
+ isinstance(obj_dict[disjoints[0]], EDBPadstackInstance)
+ or clean_disjoints_less_than
+ and obj_dict[disjoints[0]].area() < clean_disjoints_less_than
+ ):
+ try:
+ obj_dict[disjoints[0]].delete()
+ except KeyError:
+ pass
+ else:
+ new_net_name = generate_unique_name(net, n=6)
+ net_obj = self._pedb.nets.find_or_create_net(new_net_name)
+ if net_obj:
+ new_nets.append(net_obj.GetName())
+ for geo in disjoints:
+ try:
+ obj_dict[geo].net_name = net_obj
+ except KeyError:
+ pass
+ disjoints_objects.extend(disjoints)
+ self._pedb._logger.info("Found {} objects in {} new nets.".format(len(disjoints_objects), len(new_nets)))
+ self._pedb._logger.info_timer("Disjoint Cleanup Completed.", timer_start)
+
+ return new_nets
+
+ def illegal_net_names(self, fix=False):
+ """Find and fix illegal net names."""
+ pattern = r"[\(\)\\\/:;*?<>\'\"|`~$]"
+
+ nets = self._pedb.nets.nets
+
+ renamed_nets = []
+ for net, val in nets.items():
+ if re.findall(pattern, net):
+ renamed_nets.append(net)
+ if fix:
+ new_name = re.sub(pattern, "_", net)
+ val.name = new_name
+
+ self._pedb._logger.info("Found {} illegal net names.".format(len(renamed_nets)))
+ return
+
+ def illegal_rlc_values(self, fix=False):
+ """Find and fix rlc illegal values."""
+ inductors = self._pedb.components.inductors
+
+ temp = []
+ for k, v in inductors.items():
+ componentProperty = v.edbcomponent.GetComponentProperty()
+ model = componentProperty.GetModel().Clone()
+ pinpairs = model.PinPairs
+
+ if not len(list(pinpairs)): # pragma: no cover
+ temp.append(k)
+ if fix:
+ v.rlc_values = [0, 1, 0]
+
+ self._pedb._logger.info("Found {} inductors have no value.".format(len(temp)))
+ return
diff --git a/pyaedt/edb_core/materials.py b/pyaedt/edb_core/materials.py
index 9f1fdd4c4da..154ef5d5d4b 100644
--- a/pyaedt/edb_core/materials.py
+++ b/pyaedt/edb_core/materials.py
@@ -1,7 +1,9 @@
from __future__ import absolute_import # noreorder
import difflib
+import fnmatch
import logging
+import os
import warnings
from pyaedt import is_ironpython
@@ -297,40 +299,59 @@ def _json_format(self):
@pyaedt_function_handler()
def _load(self, input_dict):
+ default_material = {
+ "name": "default",
+ "conductivity": 0,
+ "loss_tangent": 0,
+ "magnetic_loss_tangent": 0,
+ "mass_density": 0,
+ "permittivity": 1,
+ "permeability": 1,
+ "poisson_ratio": 0,
+ "specific_heat": 0,
+ "thermal_conductivity": 0,
+ "youngs_modulus": 0,
+ "thermal_expansion_coefficient": 0,
+ "dielectric_model_frequency": None,
+ "dc_permittivity": None,
+ }
if input_dict:
- self.conductivity = input_dict["conductivity"]
- self.loss_tangent = input_dict["loss_tangent"]
- self.magnetic_loss_tangent = input_dict["magnetic_loss_tangent"]
- self.mass_density = input_dict["mass_density"]
- self.permittivity = input_dict["permittivity"]
- self.permeability = input_dict["permeability"]
- self.poisson_ratio = input_dict["poisson_ratio"]
- self.specific_heat = input_dict["specific_heat"]
- self.thermal_conductivity = input_dict["thermal_conductivity"]
- self.youngs_modulus = input_dict["youngs_modulus"]
- self.thermal_expansion_coefficient = input_dict["thermal_expansion_coefficient"]
- if input_dict["dielectric_model_frequency"] is not None:
+ for k, v in input_dict.items():
+ default_material[k] = v
+
+ self.conductivity = default_material["conductivity"]
+ self.loss_tangent = default_material["loss_tangent"]
+ self.magnetic_loss_tangent = default_material["magnetic_loss_tangent"]
+ self.mass_density = default_material["mass_density"]
+ self.permittivity = default_material["permittivity"]
+ self.permeability = default_material["permeability"]
+ self.poisson_ratio = default_material["poisson_ratio"]
+ self.specific_heat = default_material["specific_heat"]
+ self.thermal_conductivity = default_material["thermal_conductivity"]
+ self.youngs_modulus = default_material["youngs_modulus"]
+ self.thermal_expansion_coefficient = default_material["thermal_expansion_coefficient"]
+ if default_material["dielectric_model_frequency"] is not None: # pragma: no cover
if self._edb_material_def.GetDielectricMaterialModel():
model = self._edb_material_def.GetDielectricMaterialModel()
- self.dielectric_model_frequency = input_dict["dielectric_model_frequency"]
- self.loss_tangent_at_frequency = input_dict["loss_tangent_at_frequency"]
- self.permittivity_at_frequency = input_dict["permittivity_at_frequency"]
- if input_dict["dc_permittivity"] is not None:
+ self.dielectric_model_frequency = default_material["dielectric_model_frequency"]
+ self.loss_tangent_at_frequency = default_material["loss_tangent_at_frequency"]
+ self.permittivity_at_frequency = default_material["permittivity_at_frequency"]
+ if default_material["dc_permittivity"] is not None:
model.SetUseDCRelativePermitivity(True)
- self.dc_permittivity = input_dict["dc_permittivity"]
- self.dc_conductivity = input_dict["dc_conductivity"]
+ self.dc_permittivity = default_material["dc_permittivity"]
+ self.dc_conductivity = default_material["dc_conductivity"]
else:
if not self._pclass.add_djordjevicsarkar_material(
- input_dict["name"],
- input_dict["permittivity_at_frequency"],
- input_dict["loss_tangent_at_frequency"],
- input_dict["dielectric_model_frequency"],
- input_dict["dc_permittivity"],
- input_dict["dc_conductivity"],
+ default_material["name"],
+ default_material["permittivity_at_frequency"],
+ default_material["loss_tangent_at_frequency"],
+ default_material["dielectric_model_frequency"],
+ default_material["dc_permittivity"],
+ default_material["dc_conductivity"],
):
self._pclass._pedb.logger.warning(
'Cannot set DS model for material "{}". Check for realistic '
- "values that define DS Model".format(input_dict["name"])
+ "values that define DS Model".format(default_material["name"])
)
else:
# To unset DS model if already assigned to the material in database
@@ -346,12 +367,38 @@ def __getitem__(self, item):
def __init__(self, pedb):
self._pedb = pedb
+ self._syslib = os.path.join(self._pedb.base_path, "syslib")
+ self._personal_lib = None
+ self._materials_in_aedt = None
if not self.materials:
self.add_material("air")
self.add_material("copper", 1, 0.999991, 5.8e7, 0, 0)
self.add_material("fr4_epoxy", 4.4, 1, 0, 0.02, 0)
self.add_material("solder_mask", 3.1, 1, 0, 0.035, 0)
+ @property
+ def materials_in_aedt(self):
+ """Retrieve the dictionary of materials available in AEDT syslib."""
+ if self._materials_in_aedt:
+ return self._materials_in_aedt
+ self._materials_in_aedt = self._read_materials()
+ return self._materials_in_aedt
+
+ @property
+ def syslib(self):
+ """Retrieve the project sys library."""
+ return self._syslib
+
+ @property
+ def personallib(self):
+ """Get or Set the user personallib."""
+ return self._personal_lib
+
+ @personallib.setter
+ def personallib(self, value):
+ self._personal_lib = value
+ self._materials_in_aedt = self._read_materials()
+
@pyaedt_function_handler()
def _edb_value(self, value):
return self._pedb.edb_value(value)
@@ -788,3 +835,137 @@ def get_property_by_material_name(self, property_name, material_name):
else:
return property_box.ToDouble()
return False
+
+ @pyaedt_function_handler()
+ def add_material_from_aedt(self, material_name):
+ """Add a material read from ``syslib amat`` library.
+
+ Parameters
+ ----------
+ material_name : str
+ Material name.
+
+ Returns
+ -------
+ bool
+ "True`` when successful, ``False`` when failed.
+ """
+ if material_name in self.materials_in_aedt:
+ if material_name in list(self.materials.keys()):
+ self._pedb.logger.warning("Material {} already exists. Skipping it.".format(material_name))
+ return False
+ new_material = self.add_material(name=material_name)
+ material = self.materials_in_aedt[material_name]
+ try:
+ new_material.permittivity = float(material["permittivity"])
+ except (KeyError, TypeError):
+ pass
+ try:
+ new_material.conductivity = float(material["conductivity"])
+ except (KeyError, TypeError):
+ pass
+ try:
+ new_material.mass_density = float(material["mass_density"])
+ except (KeyError, TypeError):
+ pass
+ try:
+ new_material.permeability = float(material["permeability"])
+ except (KeyError, TypeError):
+ pass
+ try:
+ new_material.loss_tangent = float(material["dielectric_loss_tangent"])
+ except (KeyError, TypeError):
+ pass
+ try:
+ new_material.specific_heat = float(material["specific_heat"])
+ except (KeyError, TypeError):
+ pass
+ try:
+ new_material.thermal_expansion_coefficient = float(material["thermal_expansion_coeffcient"])
+ except (KeyError, TypeError):
+ pass
+ return True
+
+ @pyaedt_function_handler()
+ def load_amat(self, amat_file):
+ """Load material from an amat file and add materials to Edb.
+
+ Parameters
+ ----------
+ amat_file : str
+ Full path to the amat file to read and add to the Edb.
+ """
+ material_dict = self._read_materials(amat_file)
+ for material_name, material in material_dict.items():
+ if not material_name in list(self.materials.keys()):
+ new_material = self.add_material(name=material_name)
+ try:
+ new_material.permittivity = float(material["permittivity"])
+ except (KeyError, TypeError):
+ pass
+ try:
+ new_material.conductivity = float(material["conductivity"])
+ except (KeyError, TypeError):
+ pass
+ try:
+ new_material.mass_density = float(material["mass_density"])
+ except (KeyError, TypeError):
+ pass
+ try:
+ new_material.permeability = float(material["permeability"])
+ except (KeyError, TypeError):
+ pass
+ try:
+ new_material.loss_tangent = float(material["dielectric_loss_tangent"])
+ except (KeyError, TypeError):
+ pass
+ try:
+ new_material.specific_heat = float(material["specific_heat"])
+ except (KeyError, TypeError):
+ pass
+ try:
+ new_material.thermal_expansion_coefficient = float(material["thermal_expansion_coeffcient"])
+ except (KeyError, TypeError):
+ pass
+ return True
+
+ @pyaedt_function_handler()
+ def _read_materials(self, mat_file=None):
+ def get_mat_list(file_name, mats):
+ from pyaedt.generic.LoadAEDTFile import load_entire_aedt_file
+
+ mread = load_entire_aedt_file(file_name)
+ for mat, mdict in mread.items():
+ if mat != "$base_index$":
+ try:
+ mats[mat] = mdict["MaterialDef"][mat]
+ except KeyError:
+ mats[mat] = mdict
+
+ if mat_file and os.path.exists(mat_file):
+ materials = {}
+ get_mat_list(mat_file, materials)
+ return materials
+
+ amat_sys = [
+ os.path.join(dirpath, filename)
+ for dirpath, _, filenames in os.walk(self.syslib)
+ for filename in filenames
+ if fnmatch.fnmatch(filename, "*.amat")
+ ]
+ amat_personal = []
+ if self.personallib:
+ amat_personal = [
+ os.path.join(dirpath, filename)
+ for dirpath, _, filenames in os.walk(self.personallib)
+ for filename in filenames
+ if fnmatch.fnmatch(filename, "*.amat")
+ ]
+ materials = {}
+ for amat in amat_sys:
+ get_mat_list(amat, materials)
+
+ if amat_personal:
+ for amat in amat_personal:
+ get_mat_list(amat, materials)
+ return materials
diff --git a/pyaedt/edb_core/nets.py b/pyaedt/edb_core/nets.py
index 30ae3591510..35747b59830 100644
--- a/pyaedt/edb_core/nets.py
+++ b/pyaedt/edb_core/nets.py
@@ -6,8 +6,6 @@
import warnings
from pyaedt.edb_core.edb_data.nets_data import EDBNetsData
-from pyaedt.edb_core.edb_data.padstacks_data import EDBPadstackInstance
-from pyaedt.edb_core.edb_data.primitives_data import EDBPrimitives
from pyaedt.edb_core.general import convert_py_list_to_net_list
from pyaedt.generic.constants import CSS4_COLORS
from pyaedt.generic.general_methods import generate_unique_name
@@ -46,7 +44,6 @@ def __getitem__(self, name):
def __init__(self, p_edb):
self._pedb = p_edb
- self._nets = {}
self._nets_by_comp_dict = {}
self._comps_by_nets_dict = {}
@@ -89,10 +86,10 @@ def nets(self):
dict[str, :class:`pyaedt.edb_core.edb_data.nets_data.EDBNetsData`]
Dictionary of nets.
"""
-
+ temp = {}
for net in self._layout.nets:
- self._nets[net.name] = EDBNetsData(net.api_object, self._pedb)
- return self._nets
+ temp[net.name] = EDBNetsData(net.api_object, self._pedb)
+ return temp
@property
def netlist(self):
@@ -1149,6 +1146,9 @@ def find_and_fix_disjoint_nets(
):
"""Find and fix disjoint nets from a given netlist.
+ .. deprecated::
+ Use new property :func:`edb.layout_validation.disjoint_nets` instead.
+
Parameters
----------
net_list : str, list, optional
@@ -1170,158 +1170,10 @@ def find_and_fix_disjoint_nets(
>>> renamed_nets = edb_core.nets.find_and_fix_disjoint_nets(["GND","Net2"])
"""
- timer_start = self._logger.reset_timer()
-
- if not net_list:
- net_list = list(self.nets.keys())
- elif isinstance(net_list, str):
- net_list = [net_list]
- _objects_list = {}
- _padstacks_list = {}
- for prim in self._pedb.modeler.primitives:
- n_name = prim.net_name
- if n_name in _objects_list:
- _objects_list[n_name].append(prim)
- else:
- _objects_list[n_name] = [prim]
- for pad in list(self._pedb.padstacks.instances.values()):
- n_name = pad.net_name
- if n_name in _padstacks_list:
- _padstacks_list[n_name].append(pad)
- else:
- _padstacks_list[n_name] = [pad]
- new_nets = []
- disjoints_objects = []
- self._logger.reset_timer()
- for net in net_list:
- net_groups = []
- obj_dict = {}
- for i in _objects_list.get(net, []):
- obj_dict[i.id] = i
- for i in _padstacks_list.get(net, []):
- obj_dict[i.id] = i
- objs = list(obj_dict.values())
- l = len(objs)
- while l > 0:
- l1 = objs[0].get_connected_object_id_set()
- l1.append(objs[0].id)
- repetition = False
- for net_list in net_groups:
- if set(l1).intersection(net_list):
- net_groups.append([i for i in l1 if i not in net_list])
- repetition = True
- if not repetition:
- net_groups.append(l1)
- objs = [i for i in objs if i.id not in l1]
- l = len(objs)
- if len(net_groups) > 1:
-
- def area_calc(elem):
- sum = 0
- for el in elem:
- try:
- if isinstance(obj_dict[el], EDBPrimitives):
- if not obj_dict[el].is_void:
- sum += obj_dict[el].area()
- except:
- pass
- return sum
-
- if order_by_area:
- areas = [area_calc(i) for i in net_groups]
- sorted_list = [x for _, x in sorted(zip(areas, net_groups), reverse=True)]
- else:
- sorted_list = sorted(net_groups, key=len, reverse=True)
- for disjoints in sorted_list[1:]:
- if keep_only_main_net:
- for geo in disjoints:
- try:
- obj_dict[geo].delete()
- except KeyError:
- pass
- elif len(disjoints) == 1 and (
- isinstance(obj_dict[disjoints[0]], EDBPadstackInstance)
- or clean_disjoints_less_than
- and obj_dict[disjoints[0]].area() < clean_disjoints_less_than
- ):
- try:
- obj_dict[disjoints[0]].delete()
- except KeyError:
- pass
- else:
- new_net_name = generate_unique_name(net, n=6)
- net_obj = self.find_or_create_net(new_net_name)
- if net_obj:
- new_nets.append(net_obj.GetName())
- for geo in disjoints:
- try:
- obj_dict[geo].net_name = net_obj
- except KeyError:
- pass
- disjoints_objects.extend(disjoints)
- self._logger.info("Found {} objects in {} new nets.".format(len(disjoints_objects), len(new_nets)))
- self._logger.info_timer("Disjoint Cleanup Completed.", timer_start)
-
- return new_nets
-
- @pyaedt_function_handler()
- def find_dc_shorts(self, net_list=None):
- """Find DC shorts on layout.
-
- Parameters
- ----------
- net_list : str or list[str]
- Optional
-
- Returns
- -------
- List[List[str, str]]
- [[net name, net name]].
-
- Examples
- --------
-
- >>> edb = Edb("edb_file")
- >>> dc_shorts = edb.nets.find_dc_shorts()
-
- """
- if not net_list:
- net_list = list(self.nets.keys())
- elif isinstance(net_list, str):
- net_list = [net_list]
- _objects_list = {}
- _padstacks_list = {}
- for prim in self._pedb.modeler.primitives:
- n_name = prim.net_name
- if n_name in _objects_list:
- _objects_list[n_name].append(prim)
- else:
- _objects_list[n_name] = [prim]
- for pad in list(self._pedb.padstacks.instances.values()):
- n_name = pad.net_name
- if n_name in _padstacks_list:
- _padstacks_list[n_name].append(pad)
- else:
- _padstacks_list[n_name] = [pad]
- dc_shorts = []
- for net in net_list:
- objs = []
- for i in _objects_list.get(net, []):
- objs.append(i)
- for i in _padstacks_list.get(net, []):
- objs.append(i)
- try:
- connected_objs = objs[0]._get_connected_object_obj_set()
- connected_objs.append(objs[0].api_object)
- net_dc_shorts = [obj for obj in connected_objs if not obj.GetNet().GetName() == net]
- if net_dc_shorts:
- dc_nets = list(set([obj.GetNet().GetName() for obj in net_dc_shorts]))
- for dc in dc_nets:
- if dc:
- dc_shorts.append([net, dc])
- except:
- pass
- return dc_shorts
+ warnings.warn("Use new function :func:`edb.layout_validation.disjoint_nets` instead.", DeprecationWarning)
+ return self._pedb.layout_validation.disjoint_nets(
+ net_list, keep_only_main_net, clean_disjoints_less_than, order_by_area
+ )
@pyaedt_function_handler()
def merge_nets_polygons(self, net_list):
diff --git a/pyaedt/edb_core/stackup.py b/pyaedt/edb_core/stackup.py
index 898f5be9432..09043aab508 100644
--- a/pyaedt/edb_core/stackup.py
+++ b/pyaedt/edb_core/stackup.py
@@ -666,7 +666,10 @@ def add_layer(
fillMaterial = "fr4_epoxy"
if material.lower() not in materials_lower:
- logger.error(material + " does not exist in material library")
+ if material in self._pedb.materials.materials_in_aedt:
+ self._pedb.materials.add_material_from_aedt("material")
+ else:
+ logger.error(material + " does not exist in material library")
else:
material = materials_lower[material.lower()]
@@ -1636,54 +1639,87 @@ def residual_copper_area_per_layer(self):
temp_data = {name: area / outline_area * 100 for name, area in temp_data.items()}
return temp_data
+ @pyaedt_function_handler
+ def _import_dict(self, json_dict):
+ """Import stackup from a dictionary."""
+ for k, v in json_dict.items():
+ if k == "materials":
+ for material in v.values():
+ self._pedb.materials._load_materials(material)
+ if k == "layers":
+ if len(list(v.values())) == len(list(self.stackup_layers.values())):
+ imported_layers_list = [l_dict["name"] for l_dict in list(v.values())]
+ layout_layer_list = list(self.stackup_layers.keys())
+ for layer_name in imported_layers_list:
+ layer_index = imported_layers_list.index(layer_name)
+ if layout_layer_list[layer_index] != layer_name:
+ self.stackup_layers[layout_layer_list[layer_index]].name = layer_name
+ prev_layer = None
+ for layer_name, layer in v.items():
+ if layer["name"] not in self.stackup_layers:
+ default_layer = {
+ "name": "default",
+ "type": "signal",
+ "material": "copper",
+ "dielectric_fill": "fr4_epoxy",
+ "thickness": 3.5000000000000004e-05,
+ "etch_factor": 0.0,
+ "roughness_enabled": False,
+ "top_hallhuray_nodule_radius": 0.0,
+ "top_hallhuray_surface_ratio": 0.0,
+ "bottom_hallhuray_nodule_radius": 0.0,
+ "bottom_hallhuray_surface_ratio": 0.0,
+ "side_hallhuray_nodule_radius": 0.0,
+ "side_hallhuray_surface_ratio": 0.0,
+ "upper_elevation": 0.0,
+ "lower_elevation": 0.0,
+ "color": [242, 140, 102],
+ }
+
+ if not layer["type"] == "signal":
+ default_layer["color"] = [27, 110, 76]
+
+ for k, v in layer.items():
+ default_layer[k] = v
+
+ layer = default_layer
+
+ if not prev_layer:
+ self.add_layer(
+ layer_name,
+ method="add_on_top",
+ layer_type=layer["type"],
+ material=layer["material"],
+ fillMaterial=layer["dielectric_fill"],
+ thickness=layer["thickness"],
+ )
+ prev_layer = layer_name
+ else:
+ self.add_layer(
+ layer_name,
+ base_layer=layer_name,
+ method="insert_below",
+ layer_type=layer["type"],
+ material=layer["material"],
+ fillMaterial=layer["dielectric_fill"],
+ thickness=layer["thickness"],
+ )
+ prev_layer = layer_name
+ if layer_name in self.stackup_layers:
+ self.stackup_layers[layer["name"]]._load_layer(layer)
+ return True
+
@pyaedt_function_handler
def _import_json(self, file_path):
+ """Import stackup from a json file."""
if file_path:
f = open(file_path)
json_dict = json.load(f) # pragma: no cover
- for k, v in json_dict.items():
- if k == "materials":
- for material in v.values():
- self._pedb.materials._load_materials(material)
- if k == "layers":
- if len(list(v.values())) == len(list(self.stackup_layers.values())):
- imported_layers_list = [l_dict["name"] for l_dict in list(v.values())]
- layout_layer_list = list(self.stackup_layers.keys())
- for layer_name in imported_layers_list:
- layer_index = imported_layers_list.index(layer_name)
- if layout_layer_list[layer_index] != layer_name:
- self.stackup_layers[layout_layer_list[layer_index]].name = layer_name
- prev_layer = None
- for layer_name, layer in v.items():
- if layer["name"] not in self.stackup_layers:
- if not prev_layer:
- self.add_layer(
- layer_name,
- method="add_on_top",
- layer_type=layer["type"],
- material=layer["material"],
- fillMaterial=layer["dielectric_fill"],
- thickness=layer["thickness"],
- )
- prev_layer = layer_name
- else:
- self.add_layer(
- layer_name,
- base_layer=layer_name,
- method="insert_below",
- layer_type=layer["type"],
- material=layer["material"],
- fillMaterial=layer["dielectric_fill"],
- thickness=layer["thickness"],
- )
- prev_layer = layer_name
- if layer_name in self.stackup_layers:
- self.stackup_layers[layer["name"]]._load_layer(layer)
- return True
+ return self._import_dict(json_dict)
@pyaedt_function_handler
def _import_csv(self, file_path):
- """Import stackup defnition from a CSV file.
+ """Import stackup definition from a CSV file.
Parameters
----------
@@ -2092,7 +2128,9 @@ def load(self, file_path):
>>> edb.stackup.load("stackup.xml")
"""
- if file_path.endswith(".csv"):
+ if isinstance(file_path, dict):
+ return self._import_dict(file_path)
+ elif file_path.endswith(".csv"):
return self._import_csv(file_path)
elif file_path.endswith(".json"):
return self._import_json(file_path)
diff --git a/pyaedt/emit_core/results/revision.py b/pyaedt/emit_core/results/revision.py
index 6519a34c0da..f4fa48f7ddd 100644
--- a/pyaedt/emit_core/results/revision.py
+++ b/pyaedt/emit_core/results/revision.py
@@ -79,7 +79,7 @@ def _load_revision(self):
Load this revision.
Examples
- ----------
+ --------
>>> aedtapp.results.revision.load_revision()
"""
if self.revision_loaded:
@@ -105,7 +105,7 @@ def result_mode_error():
@pyaedt_function_handler()
def get_interaction(self, domain):
"""
- Creates a new interaction for a domain.
+ Create a new interaction for a domain.
Parameters
----------
@@ -118,7 +118,7 @@ def get_interaction(self, domain):
Interaction object.
Examples
- ----------
+ --------
>>> domain = aedtapp.results.interaction_domain()
>>> rev.get_interaction(domain)
@@ -146,7 +146,7 @@ def run(self, domain):
Interaction object.
Examples
- ----------
+ --------
>>> domain = aedtapp.results.interaction_domain()
>>> rev.run(domain)
@@ -180,7 +180,7 @@ def is_domain_valid(self, domain):
``InteractionDomain`` object for constraining the analysis parameters.
Examples
- ----------
+ --------
>>> domain = aedtapp.interaction_domain()
>>> aedtapp.results.current_revision.is_domain_valid(domain)
True
@@ -200,12 +200,12 @@ def get_instance_count(self, domain):
``InteractionDomain`` object for constraining the analysis parameters.
Returns
- --------
+ -------
count : int
Number of instances in the domain for the current revision.
Examples
- ----------
+ --------
>>> domain = aedtapp.interaction_domain()
>>> num_instances = aedtapp.results.current_revision.get_instance_count(domain)
"""
@@ -228,7 +228,7 @@ def get_receiver_names(self):
List of receiver names.
Examples
- ----------
+ --------
>>> rxs = aedtapp.results.current_revision.get_reciver_names()
"""
if self.revision_loaded:
@@ -262,7 +262,7 @@ def get_interferer_names(self, interferer_type=None):
List of interfering systems' names.
Examples
- ----------
+ --------
>>> transmitters = aedtapp.results.current_revision.get_interferer_names(InterfererType.TRANSMITTERS)
>>> emitters = aedtapp.results.current_revision.get_interferer_names(InterfererType.EMITTERS)
>>> both = aedtapp.results.current_revision.get_interferer_names(InterfererType.TRANSMITTERS_AND_EMITTERS)
@@ -301,7 +301,7 @@ def get_band_names(self, radio_name, tx_rx_mode=None):
List of ``tx`` or ``rx`` band/waveform names.
Examples
- ----------
+ --------
>>> bands = aedtapp.results.current_revision.get_band_names('Bluetooth', TxRxMode.RX)
>>> waveforms = aedtapp.results.current_revision.get_band_names('USB_3.x', TxRxMode.TX)
"""
@@ -340,7 +340,7 @@ def get_active_frequencies(self, radio_name, band_name, tx_rx_mode, units=""):
List of ``tx`` or ``rx`` radio/emitter frequencies.
Examples
- ----------
+ --------
>>> freqs = aedtapp.results.current_revision.get_active_frequencies(
'Bluetooth', 'Rx - Base Data Rate', TxRxMode.RX)
"""
@@ -361,7 +361,7 @@ def notes(self):
Add notes to the revision.
Examples
- ----------
+ --------
>>> aedtapp.results.current_revision.notes = "Added a filter to the WiFi Radio."
>>> aedtapp.results.current_revision.notes
'Added a filter to the WiFi Radio.'
@@ -383,7 +383,7 @@ def n_to_1_limit(self):
- A value of ``-1`` allows unlimited N to 1. (N is set to the maximum.)
Examples
- ----------
+ --------
>>> aedtapp.results.current_revision.n_to_1_limit = 2**20
>>> aedtapp.results.current_revision.n_to_1_limit
1048576
@@ -477,12 +477,30 @@ def interference_type_classification(self, domain, use_filter=False, filter_list
domain.set_receiver(rx_radio, rx_band)
domain.set_interferer(tx_radio, tx_band)
interaction = self.run(domain)
+ # check for valid interaction, this would catch any disabled radio pairs
+ if not interaction.is_valid():
+ continue
+
domain.set_receiver(rx_radio, rx_band, rx_freq)
tx_freqs = self.get_active_frequencies(tx_radio, tx_band, modeTx)
for tx_freq in tx_freqs:
domain.set_interferer(tx_radio, tx_band, tx_freq)
instance = interaction.get_instance(domain)
- tx_prob = instance.get_largest_problem_type(ResultType.EMI).replace(" ", "").split(":")[1]
+ if not instance.has_valid_values():
+ # check for saturation somewhere in the chain
+ # set power so its flagged as strong interference
+ if instance.get_result_warning() == "An amplifier was saturated.":
+ max_power = 200
+ else:
+ # other warnings (e.g. no path from Tx to Rx,
+ # no power received, error in configuration, etc)
+ # should just be skipped
+ continue
+ else:
+ tx_prob = (
+ instance.get_largest_problem_type(ResultType.EMI).replace(" ", "").split(":")[1]
+ )
+ power = instance.get_value(ResultType.EMI)
if (
rx_start_freq - rx_channel_bandwidth / 2
<= tx_freq
@@ -500,14 +518,10 @@ def interference_type_classification(self, domain, use_filter=False, filter_list
in_filters = True
# Save the worst case interference values
- if (
- instance.has_valid_values()
- and instance.get_value(ResultType.EMI) > max_power
- and in_filters
- ):
- prob = instance.get_largest_problem_type(ResultType.EMI)
- max_power = instance.get_value(ResultType.EMI)
+ if power > max_power and in_filters:
+ max_power = power
largest_rx_prob = rx_prob
+ prob = instance.get_largest_problem_type(ResultType.EMI)
largest_tx_prob = prob.replace(" ", "").split(":")
if max_power > -200:
@@ -622,6 +636,9 @@ def protection_level_classification(
domain.set_receiver(rx_radio, rx_band)
domain.set_interferer(tx_radio, tx_band)
interaction = self.run(domain)
+ # check for valid interaction, this would catch any disabled radio pairs
+ if not interaction.is_valid():
+ continue
domain.set_receiver(rx_radio, rx_band, rx_freq)
tx_freqs = self.get_active_frequencies(tx_radio, tx_band, modeTx)
@@ -630,7 +647,18 @@ def protection_level_classification(
for tx_freq in tx_freqs:
domain.set_interferer(tx_radio, tx_band, tx_freq)
instance = interaction.get_instance(domain)
- power = instance.get_value(mode_power)
+ if not instance.has_valid_values():
+ # check for saturation somewhere in the chain
+ # set power so its flagged as "damage threshold"
+ if instance.get_result_warning() == "An amplifier was saturated.":
+ max_power = 200
+ else:
+ # other warnings (e.g. no path from Tx to Rx,
+ # no power received, error in configuration, etc)
+ # should just be skipped
+ continue
+ else:
+ power = instance.get_value(mode_power)
if power > damage_threshold:
classification = "damage"
@@ -648,8 +676,8 @@ def protection_level_classification(
else:
filtering = True
- if instance.get_value(mode_power) > max_power and filtering:
- max_power = instance.get_value(mode_power)
+ if power > max_power and filtering:
+ max_power = power
# If the worst case for the band-pair is below the power thresholds, then
# there are no interference issues and no offset is required.
diff --git a/pyaedt/generic/general_methods.py b/pyaedt/generic/general_methods.py
index fe0bc64b8be..5d325359349 100644
--- a/pyaedt/generic/general_methods.py
+++ b/pyaedt/generic/general_methods.py
@@ -1084,7 +1084,7 @@ def _create_json_file(json_dict, full_json_path):
# ----------
# version : str, optional
# Version to check. The default is ``None``, in which case all versions are checked.
-# When specififying a version, you can use a three-digit format like ``"222"`` or a
+# When specifying a version, you can use a three-digit format like ``"222"`` or a
# five-digit format like ``"2022.2"``.
# student_version : bool, optional
# Whether to check for student version sessions. The default is ``False``.
@@ -1275,7 +1275,7 @@ def grpc_active_sessions(version=None, student_version=False, non_graphical=Fals
----------
version : str, optional
Version to check. The default is ``None``, in which case all versions are checked.
- When specififying a version, you can use a three-digit format like ``"222"`` or a
+ When specifying a version, you can use a three-digit format like ``"222"`` or a
five-digit format like ``"2022.2"``.
student_version : bool, optional
Whether to check for student version sessions. The default is ``False``.
diff --git a/pyaedt/generic/plot.py b/pyaedt/generic/plot.py
index 180e39a5772..0d68f27f88e 100644
--- a/pyaedt/generic/plot.py
+++ b/pyaedt/generic/plot.py
@@ -1516,6 +1516,7 @@ def plot(self, export_image_path=None):
bool
"""
self.pv = pv.Plotter(notebook=self.is_notebook, off_screen=self.off_screen, window_size=self.windows_size)
+ self.pv.enable_ssao()
self.meshes = None
if self.background_image:
self.pv.add_background_image(self.background_image)
@@ -1582,8 +1583,8 @@ def plot(self, export_image_path=None):
if self.show_axes:
self.pv.show_axes()
- if not self.is_notebook:
- self.pv.show_grid(color=tuple(axes_color), grid=self.show_grid)
+ if not self.is_notebook and self.show_grid:
+ self.pv.show_grid(color=tuple(axes_color), grid=self.show_grid, fmt="%.2e")
if self.bounding_box:
self.pv.add_bounding_box(color=tuple(axes_color))
self.pv.set_focus(self.pv.mesh.center)
diff --git a/pyaedt/generic/settings.py b/pyaedt/generic/settings.py
index f970d20601d..4cf34adcb04 100644
--- a/pyaedt/generic/settings.py
+++ b/pyaedt/generic/settings.py
@@ -65,6 +65,22 @@ def __init__(self):
self._desktop_launch_timeout = 90
self._number_of_grpc_api_retries = 6
self._retry_n_times_time_interval = 0.1
+ self._wait_for_license = False
+
+ @property
+ def wait_for_license(self):
+ """Whether if Electronics Desktop has to be launched with ``-waitforlicense`` flag enabled or not.
+ Default is ``False``.
+
+ Returns
+ -------
+ bool
+ """
+ return self._wait_for_license
+
+ @wait_for_license.setter
+ def wait_for_license(self, value):
+ self._wait_for_license = value
@property
def retry_n_times_time_interval(self):
diff --git a/pyaedt/hfss.py b/pyaedt/hfss.py
index f3c7ceeffc7..4eca4251975 100644
--- a/pyaedt/hfss.py
+++ b/pyaedt/hfss.py
@@ -321,7 +321,7 @@ def _create_boundary(self, name, props, boundary_type):
"""Create a boundary.
Parameters
- ---------
+ ----------
name : str
Name of the boundary.
props : list
@@ -6467,3 +6467,85 @@ def set_radiated_power_calc_method(self, method="Auto"):
"""
self.oradfield.EditRadiatedPowerCalculationMethod(method)
return True
+
+ @pyaedt_function_handler()
+ def set_mesh_fusion_settings(self, component=None, volume_padding=None, priority=None):
+ # type: (list|str, list, list) -> bool
+
+ """Set mesh fusion settings in Hfss.
+
+ component : list, optional
+ List of active 3D Components.
+ The default is ``None``, in which case components are disabled.
+ volume_padding : list, optional
+ List of mesh envelope padding, the format is ``[+x, -x, +y, -y, +z, -z]``.
+ The default is ``None``, in which case all zeros are applied.
+ priority : list, optional
+ List of components with the priority flag enabled. The default is ``None``.
+
+ Returns
+ -------
+ bool
+ ``True`` when successful, ``False`` when failed.
+
+ References
+ ----------
+
+ >>> oDesign.SetDoMeshAssembly
+
+ Examples
+ --------
+
+ >>> import pyaedt
+ >>> app = pyaedt.Hfss()
+ >>> app.set_mesh_fusion_settings(component=["Comp1", "Comp2"],
+ ... volume_padding=[[0,0,0,0,0,0], [0,0,5,0,0,0]],
+ ... priority=["Comp1"])
+ """
+ arg = ["NAME:AllSettings"]
+ arg2 = ["NAME:MeshAssembly"]
+ arg3 = ["NAME:Priority Components"]
+
+ if component and not isinstance(component, list):
+ component = [component]
+
+ if not volume_padding and component:
+ for comp in component:
+ if comp in self.modeler.user_defined_component_names:
+ mesh_assembly_arg = ["NAME:" + comp]
+ mesh_assembly_arg.append("MeshAssemblyBoundingVolumePadding:=")
+ mesh_assembly_arg.append(["0", "0", "0", "0", "0", "0"])
+ arg2.append(mesh_assembly_arg)
+ else:
+ self.logger.warning(comp + " does not exist.")
+
+ elif component and isinstance(volume_padding, list) and len(volume_padding) == len(component):
+ count = 0
+ for comp in component:
+ padding = [str(pad) for pad in volume_padding[count]]
+ if comp in self.modeler.user_defined_component_names:
+ mesh_assembly_arg = ["NAME:" + comp]
+ mesh_assembly_arg.append("MeshAssemblyBoundingVolumePadding:=")
+ mesh_assembly_arg.append(padding)
+ arg2.append(mesh_assembly_arg)
+ else:
+ self.logger.warning("{0} does not exist".format(str(comp)))
+ count += 1
+ elif component and isinstance(volume_padding, list) and len(volume_padding) != len(component):
+ self.logger.error("Volume padding length is different than component list length.")
+ return False
+
+ if priority and not isinstance(priority, list):
+ priority = [priority]
+
+ if component and priority:
+ for p in priority:
+ if p in self.modeler.user_defined_component_names:
+ arg3.append(p)
+ else:
+ self.logger.warning("{0} does not exist".format(str(p)))
+
+ arg.append(arg2)
+ arg.append(arg3)
+ self.odesign.SetDoMeshAssembly(arg)
+ return True
diff --git a/pyaedt/icepak.py b/pyaedt/icepak.py
index 8fa6dcdba92..c7ccc225e07 100644
--- a/pyaedt/icepak.py
+++ b/pyaedt/icepak.py
@@ -4066,7 +4066,7 @@ def assign_solid_block(
set to ``"Transient"``, acceptable values are `"Exponential"``, `"Linear"``,
``"Piecewise Linear"``, ``"Power Law"``, ``"Sinusoidal"``, and ``"SquareWave"``.
- For the ``"Values"`` key, a list of strings contain the parameters required by
- the ``"Function"`` key selection. For example, whn``"Linear"`` is set as the
+ the ``"Function"`` key selection. For example, when``"Linear"`` is set as the
``"Function"`` key, two parameters are required: the value of the variable
at t=0 and the slope of the line. For the parameters required by each
``"Function"`` key selection, see the Icepak documentation. The parameters
@@ -4199,7 +4199,7 @@ def assign_hollow_block(
When the ``"Type"`` key is set to ``"Transient"``, acceptable values are `"Exponential"``, `"Linear"``,
``"Piecewise Linear"``, ``"Power Law"``, ``"Sinusoidal"``, and ``"Square Wave"``.
- For the ``"Values"`` key, a list of strings contain the parameters required by the ``"Function"``
- key selection. For example, whn``"Linear"`` is set as the ``"Function"`` key, two parameters are required:
+ key selection. For example, when``"Linear"`` is set as the ``"Function"`` key, two parameters are required:
the value of the variable at t=0 and the slope of the line.
For the parameters required by each ``"Function"`` key selection, see the Icepak documentation.
The parameters must contain the units where needed.
diff --git a/pyaedt/maxwell.py b/pyaedt/maxwell.py
index 114d786f4a7..67acc669ebe 100644
--- a/pyaedt/maxwell.py
+++ b/pyaedt/maxwell.py
@@ -10,7 +10,6 @@
from pyaedt.application.Analysis3D import FieldAnalysis3D
from pyaedt.application.Variables import decompose_variable_value
-from pyaedt.generic.DataHandlers import float_units
from pyaedt.generic.constants import SOLUTIONS
from pyaedt.generic.general_methods import generate_unique_name
from pyaedt.generic.general_methods import open_file
@@ -1231,6 +1230,25 @@ def assign_force(self, input_object, reference_cs="Global", is_virtual=True, for
----------
>>> oModule.AssignForce
+
+ Examples
+ --------
+
+ Assign virtual force to a magnetic object:
+
+ >>> iron_object = m3d.modeler.create_box([0, 0, 0], [2, 10, 10], name="iron")
+ >>> magnet_object = m3d.modeler.create_box([10, 0, 0], [2, 10, 10], name="magnet")
+ >>> m3d.assign_material(iron_object, "iron")
+ >>> m3d.assign_material(magnet_object, "NdFe30")
+ >>> m3d.assign_force("iron", force_name="force_iron", is_virtual=True)
+
+ Assign Lorentz force to a conductor:
+
+ >>> conductor1 = m3d.modeler.create_box([0, 0, 0], [1, 1, 10], name="conductor1")
+ >>> conductor2 = m3d.modeler.create_box([10, 0, 0], [1, 1, 10], name="conductor2")
+ >>> m3d.assign_material(conductor1, "copper")
+ >>> m3d.assign_material(conductor2, "copper")
+ >>> m3d.assign_force("conductor1", force_name="force_copper", is_virtual=False) # conductor, use Lorentz force
"""
if self.solution_type not in ["ACConduction", "DCConduction"]:
input_object = self.modeler.convert_to_selections(input_object, True)
@@ -2242,7 +2260,7 @@ def _create_boundary(self, name, props, boundary_type):
"""Create a boundary.
Parameters
- ---------
+ ----------
name : str
Name of the boundary.
props : list
@@ -2810,23 +2828,19 @@ def xy_plane(self, value=True):
@property
def model_depth(self):
"""Model depth."""
-
- if "ModelDepth" in self.design_properties:
- value_str = self.design_properties["ModelDepth"]
- a = None
- try:
- a = float_units(value_str)
- except:
- a = self.variable_manager[value_str].value
- finally:
- return a
+ design_settings = self.design_settings()
+ if "ModelDepth" in design_settings:
+ value_str = design_settings["ModelDepth"]
+ return value_str
else:
return None
@model_depth.setter
def model_depth(self, value):
"""Set model depth."""
- return self.change_design_settings({"ModelDepth": self.modeler._arg_with_dim(value, self.modeler.model_units)})
+ if isinstance(value, float) or isinstance(value, int):
+ value = self.modeler._arg_with_dim(value, self.modeler.model_units)
+ self.change_design_settings({"ModelDepth": value})
@pyaedt_function_handler()
def generate_design_data(self, linefilter=None, objectfilter=None):
diff --git a/pyaedt/misc/install_extra_toolkits.py b/pyaedt/misc/install_extra_toolkits.py
index 8d97f8c5425..3748895fe1e 100644
--- a/pyaedt/misc/install_extra_toolkits.py
+++ b/pyaedt/misc/install_extra_toolkits.py
@@ -22,13 +22,6 @@
"installation_path": "Project",
"package_name": "ansys.aedt.toolkits.choke",
},
- "TemplateToolkit": {
- "pip": "git+https://github.com/ansys/pyaedt-toolkit-template.git",
- "image": "pyansys.png",
- "toolkit_script": "ansys/aedt/toolkits/template/run_toolkit.py",
- "installation_path": "Project",
- "package_name": "ansys.aedt.toolkits.template",
- },
}
diff --git a/pyaedt/misc/pyaedt_local_config.acf b/pyaedt/misc/pyaedt_local_config.acf
index cb73887a0d1..4a496f85e08 100644
--- a/pyaedt/misc/pyaedt_local_config.acf
+++ b/pyaedt/misc/pyaedt_local_config.acf
@@ -1,7 +1,7 @@
$begin 'Configs'
$begin 'Configs'
$begin 'DSOConfig'
- ConfigName='PyAEDT'
+ ConfigName='pyaedt_config'
DesignType='HFSS'
$begin 'DSOMachineList'
$begin 'DSOMachineInfo'
diff --git a/pyaedt/modeler/advanced_cad/stackup_3d.py b/pyaedt/modeler/advanced_cad/stackup_3d.py
index a634d9e623d..1c043f58c51 100644
--- a/pyaedt/modeler/advanced_cad/stackup_3d.py
+++ b/pyaedt/modeler/advanced_cad/stackup_3d.py
@@ -1333,7 +1333,7 @@ def signals(self):
@property
def objects(self):
- """List of obects created.
+ """List of objects created.
Returns
-------
diff --git a/pyaedt/modeler/cad/Modeler.py b/pyaedt/modeler/cad/Modeler.py
index e10a55c80e6..ab4bfaa1511 100644
--- a/pyaedt/modeler/cad/Modeler.py
+++ b/pyaedt/modeler/cad/Modeler.py
@@ -482,7 +482,7 @@ def create(
axis_position : int, FacePrimitive, EdgePrimitive, VertexPrimitive
Specify where the X or Y axis is pointing. The position must belong to the face where the
coordinate system is defined.
- Select which axis is considered with the option ``axix``.
+ Select which axis is considered with the option ``axis``.
If a face is specified, the position is placed on the face center. It must be the same as ``face``.
If an edge is specified, the position is placed on the edce midpoint.
If a vertex is specified, the position is placed on the vertex.
@@ -2476,7 +2476,7 @@ def create_face_coordinate_system(
axis_position : int, FacePrimitive, EdgePrimitive, VertexPrimitive
Specify where the X or Y axis is pointing. The position must belong to the face where the
coordinate system is defined.
- Select which axis is considered with the option ``axix``.
+ Select which axis is considered with the option ``axis``.
If a face is specified, the position is placed on the face center. It must be the same as ``face``.
If an edge is specified, the position is placed on the edce midpoint.
If a vertex is specified, the position is placed on the vertex.
@@ -4480,6 +4480,9 @@ def unite(self, unite_list, purge=False, keep_originals=False):
szSelections = self.convert_to_selections(objs)
vArg1 = ["NAME:Selections", "Selections:=", szSelections]
vArg2 = ["NAME:UniteParameters", "KeepOriginals:=", keep_originals]
+ if settings.aedt_version > "2022.2":
+ vArg2.append("TurnOnNBodyBoolean:=")
+ vArg2.append(True)
self.oeditor.Unite(vArg1, vArg2)
if szSelections.split(",")[0] in self.unclassified_names:
self.logger.error("Error in uniting objects.")
@@ -5296,7 +5299,7 @@ def explicitly_subtract(self, diellist, metallist):
@pyaedt_function_handler()
def find_port_faces(self, port_sheets):
- """Find the vaccums given a list of input sheets.
+ """Find the vacuums given a list of input sheets.
Starting from a list of input sheets, this method creates a list of output sheets
that represent the blank parts (vacuums) and the tool parts of all the intersections
diff --git a/pyaedt/modeler/cad/Primitives3D.py b/pyaedt/modeler/cad/Primitives3D.py
index e00948b94ae..45c352abd12 100644
--- a/pyaedt/modeler/cad/Primitives3D.py
+++ b/pyaedt/modeler/cad/Primitives3D.py
@@ -72,8 +72,8 @@ def create_box(self, position, dimensions_list, name=None, matname=None):
Returns
-------
- :class:`pyaedt.modeler.cad.object3d.Object3d`
- 3D object.
+ bool, :class:`pyaedt.modeler.cad.object3d.Object3d`
+ 3D object or ``False`` if it fails.
References
----------
@@ -82,19 +82,32 @@ def create_box(self, position, dimensions_list, name=None, matname=None):
Examples
--------
+ This example shows how to create a box in HFSS.
+ The required parameters are ``position`` that provides the origin of the
+ box and ``dimensions_list`` that provide the box sizes.
+ The optional parameter ``matname`` allows you to set the material name of the box.
+ The optional parameter ``name`` allows you to assign a name to the box.
+
+ This method applies to all 3D applications: HFSS, Q3D, Icepak, Maxwell 3D, and
+ Mechanical.
>>> from pyaedt import hfss
>>> hfss = Hfss()
>>> origin = [0,0,0]
>>> dimensions = [10,5,20]
- >>> #Material and name are not mandatory fields
- >>> box_object = hfss.modeler.primivites.create_box(origin, dimensions, name="mybox", matname="copper")
+ >>> box_object = hfss.modeler.primivites.create_box(position=origin,
+ ... dimensions_list=dimensions,
+ ... name="mybox",
+ ... matname="copper")
"""
if len(position) != 3:
- raise ValueError("Position argument must be a valid three-element list.")
+ self.logger.error("The ``position`` argument must be a valid three-element list.")
+ return False
if len(dimensions_list) != 3:
- raise ValueError("Dimension argument must be a valid 3 element List")
+ self.logger.error("The ``dimension_list`` argument must be a valid three-element list.")
+ return False
+
XPosition, YPosition, ZPosition = self._pos_with_arg(position)
XSize, YSize, ZSize = self._pos_with_arg(dimensions_list)
vArg1 = ["NAME:BoxParameters"]
@@ -130,13 +143,13 @@ def create_cylinder(self, cs_axis, position, radius, height, numSides=0, name=No
Name of the cylinder. The default is ``None``, in which case
the default name is assigned.
matname : str, optional
- Name of the material. The default is ''None``, in which case the
+ Name of the material. The default is ``None``, in which case the
default material is assigned.
Returns
-------
- :class:`pyaedt.modeler.cad.object3d.Object3d`
- 3D object.
+ bool, :class:`pyaedt.modeler.cad.object3d.Object3d`
+ 3D object or ``False`` if it fails.
References
----------
@@ -145,15 +158,31 @@ def create_cylinder(self, cs_axis, position, radius, height, numSides=0, name=No
Examples
--------
+ This example shows how to create a cylinder in HFSS.
+ The required parameters are ``cs_axis``, ``position``, ``radius``, and ``height``. The
+ ``cs_axis`` parameter provides the direction axis of the cylinder. The ``position``
+ parameter provides the origin of the cylinder. The other two parameters provide
+ the radius and height of the cylinder.
+
+ The optional parameter ``matname`` allows you to set the material name of the cylinder.
+ The optional parameter ``name`` allows to assign a name to the cylinder.
+
+ This method applies to all 3D applications: HFSS, Q3D, Icepak, Maxwell 3D, and
+ Mechanical.
+
>>> from pyaedt import Hfss
>>> aedtapp = Hfss()
- >>> cylinder_object = aedtapp.modeler..create_cylinder(cs_axis='Z', position=[0,0,0],
+ >>> cylinder_object = aedtapp.modeler.create_cylinder(cs_axis='Z', position=[0,0,0],
... radius=2, height=3, name="mycyl",
... matname="vacuum")
"""
if isinstance(radius, (int, float)) and radius < 0:
- raise ValueError("Radius must be greater than 0.")
+ self.logger.error("The ``radius`` argument must be greater than 0.")
+ return False
+ if len(position) != 3:
+ self.logger.error("The ``position`` argument must be a valid three-element list.")
+ return False
szAxis = GeometryOperators.cs_axis_str(cs_axis)
XCenter, YCenter, ZCenter = self._pos_with_arg(position)
@@ -177,8 +206,8 @@ def create_cylinder(self, cs_axis, position, radius, height, numSides=0, name=No
def create_polyhedron(
self,
cs_axis=None,
- center_position=(0.0, 0.0, 0.0),
- start_position=(0.0, 1.0, 0.0),
+ center_position=[0.0, 0.0, 0.0],
+ start_position=[0.0, 1.0, 0.0],
height=1.0,
num_sides=12,
name=None,
@@ -210,8 +239,8 @@ def create_polyhedron(
Returns
-------
- :class:`pyaedt.modeler.cad.object3d.Object3d`
- 3D object.
+ bool, :class:`pyaedt.modeler.cad.object3d.Object3d`
+ 3D object or ``False`` if it fails.
References
----------
@@ -220,15 +249,32 @@ def create_polyhedron(
Examples
--------
+ The following examples shows how to create a regular polyhedron in HFSS.
+ The required parameters are cs_axis that provides the direction axis of the polyhedron,
+ center_position that provides the center of the polyhedron, start_position of the polyhedron,
+ height of the polyhedron and num_sides to determine the number of sides.
+ The parameter matname is optional and allows to set the material name of the polyhedron.
+ The parameter name is optional and allows to give a name to the polyhedron.
+ This method applies to all 3D applications: HFSS, Q3D, Icepak, Maxwell 3D, Mechanical.
+
>>> from pyaedt import Hfss
>>> aedtapp = Hfss()
>>> ret_obj = aedtapp.modeler.create_polyhedron(cs_axis='X', center_position=[0, 0, 0],
... start_position=[0,5,0], height=0.5,
... num_sides=8, name="mybox", matname="copper")
-
"""
test = cs_axis
cs_axis = GeometryOperators.cs_axis_str(cs_axis)
+ if len(center_position) != 3:
+ self.logger.error("The ``center_position`` argument must be a valid three-element list.")
+ return False
+ if len(start_position) != 3:
+ self.logger.error("The ``start_position`` argument must be a valid three-element list.")
+ return False
+ if center_position == start_position:
+ self.logger.error("The ``center_position`` and ``start_position`` arguments must be different.")
+ return False
+
x_center, y_center, z_center = self._pos_with_arg(center_position)
x_start, y_start, z_start = self._pos_with_arg(start_position)
@@ -257,7 +303,7 @@ def create_cone(self, cs_axis, position, bottom_radius, top_radius, height, name
cs_axis : str
Axis of rotation of the starting point around the center point.
The default is ``None``, in which case the Z axis is used.
- center_position : list, optional
+ position : list, optional
List of ``[x, y, z]`` coordinates for the center position
of the bottom of the cone.
bottom_radius : float
@@ -275,8 +321,8 @@ def create_cone(self, cs_axis, position, bottom_radius, top_radius, height, name
Returns
-------
- :class:`pyaedt.modeler.cad.object3d.Object3d`
- 3D object.
+ bool, :class:`pyaedt.modeler.cad.object3d.Object3d`
+ 3D object or ``False`` if it fails.
References
----------
@@ -285,6 +331,19 @@ def create_cone(self, cs_axis, position, bottom_radius, top_radius, height, name
Examples
--------
+ This example shows how to create a cone in HFSS.
+ The required parameters are ``cs_axis``, ``position``, ``bottom_radius``, and
+ ``top_radius``. The ``cs_axis`` parameter provides the direction axis of
+ the cone. The ``position`` parameter provides the starting point of the
+ cone. The ``bottom_radius`` and ``top_radius`` parameters provide the
+ radius and `eight of the cone.
+
+ The optional parameter ``matname`` allows you to set the material name of the cone.
+ The optional parameter ``name`` allows you to assign a name to the cone.
+
+ This method applies to all 3D applications: HFSS, Q3D, Icepak, Maxwell 3D, and
+ Mechanical.
+
>>> from pyaedt import Hfss
>>> aedtapp = Hfss()
>>> cone_object = aedtapp.modeler.create_cone(cs_axis='Z', position=[0, 0, 0],
@@ -293,13 +352,20 @@ def create_cone(self, cs_axis, position, bottom_radius, top_radius, height, name
"""
if bottom_radius == top_radius:
- raise ValueError("Bottom radius and top radius must have different values.")
+ self.logger.error("the ``bottom_radius`` and ``top_radius`` arguments must have different values.")
+ return False
if isinstance(bottom_radius, (int, float)) and bottom_radius < 0:
- raise ValueError("Bottom radius must be greater than 0.")
+ self.logger.error("The ``bottom_radius`` argument must be greater than 0.")
+ return False
if isinstance(top_radius, (int, float)) and top_radius < 0:
- raise ValueError("Top radius must be greater than 0.")
+ self.logger.error("The ``top_radius`` argument must be greater than 0.")
+ return False
if isinstance(height, (int, float)) and height <= 0:
- raise ValueError("Height must be greater than 0.")
+ self.logger.error("The ``height`` argument must be greater than 0.")
+ return False
+ if len(position) != 3:
+ self.logger.error("The ``position`` argument must be a valid three-element list.")
+ return False
XCenter, YCenter, ZCenter = self._pos_with_arg(position)
szAxis = GeometryOperators.cs_axis_str(cs_axis)
@@ -339,8 +405,8 @@ def create_sphere(self, position, radius, name=None, matname=None):
Returns
-------
- :class:`pyaedt.modeler.cad.object3d.Object3d`
- 3D object.
+ bool, :class:`pyaedt.modeler.cad.object3d.Object3d`
+ 3D object or ``False`` if it fails.
References
----------
@@ -349,16 +415,26 @@ def create_sphere(self, position, radius, name=None, matname=None):
Examples
--------
+ This example shows how to create a sphere in HFSS.
+ The required parameters are ``position``, which provides the center of the sphere, and
+ ``radius``, which is the radius of the sphere. The optional parameter ``matname``
+ allows you to set the material name of the sphere. The optional parameter
+ ``name`` allows to assign a name to the sphere.
+
+ This method applies to all 3D applications: HFSS, Q3D, Icepak, Maxwell 3D, and
+ Mechanical.
+
>>> from pyaedt import Hfss
>>> aedtapp = Hfss()
>>> ret_object = aedtapp.modeler.create_sphere(position=[0,0,0], radius=2,
... name="mysphere", matname="copper")
-
"""
if len(position) != 3:
- raise ValueError("Position argument must be a valid 3 elements List.")
+ self.logger.error("The ``position`` argument must be a valid three-element list.")
+ return False
if isinstance(radius, (int, float)) and radius < 0:
- raise ValueError("Radius must be greater than 0.")
+ self.logger.error("The ``radius`` argument must be greater than 0.")
+ return False
XCenter, YCenter, ZCenter = self._pos_with_arg(position)
@@ -398,8 +474,8 @@ def create_torus(self, center, major_radius, minor_radius, axis=None, name=None,
Returns
-------
- :class:`pyaedt.modeler.cad.object3d.Object3d`
- 3D object.
+ bool, :class:`pyaedt.modeler.cad.object3d.Object3d`
+ 3D object or ``False`` if it fails.
References
----------
@@ -409,17 +485,23 @@ def create_torus(self, center, major_radius, minor_radius, axis=None, name=None,
Examples
--------
Create a torus named ``"mytorus"`` about the Z axis with a major
- radius of 1, minor radius of 0.5, and a material of ``"copper"``.
+ radius of 1 , minor radius of 0.5, and a material of ``"copper"``.
+ The optional parameter ``matname`` allows you to set the material name of the sphere.
+ The optional parameter ``name`` allows you to give a name to the sphere.
+
+ This method applies to all 3D applications: HFSS, Q3D, Icepak, Maxwell 3D, and
+ Mechanical.
+
>>> from pyaedt import Hfss
>>> hfss = Hfss()
>>> origin = [0, 0, 0]
- >>> torus = hfss.modeler.create_torus(origin, major_radius=1,
+ >>> torus = hfss.modeler.create_torus(center=origin, major_radius=1,
... minor_radius=0.5, axis="Z",
... name="mytorus", material_name="copper")
-
"""
if len(center) != 3:
- raise ValueError("Center argument must be a valid 3 element sequence.")
+ self.logger.error("The ``center`` argument must be a valid three-element list.")
+ return False
# if major_radius <= 0 or minor_radius <= 0:
# raise ValueError("Both major and minor radius must be greater than 0.")
# if minor_radius >= major_radius:
@@ -525,12 +607,15 @@ def create_bondwire(
>>> object_id = hfss.modeler.create_bondwire(origin, endpos,h1=0.5, h2=0.1, alpha=75, beta=4,
... bond_type=0, name="mybox", matname="copper")
"""
+ if len(start_position) != 3:
+ self.logger.error("The ``start_position`` argument must be a valid three-Element List")
+ return False
x_position, y_position, z_position = self._pos_with_arg(start_position)
+ if len(end_position) != 3:
+ self.logger.error("The ``end_position`` argument must be a valid three-Element List")
+ return False
x_position_end, y_position_end, z_position_end = self._pos_with_arg(end_position)
- if x_position is None or y_position is None or z_position is None:
- raise AttributeError("Position Argument must be a valid 3 Element List")
-
cont = 0
x_length = None
y_length = None
@@ -549,8 +634,6 @@ def create_bondwire(
z_length = "(" + str(n) + ") - (" + str(m) + ")"
cont += 1
- if x_length is None or y_length is None or z_length is None:
- raise AttributeError("Dimension Argument must be a valid 3 Element List")
if bond_type == 0:
bondwire = "JEDEC_5Points"
elif bond_type == 1:
@@ -623,15 +706,17 @@ def create_rectangle(self, csPlane, position, dimension_list, name=None, matname
Returns
-------
- :class:`pyaedt.modeler.cad.object3d.Object3d`
- 3D object.
+ bool, :class:`pyaedt.modeler.cad.object3d.Object3d`
+ 3D object or ``False`` if it fails.
References
----------
>>> oEditor.CreateRectangle
-
"""
+ if len(dimension_list) != 2:
+ self.logger.error("The ``dimension_list`` argument must be a valid two-element list.")
+ return False
szAxis = GeometryOperators.cs_plane_to_axis_str(csPlane)
XStart, YStart, ZStart = self._pos_with_arg(position)
@@ -686,6 +771,27 @@ def create_circle(
>>> oEditor.CreateCircle
+ Examples
+ --------
+ The following example shows how to create a circle in HFSS.
+ The required parameters are ``cs_plane``, ``position``, ``radius``,
+ and ``num_sides``. The ``cs_plane`` parameter provides the plane
+ that the circle is designed on. The ``position`` parameter provides
+ the origin of the circle. The ``radius`` and ``num_sides`` parameters
+ provide the radius and number of discrete sides of the circle,
+
+ The optional parameter ``matname`` allows you to set the material name
+ of the circle. The optional parameter ``name`` allows you to assign a name
+ to the circle.
+
+ This method applies to all 3D applications: HFSS, Q3D, Icepak, Maxwell 3D,
+ and Mechanical.
+
+ >>> from pyaedt import Hfss
+ >>> aedtapp = Hfss()
+ >>> circle_object = aedtapp.modeler.create_circle(cs_plane='Z', position=[0,0,0],
+ ... radius=2, num_sides=8, name="mycyl",
+ ... matname="vacuum")
"""
non_model_flag = ""
if non_model:
@@ -741,6 +847,31 @@ def create_ellipse(self, cs_plane, position, major_radius, ratio, is_covered=Tru
>>> oEditor.CreateEllipse
+ Examples
+ --------
+ The following example shows how to create an ellipse in HFSS.
+ The required parameters are ``cs_plane``, ``position``, ``major_radius``,
+ ``ratio``, and ``is_covered``. The ``cs_plane`` parameter provides
+ the plane that the ellipse is designed on. The ``position`` parameter
+ provides the origin of the ellipse. The ``major_radius`` parameter provides
+ the radius of the ellipse. The ``ratio`` parameter is a ratio between the
+ major radius and minor radius of the ellipse. The ``is_covered`` parameter
+ is a flag indicating if the ellipse is covered.
+
+ The optional parameter ``matname`` allows you to set the material name
+ of the ellipse. The optional parameter ``name`` allows you to assign a name
+ to the ellipse.
+
+ This method applies to all 3D applications: HFSS, Q3D, Icepak, Maxwell 3D,
+ and Mechanical.
+
+ >>> from pyaedt import Hfss
+ >>> aedtapp = Hfss()
+ >>> ellipse = aedtapp.modeler.create_ellipse(cs_plane='Z', position=[0,0,0],
+ ... major_radius=2, ratio=2, is_covered=True, name="myell",
+ ... matname="vacuum")
+
+
"""
szAxis = GeometryOperators.cs_plane_to_axis_str(cs_plane)
XStart, YStart, ZStart = self._pos_with_arg(position)
@@ -833,6 +964,33 @@ def create_equationbased_curve(
>>> oEditor.CreateEquationCurve
+ Examples
+ --------
+ The following example shows how to create an equation- based curve in HFSS.
+ The required parameters are ``cs_plane``, ``position``, ``major_radius``,
+ ``ratio``, and ``is_covered``. The ``cs_plane`` parameter provides
+ the plane that the ellipse is designed on. The ``position`` parameter
+ provides the origin of the ellipse. The ``major_radius`` parameter provides
+ the radius of the ellipse. The ``ratio`` parameter is a ratio between the
+ major radius and minor radius of the ellipse. The ``is_covered`` parameter
+ is a flag indicating if the ellipse is covered.
+
+ The optional parameter ``matname`` allows you to set the material name
+ of the ellipse. The optional parameter ``name`` allows you to assign a name
+ to the ellipse.
+
+ This method applies to all 3D applications: HFSS, Q3D, Icepak, Maxwell 3D,
+ and Mechanical.
+
+ >>> from pyaedt import Hfss
+ >>> aedtapp = Hfss()
+ >>> eq_xsection = self.aedtapp.modeler.create_equationbased_curve(x_t="_t",
+ ... y_t="_t*2",
+ ... num_points=200,
+ ... z_t=0,
+ ... t_start=0.2,
+ ... t_end=1.2,
+ ... xsection_type="Circle")
"""
x_section = self._crosssection_arguments(
type=xsection_type,
@@ -905,18 +1063,49 @@ def create_helix(
Returns
-------
- :class:`pyaedt.modeler.cad.object3d.Object3d`
- 3D object.
+ bool, :class:`pyaedt.modeler.cad.object3d.Object3d`
+ 3D object or ``False`` if it fails.
References
----------
>>> oEditor.CreateHelix
+ Examples
+ --------
+ The following example shows how to create a polyline and then create an helix from the polyline.
+ This method applies to all 3D applications: HFSS, Q3D, Icepak, Maxwell 3D, and
+ Mechanical.
+
+ >>> from pyaedt import Hfss
+ >>> aedtapp = Hfss()
+ >>> udp1 = [0, 0, 0]
+ >>> udp2 = [5, 0, 0]
+ >>> udp3 = [10, 5, 0]
+ >>> udp4 = [15, 3, 0]
+ >>> polyline = aedtapp.modeler.create_polyline(
+ ... [udp1, udp2, udp3, udp4], cover_surface=False, name="helix_polyline"
+ ... )
+
+ >>> helix_right_turn = aedtapp.modeler.create_helix(
+ ... polyline_name=polyline.name,
+ ... position=[0, 0, 0],
+ ... x_start_dir=0,
+ ... y_start_dir=1.0,
+ ... z_start_dir=1.0,
+ ... num_thread=1,
+ ... right_hand=True,
+ ... radius_increment=0.0,
+ ... thread=1.0,
+ ... )
"""
if not polyline_name or polyline_name == "":
- raise ValueError("The name of the polyline cannot be an empty string.")
+ self.logger.error("The name of the polyline cannot be an empty string.")
+ return False
+ if len(position) != 3:
+ self.logger.error("The ``position`` argument must be a valid three-element list.")
+ return False
x_center, y_center, z_center = self._pos_with_arg(position)
vArg1 = ["NAME:Selections"]
@@ -958,19 +1147,28 @@ def convert_segments_to_line(self, object_name):
Parameters
----------
- object_name : int, str, or Object3d
+ object_name : int, str, or :class:`pyaedt.modeler.cad.object3d.Object3d`
Specified for the object.
Returns
-------
- :class:`pyaedt.modeler.cad.object3d.Object3d`
- 3D object.
+ bool
+ ``True`` if successful, ``False`` if it fails.
References
----------
>>> oEditor.ChangeProperty
+ Examples
+ --------
+
+ >>> from pyaedt import Hfss
+ >>> aedtapp = Hfss()
+ >>> edge_object = aedtapp.modeler.create_object_from_edge("my_edge")
+ >>> aedtapp.modeler.generate_object_history(edge_object)
+ >>> aedtapp.modeler.convert_segments_to_line(edge_object.name)
+
"""
this_object = self._resolve_object(object_name)
edges = this_object.edges
@@ -1010,8 +1208,8 @@ def create_udm(
Returns
-------
- :class:`pyaedt.modeler.components_3d.UserDefinedComponent`
- User-defined component object.
+ bool, :class:`pyaedt.modeler.components_3d.UserDefinedComponent`
+ User-defined component object or ``False`` if it fails.
References
----------
@@ -1117,11 +1315,15 @@ def create_spiral(
Returns
-------
- :class:`pyaedt.modeler.Object3d.Polyline`
- Polyline object.
+ bool, :class:`pyaedt.modeler.Object3d.Polyline`
+ Polyline object or ``False`` if it fails.
"""
- assert internal_radius > 0, "Internal Radius must be greater than 0."
- assert faces > 0, "Faces must be greater than 0."
+ if internal_radius < 0:
+ self.logger.error("The ``internal_radius`` argument must be greater than 0.")
+ return False
+ if faces < 0:
+ self.logger.error("The ``faces`` argument must be greater than 0.")
+ return False
dtheta = 2 * pi / faces
theta = pi / 2
pts = [(internal_radius, 0, elevation), (internal_radius, internal_radius * tan(dtheta / 2), elevation)]
diff --git a/pyaedt/modeler/cad/elements3d.py b/pyaedt/modeler/cad/elements3d.py
index b7f82b8f46a..4b3f549022e 100644
--- a/pyaedt/modeler/cad/elements3d.py
+++ b/pyaedt/modeler/cad/elements3d.py
@@ -69,7 +69,7 @@ class EdgeTypePrimitive(object):
@pyaedt_function_handler()
def fillet(self, radius=0.1, setback=0.0):
- """Add a fillet to the selected edge.
+ """Add a fillet to the selected edges in 3D/vertices in 2D.
Parameters
----------
@@ -98,7 +98,7 @@ def fillet(self, radius=0.1, setback=0.0):
if self._object3d.is3d:
edge_id_list = [self.id]
else:
- self._object3d.logger.error("Filet is possible only on a vertex in 2D designs.")
+ self._object3d.logger.error("Fillet is possible only on a vertex in 2D designs.")
return False
vArg1 = ["NAME:Selections", "Selections:=", self._object3d.name, "NewPartsModelFlag:=", "Model"]
@@ -116,7 +116,7 @@ def fillet(self, radius=0.1, setback=0.0):
@pyaedt_function_handler()
def chamfer(self, left_distance=1, right_distance=None, angle=45, chamfer_type=0):
- """Add a chamfer to the selected edge.
+ """Add a chamfer to the selected edges in 3D/vertices in 2D.
Parameters
----------
diff --git a/pyaedt/modeler/modeler3d.py b/pyaedt/modeler/modeler3d.py
index c691e9892ba..48ed3f6288b 100644
--- a/pyaedt/modeler/modeler3d.py
+++ b/pyaedt/modeler/modeler3d.py
@@ -1390,7 +1390,7 @@ def change_region_padding(self, padding_data, padding_type, direction=None, regi
``True`` if successful, else ``None``.
Examples
- ----------
+ --------
>>> import pyaedt
>>> app = pyaedt.Icepak()
>>> app.modeler.change_region_padding("10mm", padding_type="Absolute Offset", direction="-X")
@@ -1474,7 +1474,7 @@ def change_region_coordinate_system(self, region_cs="Global", region_name="Regio
``True`` if successful, else ``None``.
Examples
- ----------
+ --------
>>> import pyaedt
>>> app = pyaedt.Icepak()
>>> app.modeler.create_coordinate_system(origin=[1, 1, 1], name="NewCS")
diff --git a/pyaedt/modeler/pcb/object3dlayout.py b/pyaedt/modeler/pcb/object3dlayout.py
index d1d2cb7e759..9ccf0fdae8f 100644
--- a/pyaedt/modeler/pcb/object3dlayout.py
+++ b/pyaedt/modeler/pcb/object3dlayout.py
@@ -1879,7 +1879,7 @@ class PDSHole(object):
The default is ``"0mm"``.
ypos : str, optional
The default is ``"0mm"``.
- rot : str, otpional
+ rot : str, optional
Rotation in degrees. The default is ``"0deg"``.
"""
diff --git a/pyaedt/modules/AdvancedPostProcessing.py b/pyaedt/modules/AdvancedPostProcessing.py
index 96e6853e576..b5373a42e15 100644
--- a/pyaedt/modules/AdvancedPostProcessing.py
+++ b/pyaedt/modules/AdvancedPostProcessing.py
@@ -199,7 +199,7 @@ def get_model_plotter_geometries(
assert self._app._aedt_version >= "2021.2", self.logger.error("Object is supported from AEDT 2021 R2.")
files = []
- if get_objects_from_aedt:
+ if get_objects_from_aedt and self._app.solution_type not in ["HFSS3DLayout", "HFSS 3D Layout Design"]:
files = self.export_model_obj(
obj_list=objects,
export_as_single_objects=plot_as_separate_objects,
@@ -321,6 +321,7 @@ def plot_field_from_fieldplot(
dark_mode=False,
show_grid=False,
show_bounding=False,
+ show_legend=True,
):
"""Export a field plot to an image file (JPG or PNG) using Python PyVista.
@@ -364,21 +365,25 @@ def plot_field_from_fieldplot(
Whether to display the axes grid or not. The default is ``False``.
show_bounding : bool, optional
Whether to display the axes bounding box or not. The default is ``False``.
+ show_legend : bool, optional
+ Whether to display the legend or not. The default is ``True``.
Returns
-------
:class:`pyaedt.generic.plot.ModelPlotter`
Model Object.
"""
+ is_pcb = False
+ if self._app.solution_type in ["HFSS3DLayout", "HFSS 3D Layout Design"]:
+ is_pcb = True
if not plot_folder:
self.ofieldsreporter.UpdateAllFieldsPlots()
else:
self.ofieldsreporter.UpdateQuantityFieldsPlots(plot_folder)
file_to_add = self.export_field_plot(plotname, self._app.working_directory)
-
model = self.get_model_plotter_geometries(generate_mesh=False, get_objects_from_aedt=plot_cad_objs)
-
+ model.show_legend = show_legend
model.off_screen = not show
if dark_mode:
model.background_color = [40, 40, 40]
@@ -395,6 +400,8 @@ def plot_field_from_fieldplot(
model.camera_position = view
elif view != "isometric":
self.logger.warning("Wrong view setup. It has to be one of xy, xz, yz, isometric.")
+ if is_pcb:
+ model.z_scale = 5
if scale_min and scale_max:
model.range_min = scale_min
@@ -427,6 +434,8 @@ def plot_field(
dark_mode=False,
show_bounding=False,
show_grid=False,
+ show_legend=True,
+ filter_objects=[],
):
"""Create a field plot using Python PyVista and export to an image file (JPG or PNG).
@@ -477,12 +486,18 @@ def plot_field(
Whether to display the axes grid or not. The default is ``False``.
show_bounding : bool, optional
Whether to display the axes bounding box or not. The default is ``False``.
+ show_legend : bool, optional
+ Whether to display the legend or not. The default is ``True``.
+ filter_objects : list, optional
+ Objects list for filtering the ``CutPlane`` plots.
Returns
-------
:class:`pyaedt.generic.plot.ModelPlotter`
Model Object.
"""
+ if os.getenv("PYAEDT_DOC_GENERATION", "False").lower() in ("true", "1", "t"): # pragma: no cover
+ show = False
if not setup_name:
setup_name = self._app.existing_analysis_sweeps[0]
if not intrinsics:
@@ -496,7 +511,9 @@ def plot_field(
elif plot_type == "Volume":
plotf = self.create_fieldplot_volume(object_list, quantity, setup_name, intrinsics)
else:
- plotf = self.create_fieldplot_cutplane(object_list, quantity, setup_name, intrinsics)
+ plotf = self.create_fieldplot_cutplane(
+ object_list, quantity, setup_name, intrinsics, filter_objects=filter_objects
+ )
# if plotf:
# file_to_add = self.export_field_plot(plotf.name, self._app.working_directory, plotf.name)
@@ -516,6 +533,7 @@ def plot_field(
dark_mode=dark_mode,
show_grid=show_grid,
show_bounding=show_bounding,
+ show_legend=show_legend,
)
if not keep_plot_after_generation:
plotf.delete()
@@ -545,6 +563,8 @@ def plot_animated_field(
dark_mode=False,
show_grid=False,
show_bounding=False,
+ show_legend=True,
+ filter_objects=[],
):
"""Create an animated field plot using Python PyVista and export to a gif file.
@@ -596,12 +616,18 @@ def plot_animated_field(
Whether to display the axes grid or not. The default is ``False``.
show_bounding : bool, optional
Whether to display the axes bounding box or not. The default is ``False``.
+ show_legend : bool, optional
+ Whether to display the legend or not. The default is ``True``.
+ filter_objects : list, optional
+ Objects list for filtering the ``CutPlane`` plots.
Returns
-------
:class:`pyaedt.generic.plot.ModelPlotter`
Model Object.
"""
+ if os.getenv("PYAEDT_DOC_GENERATION", "False").lower() in ("true", "1", "t"): # pragma: no cover
+ show = False
if intrinsics is None:
intrinsics = {}
if not export_path:
@@ -622,7 +648,9 @@ def plot_animated_field(
elif plot_type == "Volume":
plotf = self.create_fieldplot_volume(object_list, quantity, setup_name, intrinsics)
else:
- plotf = self.create_fieldplot_cutplane(object_list, quantity, setup_name, intrinsics)
+ plotf = self.create_fieldplot_cutplane(
+ object_list, quantity, setup_name, intrinsics, filter_objects=filter_objects
+ )
if plotf:
file_to_add = self.export_field_plot(plotf.name, export_path, plotf.name + str(v))
if file_to_add:
@@ -637,6 +665,7 @@ def plot_animated_field(
model.background_color = [40, 40, 40]
model.bounding_box = show_bounding
model.show_grid = show_grid
+ model.show_legend = show_legend
if fields_to_add:
model.add_frames_from_file(fields_to_add, log_scale=log_scale)
if export_gif:
diff --git a/pyaedt/modules/Boundary.py b/pyaedt/modules/Boundary.py
index 48572ecbfcc..630fd3937cf 100644
--- a/pyaedt/modules/Boundary.py
+++ b/pyaedt/modules/Boundary.py
@@ -225,7 +225,7 @@ def targetcs(self):
Returns
-------
str
- Native Component Coordinate System
+ Native Component Coordinate System.
"""
if "TargetCS" in list(self.props.keys()):
return self.props["TargetCS"]
@@ -3690,7 +3690,7 @@ def auto_update(self, b):
Parameters
----------
- b: bool
+ b : bool
Whether to enable auto-update.
"""
@@ -3848,7 +3848,7 @@ def name(self, new_network_name):
Parameters
----------
- new_network_name: str
+ new_network_name : str
New name of the network.
"""
bound_names = [b.name for b in self._app.boundaries]
@@ -3929,11 +3929,11 @@ def add_boundary_node(self, name, assignment_type, value):
Parameters
----------
- name: str
+ name : str
Name of the node.
- assignment_type: str
+ assignment_type : str
Type assignment. Options are ``"Power"`` and ``"Temperature"``.
- value: str or float or dict
+ value : str or float or dict
String, float, or dictionary containing the value of the assignment.
If a float is passed the ``"W"`` or ``"cel"`` unit is used, depending on
the selection for the ``assignment_type`` parameter. If ``"Power"`
@@ -3943,7 +3943,7 @@ def add_boundary_node(self, name, assignment_type, value):
Returns
-------
bool
- True if successful.
+ ``True`` if successful.
Examples
--------
@@ -4082,7 +4082,7 @@ def add_nodes_from_dictionaries(self, nodes_dict):
Add nodes to the network from dictionary.
Parameters
- -------
+ ----------
nodes_dict : list or dict
A dictionary or list of dictionaries containing nodes to add to the network. Different
node types require different key and value pairs:
@@ -4431,8 +4431,8 @@ def props(self, props):
Set properties of the node.
Parameters
- -------
- props: dict
+ ----------
+ props : dict
Node properties.
"""
self._props = props
diff --git a/pyaedt/modules/Material.py b/pyaedt/modules/Material.py
index 485888cd68f..e07e596693c 100644
--- a/pyaedt/modules/Material.py
+++ b/pyaedt/modules/Material.py
@@ -14,6 +14,7 @@
"""
from collections import OrderedDict
import copy
+import warnings
from pyaedt.generic.DataHandlers import _dict2arg
from pyaedt.generic.constants import CSS4_COLORS
@@ -33,6 +34,7 @@ class MatProperties(object):
"conductivity",
"dielectric_loss_tangent",
"magnetic_loss_tangent",
+ "magnetic_coercivity",
"thermal_conductivity",
"mass_density",
"specific_heat",
@@ -43,13 +45,41 @@ class MatProperties(object):
"molecular_mass",
"viscosity",
]
- defaultvalue = [1.0, 1.0, 0, 0, 0, 0.01, 0, 0, 0, 0, 0, 0.8, 0, 0, 0, 0, 0, 0]
+ defaultvalue = [
+ 1.0,
+ 1.0,
+ 0,
+ 0,
+ 0,
+ OrderedDict(
+ {
+ "Magnitude": 0,
+ "DirComp1": 1,
+ "DirComp2": 0,
+ "DirComp3": 0,
+ }
+ ),
+ 0.01,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0.8,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ ]
defaultunit = [
None,
None,
"[siemens m^-1]",
None,
None,
+ None,
"[W m^-1 C^-1]",
"[Kg m^-3]",
"[J Kg^-1 C^-1]",
@@ -167,7 +197,7 @@ def get_defaultvalue(cls, aedtname=None):
class ClosedFormTM(object):
- """Manges closed-form thermal modifiers."""
+ """Manages closed-form thermal modifiers."""
Tref = "22cel"
C1 = 0
@@ -235,10 +265,10 @@ def __init__(self, material, name, val=None, thermalmodifier=None, spatialmodifi
if val is not None and isinstance(val, (str, float, int)):
self.value = val
- elif val is not None and val["property_type"] == "AnisoProperty":
+ elif val is not None and "property_type" in val.keys() and val["property_type"] == "AnisoProperty":
self.type = "anisotropic"
self.value = [val["component1"], val["component2"], val["component3"]]
- elif val is not None and val["property_type"] == "nonlinear":
+ elif val is not None and "property_type" in val.keys() and val["property_type"] == "nonlinear":
self.type = "nonlinear"
for e, v in val.items():
if e == "BTypeForSingleCurve":
@@ -254,6 +284,16 @@ def __init__(self, material, name, val=None, thermalmodifier=None, spatialmodifi
self._unit = v["DimUnits"]
elif e == "Temperatures":
self.temperatures = v
+ elif val is not None and isinstance(val, OrderedDict) and "Magnitude" in val.keys():
+ self.type = "vector"
+ magnitude = val["Magnitude"]
+ units = None
+ if isinstance(magnitude, str):
+ units = "".join(filter(lambda c: c.isalpha() or c == "_", val["Magnitude"]))
+ magnitude = "".join(filter(str.isdigit, val["Magnitude"]))
+ if units:
+ self.unit = units
+ self.value = [str(magnitude), str(val["DirComp1"]), str(val["DirComp2"]), str(val["DirComp3"])]
if not isinstance(thermalmodifier, list):
thermalmodifier = [thermalmodifier]
for tm in thermalmodifier:
@@ -281,8 +321,8 @@ def type(self):
Parameters
----------
type : str
- Type of properties. Options are ``simple"``,
- ``"anisotropic",`` ``"tensor"``, and ``"nonlinear",``
+ Type of properties. Options are ``"simple"``,
+ ``"anisotropic"``, ``"tensor"``, ``"vector"``, and ``"nonlinear"``
"""
return self._type
@@ -307,6 +347,11 @@ def type(self, type):
@property
def evaluated_value(self):
"""Evaluated value."""
+ evaluated_expression = []
+ if isinstance(self.value, list):
+ for value in self.value:
+ evaluated_expression.append(self._material._materials._app.evaluate_expression(value))
+ return evaluated_expression
return self._material._materials._app.evaluate_expression(self.value)
@property
@@ -322,7 +367,7 @@ def value(self, val):
if isinstance(val, list) and isinstance(val[0], list):
self._property_value[0].value = val
self.set_non_linear()
- elif isinstance(val, list):
+ elif isinstance(val, list) and self.type != "vector":
if len(val) == 3:
self.type = "anisotropic"
elif len(val) == 9:
@@ -337,6 +382,12 @@ def value(self, val):
i += 1
if self._material._material_update:
self._material._update_props(self.name, val)
+
+ elif isinstance(val, list) and self.type == "vector":
+ if len(val) == 4:
+ self._property_value[0].value = val
+ if self._material._material_update:
+ self._material._update_props(self.name, val)
else:
self.type = "simple"
self._property_value[0].value = val
@@ -1044,7 +1095,7 @@ def __init__(self, materials, name, props=None):
self._oproject = self._materials._oproject
self.logger = self._materials.logger
self.name = name
- self.coordinate_system = ""
+ self._coordinate_system = ""
self.is_sweep_material = False
if props:
self._props = props.copy()
@@ -1069,13 +1120,24 @@ def __init__(self, materials, name, props=None):
self.mod_since_lib = self._props["ModSinceLib"]
del self._props["ModSinceLib"]
+ @property
+ def coordinate_system(self):
+ """Material coordinate system."""
+ return self._coordinate_system
+
+ @coordinate_system.setter
+ def coordinate_system(self, value):
+ if value in ["Cartesian", "Cylindrical", "Spherical"]:
+ self._coordinate_system = value
+ self._update_props("CoordinateSystemType", value)
+
@pyaedt_function_handler()
def _get_args(self, props=None):
"""Retrieve the arguments for a property.
Parameters
----------
- prop : str, optoinal
+ prop : str, optional
Name of the property. The default is ``None``.
"""
if not props:
@@ -1158,6 +1220,9 @@ def _update_props(self, propname, provpavlue, update_aedt=True):
self._props[propname] = OrderedDict({"property_type": "nonlinear", pr_name: bh})
if update_aedt:
return self.update()
+ elif isinstance(provpavlue, list) and material_props_type and material_props_type == "vector":
+ if propname == "magnetic_coercivity":
+ return self.set_magnetic_coercivity(provpavlue[0], provpavlue[1], provpavlue[2], provpavlue[3])
return False
@@ -1551,6 +1616,28 @@ def diffusivity(self):
def diffusivity(self, value):
self._diffusivity.value = value
+ @property
+ def magnetic_coercivity(self):
+ """Magnetic coercivity.
+
+ Returns
+ -------
+ :class:`pyaedt.modules.Material.MatProperty`
+ Magnetic coercivity of the material.
+
+ References
+ ----------
+
+ >>> oDefinitionManager.EditMaterial
+ """
+ return self._magnetic_coercivity
+
+ @magnetic_coercivity.setter
+ def magnetic_coercivity(self, value):
+ if isinstance(value, list) and len(value) == 4:
+ self.set_magnetic_coercivity(value[0], value[1], value[2], value[3])
+ self._magnetic_coercivity.value = value
+
@property
def molecular_mass(self):
"""Molecular mass.
@@ -1839,8 +1926,24 @@ def stacking_direction(self, value):
self._update_props("stacking_direction", OrderedDict({"property_type": "ChoiceProperty", "Choice": value}))
@pyaedt_function_handler()
- def set_magnetic_coercitivity(self, value=0, x=1, y=0, z=0):
- """Set Magnetic Coercitivity for material.
+ def set_magnetic_coercitivity(self, value=0, x=1, y=0, z=0): # pragma: no cover
+ """Set magnetic coercivity for material.
+
+ .. deprecated:: 0.7.0
+
+ Returns
+ -------
+ bool
+
+ """
+ warnings.warn(
+ "`set_magnetic_coercitivity` is deprecated. Use `set_magnetic_coercivity` instead.", DeprecationWarning
+ )
+ return self.set_magnetic_coercivity(value, x, y, z)
+
+ @pyaedt_function_handler()
+ def set_magnetic_coercivity(self, value=0, x=1, y=0, z=0):
+ """Set magnetic coercivity for material.
Parameters
----------
@@ -2081,8 +2184,8 @@ def get_curve_coreloss_values(self):
return out
@pyaedt_function_handler()
- def get_magnetic_coercitivity(self):
- """Get the magnetic coercitivity values.
+ def get_magnetic_coercivity(self):
+ """Get the magnetic coercivity values.
Returns
-------
@@ -2098,6 +2201,22 @@ def get_magnetic_coercitivity(self):
)
return False
+ @pyaedt_function_handler()
+ def get_magnetic_coercitivity(self): # pragma: no cover
+ """Get the magnetic coercivity values.
+
+ .. deprecated:: 0.7.0
+
+ Returns
+ -------
+ bool
+
+ """
+ warnings.warn(
+ "`get_magnetic_coercitivity` is deprecated. Use `get_magnetic_coercivity` instead.", DeprecationWarning
+ )
+ return self.get_magnetic_coercivity()
+
@pyaedt_function_handler()
def is_conductor(self, threshold=100000):
"""Check if the material is a conductor.
diff --git a/pyaedt/modules/MeshIcepak.py b/pyaedt/modules/MeshIcepak.py
index b3df7d30034..9773f3bbed6 100644
--- a/pyaedt/modules/MeshIcepak.py
+++ b/pyaedt/modules/MeshIcepak.py
@@ -187,7 +187,7 @@ def autosettings(self):
if self.SubModels:
arg.append("SubModels:=")
arg.append(self.SubModels)
- else:
+ if self.Objects:
arg.append("Objects:=")
arg.append(self.Objects)
arg.extend(self._new_versions_fields)
@@ -242,7 +242,7 @@ def manualsettings(self):
if self.SubModels:
arg.append("SubModels:=")
arg.append(self.SubModels)
- else:
+ if self.Objects:
arg.append("Objects:=")
arg.append(self.Objects)
arg.extend(self._new_versions_fields)
@@ -669,12 +669,23 @@ def assign_mesh_region(self, objectlist=[], level=5, is_submodel=False, name=Non
except Exception: # pragma : no cover
created = False
if created:
- objectlist2 = self.modeler.object_names
- added_obj = [i for i in objectlist2 if i not in all_objs]
- if not added_obj:
- added_obj = [i for i in objectlist2 if i not in all_objs or i in objectlist]
- meshregion.Objects = added_obj
- meshregion.SubModels = None
+ if virtual_region and self._app.check_beta_option_enabled(
+ "S544753_ICEPAK_VIRTUALMESHREGION_PARADIGM"
+ ): # pragma : no cover
+ if is_submodel:
+ meshregion.Objects = [i for i in objectlist if i in all_objs]
+ meshregion.SubModels = [i for i in objectlist if i not in all_objs]
+ else:
+ meshregion.Objects = objectlist
+ meshregion.SubModels = None
+ else:
+ objectlist2 = self.modeler.object_names
+ added_obj = [i for i in objectlist2 if i not in all_objs]
+ if not added_obj:
+ added_obj = [i for i in objectlist2 if i not in all_objs or i in objectlist]
+ meshregion.Objects = added_obj
+ meshregion.SubModels = None
+
meshregion.update()
return meshregion
else:
diff --git a/pyaedt/modules/PostProcessor.py b/pyaedt/modules/PostProcessor.py
index fbdf473ded6..286207b85f1 100644
--- a/pyaedt/modules/PostProcessor.py
+++ b/pyaedt/modules/PostProcessor.py
@@ -751,7 +751,7 @@ def available_quantities_categories(
Report Display Type.
Default is `None` which will take first default type which is in most of the case "Rectangular Plot".
solution : str, optional
- Report Setup. Default is `None` which will take first nominal_adpative solution.
+ Report Setup. Default is `None` which will take first nominal_adaptive solution.
context : str, optional
Report Category. Default is `""` which will take first default context.
is_siwave_dc : bool, optional
@@ -816,7 +816,7 @@ def available_report_quantities(
Report Display Type.
Default is ``None`` which will take first default type which is in most of the case "Rectangular Plot".
solution : str, optional
- Report Setup. Default is `None` which will take first nominal_adpative solution.
+ Report Setup. Default is `None` which will take first nominal_adaptive solution.
quantities_category : str, optional
The category to which quantities belong. It has to be one of ``available_quantities_categories`` method.
Default is ``None`` which will take first default quantity.".
@@ -1145,7 +1145,18 @@ def steal_focus_oneditor(self):
return True
@pyaedt_function_handler()
- def export_report_to_file(self, output_dir, plot_name, extension, unique_file=False):
+ def export_report_to_file(
+ self,
+ output_dir,
+ plot_name,
+ extension,
+ unique_file=False,
+ uniform=False,
+ start=None,
+ end=None,
+ step=None,
+ use_trace_number_format=False,
+ ):
"""Export a 2D Plot data to a file.
This method leaves the data in the plot (as data) as a reference
@@ -1167,6 +1178,20 @@ def export_report_to_file(self, output_dir, plot_name, extension, unique_file=Fa
* (Ansoft Report Data Files) .rdat
unique_file : bool
If set to True, generates unique file in output_dit
+ uniform : bool, optional
+ Whether the export uniform points to the file. The
+ default is ``False``.
+ start : str, optional
+ Start range with units for the sweep if the ``uniform`` parameter
+ is set to ``True``.
+ end : str, optional
+ End range with units for the sweep if the ``uniform`` parameter
+ is set to ``True``.
+ step : str, optional
+ Step range with units for the sweep if the ``uniform`` parameter is
+ set to ``True``.
+ use_trace_number_format : bool, optional
+ Whether to use trace number formats. The default is ``False``.
Returns
-------
@@ -1177,6 +1202,7 @@ def export_report_to_file(self, output_dir, plot_name, extension, unique_file=Fa
----------
>>> oModule.ExportReportDataToFile
+ >>> oModule.ExportUniformPointsToFile
>>> oModule.ExportToFile
Examples
@@ -1204,13 +1230,18 @@ def export_report_to_file(self, output_dir, plot_name, extension, unique_file=Fa
if extension == ".rdat":
self.oreportsetup.ExportReportDataToFile(plot_name, file_path)
+ elif uniform:
+ self.oreportsetup.ExportUniformPointsToFile(plot_name, file_path, start, end, step, use_trace_number_format)
+
else:
self.oreportsetup.ExportToFile(plot_name, file_path)
return file_path
@pyaedt_function_handler()
- def export_report_to_csv(self, project_dir, plot_name):
+ def export_report_to_csv(
+ self, project_dir, plot_name, uniform=False, start=None, end=None, step=None, use_trace_number_format=False
+ ):
"""Export the 2D Plot data to a CSV file.
This method leaves the data in the plot (as data) as a reference
@@ -1222,6 +1253,20 @@ def export_report_to_csv(self, project_dir, plot_name):
Path to the project directory. The csv file will be plot_name.csv.
plot_name : str
Name of the plot to export.
+ uniform : bool, optional
+ Whether the export uniform points to the file. The
+ default is ``False``.
+ start : str, optional
+ Start range with units for the sweep if the ``uniform`` parameter
+ is set to ``True``.
+ end : str, optional
+ End range with units for the sweep if the ``uniform`` parameter
+ is set to ``True``.
+ step : str, optional
+ Step range with units for the sweep if the ``uniform`` parameter is
+ set to ``True``.
+ use_trace_number_format : bool, optional
+ Whether to use trace number formats. The default is ``False``.
Returns
-------
@@ -1233,8 +1278,18 @@ def export_report_to_csv(self, project_dir, plot_name):
>>> oModule.ExportReportDataToFile
>>> oModule.ExportToFile
+ >>> oModule.ExportUniformPointsToFile
"""
- return self.export_report_to_file(project_dir, plot_name, extension=".csv")
+ return self.export_report_to_file(
+ project_dir,
+ plot_name,
+ extension=".csv",
+ uniform=uniform,
+ start=start,
+ end=end,
+ step=step,
+ use_trace_number_format=use_trace_number_format,
+ )
@pyaedt_function_handler()
def export_report_to_jpg(self, project_dir, plot_name, width=0, height=0):
@@ -1247,9 +1302,9 @@ def export_report_to_jpg(self, project_dir, plot_name, width=0, height=0):
plot_name : str
Name of the plot to export.
width : int, optional
- Image width. Default is ``0`` which takes Desktop size or 500 pixel in case of non-graphical mode.
+ Image width. Default is ``0`` which takes Desktop size or 1980 pixel in case of non-graphical mode.
height : int, optional
- Image height. Default is ``0`` which takes Desktop size or 500 pixel in case of non-graphical mode.
+ Image height. Default is ``0`` which takes Desktop size or 1020 pixel in case of non-graphical mode.
Returns
-------
@@ -1264,11 +1319,11 @@ def export_report_to_jpg(self, project_dir, plot_name, width=0, height=0):
# path
npath = project_dir
file_name = os.path.join(npath, plot_name + ".jpg") # name of the image file
- if self._app.desktop_class.non_graphical:
+ if self._app.desktop_class.non_graphical: # pragma: no cover
if width == 0:
- width = 500
+ width = 1980
if height == 0:
- height = 500
+ height = 1020
self.oreportsetup.ExportImageToFile(plot_name, file_name, width, height)
return True
@@ -3450,7 +3505,7 @@ def export_model_obj(self, obj_list=None, export_path=None, export_as_single_obj
if not self._app.modeler[el].display_wireframe:
transp = 0.6
t = self._app.modeler[el].transparency
- if t:
+ if t is not None:
transp = t
files_exported.append([fname, self._app.modeler[el].color, 1 - transp])
else:
diff --git a/pyproject.toml b/pyproject.toml
index 768973cda8d..b771bc9f609 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -25,9 +25,10 @@ classifiers = [
]
dependencies = [
- "cffi == 1.15.1;platform_system=='Linux'",
+ "cffi == 1.15.1;platform_system=='Linux' and python_version == '3.7'",
+ "cffi == 1.16.0;platform_system=='Linux' and python_version > '3.7'",
"pywin32 >= 303;platform_system=='Windows'",
- "pythonnet == 3.0.2",
+ "ansys-pythonnet>=3.1.0rc2",
"rpyc==5.3.1",
"psutil",
"dotnetcore2 ==3.1.23;platform_system=='Linux'",
@@ -36,8 +37,8 @@ dependencies = [
[project.optional-dependencies]
tests = [
"ipython==8.13.0; python_version < '3.9'",
- "ipython==8.15.0; python_version >= '3.9'",
- "imageio==2.31.4",
+ "ipython==8.16.1; python_version >= '3.9'",
+ "imageio==2.31.5",
"joblib==1.3.2",
"matplotlib==3.5.3; python_version == '3.7'",
"matplotlib==3.7.3; python_version == '3.8'",
@@ -61,11 +62,11 @@ tests = [
"scikit-rf",
]
doc = [
- "ansys-sphinx-theme==0.12.0",
- "imageio==2.31.4",
+ "ansys-sphinx-theme==0.12.2",
+ "imageio==2.31.5",
"imageio-ffmpeg==0.4.9",
"ipython==8.13.0; python_version < '3.9'",
- "ipython==8.15.0; python_version >= '3.9'",
+ "ipython==8.16.1; python_version >= '3.9'",
"ipywidgets==8.1.1",
"joblib==1.3.2",
"jupyterlab==4.0.6",
@@ -73,7 +74,8 @@ doc = [
"matplotlib==3.7.3; python_version == '3.8'",
"matplotlib==3.8.0; python_version > '3.8'",
"nbsphinx==0.9.3",
- "numpydoc==1.6.0",
+ "numpydoc==1.5.0; python_version == '3.7'",
+ "numpydoc==1.6.0; python_version > '3.7'",
"osmnx",
"pypandoc==1.11",
"pytest-sphinx==0.5.0",
@@ -178,4 +180,29 @@ testpaths = [
"_unittest",
]
-
+[tool.numpydoc_validation]
+checks = [
+ "GL06", # Found unknown section
+ "GL07", # Sections are in the wrong order.
+ "GL08", # The object does not have a docstring
+ "GL09", # Deprecation warning should precede extended summary
+ "GL10", # reST directives {directives} must be followed by two colons
+ # Return
+ "RT04", # Return value description should start with a capital letter"
+ "RT05", # Return value description should finish with "."
+ # Summary
+ "SS01", # No summary found
+ "SS02", # Summary does not start with a capital letter
+ "SS03", # Summary does not end with a period
+ "SS04", # Summary contains heading whitespaces
+ "SS05", # Summary must start with infinitive verb, not third person
+ # Parameters
+ "PR10", # Parameter "{param_name}" requires a space before the colon
+ # separating the parameter name and type",
+]
+exclude = [
+ '\.AEDTMessageManager.add_message$', # bad SS05
+ '\.Modeler3D\.create_choke$', # bad RT05
+ '\._unittest\', # missing docstring for tests
+ 'HistoryProps.', # bad RT05 because of the base class named OrderedDict
+]